欢迎访问我的博客首页。
容器与容器适配器
1. 容器
顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。关联容器中的元素是按关键字来保存和访问的。
1. 容器共有的函数
函数 | = | == 和 != | insert() 和 erase() | begin() 和 end() | size() | empty() | clear() |
---|---|---|---|---|---|---|---|
操作 | 赋值 | 插入、删除 | 生成指向首尾的迭代器 | 元素个数 | 是否有元素 | 删除所有元素 |
2. 顺序容器
顺序容器中只有链表和前向链表是链式存储,其它都是连续存储。双端队列逻辑上连续,实际上分段连续。
容器 | 名称 | 实现 | 特点 |
---|---|---|---|
vector | 向量 | 连续空间 | 尾部增删快 |
deque | 双端队列 | 连续空间 | 首尾增删快 |
list/forward_list | 双向/单向链表 | 链表 | 不能随机访问 |
array | 数组 | 固定大小 | 不能增删 |
string | 字符串 | 连续空间 | 尾部增删快 |
2.1 string
1. 重要成员函数
size_t string::find(string) // 返回第一个匹配位置的下标;如果找不到,返回 npos。
string string:substr(a, len) // 返回 str[a] 开头长度为 len 的子串。
2. 删除空格
string::erase 函数的参数可以是下标也可以是迭代器,迭代器只能是 string 类型。参数是下标时,str::erase(start, len) 的作用是从 str[start] 开始删除 len 个字符,如果没有第二个参数会删除 str[start] 及后面所有字符。参数是迭代器时,str::begin(begin, end) 的作用是从 begin 指向的位置删除到 end 指向位置的前一个位置,如果只有一个参数就只删除一个字符。
std::remove_if 函数的参数是迭代器,迭代器可以是任何类型。std::remove_if(begin, end, check) 的作用是从两个迭代器指向的区间内找出不满足 check 函数的元素放在第一个迭代器开始的位置。
std::remove 函数和std::remove_if 函数类似。
// 1.string::erase。
string str = "0123456789";
string::iterator it = str.begin();
str.erase(1, 3); // str = "0456789"。
str.erase(it, it + 3); // str = "3456789"。
// 2.std::remove_if。
bool check(char c) { return c == '0'; }
string str = "010203456789";
string::iterator it = std::remove_if(str.begin(), str.end(), check);
/* str = "123456789789"。 it 指向第 2 个 '7',因为筛选后的字符 "123456789" 都在 it 前面放着。*/
// 3.std::remove。
string str = "010203456789";
string::iterator it = std::remove(str.begin(), str.end(), '0');
/* str = "123456789789"。 it 指向第 2 个 '7',因为筛选后的字符 "123456789" 都在 it 前面放着。*/
使用上面的函数删除字符串前部、后部、所有空格的方法如下:
// 1.1使用下标删除前方空格。
str.erase(
0,
str.find_first_not_of(" ")
);
// 1.2使用下标删除后方空格。
str.erase(
str.find_last_not_of(" ") + 1
);
// 2.1使用迭代器删除前方空格。
str.erase(
str.begin(),
std::find_if(str.begin(), str.end(), std::not1(std::ptr_fun(::isspace)))
);
// 2.2使用迭代器删除后方空格。
str.erase(
std::find_if(str.rbegin(), str.rend(), std::not1(std::ptr_fun(::isspace))).base(),
str.end()
);
// 3.1使用下标删除所有空格。
int index = 0;
while ((index = str.find(' ', index)) != string::npos)
str.erase(index, 1);
// 3.2使用迭代器和remove_if删除所有空格。
string::iterator it = std::remove_if(str.begin(), str.end(), isspace);
str.erase(it, str.end());
// 3.3使用迭代器和remove删除所有空格。
std::string::iterator it = std::remove(str.begin(), str.end(), ' ');
str.erase(it, str.end());
3. 关联容器
有序的关联容器使用红黑树存储,无序的关联容器使用哈希表存储。
容器 | 实现 | 特点 |
---|---|---|
map、set | 红黑树 | 有序 |
multimap、multiset | 红黑树 | 有序、关键字可重复 |
unordered_map、unordered_set | 哈希表 | 无序 |
unordered_multimap、unordered_multiset | 哈希表 | 无序、关键字可重复 |
3.1 unordered_map
因为无序容器是哈希表,所以 unordered_map 键的顺序既不是按递增顺序存储,也不是按插入顺序存储,而是按哈希值存储。也就是说,unordered_map 是无序的,且插入顺序与遍历顺序不一定相同。
void unordered_map<T1, T2>::inster({T1, T2}) // 插入键值对。
T2 unordered_map<T1, T2>::[T1] // 返回键对应的值。
iteration unordered_map<T1, T2>::find(T1) // 找不到返回unordered_map<T1, T2>::end()。
4. 容器适配器
容器、迭代器和函数都有适配器。
4.1 栈
栈可以抽象成一个水杯,杯口是栈顶,杯底是栈底。
void stack<T>::push() // 在栈顶添加元素。
T stack<T>::top() // 返回栈顶元素但不出栈。
void stack<T>::pop() // 删除栈顶元素。
size_t stack<T>::size() // 返回元素个数。
bool stack<T>::empty() // 是否为空。
4.2 队列
队列可以抽象成一个队伍,面向的方向是队首,背向的方向是队尾。
T queue<T>::front() // 返回队首元素但不删除。
void queue<T>::pop() // 删除队首元素。
void queue<T>::push() // 在队尾添加元素。
T queue<T>::back() // 返回队尾元素但不删除。
size_t queue<T>::size() // 返回元素个事。
bool queue<T>::empty() // 队列是否为空。
5. 迭代器
迭代器能不能加减 1 与数据结构是不是顺序存储有关。比如顺序容器中链表和前向链表不是连续存储的,所以它们的迭代器不能加减 1。
5.1 删除元素引起的迭代器失效
下面是删除顺序容器和关联容器的元素时,迭代器的更新方法。√ 表示正确的更新方法,× 表示错误或不受支持的更新方法。
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <forward_list>
#include <array>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <set>
using namespace std;
template<typename T>
void print(T& data) {
for (auto& x : data) {
cout << x << ' ';
}
cout << endl;
}
void test_vector() {
vector<int> data = { 1,2 };
data.push_back(3);
for (auto it = data.begin(); it != data.end();) {
if (*it == 3) {
it = data.erase(it);
}
else {
it++;
}
}
print(data);
for (auto it = data.begin(); it != data.end(); it++) {
if (*it == 2) {
it = data.insert(it, 4);
it++;
}
}
print(data);
}
void test_deque() {
deque<int> data = { 1,2 };
data.push_back(3);
for (auto it = data.begin(); it != data.end();) {
if (*it == 3) {
it = data.erase(it);
}
else {
it++;
}
}
print(data);
for (auto it = data.begin(); it != data.end(); it++) {
if (*it == 2) {
it = data.insert(it, 4);
it++;
}
}
print(data);
}
void test_list() {
list<int> data = { 1,2 };
data.push_back(3);
for (auto it = data.begin(); it != data.end();) {
if (*it == 3) {
it = data.erase(it);
//data.erase(it++);
}
else {
it++;
}
}
print(data);
for (auto it = data.begin(); it != data.end(); it++) {
if (*it == 2) {
it = data.insert(it, 4);
it++;
}
}
print(data);
}
void test_forward_list() {
forward_list<int> data = { 2,3 };
data.push_front(1);
for (auto it = data.before_begin(); ;) {
auto work = std::next(it);
if (work == data.end()) {
break;
}
if (*work == 3) {
data.erase_after(it);
}
else {
it++;
}
}
print(data);
for (auto it = data.begin(); it != data.end(); it++) {
if (*it == 1) {
it = data.insert_after(it, 4);
//data.insert_after(it, 4);
}
}
print(data);
}
void test_array() {
array<int, 3> data = { 1,2,3 };
print(data);
}
void test_string() {
string data = "123";
for (auto it = data.begin(); it != data.end(); ) {
if (*it == '3') {
it = data.erase(it);
//data.erase(it);
}
else {
it++;
}
}
print(data);
for (auto it = data.begin(); it != data.end(); it++) {
if (*it == '2') {
it = data.insert(it, '4');
it++;
}
}
print(data);
}
void test_map() {
unordered_map<int, char> data;
data.insert(pair<int, char>(1, 'a'));
data.insert(pair<int, char>(2, 'b'));
data.insert(pair<int, char>(3, 'c'));
for (auto it = data.begin(); it != data.end();) {
if (it->first == 3) {
it = data.erase(it);
//data.erase(it++);
}
else {
it++;
}
}
for (auto x : data) {
cout << x.first << ' ';
}
cout << endl;
}
void test_set() {
unordered_set<int> data = { 1,2,3 };
for (auto it = data.begin(); it != data.end();) {
if (*it == 3) {
it = data.erase(it);
//data.erase(it++);
}
else {
it++;
}
}
print(data);
}
对无序容器使用 insert 插入时,无法指定插入位置。
迭代器更新 | vector | deque | list | forward_list | array | string | 关联容器 |
---|---|---|---|---|---|---|---|
it = data.erase(it) | √ | √ | √ | × | × | √ | √ |
data.erase(it++) | × | × | √ | × | × | × | √ |
data.erase(it) | × | × | × | × | × | √ | × |
支持 find | × | × | × | × | × | √ | √ |
data.erase_after(it) | × | × | × | √ | × | × | × |
由于 erase 函数返回指向被删除元素下一个位置的迭代器,所以使用 it = data.erase(it) 是最安全的方法。