1、容器的基本操作
下图列出了,所有容器(除特殊标注的容器外)都支持的【基本】操作:
2、迭代器
2.1 迭代器支持的 公共 操作如下:
2.2 支持迭代器的容器如下:
2.3 vector
和 string
的【迭代器】支持的运算如下:
-
list
的【迭代器】不支持 关系运算符,只支持++
--
==
!=
,也不支持+
-
一个整数 -
forward_list
的迭代器也不支持 关系运算符,只支持++
==
!=
,连--
都不支持,更不支持+
-
一个整数
3、创建容器对象
有如下几种定义和初始化方式:
【值初始化】:由容器中所装【元素的类型】决定如何初始化,如果是
int
,则初始化0;如果是某种类类型,则元素由类默认初始化。
3.1 拷贝初始化
拷贝初始化有两种:
- 一种是直接【拷贝整个容器】,这要求,两者的【容器类型】和【元素类型】都一致。
- 一种是把某个容器【一段区间内的元素】拷贝到新容器,这时,两者的【容器类型】和【元素类型】都可以不一致。只要【元素类型】可以转换成【新容器的元素类型】即可。
因此,下面的拷贝初始化中,中间两条会报错:
3.2 大小参数初始化
有两种:
- 一种接收两个参数,第一个是大小
n
,第二个是值t
,则容器里有n
个元素t
。 - 一种只接收一个参数:大小
n
,容器初始容纳n
个元素。元素的值根据元素的类型进行初始化,如果是int
则初始化为0,如果元素是 类 类型,就用默认构造函数初始化,没有默认构造函数?那不行!!!必须人为指定初始值,也就是用第一种方法初始化。
目前已知的关联容器有:map,set
3.3 特殊的array
- 必须指定大小,像这样
array<int, 10> arr;
- 如果元素是 类 类型,则这个类【必须有默认构造函数】
- 和数组不同,可以用一个
array
给另一个array
赋值
array<int, 3> arr1{ 1, 2, 3 };
array<int, 3> arr2 = arr1;
4、容器赋值运算
4.1 assign()
替换
两种用法:
- 传入一对迭代器范围,用范围内元素的拷贝替换容器原有的元素。拷贝的元素要能转换成目标容器中的元素。
- 传入一个大小
n
,元素值t
,用n
个t
替换原有的元素。
4.2 swap()
交换的容器 类型和元素类型都得一样。
【swap
的原理】:除arrat
外,swap
不对任何元素进行拷贝,删除或插入,他交换的是两个容器内指向他们的数据结构的指针,而元素在内存中的地址保持不变。
因此,除string
外,只想容器的迭代器、引用和指针在swap
操作之后都不会失效。他们仍然指向原来的元素,只是已经(物是人非了)属于不同的容器了。
【两个例外】:
- 对
string
调用swap
会导致迭代器、引用和指针失效。 swap
会真正交换array
中元素的值,所以交换array
所需的时间和里面的元素数量成正比。对于array
,swap
操作后,指针引用和迭代器所绑定的元素保持不变,但元素值已经与另一个array
对应的元素值交换了。
swap
交换的两个array
的大小必须一样。
5、容器的关系运算符
每个容器都支持 ==
和 !=
,除了【无序关联容器】外的所有容器都支持 <
>
>=
<=
。关系运算符左右 容器类型,元素类型【都得相同】。
- 大小相等,元素相同,则==
- 大小不同,小的是大的 的前缀, 小的 < 大的
- 大小不同,无前缀,大小取决于第一个不相同的元素
【注:两个容器的容量(capacity)不会影响关系运算符的判定】
6、向顺序容器【添加】元素
表头列出了不支持部分操作的容器。
-
向一个
vector
string
或deque
插入元素会使所有指向容器的迭代器、引用和指针失效。 -
调用
insert
将元素插入到vector
deque
string
的任何位置都是合法的,但是插入到有的位置会很耗时 -
虽然
vector
不支持push_front()
,但可以通过insert
实现同样的功能。 -
insert
还有两个重载版本,第一个,可以在指定位置插入n
个值为t
的元素;第二个,可以在指定位置插入 指定迭代器区间内 的元素。
6.1 insert
的返回值
新标准下,insert
的返回值指向第一个和新加入元素的迭代器,如果insert
函数没有插入任何元素,则返回调用insert
函数的【第一个参数】。
这代表什么???我们可以用insert
的返回值,在【一个位置】反复插入元素。
也能实现类似push_front()
的功能,代码如下:
list<string> list1;
auto it = list.begin();
while (cin >> word)
it = list.insert(it, word); 等价于调用push_front,实现了头插
6.2 直接【构造新元素】,并插入
新标准引入三个新成员emplace_front
,emplace_back
,emplace
,
他们分别对应push_front
,push_back
,insert
。
但不同的是,他们都是根据传入的参数,现调【构造函数】,创建一个对象,压入容器中。
如下代码所示:
class X
{
public:
X() = default;
X(int a) { }
X(int a, double b) { }
X(int a, double b, char c) { }
};
int main()
{
vector<X> xvec;
下面四句分别对应四种构造函数,emplace使用这些构造函数创建对象,压入xvec容器中。
xvec.emplace_back();
xvec.emplace_back(1);
xvec.emplace_back(1, 3.14);
xvec.emplace_back(1, 3.14, 'a');
xvec.push_back( X(1, 3.14, 'a') ); 显式创建了一个临时匿名对象,压入名为xvec的容器中
cout << xvec.size() << endl; 程序会输出 5
return 0;
}
emplace
成员使用传入的参数在容器管理的内存空间中直接构造元素。
传递给emplace
函数的参数必须与元素类型的【构造函数相匹配】。
7、【访问】元素
- 不能递减
forward_list
的迭代器,且forward_list
不支持back()
- 注意
back()
,front()
,[]
,at()
返回的【都是】引用,若容器是const
的,则返回const
的引用
8、删除元素
-
删除
deque
中【除首尾位置之外的】任何元素都会使所有迭代器、引用和指针失效。 -
指向
vector
或string
中【删除点之后】位置的迭代器、引用和指针都会失效。 -
vector
和string
不支持pop_front
-
forward_list
不支持pop_back
-
erase
返回被删元素之后的迭代器
9、特殊的forward_list
forward_list
是单向链表,所以性质比较特殊。
- 当然啦,
forward_list
也有begin()
和end()
成员,也返回指向首元素和尾后元素的迭代器。 forward
没有普通的insert
,emplace
,erase
成员- 在
forward_list
中插入、删除元素既需要改元素的迭代器,也需要前驱迭代器。 erase_after
和erase
的返回值意义相同,而insert_after
和insert
的返回值意义不同
10、改变容器大小
- 同样的,如果元素是 类 类型,则要么提供初始值,要么该类有默认构造函数。
11、容器操作使迭代器(指针,引用)失效问题
11.1 添加造成的影响
- 对于
vector
和string
:如果添加导致【存储空间被重新分配】,则全部迭代器,指针和引用失效。如果为重新分配,指向【插入位置之前】的迭代器,指针和引用仍有效,之后的全部失效。 - 对于
deque
: -
- 插入到【首尾之外】的位置,【所有迭代器,指针和引用】失效。
-
- 若插入到【首尾位置】,所有【迭代器失效】,只想存在的元素的【引用和指针不会失效】。
- 对于
list
和forward_list
:随便添加,所有迭代器(包括尾后和首前),指针和引用都有效
11.2 删除造成的影响
- 对于
list
和forward_list
:除删除元素的,其他所有迭代器,指针和引用都有效。 - 对于
deque
: -
- 在【首尾之外】的位置删除,所有迭代器,指针和引用【都失效】;
-
- 删除【尾】元素,尾后迭代器失效,其他【都不受影响】。
-
- 删除【首】元素,首迭代器失效,其他【都不受影响】。
- 对于
vector
和string
:被删元素之前的迭代器,指针和引用仍有效,之后的全失效。
必须保证每次改变容器的操作之后都正确的【重新定位迭代器】。
更不能将这个end
用作while
循环结束条件。
12、vector
的内部实现 简析
12.1 管理容量的成员函数
reserve(n)
调用reserve
只会增大容器的内存空间,也就是说,若reserve
请求的大小小于当前容量,不会退回内存空间。事实上,编译器实际分配的内存空间,可能会比reserve
请求的还大。
shrink_to_fit
请求,没错,是请求编译器缩小容器的内存空间到当前的size()
大小,当然编译器可以忽略这个请求。也就是说调用shrink_to_fit
也不一定保证退回内存空间。
resize
resize
只改变容器的元素数目,并不改变容量
只要插入操作没有超过vector
的容量,vector
就不能重新分配空间。
13、string
的特殊操作
13.1 构造和拷贝string
当从一个const char*
创建string
时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。要是const char*
不是以空字符结尾的,那你必须还要提供一个计数值,告诉拷贝多少个。
如果你既不传计数值,也不以空字符结尾,或者传的计数值大于数组长度,都会报错。如下所示:
const char* c1 = "hello world!!!";
char c2[] = { 'H', 'i' };
string s1(c1);
string s2(c2, 2);
string s3(c2); 这就不对了,虽然编译器不会报错,但输出s3会输出乱码
编译器会一直往后找,直到找到 \0 为止
substr
操作:
substr
操作返回一个string
,它是调用该成员的string
的一部分或全部的拷贝。可以传递给substr
一个可选的开始位置和计数值。开始位置不能超过string
大小,计数值可以无限大,但顶多拷贝到最后一个字符。
string s2 = s1.substr(2, 1e5);
string
的insert
和 erase
能够接收下标:
s.insert(size(), 5, '!'); 在s末尾插入5个感叹号
s.erase(s.size() - 5, 5); 从s删除最后5个字符
C++是存在尾后下标的,和end()
指向相同的位置。
能接受C风格字符数组的insert
和 assign
版本:
const char* cp = "Stately, plump Buck";
s.assign(cp, 7); s == "Stately"
s.insert(s.size(), cp + 7); s == "stately, plump Buck"
对于调用insert
的代码:我们的意图是将字符插入到s[size()]
处(不存在的)元素之前的位置。在此例中,我们将cp
开始的7个字符(至多到结尾空字符之前)拷贝到s 中。
我们也可以指定将来自其他string
或子字符串的字符插入到当前string
中或【赋予】当前string
:
strign s = "some string", s2 = "some other string";
s.insert(0, s2); 在s中位置0之前插入s2的拷贝
s.insert(0, s2, 0, s2.size()); 在s[0]之前插入s2中s2[0]开始的s2.size()个字符
append
和replace
函数:详请参考《C++Primer》第323页
13.2 字符串搜索操作《C++Primer》第325页
- 搜索操作返回的都是
unsigned
类型,所以建议使用auto
接收函数的返回值
13.3 compare
函数
13.4 数字 与 字符串 转换
14、容器适配器
《C++Primer》第329页