C++标准程序库笔记之一

本篇博客笔记顺序大体按照《C++标准程序库(第1版)》各章节顺序编排。

--------------------------------------------------------------------------------------------

2. C++及其标准程序库简介

2.2-1

注意:如果要把一个template中的某个标识符号指定为一种型别,就算意图显而易见,关键字typename也不可或缺,因此C++的一般规则是,除了以typename修饰之外,template内的任何标识符号都被视为一个值(value)而非一个型别。如:

// 关键字typename被用来作为型别之前的标识符号
template <class T>
class MyClass
{
    typename T::Subtype * ptr;
    ....
};

2.2-2
class member function 可以是个template(类的成员函数模板),但这样的member template 既不能是virtual,也不能有缺省参数。(原因?好像《Effective C++ 》有讲过这个问题*-*)

2.2-3

template <class T>
class MyClass
{
    private:
        T value;
    public:
        template <class X>
        void assign(const MyClass<X>& x)
        {
            // this型别可以直接取用(在类里面操作私有或保护成员);x作为一个对象,要取用私有或保护成员,必须使用getValue()之类的接口
            // this.value = x.getValue;
            value = x.getValue();
        };
        T getValue() const
        {
            return value;
        }
};

2.2-4
Template constructor 是member template 的一种特殊形式。Template constructor 通常用于“在复制对象是实现隐式型别转换”。注意,template constructor并不遮蔽implicit copy constructor。如果型别完全吻合,implicit copy constructor (隐式拷贝构造函数)就会被产生出来并被调用。如下:

template <class T>
class MyClass
{
    public:
        template <class U>
        MyClass (const MyClass<U>& x);
        ...
};
void f()
{
    MyClass <double> xd;
    ...
    MyClass<double> xd2(xd);       // calls built-in copy constructor
    MyClass<int> xi(xd);          // calls template constructor
    ...
};

2.2-5
异常throw 语句开始了stack unwinding(堆栈辗转开解)过程,也就是说,它将使得退离任何函数区段时的行为就像以return语句返回一样,然后程序却不会跳转到任何地点。对于所有被声明于某区段——而该区段却因程序异常而退离——的局部对象而言,其destructor(析构函数)会被调用。Stack unwinding的动作会持续知道退出main() 或直到有某个catch子句捕捉并处理了该异常为止。

2.2-6
命名空间namespace与class雷同,要引用该namespace内的符号,必须加上namespace标识符。不同于class的是,namespace是开放的,你可以在不同模块(modules)中定义和扩展namespace(也即,和class 不同,namespace 具有扩展开放性,可以出现在任何源码文件中。因此你可以利用一个namespace来定义一些组件,而它们可散步于多个实质模块上)。

2.2-7
根据C++标准规格,只有两种mian() 是可移植的:

int main()
{
    .....
}
// 和
int main(int argc, char* argv[])
{
    ....
}

这里argv(命令行参数数组)也可定义为char**。请注意,由于不允许“不言而喻”的返回型别int,所以返回型别必须明白写为int。你可以使用return语句来结束main(),但不必一定如此。这一点和C不同,换句话说,C++在main()的末尾定义了一个隐式的:  return 0; 这意味如果你不采用return 语句离开main(),实际上就表示成功退出(传回任何一个非零值都代表某种失败)。

--------------------------------------------------------------------------------------------

3. 一般概念

3.4-1

将C++标准程序库中所有标识符都定义于namespace std里头,这种做法是标准化过程中引入的。这个做法不具向下兼容性,因为原先的C/C++头文件都将C++标准程序库的标识符定义于全局范围(global scope)。此外标准化过程中有些classes 的接口也有了更动。为此,特别引入了一套新的头文件命名风格。如:

#include <string>        // C++ class string
#include <cstring>     // char* functions from C, was : <string.h>
#include <cstdlib>     // char* functions from C, was : <stdlib.h>

3.4-2
标准异常类别可分为三组:
(1)语言本身支持的异常;
(2)C++标准程序库发出的异常;
(3)程序作用域(scope of a program)之外发出的异常。
图3-1

所有标准异常的接口只含一个成员函数:what(),用以获取“型别本身以外的附加信息”,它返回一个以null结束的字符串。除此之外,再没有任何异常提供任何其它成员函数,能够描述异常的种类。

3.4-3

C++标准程序库在许多地方采用特殊对象来处理内存配置和寻址,这样的对象称为配置器(allocator)。配置器体现出一种特定的内存模型(memory model),成为一个抽象表征,表现出“内存需求”至“内存低阶调用”的转换。如果运用多个不同的配置器对象,你便可以在同一个程序中采用不同的内存模型。

--------------------------------------------------------------------------------------------

4. 通用工具

4.2-1
参见博客 为什么需要auto_ptr_ref
4.3-1
数值极限
一般来说,数值型别的极值是一个与平台相关的特性。C++标准程序库通过template numeric_limits提供这些极值,取代传统C语言所采用的预处理常数。C++ Standard规定了各种型别必须保证的最小精度。

4.4-1

函数swap用来交换两对象的值。前提,只有当swap()所依赖的copy构造操作和assignment操作行为存在时,这个调用才可能有效。swap()的最大优势在于,透过template specialization(模板特化)或function overloading(函数重载),我们可以为更复杂的型别提供特殊的实作版本。

4.6-1

头文件<cstddef>和<cstdlib>和其C对应版本兼容。注意,C语言中的NULL通常定义为(void*)0。在C++中这并不正确,NULL的型别必须是个整数型别,否则你无法将NULL赋值给一个指针。这是因为C++并没有定义从void*到任何其他型别的自动转型操作。

4.6-2
函数exit()和abort()可用来在任意地点终止程序运行,无需返回main():
(1)exit()会销毁所有static对象,将所有缓冲区(buffer)清空(flushes),关闭所有I/O通道(channels),然后终止程序(之前会先调用经由atexit()登录的函数)。如果atexit()登录的函数抛出异常,就会调用terminate()。
(2)abort()会立刻终止函数,不做任何清理(clean up)工作。
这两个函数都不会销毁局部对象(local objects),因为堆栈辗转开展动作(stack unwinding)不会被执行起来。为确保所有局部对象的析构函数获得调用,你应该运用异常(exceptions)或正常返回机制,然后再由main()离开。

--------------------------------------------------------------------------------------------
5. Standard Template Library(STL),标准模板库
5.3-1 迭代器
(1)牢记一点,每一种容器都提供了自己的迭代器(《STL源码剖析》有详细解释)。这些迭代器了解该种容器的内部结构,所以能够知道如何正确行进,事实上,每一种容器都将其迭代器以嵌套(nested)方式定义于内部,因此各种迭代器的接口相同,型别却不同。透过迭代器的协助,我们只需撰写一次算法(注意,算法并非容器类别的成员函数,而是一种搭配迭代器使用的全局函数),就可以将它应用于任意容器之上,这是因为所有容器的迭代器都提供一致的接口(容器--迭代器--算法)。

(2)Multimaps不允许我们使用subscript(下标)操作符,因为multimaps允许单一索引对应到多个不同元素,而下标操作符却只能处理单一实值。你必须先产生一个“键值/实值”对组,然后再插入multimap。

5.4-1

算法 如果某个算法用来处理多个区间,那么当你调用它时,务必确保第二(以及其它)区间所拥有的元素个数,至少和第一区间内的元素个数相同。特别是,执行涂写动作时,务必确保目标区间够大。

5.6-1

更易型算法

(1)算法不能自己移除容器元素,这是STL为了获取灵活性而付出的代价。透过“以迭代器为接口”,STL将数据结构和算法分离开来。然而,迭代器只不过是“容器中某一位置”的抽象概念而已。一般来说,迭代器对自己所属的容器一无所知。任何“以迭代器访问容器元素”的算法,都不得(也无法)透过迭代器调用容器类别所提供的任何成员函数。

(2)切记:更易型算法(指那些会移除remove、重排resort、修改modify元素的算法)都不得用于关联式容器身上,因为如果更易型算法用于关联式容器身上,会改变某位置上的值,进而破坏其已序(sorted)特性,那也就违反了关联式容器的基本原则:容器内的元素总是根据某个排序准则自动排序。因此,为了保证这个原则,关联式容器的所有迭代器均被声明为指向常量(const iterator)。每一种关联式容器都提供用以移除元素的成员函数。

(3)有时,针对一个容器,标准库同时提供了STL算法和容器成员函数执行相同操作,如果想要有更高效率,那么使用容器成员函数会是更优选择,因为STL算法针对的是所有容器进行抽象实现。代价是,一旦更换另一种容器,就不得不改动程序代码。

5.9-1

仿函数(functors,function objects) (1)仿函数是“smart functions”(智能型函数) “行为类似指针”的对象,我们称为“smart pointers”。“行为类似函数” 的对象,我们也可以称为“smart functions”,因为它们的能力可以超越 operator ()。仿函数可以拥有成员函数和成员变量,这意味仿函数拥有状态(state)。事实上,<在同一时间里,由某个仿函数所代表的单一函数>,可能有不同的状态。这在一般函数中是不可能的。另一个好处是,你可以在执行期初始化它们——当然必须在它们被使用(被调用)之前。

class AddValue
{
    private:
        int theValue;       // the value to add
    public:
        AddValue(int v) : theValue(v) { }
    void operator () (int& elem) const
    {
        elem += theValue;
    }
};

int main()
{
    list<int> coll;
    for(int i = 1; i <= 9 ; ++i)
    {
        coll.push_back(i);
    }

    print_elements(coll, "initialized : ");      // 打印容器元素

    for_each(coll.begin(), coll.end(),
        AddValue(10));      // 执行期才指定数值,通过仿函数的成员变量theValue保存这个状态

    print_elements(coll, "after adding 10 : ");      // 打印容器元素

    for_each(coll.begin(), coll.end(),
        AddValue(*coll.begin()));

    print_elements(coll, "after adding first element : ");      // 打印容器元素
}

输出:
initialized : 1 2 3 4 5 6 7 8 9
after adding 10 : 11 12 13 14 15 16 17 18 19
after adding first element : 22 23 24 25 26 27 28 29 30

(2)每个仿函数都有自己的型别
<一般函数,唯有在它们的标记式(signatures)不同时,才算型别不同。而仿函数即使标记式相同,也可以有不同的型别>。事实上,由仿函数定义的每一个函数行为都有其自己的型别。这对于“利用template实现泛型编程”乃是一个卓越贡献,因为如此一来,我们便可以将函数行为当做template参数来运用。这使得不同型别的容器可以使用同类型的仿函数作为排序准则。这可以确保你不会在排序准则不同的群集之间赋值、合并和比较。你甚至可以设计仿函数继承体系,以此完成某些特别事情,例如在一个总体原则下确立某些特殊情况。

AddValue addx(x);        // function object that adds value x
AddValue addy(y);        // add y

for_each(coll.begin(), coll.end(), addx);
...
for_each(coll.begin(), coll.end(), addy);
...
for_each(coll.begin(), coll.end(), addx);

(3)仿函数通常比一般函数速度快
就template概念而言,由于更多细节在编译期就已确定,所以通常可能进行更好的最佳化。所以,传入一个仿函数(而非一般函数),可能获得更好的性能。

仿函数相关的更多信息参见《STL源码剖析》

5.10-1

Value语意VS. Reference语意

所有容器都会建立元素副本,并返回该副本。这意味容器内的元素与你放进去的对象“相等(equal)”但非“同一(identical)”。如果你修改容器中的元素,实际改变的是副本而不是原先对象。这意味STL容器所提供的是“value语意”。它们所容纳的是你所安插的对象值,而不是对象本身。然而实用上你也许需要用到“reference语意”,让容器容纳元素的reference。

STL只支持value语意,不支持reference语意,好处:

(1)元素的拷贝很简单;

(2)使用reference时容易导致错误。你必须确保reference所指向的对象仍然健在,并需小心对付偶尔出现的循环引用状态。

缺点:

(1)“拷贝元素”可能导致不好的效能;有时甚至无法拷贝;

(2)无法在数个不同的容器中管理同一份对象。

实用上你同时需要两种作法。你不但需要一份独立(于原先对象)的拷贝(此乃value语意),也需要一份代表原书记、以能相应改变原值的拷贝(此乃reference语意)。不幸的是,C++标准程序库不支持reference语意。不过我们可以利用value语意来实现reference语意。 一个显而易见的方法是以指针作为元素(C程序员或许很能认可“以指针实现reference语意”的手法。因为在C语言中函数的参数只能passed by value(传值),因此需要通过指针才能实现所谓的call by reference)。为了避免资源泄漏,可以使用智能指针。然而我们不能使用auto_ptr,因为它不符合作为容器元素所需的基本要求。当auto_ptr执行了拷贝(copy)或赋值(assign)动作后,目标对象与原对象并不相等:原来的那个auto_ptr发生了变化,其值并不是被拷贝了,而是被转移了。 你可以使用带有“引用计数”的智能指针实现STL容器的reference语意,但即使这样也很麻烦,举个例子,如果你拥有直接存取元素的能力,你就可以更改元素值,而这在关联式容器中却会打破元素顺序关系。

《C++标准程序库》6.8节提供了一个实现STL容器reference语意的例子。

5.11-1

STL的设计原则是效率优先,安全次之。错误检查相当花时间,所以几乎没有。C++标准程序库指出,对于STL的任何运用,如果违反规则,将会导致未定义行为。 但C++标准程序库还是提供了相应保证,如表6.35.

注意,所有这些保证都有一个前提:析构函数不得抛出异常。

时间: 2024-08-25 01:03:06

C++标准程序库笔记之一的相关文章

《C++标准程序库》笔记之二

本篇博客笔记顺序大体按照<C++标准程序库(第1版)>各章节顺序编排. -------------------------------------------------------------------------------------------- 6. STL 容器 6.1-1 本节讲述STL容器的共通能力.其中大部分都是必要条件,所有STL容器都必须满足那些条件.三个最最核心的能力是: (1)所有容器提供的都是“value语意”而非“reference语意”.容器进行元素的安插操作

《C++标准程序库》笔记之四

本篇博客笔记顺序大体按照<C++标准程序库(第1版)>各章节顺序编排. -------------------------------------------------------------------------------------------- 13 以Stream Classes 完成输入和输出 13.1 String对象 (1)C++ I/O 由streams完成.所谓stream就是一条数据“流”.输出操作被解读为“数据流入stream”,输入操作则是“数据流出stream”

C++标准程序库读书笔记-第三章

1.命名空间(namespace)std C++标准程序库中的所有标示符都被定义于一个名为std的namespace 2.标准异常类别 (1)语言本身或标准程序库所抛出的所有异常,都派生自基类exception (2) (3)标准异常类别分为三组 语言本身支持的异常 C++标准程序库发出的异常(派生自logic_error) 程序作用域之外发出的异常(派生自runtime_error) (4)异常类别的头文件     基础类别exception和bad_exception定义于<exceptio

&lt;&lt;C++标准程序库&gt;&gt;中的STL简单学习笔记

0. 内容为个人学习笔记, 仅供参考, 如有错漏, 欢迎指正! 1. STL中的所有组件都是由模板构成的, 所以其元素可以是任意型别的. 组件有: - 容器: 管理某类对象的集合. 不同的容器有各自的优缺点. - 迭代器: 用来在一个对象集群(Collection of Objects) 的元素上进行遍历. 这个CoB可以是容器/容器的一部分. 每种容器都提供了自己的迭代器. - 算法(Algorithm): 用来处理集群内的元素(比如: 查询,修改,排序等). - 适配器(adapter) -

STL学习笔记(第二章 C++及其标准程序库简介)

本章重点是介绍与C++标准程序库相关的几个最重要的语言新特性 template(模板) 程序库中几乎所有东西都被设计成template形式.所谓templates,是针对“一个或多个尚未明确的性别”所撰写的函数或类别. 下面是一个典型例子 template<class T> inline const T& max(const T& a,const T& b) { return a < b ? b : a; { template并非一次编译便产生出社和所有型别的代码

《C++标准程序库》学习笔记(一)C++相关特性

抱着本厚厚的<C++标准库>读了几天,想想也该写点关于用法的总结,一来怕今后容易忘记,二来将书上的事例重新敲一遍,巩固对程序库相关知识的了解.今天开第一篇,以后不固定更新.当然,笔者所读为该书为基于C++98的第一版,已有一定的年代感,不过虽然C++11的推出已有一定的时日,但是在普及上还需要一定的时间,因而,这本中文译本还是有一定的可读性的.这本书更新的版本为英文第二版,很遗憾还未出现其中文译本. 由于是开篇,本文所讲都很基础,但这些基础内容对后面的学习是非常重要的. 1  C++标准 C+

C++标准程序库读书笔记-第二章新的语言特性

1.基本类型的显式初始化 如果采用不含参数.明确的constructor(构造函数)调用语法,基本型别会被初始化为零: int i1; //undefined value int i2 = int(); //initialized with zero 这个特性可以确保我们在撰写template程序代码时,任何型别都有一个确切的初值.例如下面这个函数中,x保证被初始化为零. template <class T> void f() { T x = T(); }

《Effective C++》读书笔记汇总

我之前边读<Effective C++>边写下每个条款的读书笔记,这一版是C++11之前的版本.这里我将每个条款令我印象深刻的点小结一下. 1.C++包括:Plain C(面向过程).OOP(面向对象).模板(泛型和模板元编程).STL(C++标准库). 2.用inline.enum.const代替#define.#define定义的宏,一旦复杂起来,高手都很难掌控.不要带入C的习惯. 3.灵活使用const前缀.不需要进行改变的数据加上const前缀.指针的const前缀有两种形式,cons

C++基础和STL,Effective C++笔记

C++基础 static static变量存储在静态数据区 相对于function:在函数内,变量,内存只被分配一次,多次调用值相同 相对于其他模块(.c文件):变量和函数,不能被模块外其他函数访问(private) 相对于类:类中的static变量和函数属于整个类,而不是对象 全局变量 VS 全局静态变量 若程序由一个源文件构成时,全局变量与全局静态变量没有区别. 若程序由多个源文件构成时,全局变量与全局静态变量不同:全局静态变量使得该变量成为定义该变量的源文件所独享,即:全局静态变量对组成该