一个容器就是一些特定类型对象的集合。顺序容器的顺序与元素加入容器时读的位置相对应。
1.顺序容器概述
array对象的大小是固定的。
- 确定使用哪种顺序容器
2.容器库概览
本小节介绍所有容器都适用的操作。每个容器都定义在一个头文件中,文件名与类型名相同。容器均定义为模板类,我们必须提供额外信息(如元素类型信息,容器大小,初始值)来生成特定的容器类型。
(1)迭代器
解引用运算符访问元素;递增运算符移动。所有迭代器都定义了递增运算符。forward_list不支持递减运算符。
仅用于string ,vector,deque和array。
-
迭代器范围
-
使用左闭合范围蕴含的编程假定
#include <iostream>
#include <vector>
using namespace std;
bool find1(vector<int>::iterator iter1, vector<int>::iterator iter2, int m) {
while (iter1 < iter2) {
if (*iter1 == m){
return true;
}
++iter1;
}
return false;
}
int main() {
vector<int> vec = { 1, 4, 7, 2, 8, 5, 9, 6, 3 };
cout << vec.size() << endl;
//cout << *vec.begin() << endl;
vector<int>::iterator iter1 = vec.begin()+2;
vector<int>::iterator iter2 = vec.begin()+7;
bool ans = find1(iter1, iter2, 6);
cout << ans << endl; //7,2,8,5,9
system("pause");
return 0;
}
(2)容器类型成员
反向迭代器、元素类型(value_type)、引用(reference或const_reference)等。
(3)begin和end成员
三种:begin,end;
cbegin,cend;
rbegin,rend;
与const指针和引用类似,可以将一个普通的iterator转换为对应的const_iterator,但反之不行。
可直接使用auto声明。
(4)容器定义和初始化
每个容器都定义了一个默认构造函数。
-
将一个容器初始化为另一个容器的拷贝
两种方法:直接拷贝整个容器
拷贝由一个迭代器对应指定的元素范围
-
列表初始化
对于除array之外的容器类型,初始化列表还隐含地指定了容器的大小:容器将包含与初始值一样多的元素。
vector<int> vec = { 1, 4, 7, 2, 8, 5, 9, 6, 3 };
- 与顺序容器大小相关的构造函数
顺序容器(array除外)支持(容器大小,初始值)的初始化方法。
注:关联容器不支持。
vector<int> a(10); //定义了10个整型元素的向量,但没有给出初值,其值是不确定的 {0,0,0,0,0,0,0,0,0,0}
vector<int> b(10, 1); //{1,1,1,1,1,1,1,1,1,1}
- 标准库array具有固定大小
与内置数组一样,标准库array的大小也是类型的一部分。当定义一个array时,除了指定元素类型,还要指定容器大小。
虽然我们不能对内置数组类型进行拷贝或对象赋值操作,但array并无此限制。只要满足array的元素类型和大小一样即可。
(5)赋值和swap
赋值运算符将其左边容器中的全部元素替换为右边容器中元素的拷贝。
标准库array类型允许赋值,赋值号左右两边的运算对象必须具有相同的类型。但是array类型不支持assign,也不允许花括号包围的值列表进行赋值。
- 使用assign
赋值运算符要求赋值号左右两边的运算对象必须具有相同的类型。assign允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。
传递给assign的迭代器不能指向调用assign的容器。 - 使用swap
swap操作交换两个相同类型容器的内容。
array的swap操作:真正交换两容器的元素,交换两个array所需的时间与array中元素的数目成正比。两个array在swap操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另一个array中对应元素的值进行了交换。
其他容器的swap操作:元素本身并未交换,只是交换了两个容器的内部数据结构。除string外,指向容器的指针、引用和迭代器在swap操作之后都不会失效。它们仍指向swap操作之前所指向的那些元素。但是,在swap之后,这些元素已经属于不同的容器了。但是,对string调用swap会导致迭代器、引用和指针失效。
(6)容器大小操作
size:返回容器中元素的数目
empty:判断size是否为0
max_size:返回一个大于或等于该类型容器所能容纳的最大元素数的值
除forward_list不支持size外,所有容器都支持上述3个操作。
(7)关系运算符
每个容器类型都支持相等运算符(==和!=);除了无序关联容器外的所有容器都支持关系运算符(>,>=,<,<=)。关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。
容器的比较机制:
- 容器的关系运算符使用元素的关系运算符完成比较
只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。
3.顺序容器操作
(1)向顺序容器添加元素
除array外,所有标准库容器都提供灵活的内存管理。在运行时可以动态添加或删除元素来改变容器大小。
-
push_back
vector,deque,list,string支持
当我们用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。随后对容器中元素的任何改变都不会影响原始对象。 -
push_front
list,forward_list,deque支持 -
在容器的特定位置添加元素-insert
vector,deque,list,string支持
基本形式:vector.insert(迭代器,元素);
insert解决了push_front的局限 -
插入范围内元素
基本形式:vector.insert(迭代器,个数,元素);
vector.insert(迭代器,迭代器2,迭代器2);
vector.insert(迭代器,{初始化列表});
如果我们传递给insert一对迭代器,它们不能指向添加元素的目标容器。 -
使用insert的返回值
在新标准下,接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器。
-
emplace操作
emplace_front – push_front
emplace – insert
emplace_back – push_back
区别:当调用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。
(2)访问元素
包括array在内的每个顺序容器都有一个front成员函数,除了forward_list之外的所有顺序容器都有一个back成员函数。
-
访问成员函数返回的是引用
在容器中访问元素的成员函数返回的都是引用。如果容器是一个const对象,则返回值是const的引用。如果我们使用auto变量来保存这些函数的返回值,并且希望使用此变量来改变元素的值,必须将变量定义为引用类型auto &。
-
下标操作和安全的随机访问
使用下标运算符.:程序员必须保证下标有效,编译器并不检查下标是否在合法范围内。
使用at成员函数:如果下标越界,at会抛出一个out_of_range异常。
(3)删除元素
-
pop_front和pop_back成员函数
vector和string不支持pop_front,forward_list不支持pop_back。这些操作返回void。 -
从容器内部删除一个元素-erase
成员函数erase从容器中指定位置删除元素。
基本形式:vector.erase(迭代器);
返回指向删除的元素之后位置的迭代器。
-
删除多个元素
基本形式:vector.erase(迭代器,迭代器);
返回指向删除的最后一个元素之后位置的迭代器。
删除所有元素:vector.clear();
vector.erase(vector.begin(),vector.end());
(4)特殊的foward_list操作
forward_list:单向链表。当添加或删除一个元素时,添加或删除的元素之前的那个元素的后继会发生改变。
例:从forward_list删除奇数元素:
(5)改变容器大小
如果容器保存的是类类型元素,且resize向容器添加新元素,则我们必须提供初始值,或者元素类型必须提供一个默认构造函数。
(6)容器操作可能使迭代器失效
- 编写改变容器的循环程序
添加/删除vector、string或deque元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。程序必须保证每个循环步中都更新迭代器、引用或指针。 - 不要保存end返回的迭代器
添加或删除元素的循环程序必须反复调用end,而不能在循环之前保存end返回的迭代器。
4.vector对象是如何增长的
vector连续存储,分配空间问题,采用减少容器空间重新分配次数的策略。
-
管理容器的成员函数
只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量。
resize成员函数只改变容器中元素的数目,而不改变容器的容量。
shrink_to_fit只是一个请求,标准库并不保证退还内存。 -
capacity和size
容器的size指它已经保存的元素的数目;
capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。
5.额外的string操作
(1)构造string的其他方法
(2)改变string的其他方法
除了接受迭代器版本的inesrt和erase版本外,string还提供了接受下标的版本。
//通过n个字符改变
s1.insert(s1.size(), 5, '?'); //"Hello world!!!?????"
s1.erase(s1.size() - 6, 4); //从倒数第6位开始,删除4个元素:"Hello world!!??"
//通过数组改变
const char *cp = "Stately,plump Buck";
s1.assign(cp, 7); //"Stately"
s1.insert(s1.size(), cp + 7); //"Stately,plump Buck"
//通过字符串改变
s2 = "haha shhh";
s1.insert(s1.size(), s2); //"Stately,plump Buckhaha shhh"
s1.insert(8, s2, 0,5); //"Stately,haha plump Buckhaha shhh"
- append和replace函数
//append和replace
s2.append("!!!"); //"haha shhh!!!"
s2.append(s1); //"haha shhh!!!Stately,haha plump Buckhaha shhh"
s2.append(s1, 4,6); //插入4开始的6个字符:"haha shhh!!!Stately,haha plump Buckhaha shhhely,ha"
s2.replace(12, 38, "OK"); //"haha shhh!!!OK"
- 改变string的多种重载函数
(3)string搜索操作
string类提供了6个不同的搜索函数,每个函数都有4个重载版本。每个搜索操作都返回一个string::size_type值(unsigned_int),表示匹配发生位置的下标。
-
普通搜索
-
指定在哪里开始搜索
-
逆向搜索
(4)compare函数
(5)数值转换
6.容器适配器
除顺序容器外,标准库还定义了三个顺序容器适配器:stack,queue和priority_queue。
- 定义一个适配器
每个适配器都定义两个构造函数:默认构造函数和拷贝构造函数。
默认情况下,stack和queue是基于deque实现的,priority_queue是基于vector实现的。我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型。
所有适配器都要求容器具有添加和删除元素的能力,因此,适配器不能构造在array之上。
适配器 | 操作 | 可基于 | 不可基于 |
---|---|---|---|
stack | push_back,pop_back,back | vector,list,deque等 | array,forward_list |
queue | back,push_back,front,pop_front | list,deque | vector |
priority_queue | front,push_back,pop_back,随机访问 | vector,deque | list |
-
栈适配器
stack类型定义在stack头文件中。每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作。我们只可以使用适配器操作,而不可以使用底层容器类型的操作。
-
队列适配器
queue和priority_queue适配器定义在queue头文件中。