1、STL组件:容器、迭代器、算法。
2、容器用来管理一大群元素。为了适应不同应用,STL提供了不同的容器。
总的来说,容器可分为三大类:
- 顺序容器(sequence container),这是一种有序(ordered)集合,其内每个元素均有确凿的位置–取决于插入时机和地点,与元素值无关。他们的排序次序将和置入次序一致。STL提供了5个定义好的顺序容器:array、vector、deque、list和forward_list。
- 关联式容器(associative container),这是一种已排序(sorted)集合,元素位置取决于其value(或可key)和给定的某个排序准则。他们的值决定他们的次序,和插入次序无关。STL提供了4个关联式容器:set、multiset、map和multimap。
- 无序容器(unordered container),这是一种无序(unorderd)集合,其内每个元素的位置无关紧要,唯一重要的某特定元素是否位于此集合内。元素值、安插顺序,都不影响元素的位置,而且元素的位置有可能在容器生命中被改变。
3、顺序容器定义和初始化
C c; | 默认构造函数 |
C c1(c2) | 拷贝初始化 |
C c1 = c2 | 拷贝初始化 |
C c{a,b,c…} | 初始值列表初始化 |
C c = {a,b,c…} | 初始值列表初始化 |
C c(beg,end) | 迭代器初始化(array不适用) |
只有顺序容器(不包括array)的构造函数才能接受大小参数
C seq(n) | seq包含n个元素,这些元素进行了值初始化 |
C seq(n,t) | seq包含n个初始化为值t的元素 |
- array具有固定大小,所以迭代器初始化、接受大小参数的初始化不适用。
- 不能对内置数组类型进行拷贝或对象赋值操作,但array并无此限制。
4、容器赋值运算
c1 = c2 | 拷贝 |
c = {a,b,c…} | 初始化列表拷贝 |
swap(c1,c2) | 交换c1和c2中的元素 |
c1.swap(c2) | 交换c1和c2中的元素,swap通常必从c2向c1拷贝元素快得多 |
assign操作不适合用于关联容器和array
seq.assign(beg,end) | 迭代器替换 |
seq.assign(initlist) | 初始值列表替换 |
seq.assign(n,t) | 替换为n个值为t的元素 |
- array<T,N> 具有单独的赋值函数,c.fill(val)
- 除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。
- 元素不会被移动的事实意味着,除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效,他们仍指向swap操作之前所指向的那些元素。但是,swap之后,这些元素已经属于不同的容器了。
8 int main(){
9 vector<string> s1 = {"a"};
10 vector<string> s2 = {"b"};
11
12 auto pos = s1.begin();
13 s1.swap(s2);
14
15 cout << s1[0] << endl;
16 cout << *pos << endl;
17
18
19 return 0;
20 }
运行结果
b
a
- 对string调用swap会导致迭代器、引用和指针失效。
- 对于array,在swap操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另一个array中对应元素的值进行了交换。
8 int main(){
9 array<string,1> s1 = {"a"};
10 array<string,1> s2 = {"b"};
11
12 auto pos = s1.begin();
13 s1.swap(s2);
14
15 cout << s1[0] << endl;
16 cout << *pos << endl;
17
18
19 return 0;
20 }
运行结果
b
b
5、容器大小操作
c.empty() | 返回容器是否为空 |
c.size() | 返回目前的元素个数,forward_list不支持 |
c.max_size() | 返回元素个数的最大可能量 |
- c++ standard :我们希望forward_list和你自己手写的C-style singly linked list相较之下没有任何空间或时间上的额外开销。任何性质如果与这个目标相抵触,我们就放弃该性质。所以不提供size()函数。
6、关系运算符
- 每个容器类型都支持相等运算符(==和!=);
- 除了无序关联容器(unordered container)外的所有容器都支持关系运算符(>、 >=、 <、 <=)。
7、添加元素
- 这些操作会改变容器的大小;array不支持这些操作。
- forward_list有自己专有版本的insert和emplace。
- vector和string不支持push_front和emplace_front。
- forward_list没有指向最末元素的锚点(anchor)。基于这个原因,forward_list不提供“用以处理最末元素”的成员函数如back()、push_back()和pop_back()。
c.push_back(t) | 在c的尾部创建一个值为t或由args创建的元素,返回void |
c.emplace_back(args) | |
c.push_front(t) | 在c的头部创建一个值为t或由args创建的元素,返回void |
c.emplace_front(args) | |
c.insert(p,t) | 在迭代器p指向的元素之前创建一个值为t或由args创建的元素,返回指向新添加的元素的迭代器 |
c.emplace(p,args) | |
c.insert(p,n,t) | 在迭代器p指向的元素之前插入n个值为t的元素,返回指向新添加的第一个元素的迭代器 |
c.insert(p,beg,end) | 将迭代器beg和end指定的范围内的元素插入到迭代器p指向的元素之前,返回指向新添加的第一个元素的迭代器 |
c.insert(p,initlist) | 将初始值列表插入到迭代器p指向的元素之前,返回指向新添加的第一个元素的迭代器 |
- 当调用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。
- emplace函数在容器中直接构造元素。传递给emplace函数的参数必须与元素类型的构造函数相匹配。
//在c的末尾构造一个Sales_data对象
//使用三个参数的Sales_data构造函数
c.emplace_back("978-0590353403", 25, 15.99);
c.push_back("978-0590353403", 25, 15.99); //错误:没有接受三个参数的push_back版本
c.push_back(Sales_data("978-0590353403", 25, 15.99)); //正确:创建一个临时的Sales_data对象传递给push_back
8、访问元素
- at和下标操作只适合用于string、vector、deque和array。
- forward_list不支持back()。
c.back() | 返回c中尾元素的引用,若c为空,函数行为未定义 |
c.front() | 返回c中首元素的引用,若c为空,函数行为未定义 |
c[n] | 返回c中下标为n的元素的引用,n是一个无符号整数,若n越界,则函数行为未定义 |
c.at(n) | 返回下标为n的元素的应用,如果下标越界,则抛出out_of_range异常 |
- 在容器中访问元素的成员函数返回的都是引用。我们可以用来改变元素的值:
if (!c.empty(){
c.front() = 42;
auto &v = c.back();
v = 1024; //改变c中的元素
auto v2 = c.back();
v2 = 0; //未改变c中的引用
}
9、删除元素
- 这些操作会改变容器的大小,所以不适用于array。
- forward_list有特殊版本的erase。
- forward_list不支持pop_back;vector和string不支持pop_front。
c.pop_back() | 删除c中尾元素,若c为空,函数行为未定义。函数返回void |
c.pop_front() | 删除c中首元素,若c为空,函数行为未定义。函数返回void |
c.erase( p ) | 删除迭代器p所指定的元素,返回一个指向被删元素之后元素的迭代器,若p指向尾元素,则返回尾后迭代器,若p是尾后迭代器,函数行为未定义 |
c.erase(beg,end) | 删除迭代器beg和end所指定范围内的元素,返回一个指向最后一个被删元素之后元素的迭代器,若e本身就是尾后迭代器,则函数也返回尾后迭代器 |
c.clear() | 删除c中所有的元素 |
10、特殊的forward_list操作
- 当添加或删除一个元素时,删除或添加的元素之前的那个元素的后继会发生改变。但在单向链表中,没有简单的方法来获取一个元素的前驱。
- forward_list并未定义insert、emplace和erase,而是定义了insert_after、emplace_after和erase_after的操作。
- forward_list定了了首前(off-the-beginning)迭代器,这个迭代器允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素。
lst.before_begin() | 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用 |
lst.cbefore_begin() | 返回一个const_iterator |
lst.insert_after(p,t) | 在迭代器p之后的位置插入元素t |
lst.insert_after(p,n,t) | 在迭代器p之后的位置插入n个元素t |
lst.insert_after(p,beg,end) | |
lst.insert_after(p,initlist) | |
emplace_after(p,args) | 使用args在p指定的位置之后创建一个元素,返回一个指向这个新元素的迭代器,若p为尾后迭代器,则函数行为未定义 |
lst.erase_after( p ) | 删除p指向的位置之后的元素 |
lst.erase_after(beg,end) | 删除从beg之后直到end之前的元素 |
- 当在forward_list中添加或删除元素时,我们必须关注两个迭代器–一个指向我们要处理的元素,另一个指向起前驱。
5 int main(){
6 forward_list<int> coll = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
7 auto pre = coll.before_begin();
8 auto cur = coll.begin();
9
10 while (cur != coll.end()){
11 if (0 == *cur % 2){
12 cur = coll.erase_after(pre);
13 }
14 else{
15 pre = cur;
16 ++cur;
17 }
18 }
19
20 for (const auto &elem : coll){
21 cout << elem << " ";
22 }
23 cout << endl;
24
25 return 0;
26 }
运行结果
1 3 5 7 9
11、改变容器大小
- 可以用resize来增大或缩小容器,与往常一样,array不支持resize。
- 如果当前大小小于所要求的大小,容器后部的元素会被删除,如果当前大小小于新大小,会将新元素添加到容器后部。
c.resize(n) | 调整c的大小为n个元素 |
c.resize(n,t) | 调整c的大小为n个元素 |
12、容器操作可能使迭代器失效
- 向容器中添加元素和从容器中删除元素的 操作可能会使容器元素的指针、引用或迭代器失效。一个失效的指针、引用或迭代器将不再表示任何元素。
- 使用失效的指针、引用或迭代器是一种严重的程序设计错误,很可能引起与未初始化指针一样的问题。
向容器添加元素后:
- 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。
- 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
- 对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
删除元素后:
- 指向删除元素的迭代器、指针和引用会失效。
- 对于list和forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。
- 对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果是删除deque的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响。
- 对于vector和string,指向被删元素之前元素的迭代器、引用和指针仍有效。
- 当我们删除元素时,尾后迭代器总是会失效。
example 1
//循环内,删除偶数元素,复制每个奇数元素
5 int main(){
6 vector<int> coll = {0,1,2,3,4,5,6,7,8,9};
7
8 auto pos = coll.begin();
9
10 while (pos != coll.end()){
11 if (*pos % 2){
12 pos = coll.insert(pos, *pos);
13 pos += 2;
14 }
15 else{
16 pos = coll.erase(pos);
17 }
18 }
19
20 for (const auto &elem : coll){
21 cout << elem << " ";
22 }
24 cout << endl;
23
24 return 0;
25 }
运行结果
1 1 3 3 5 5 7 7 9 9
example 2:不要保存end返回的迭代器
5 //灾难:此循环的行为是未定义的
6 auto begin = v.begin(),
7 end = v.end(); //保存尾迭代器的值是一个坏主意
8
9 while (begin != end){
10 ++begin; //向前移动begin,因为我们想在此元素之后插入元素
11 begin = v.insert(begin, 32); //插入新值
12 ++begin; //向前移动begin跳过我们刚刚加入的元素
13 }