@taqikema
2018-01-06T13:58:58.000000Z
字数 5061
阅读 1097
C++Primer
学习记录
顺序容器
string和 vector,元素保存在连续空间中,优点是随机访问快,缺点是中间位置添加元素时很慢。
list和 forward_list,非连续存储,优点是任何位置的添加和删除元素都很快,缺点是不支持随机访问,为了访问一个元素,需要遍历在其之前的所有元素。
deque,双端队列,优点是支持快速随机访问、两端添加或删除元素很快,缺点是中间位置添加或删除元素较慢。
array,固定大小数组,与内置数组有些相似。优点是支持快速随机访问,缺点是不能改变容器大小。
forward_list,单项列表,可以达到与最好的手写的单向链表数据结构相当的性能。
通常,使用 vector是最好的选择,除非你有很好的理由选择其他容器。当不确定使用那种容器时,可以在程序中只是用 vector和 list公共的操作:使用迭代器,不使用下标操作,避免随机访问。这样,在必要时更换成 vector或 list都很方便。
不同的容器对所存储的元素类型有其自己的特殊要求,可以为不支持特定操作需求的类型定义容器,但这种情况下就只能使用那些没有特殊要求的容器操作了。比如,
// 假定 noDefault是一个没有默认构造函数的类型
// init是一个 noDefault类对象,下面语句执行的是 noDefault的拷贝构造函数
vector<noDefault> v1(10, init); // 正确
// 下面语句执行的是 noDefault的默认构造函数
vector<noDefault> v1(10, init); // 错误
[begin, end)
。迭代器范围是标准库的基础,无论是顺序容器,还是关联容器;无论是否支持随机访问的容器,对其元素的访问都可以通过迭代器完成。这样,就为标准库中的所有容器都提供了一个统一的接口。auto与 begin或 end结合使用时,获得的迭代器类型依赖于容器类型;但以 c开头的版本总是可以获得 const_iterator的,与容器类型无关。
auto it7 = a.begin(); // 仅当 a是 const时, it7是 const_iterator
auto it8 = a.cbegin(); // it8是 const_iterator
除 array之外,其他容器的默认构造函数都会创建一个指定类型的空容器,而 array默认构造的容器是非空的:它包含了与其大小一样多的元素,这些元素都被默认初始化。
使用一个容器的拷贝来创建另一个容器时,两个容器的类型及其元素类型必须当使用迭代器进行元素拷贝时,容器类型可以不同,元素类型也可以不同,只要能够进行转换即可。
list<string> aut = { "the","then" };
vector<const char*> aa = { "a","an" };
vector<string> words1(aut); // 错误,容器类型不相同
vector<string> words2(aa); // 错误,元素类型不相同
list<string> words3(aa.begin(), aa.end()); // 正确,const char*可以转换为 string
大小是 array类型的一部分,为了使用 array类型,必须同时指定元素类型和大小。
array<int, 10>::size_type i; // 容器类型包括元素类型和大小
array<int>::size_type j; // 错误,array<int>不是一个类型
内置数组类型不可以进行拷贝或赋值操作,但 array并无此限制,但要求 array的元素类型和大小都必须一样。
int digs[10] = {0, 1, 2};
int cpy[3] = digs; // 错误
array<int, 3> digits = {0, 1, 2};
array<int, 3> copy = digits; // 正确
调用 assign操作后,容器中旧元素会被替换,即调用 assign操作的对象容器的迭代器此时已经不可用,所以传递给 assign的迭代器不能指向调用 assign的容器。下面代码就是错误的。
list<string> names = { "a","an", "the" };
names.assign(names.cbegin() + 1, names.cend() - 1); // 错误
赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而 swap操作将容器内容交换不会导致指向容器的迭代器、引用和指针失效(array和 string类型除外,它们仍然会失效)。不会失效可以理解为只是交换了指针所指向的地址,指针所指向的值本身并没有发生变化,所以迭代器(指向原来物理内存)仍旧有效。而真正交换元素,则会发生元素类型的拷贝构造和析构,因此物理内存发生了改变,原来的迭代器也就失效了。
用一个对象初始化容器,或将一个对象插入到容器中时,实际上放入倒容器中的是对象值的一个拷贝,而不是对象本身。随后对容器中元素的任何改变都不会影响到原始对象,反之亦然。
insert允许我们在容器中的任意位置插入元素,而对于容器存在指向最后一个元素之后的尾后迭代器和指向第一个元素的迭代器,所以如果想在容器头部也能插入元素,insert只能将元素插入到迭代器所指定的位置之前。返回指向新添加的元素的迭代器。
在容器中访问元素的成员函数返回的都是引用。所以,如果希望使用 auto变量来改变元素值,需要将变量定义为引用类型。
auto v1 = c.back(); // v1是一个值拷贝
auto &v2 = c.back(); // v2是一个引用
erase操作,删除迭代器所指定的元素,返回一个指向被删除元素之后元素的迭代器。在遍历操作中删除某些特定值时,可以使用如下语句递增循环变量。
iter = vec.erase(iter);
改变容器元素大。如果当前大小大于所要求的大小,容器后部的元素会被删除;反之,会将新元素添加到容器后部:
list<int> ilist(10, 42);
ilist.resize(15); // 将 5个值为 0的元素添加到末尾
ilist.resize(25, -1); // 将 10个值为 -1的元素添加到末尾
ilist.resize(5); // 从末尾删除 20个元素
容器操作可能使迭代器、引用或指针失效,使用失效的迭代器、指针或引用是一种严重的程序设计错误。
size
与容器的最大元素数目capacity
往往并不相同。从一个 const char*创建 string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。如果不是以空字符结尾,则必须再传递一个计数值。如果未传递计数值且数组不是以空字符结尾,或者传递的计数值大于数组大小,则函数行为未定义。
const char *cp = "Hello";
char noNull = {'H', 'i'};
string s1(cp); // s1 == "Hello"
string s2(noNull, 2); // s2 == "Hi"
string s3(noNull); // 行为未定义!
string s4(noNull, 3); // 行为未定义!
string s(s2, pos2, len2)
,不管 len2的值是多少,构造函数自多拷贝到 s2的末尾,不会报错。
适配器,使得某种事物的行为看起来像另外一种事物一样。标准库中,有容器、迭代器和函数适配器三种。
标准库定义了三个顺序容器适配器,stack、queue和 priority_queue。所有适配器都要求底层容器具有添加和删除元素的能力,所以适配器不能构造在 array之上。另外,还都要求具有访问尾元素的能力,所以 forward_list也不行。