文章目录
一、容器库概述
1.1 迭代器
所有标准库容器都可以使用迭代器,但是只有少数几种支持下标运算符(vector
、deque
、map
)。有效迭代器指向容器中的某个元素,或者指向容器中尾元素的下一个位置,后者成为尾后迭代器,其余都是无效的。
迭代器运算符
使用==
和!=
可以比较两个有效迭代器是否相等;和指针类似,也能通过解引用有效迭代器获取它所指的元素(不包括尾后迭代器)。
一个迭代器范围由一对迭代器表示,一个指向容器C中的元素,一个是容器C的尾后迭代器,如左闭合区间[begin, end)
。
1.2 容器类型别名
通过类型别名,可以在不了解容器中元素类型的情况下使用它。如果需要元素类型,可以使用value_type
,如果需要一个元素的引用可以使用reference
或const_reference
。
1.3 容器的定义与初始化
array
与其他容器不同,大小也是其类型的一部分,定义时除了指定元素类型,还要指定容器大小,如:array<int, const_n> a1;
。
C c1; //默认初始化,c1为空
array<int, const_n> a1; //array默认初始化有别于其它容器,元素个数为const_n
C c2(c1); //c2初始化为c1的拷贝,c1和c2必须类型相同,如果是array,大小还要相同
C c3 = c1; //同上
C c4 {
a, b, c... }; //c4初始化为列表中元素的拷贝,列表元素与容器元素类型需兼容,如果是array,列表元素数小于等于array大小
C c5 = {
a, b, c... }; // 同上
C c6(begin, end); //初始化为迭代器begin,end所指范围元素的拷贝,类型需兼容,array不适用
// 只有顺序容器才会用
C c7(n); //c7包含n个元素,元素值会按照类型默认初始化,string不适用
C c8(n, t); //c8包含n个初始值为t的元素
1.4 赋值和swap
容器赋值运算:assign
只适用于顺序容器;array
的类型包括元素数目,所以可能改变大小的赋值都不支持。
赋值运算符要求左右两边的运算对象具有相同的类型,它将左边容器中的全部元素,替换为右边容器中元素的拷贝。
- 对于非
array
类型,赋值运算与左右两边容器的大小没有关系,如c1.size() == 1
,c2.size() == 3
,赋值完后两个size
都为3
。 - 而
array
类型与内置数组不同,它允许赋值,赋值号左右两边的类型必须相同(array
的大小也属于类型)。由于右边运算对象的大小可能与左边运算对象的大小不同,因此array
类型不支持assign
,也不允许用值列表赋值,但是可以列表初始化。array<int, 3> a1 = { 1, 2, 3 } array<int, 3> a2 = { 0 }; a1 = a2; a2 = { 0 }; // 错误:不能将一个花括号列表赋予数组
1)assign(仅顺序容器)
顺序容器定义了一个名为assign
的成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。由于被旧元素替换,因此传递给assign
的新迭代器不能指向调用assign
的容器。
list<string> names;
names.push_back("123");
vector<const char*> old_style;
old_style.push_back("abc");
list<string>::iterator names_it_begin = names.begin();
list<string>::iterator names_it_end = names.end();
// 此时的迭代器指向原来的容器
for (list<string>::iterator it = names_it_begin; it != names_it_end; it++)
cout << *it << endl; //输出:123
//names = old_style; // 错误:容器类型不匹配
names.assign(old_style.begin(), old_style.end());
// 此时的迭代器指向新的容器
for (list<string>::iterator it = names_it_begin; it != names_it_end; it++)
cout << *it << endl; //输出:abc
2)swap
swap
操作交换两个相同类型容器的内容,调用swap
之后,两个容器的内容将会交换。
-
swap
两个array
会真正交换它们的元素,因此所需时间与array
中元素数目成正比。 -
除了
array
之外,swap
不对任何元素进行拷贝、删除或插入操作,元素本身并未交换,swap
只是交换了两个容器的内部数据结构,因此可以保证再常数时间内完成。 -
除
string
之外,指向容器的迭代器、引用和指针在swap
操作之后都不会失效,它们仍指向swap
操作之前所指的那些元素。list<string> names; names.push_back("123"); list<string> old_style; old_style.push_back("abc"); list<string>::iterator names_it_begin = names.begin(); list<string>::iterator old_style_begin = old_style.begin(); cout << *names_it_begin << endl; //输出:123 cout << *old_style_begin << endl; //输出:abc swap(names, old_style); // 迭代器仍指向swap操作之前所指的那些元素 cout << *names_it_begin << endl; //输出:123 cout << *old_style_begin << endl; //输出:abc
1.5 关系运算符
每个容器 类型都支持相等运算符(==
和!=
);除了无序关联容器 外的所有容器都支持关系运算符(>、>=、<、<=
),关系运算符左右两边的运算对象必须是相同类型的容器,且必须保证元素类型相同。
【注】容器的关系运算符其实是使用元素的关系运算符完成比较:相等运算符使用的是元素的==
运算符实现比较;其他关系运算符使用元素的<
运算符完成比较。如果元素类型不支持所需运算符,那么保存这种元素的容器就不能使用相应的关系运算。
二、顺序容器
1.1 顺序容器的类型
除了array
外,其他容器都有高效的内存管理;array
大小固定,不支持添加删除等改变数组大小的操作,现代C++更应该使用array
而不是内置数组;forward_list
设计目标是与最好的手写单链表性能相当,因此没有size
操作,因为size
的保存和计算都会产生额外的开销。
1.2 确定使用哪种容器
选择容器遵从以下基本原则:
- 通常使用
vector
是最好的选择,如果有足够的理由可以选择其他容器。 - 需要随机访问,或在头部尾部插入删除,
vector
、deque
。 - 需要在容器中间插入删除,
list
、forward_list
。 - 如果程序只需在读取输入时才需要在中间位置插入元素,随后需要随机访问。
a)先将插入元素放在vector
的尾部,再调用sort
函数重排容器。
b)考虑插入阶段使用list
,输入完成后将list
中的内容拷贝到vector
中。 - 如果不确定使用哪种容器,可以在程序中只是用
vector
和list
的公共操作:使用迭代器,不使用下标,这样在必要时可以切换。
1.3 容器的定义与初始化
vector<int> vec1; //默认初始化,vec1为空
vector<int> vec2(vec1); //使用vec1初始化vec2
vector<int> vec3(vec1.begin(), vec1.end()); //使用vec1初始化vec2
vector<int> vec4(10); //10个值为0的元素
vector<int> vec5(10, 4); //10个值为4的元素
vector<string> vec6(10, "hello"); //10个值为hello的元素
vector<int> vec7 {
10 }; // 列表初始化vector,1个元素,值为10
vector<string> vec8 {
"qwe", "asd", "zxc" }; // 3个元素,分别是"qwe", "asd", "zxc"
vector<string> vec9 = {
"qwe", "asd", "zxc" }; // 等同与vec7
1.4 容器基本操作
在这里插入代码片