容器与容器适配器

本文探讨了C++中容器的种类(如vector、deque、list等)、顺序与关联容器的区别,重点讲解了string的操作技巧,以及unordered_map的特性。此外,还介绍了容器适配器的应用,如栈和队列的实现,以及迭代器在删除元素后如何正确更新。
摘要由CSDN通过智能技术生成

欢迎访问我的博客首页


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 插入时,无法指定插入位置。

迭代器更新vectordequelistforward_listarraystring关联容器
it = data.erase(it)××
data.erase(it++)×××××
data.erase(it)××××××
支持 find×××××
data.erase_after(it)××××××

  由于 erase 函数返回指向被删除元素下一个位置的迭代器,所以使用 it = data.erase(it) 是最安全的方法。

6. 参考


  1. 容器都有的操作
  2. erase的两种常见错误
  3. 双端队列的底层实现
  4. 去掉 string 前后的空格
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值