容器
容器种类
vector
可变大小数组list
双向链表forward_list
单向链表deque
双向队列array
固定大小数组string
数组字符串,与vector
类系
每一种容器都有自身的优缺点,下面总结下
数组类容器:vector
array
string
这一类容器的优点是支持随机访问,访问速度比较快,但是要执行插入或者删除操作时效率低
链表类:list
forward_list
这一类数组的优缺点与之相反,不支持随机访问,如果想访问特定数据只能遍历,但是在执行插入和删除时比较方便
队列类:deque
双向队列,支持随机访问,在队尾或者队首删除或者插入元素时效率较快
特别的需要注意array
容器是固定大小数组,和vector
不同的是array
不能再次添加或者删除元素也就是不能使用insert
和erase
方法
容器操作两个重要方法
swap
1 | // 函数原型 |
作用是交换两个容器之间的元素,使用swap
方法比拷贝速度要快,我的理解是swap
函数可能是直接在底层交换的两个函数的指针,并没有真正的进行交换,只是我的猜测后面还需要学习STL
源代码来深入了解这里面的细节
assign
使用assign
方法进行赋值运算,可以直接从一个列表进行赋值,也可以将一个容器中的值赋值到另一个容器中,当然,这两个容器中存放的类型可以不相同,但是要求二者进行强制转换操作,例如:
int
-> double
double
-> int
const char*
-> string
上面这些可以进行强制转换的操作都可以,但是如果是两个容器之间直接进行拷贝,那么容器中存放值的内容必须相等
1 | std::vector<double> dvec = {1, 2, 3, 4, 5, 6}; |
forward_list的特殊性
因为forward_list
是单向链表,删除或者添加一个元素最多会涉及到三个元素(当前元素,元素前驱,元素后继),所以他的插入和删除函数名字都带有after
1 | lst.before_begin() |
这是因为在单向链表中插入或删除元素需要获得该元素的前驱,使用前驱元素来删除目标元素,所以会带有after
1 | // 删除forward_list中的奇数元素 |
1 | void find_str(std::forward_list<std::string>& slst, std::string& str1, std::string& str2) { |
改变容器大小
使用resize
方法改变容器大小,需要注意resize
方法不适用与array<>
容器
1 | std::list<int> ilist(10, 42); // 初始化list容器大小为10 |
迭代器
迭代器失效问题
向容器中添加,或者删除元素的操作可能会使指向容器的指针,引用或者迭代器失效,这种失效类似与指针的非法访问问题。对于这些操作我的理解是在进行添加或者删除操作过后容器在内存中存储的位置可能会发生改变,导致原本指向容器的指针现在指向的是一片不能访问的内存区域。这就可以理解为什么像之前的inster
或者erase
等函数在添加或者删除容器元素后会返回一个迭代器,意义就在与可以更新容器迭代器。
下面展示一个关于迭代器更新的程序
1 | std::vector<int> vi = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
不要保存end()返回的迭代器
这个问题还是比较好理解的,因为容器的大小是实时更新的,加入将元素插入容器的末尾,那么end
方法返回的迭代器不同了
1 | //错误程序实例 |
上面这段代码在运行时会崩溃,原因就是在在插入新元素后没有跟新begin
迭代器,以及直接保存了end
返回的迭代器,正确的写法应该是使用vi.end()
来表示容器末尾,然后使用insert
返回的新迭代器来更新begin
迭代器
1 | // 正确遍历方法 |
vector容器增长问题
对于vector
中的reserve
,size
,capacity
这几种方法比较模糊,记录一下区别
- size:返回的是当前容器中元素的个数
- capacity:返回容器在不进行扩容情况下最大能容纳的元素个数
- reserve:分配至少能容纳n个元素的空间
- shrink_to_fit:将最大容纳个数变成当前容器中的元素个数(size个数)
但是虽然容器最大容纳个数由capacity
决定,但是在访问时只能访问size
的大小,超过size
大小会造成越界