【C++】STL知用篇之容器

[C++]STL知用篇之容器


前言

容器,置物之所也。


一个容器就是一些特定类型对象的集合。根据“数据在容器中的排列”特性,可将容器分为序列式(sequence)和关联式(associative)两种。

一、顺序容器

序列式容器/顺序容器(sequential container)——保存相同类型对象有序集合的类型,顺序容器中元素可以通过其位置来访问,其特点是元素可序(ordered)但未必有序(sorted)。

1.1顺序容器概述

顺序容器类型容器描述
vector可变大小数组,支持快速随机访问,尾部之外位置插入或删除元素可能很慢
deque双端队列,支持快速随机访问,头尾插入/删除速度很快
list双向链表,只支持双向顺序访问,在任何位置插入或删除速度都很快
forward_list单向链表,只支持单向顺序访问,在任何位置插入或删除元素速度都很快
array数组,支持快速随机访问,不能添加或删除元素
string字符串,与vector类似,但专门用于存储字符,随机访问块,在尾部插入/删除很快
  • string与vector都是将元素保存在连续的内存空间中,添加元素时可能需要分配新的存储空间,再将元素移动到新的存储空间中。
  • list与forward_list的设计目的是使得在任何位置添加或删除元素很快速,但作为代价,两个容器不支持随机访问
  • deque支持快速随机访问,在两端添加/删除元素很快,但在中间位置添加或删除元素可能很慢。

1.1.1 迭代器

标准容器类型上所有的迭代器都允许访问容器中的元素,访问的方式是通过解引用运算符来实现的,标准库容器的所有迭代器都定义了递增运算符。

获取迭代器描述
c.begin() , c.end()返回指向容器c首元素和尾元素之后的迭代器
c.cbegin(), c.cend()返回const_iterator
c.rbegin() , c.rend()返回指向容器c尾元素和首元素之前的迭代器
c.crbegin(), c.crend()返回const_reverse_iterator

迭代器范围:[begin, end)——左开右闭

1.1.2 容器的构造与初始化

容器的构造与初始化描述
C c默认构造函数。如果容器为array,按元素默认方式初始化;否则c为空
C c1(c2)c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
C c1 = c2c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
C c{a, b, c …}列表初始化,列表中元素类型必须与C的元素类型相容。
C c = {a, b, c …}列表初始化,列表中元素类型必须与C的元素类型相容。
C c(b, e)c初始化为迭代器b和e指定范围间元素的拷贝,范围内的元素类型必须与C的元素类型相容
顺序容器(不包含array)接受大小参数的构造函数描述
C seq(n)seq包含n个元素,这些元素进行了值初始化(string不适用)
C seq(n, t)seq包含n个初始化为t的元素

1.1.3 赋值与swap

容器的赋值描述
c1 = c2将c1中的元素替换为c2中元素的拷贝,c1与c2必须具有相同的类型
c = {a, b, c …}将c1中的元素替换为初始化列表中元素的拷贝(array不适用)
swap(c1, c2)交换c1与c2的元素,二者必须有相同的元素类型
c1.swap(c2)交换c1与c2的元素,二者必须有相同的元素类型

assign不适用于关联容器和array

assign描述
seq.assign(b, e)将seq中的元素替换为迭代器b与e所表示范围中的元素,迭代器b与e不能指向seq中的元素
seq.assign(il)将seq中的元素替换为初始化列表il中的元素
seq.assign(n, t)将seq中的元素替换为n个值为t的元素

1.1.4 容器大小操作

  • size():返回容器中元素数目
  • max_size():返回容器可保存最大元素数目
  • empty():若c中存储了元素返回false,否则返回true
  • capacity():不分配新的内存空间的前提下最多可以保存多少元素

关系运算符:
每个容器类型都支持(==与!=),除了无序关联容器外的所有容器都支持关系运算符(>, <, >=, <=)。比较两个容器实际上是逐元素比较。

  • 若两个容器大小相同且所有元素两两对应相等,则两个容器相等,反之不等
  • 若两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器
  • 若两个容器都不是另一个容器的前缀子序列,则比较结果取决于第一个不相等元素的比较结果

改变容器大小:
可以使用resize增大或缩小容器(array不适用)。如果当前大小大于所要求的大小,容器后部的元素会被删除;如果当前大小小于新的大小,会将新元素添加到容器后部。

resize描述
c.resize(n)调整c大小为n个元素,若n < c.size(),则多出的元素被丢弃;若必须添加新元素,对新元素值进行初始化
c.resize(n, t)调整c大小为n个元素。任何新添加的元素初始化为t

1.1.5 顺序容器操作

添加元素:

添加元素操作描述
c.push_back(t)在c尾部创建一个值为t的元素,返回void
c.emplace_back(args)在c尾部创建一个由args创建的元素,返回void
c.push_front(t)在c头部创建一个值为t的元素,返回void
c.emplace_front(args)在c头部创建一个由args创建的元素,返回void
c.insert(p, t)在迭代器p指向元素之前创建一个值为t的元素,返回指向新添加元素的迭代器;
c.emplace(p, args)在迭代器p指向元素之前创建一个由args创建的的元素,返回指向新添加元素的迭代器;
c.insert(p, n, t)在迭代器p指向元素之前创建n个值为t的元素,返回指向新添加的第一个元素的迭代器;若n为0返回p
c.insert(p, b, e)将迭代器b与e指定范围内元素插入p指向元素之前,b与e不能指向c中的元素,返回新添加第一个元素的迭代器,若范围为空返回p
c.insert(p, il)il是一个花括号包围的元素值列表。操作将这些元素插入p指向元素之前,返回新添加第一个元素的迭代器,若列表为空返回p

注意:

  1. 向一个vector、string或deque插入元素会使得所有指向容器的迭代器、引用、指针失效
  2. 容器元素是拷贝。但使用对象初始化容器或将一个对象插入容器时,实际上放入容器中的是对象值的一个拷贝,容器中的元素与提供值的对象之间没有任何关联。随后对容器中元素的任何改变不会影响到原始对象,反之亦然。

访问元素:
如果容器中没有元素,访问操作的结果是未定义的。

访问元素操作描述
c.back()返回尾元素的引用
c.front()返回首元素的引用
c[n]返回c中下标为n的元素的引用
c.at(n)返回下标为n的元素的引用

注意:

  1. at和下标操作只适用于string、vector、deque和array
  2. 访问成员函数(back, front, at 和下标)返回的是引用

删除元素:

删除元素操作描述
c.pop_back()删除c中尾元素,返回void
c.pop_front()删除c中首元素,返回void
c.erase( p)删除迭代器p所指向的元素,返回一个指向被删除元素之后元素的迭代器;若p指向尾元素,返回尾迭代器;若p是尾后迭代器,则函数行为未定义
c.erase(b,e)删除迭代器b与e所指定范围内的元素。返回指向最后一个被删除元素之后元素的迭代器;若e本身是尾后迭代器,则函数返回尾后迭代器
c.clear()删除c中所有元素,返回void

注意:

  1. 上述操作改变容器大小,不适合array
  2. forward_list有特殊版本erase,且不适合pop_back
  3. vector、string不支持pop_front

注意

容器操作可能使迭代器失效

向容器中添加或删除元素都可能使得指向容器元素的指针、引用和迭代器失效,一个失效的指针、引用或迭代器将不再表示任何元素

1.添加元素

  • 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针、引用都会失效;如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针、引用仍然有效,但是插入位置之后的将会失效。
  • 对于deque,插入到除首尾位置之外的任何位置将会导致迭代器、引用、指针失效。如果在首尾位置添加元素,则迭代器会失效,而指向存在的元素的引用和指针不会失效。
  • 对于list或forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针、引用仍然有效。

2.删除元素(指向被删除元素的迭代器、指针、引用显然失效,注意:删除元素后尾后迭代器总是失效的)

  • 对于list或forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、指针、引用仍然有效。
  • 对于deque,如果在首尾之外的任何位置删除元素,指向被删除元素之外的其它元素的迭代器、指针、引用也会失效;若删除的是尾元素,则尾后迭代器失效,其它迭代器、引用、指针不受影响;如果删除的是首元素,这些也不会受影响。
  • 对于vector和string,指向被删除元素之前的迭代器、指针、引用仍有效。

1.2 vector

  • vectors是可变大小数组,支持快速随机访问,在尾部之外的位置插入或删除元素可能很慢。
  • vector是动态空间,随着元素的加入,其内部机制会自行扩充空间来容纳新元素。为了降低空间配置的速度成本,vector实际配置空间大小比客户端需求的更大一些,以备将来可能的扩充。扩充的具体操作是当vector备用空间不足时,配置一块原大小两倍的空间,将原内容拷贝到新的空间并释放旧空间(配置新空间、复制数据、释放旧空间)

1.2.1 vector构造与初始化

  • C c:默认构造函数。如果容器为array,按元素默认方式初始化;否则c为空
  • C c1(c2): c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
  • C c1 = c2:c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
  • C c{a, b, c …}|:列表初始化,列表中元素类型必须与C的元素类型相容。
  • C c = {a, b, c …}: 列表初始化,列表中元素类型必须与C的元素类型相容。
  • C c(b, e):c初始化为迭代器b和e指定范围间元素的拷贝,范围内的元素类型必须与C的元素类型相容
  • C seq(n):seq包含n个元素,这些元素进行了值初始化
  • C seq(n, t) : seq包含n个初始化为t的元素
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
//默认构造函数构造一个空的容器
vector<string> v1;

//将一个容器初始化为另一个容器的拷贝(注意两个容器类型与元素类型必须匹配)
list<string> l_test = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};
vector<const char*> v_test1 = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};
vector<string> v_test2 = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};
vector<string> v2(l_test);//error,容器类型不匹配
vector<string> v2(v_test1);//error,容器中元素类型不匹配
vector<string> v2(v_test2);//正确,容器类型与元素类型均匹配

//列表初始化
vector<string> v3 = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};

//与顺序容器大小相关的构造函数
vector<int> v4(3, 9);//初始化有三个int元素,其值均为9
vector<int> v5(10);//10个元素,初始化为0
vector<string> v6(10);//10个元素,每个都为空的string

//容器的容器
vector<vector<string>> v7;//v7中的元素类型是string类型的vector

1.2.2 vector相关元素操作

  • v.push_back():将新元素插入vector尾部
  • v.pop_back():将尾端元素拿掉并调整大小(尾部标记前移一位)
  • v.erase(iterator):清除某位置上元素
  • v.insert(iterator, value):在某个位置插入元素
  • v.clear(): 清除容器中所有元素

测试代码如下:

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
	vector<string> friends;
	cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;//0 0
	//尾部插入元素
	friends.push_back("Chandler");
	friends.push_back("Monica");
	friends.push_back("Joey");
	friends.push_back("Rachel");
	friends.push_back("Ross");
	friends.push_back("Phoebe");
	
	//遍历元素
	vector<string>::iterator ite;
	for (ite = friends.begin(); ite != friends.end(); ite++) {
		cout << *ite << " ";
	}//Chandler Monica Joey Rachel Ross Phoebe
	cout << endl;	

	//测试size、capacity
	//尾部弹出元素
	friends.push_back("Alan");
	friends.push_back("Bob");
	friends.push_back("Cindy");
	cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 9 9
	friends.push_back("David");
	cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 10 13(备用空间不足,capacity扩充)
	friends.pop_back();
	friends.pop_back();
	friends.pop_back();
	friends.pop_back();
	cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 6  13(注意capacity没有减小)

	//查找容器中是否含有某个元素
	ite = find(friends.begin(), friends.end(), "Chandler");
	if (ite != friends.end()) {
		cout << *ite << endl; // Chandler
	}
	//插入元素
	ite = find(friends.begin(), friends.end(), "Rachel");
	friends.insert(ite, "Alan");
	cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 7 13
	ite = find(friends.begin(), friends.end(), "Alan");
	friends.erase(ite);
	cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 6 13

	//清除容器中所有元素
	friends.clear();
	cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;//0 13(注意:capacity没有改变)
	
	return 0;
}

1.3 list

  • list是一个双向链表,其迭代器具有前移、后移功能。list对空间的运用绝对精准,一点儿也不浪费,对于任何位置的元素插入与删除,永远是常数时间。
  • list节点在存储空间中不连续存在,不存在类似vector那样备用空间不足时重新配置、数据移动的操作,所以list插入前所有迭代器在插入操作之后仍有效

1.3.1 list构造与初始化

  • C c:默认构造函数。如果容器为array,按元素默认方式初始化;否则c为空
  • C c1(c2): c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
  • C c1 = c2:c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
  • C c{a, b, c …}|:列表初始化,列表中元素类型必须与C的元素类型相容。
  • C c = {a, b, c …}: 列表初始化,列表中元素类型必须与C的元素类型相容。
  • C c(b, e):c初始化为迭代器b和e指定范围间元素的拷贝,范围内的元素类型必须与C的元素类型相容
  • C seq(n):seq包含n个元素,这些元素进行了值初始化
  • C seq(n, t) : seq包含n个初始化为t的元素
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	//C c
	list<string> ls1;
	
	//C seq(n, t)
	list<int> ls2(10, 1);

	//C c(b, e)
	int arr[] = { 0, 1, 2, 3, 4, 5 };
	list<int> ls3(arr, arr + sizeof(arr) / sizeof(arr[0]));
	list<int> ls9(ls2.begin(), ls2.end());

	//C c1(c2)
	//C c1 = c2
	list<int> ls4(ls2);
	list<int> ls5 = ls2;

	//C c{a, b, c...}
	//C c = {a, b, c...}
	list<string> ls6 = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};      
	list<string> ls7{"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe" };

	//C seq(n)
	list<string> ls8(6);
	return 0;
}

1.3.2 list相关元素操作

  • ls.front():返回首部元素
  • ls.back():返回尾部元素
  • ls.push_front():首部插入元素
  • ls.push_back():尾部插入元素
  • ls.insert(ite, value):在指定位置插入值为value的元素
  • ls.erase(ite):删除指定位置上的元素
  • ls.clear():清除所有元素
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	
	list<string> friends;
	cout << friends.empty() << endl;//1
	//尾部插入元素
	friends.push_back("Chandler");
	friends.push_back("Monica");
	friends.push_back("Joey");
	friends.push_back("Rachel");
	friends.push_back("Ross");
	friends.push_back("Phoebe");
	cout << friends.empty() << endl;//0
	cout << "size: " << friends.size() << endl;//6
	cout << friends.front() << endl;//Chandler
	cout << friends.back() << endl;//Phoebe

	//首部插入
	friends.push_front("Bob");
	cout << friends.front() << endl;//Bob

	//遍历元素
	list<string>::iterator ite;
	for (ite = friends.begin(); ite != friends.end(); ite++) {
		cout << *ite << " ";
	}//Bob Chandler Monica Joey Rachel Ross Phoebe
	cout << endl;

	//插入元素
	ite = find(friends.begin(), friends.end(), "Monica");
	cout << *ite << endl;//Monica
	list<string>::iterator ite2 = friends.insert(ite, "Alan");
	cout << *ite << " " << *ite2 << endl;// Monica(注意插入元素后原迭代器仍然有效)  Alan

	//删除元素
	friends.erase(ite2);
	friends.erase(friends.begin());
	cout << friends.size() << endl;//6

	//清空
	friends.clear();

	//交换
	list<string> temp{ "hello", "world" };
	swap(friends, temp);
	for (ite = friends.begin(); ite != friends.end(); ite++) {
		cout << *ite << " ";
	}//hello world

	return 0;
}

1.4 deque

  • deque是双端队列,其中的元素可以通过下标访问。deque双端开口,可以分别在头尾两端进行元素的插入和删除,且在两端插入/删除元素不会导致重新分配空间。
  • deque没有容量(capacity)的概念,其由一段一段定量连续空间构成,一旦需要在deque头尾两端增加新的空间,便配置一段定量连续空间串接在deque的头端或尾端。
  • deque的随机访问元素效率不如vector,对于deque排序操作为了提高效率可先将deque中元素赋值到vector中进行排序,再将排序后结构传入deque。

1.4.1 deque构造与初始化

  • C c:默认构造函数。如果容器为array,按元素默认方式初始化;否则c为空
  • C c1(c2): c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
  • C c1 = c2:c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
  • C c{a, b, c …}|:列表初始化,列表中元素类型必须与C的元素类型相容。
  • C c = {a, b, c …}: 列表初始化,列表中元素类型必须与C的元素类型相容。
  • C c(b, e):c初始化为迭代器b和e指定范围间元素的拷贝,范围内的元素类型必须与C的元素类型相容
  • C seq(n):seq包含n个元素,这些元素进行了值初始化
  • C seq(n, t) : seq包含n个初始化为t的元素
#include <deque>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	//C c
	deque<string> dq1;

	//C seq(n, t)
	deque<int> dq2(10, 1);

	//C c(b, e)
	int arr[] = { 0, 1, 2, 3, 4, 5 };
	deque<int> dq3(arr, arr + sizeof(arr) / sizeof(arr[0]));
	deque<int>::iterator ite;
	for (ite = dq3.begin(); ite != dq3.end(); ite++) {
		cout << *ite << " ";
	}//0 1 2 3 4 5
	cout << endl;
	deque<int> dq4(dq2.begin(), dq2.end());
	for (ite = dq4.begin(); ite != dq4.end(); ite++) {
		cout << *ite << " ";
	}//1 1 1 1 1 1 1 1 1 1
	cout << endl;

	//C c1(c2)
	//C c1 = c2
	deque<int> dq5(dq4);
	deque<int> dq6 = dq4;


	//C c{a, b, c...}
	//C c = {a, b, c...}
	deque<string> dq7 = { "Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe" };
	deque<string> dq8{ "Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe" };

	//C seq(n)
	deque<int> dq9(6);
	for (ite = dq9.begin(); ite != dq9.end(); ite++) {
		cout << *ite << " ";
	}//0 0 0 0 0 0
	return 0;
}

1.4.2 deque相关元素操作

  • dq.front():返回首部元素
  • dq.back():返回尾部元素
  • dq.push_front():首部插入元素
  • dq.push_back():尾部插入元素
  • dq.pop_front():首部删除元素
  • dq.pop_back():尾部删除元素
  • dq.insert(ite, value):在指定位置插入值为value的元素
  • dq.erase(ite):删除指定位置上的元素
  • dq.clear():清除所有元素
#include <deque>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	deque<string> friends;
	cout << friends.empty() << endl;//1
	//尾部插入元素
	friends.push_back("Chandler");
	friends.push_back("Monica");
	friends.push_back("Joey");

	//头部插入元素
	friends.push_front("Rachel");
	friends.push_front("Ross");
	friends.push_front("Phoebe");
	cout << friends.empty() << endl;//0
	cout << "size: " << friends.size() << endl;//6
	cout << friends.front() << endl;//Phoebe
	cout << friends.back() << endl;//Joey


	friends.push_front("Alan");
	cout << friends.front() << endl; //Alan
	//头部删除
	friends.pop_front();
	cout << friends.front() << endl;//Phoebe

	friends.push_back("Bob");
	cout << friends.back() << endl;//Bob
	//尾部删除
	friends.pop_back();
	cout << friends.back() << endl;//Joey

	//遍历元素
	deque<string>::iterator ite;
	for (ite = friends.begin(); ite != friends.end(); ite++) {
		cout << *ite << " ";
	}//Phoebe Ross Rachel Chandler Monica Joey
	cout << endl;
	
	//通过下标访问元素
	for (int i = 0; i < friends.size(); i++) {
		cout << friends[i] << " ";
	}//Phoebe Ross Rachel Chandler Monica Joey
	cout << endl;

	//插入元素
	ite = find(friends.begin(), friends.end(), "Monica");
	cout << *ite << endl;//Monica
	deque<string>::iterator ite2 = friends.insert(ite, "Alan");
	//cout << *ite << " " << *ite2 << endl;// 注意插入元素后原迭代器失效

	//删除元素
	friends.erase(ite2);
	cout << friends.size() << endl;//6

	//清空
	friends.clear();

	//交换
	deque<string> temp{ "hello", "world" };
	swap(friends, temp);
	for (ite = friends.begin(); ite != friends.end(); ite++) {
		cout << *ite << " ";
	}//hello world
	
	return 0;
}

1.5 容器适配器

本质上适配器是一种机制,使某种事物的行为看起来像另一种事物。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。

所有容器适配器都支持的操作和类型描述
size_type一种类型,足以保存当前类型最大对象的大小
value_type元素类型
container_type实现适配器底层容器类型
A a创建一个名为a的空适配器
A a( c )创建一个名为a的适配器,带有容器c的一个拷贝
关系运算符==、!=、>=、<=、>、<;返回底层容器的比较结果
a.empty()a是否为空
a.size()返回a中元素数量
swap(a, b)交换a,b内容;a、b必须类型相同且底层容器类型也必须相同
a.swap(b)交换a,b内容;a、b必须类型相同且底层容器类型也必须相同

每个适配器都定义两个构造函数:

  1. 默认构造函数创建一个新的对象
  2. 接受一个容器的构造函数拷贝该容器来初始化适配器

1.5.1 stack

  • stack是一种先进后出(FIFO)的数据结构,只有顶部一个出口。stack允许新增、移除元素、取得最顶端元素。但是除了最顶端外,没有任何办法可以存取stack的其他元素。stack没有迭代器,不允许有遍历行为。
  • stack可以使用deque或list或vector作为底层容器。SGI STL使用deque作为缺省情况下stack的底部结构。
1.5.1.1 stack构造与初始化:
  • 默认构造函数创建一个新的对象
  • 接受一个容器的构造函数拷贝该容器来初始化适配器(若stack底层容器为deque(list/vector),可以使用一个deque(list/vector)对象初始化stack)。
#include <deque>
#include <stack>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	//A a
	stack<int> s1;
	
	deque<int> dq{0,1,2,3,4,5,6,7,8,9};
	//A a(c)
	//使用deque初始化一个stack对象(底层容器为deque)
	stack<int> s2(dq);
	while (!s2.empty()) {
		cout << s2.top() << " ";
		s2.pop();
	}//9 8 7 6 5 4 3 2 1 0
	
	//使用vector作为底层容器
	vector<int> v1(10, 1);
	stack<int, vector<int>> s3(v1);


	return 0;
}
1.5.1.2 stack相关元素操作
  • s.pop():删除栈顶元素,但不返回元素值
  • s.push(item):创建一个新的元素压入栈顶,该元素通过拷贝或移动item而来
  • s.emplace(args):创建一个新的元素压入栈顶,该元素通过args构造而来
  • s.top():返回栈顶元素,但不将元素弹出栈顶
#include <deque>
#include <stack>
#include <iostream>
#include <algorithm>
using namespace std;


int main() {
	stack<string> friends;
	cout << friends.empty() << endl;//1
	//push
	friends.push("Chandler");
	friends.push("Monica");
	friends.push("Joey");
	friends.push("Rachel");
	friends.push("Ross");
	friends.push("Phoebe");

	//size、top
	cout << friends.size() << endl;//6
	cout << friends.top() << endl;//Phoebe

	//pop
	friends.pop(); cout << friends.top() << endl;//Ross
	friends.pop(); cout << friends.top() << endl;//Rachel
	friends.pop(); cout << friends.top() << endl;//Joey

	return 0;
}

1.5.2 queue

  • queue是一种先进先出(FIFO)的数据结构。queue允许新增、移除元素、从最底端加入元素、取得最顶端元素。除了最底端可以加入、最顶端可以取出外,没有其它方法可以存取queue的其他元素。queue没有迭代器,不允许遍历。
  • queue使用deque或list作为底层容器。SGI STL使用deque作为缺省情况下queue的底层结构。
1.5.2.1 queue构造与初始化:
  • 默认构造函数创建一个新的对象
  • 接受一个容器的构造函数拷贝该容器来初始化适配器
#include <deque>
#include <queue>
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	//A a
	queue<int> q1;

	//A a(c)
	deque<int> dq{ 0,1,2,3,4,5,6,7,8,9 };
	queue<int> q2(dq);
	while (!q2.empty()) {
		cout << q2.front() << " ";
		q2.pop();
	}//0 1 2 3 4 5 6 7 8 9
	cout << endl;

	//使用list作为底层容器
	list<int> l1{ 0,1,2,3,4,5,6,7,8,9 };
	queue<int, list<int>> q3(l1);
	cout << q3.size() << endl;//10

	return 0;
}
1.5.2.2 queue相关元素操作
  • q.pop():删除queue首元素但不返回此元素
  • q.front():返回首元素但不删除
  • q.back():返回尾元素但不删除
  • q.push(item):在queue末尾添加一个值为item的元素
  • q.emplace(args):在queue末尾添加一个由args创造的元素
#include <deque>
#include <queue>
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	queue<string> friends;
	cout << friends.empty() << endl;//1
	//push
	friends.push("Chandler");
	friends.push("Monica");
	friends.push("Joey");

	cout << friends.front() << endl;//Chandler
	cout << friends.back() << endl;//Joey

	friends.push("Rachel");
	friends.push("Ross");
	friends.push("Phoebe");

	//size、top
	cout << friends.size() << endl;//6
	cout << friends.front() << endl;//Chandler
	cout << friends.back() << endl;//Phoebe

	//pop
	friends.pop(); cout << friends.front() << endl;//Monica
	friends.pop(); cout << friends.front() << endl;//Joey
	friends.pop(); cout << friends.front() << endl;//Rachel
	cout << friends.size() << endl;//3

	return 0;
}

1.5.3 priority_queue

  • priority_queue是带权值的queue,内部的元素并非按照被推入的次序排列,而是自动依照元素的权值进行排序,权值最高者排在最前面。priority_queue顶端元素(权值最高者)才有机会被外界取用,并且priority_queue没有迭代器也不能进行遍历行为。
  • priority_queue缺省情况下以vector为底部容器,利用max-heap实现。
1.5.3.1 priority_queue构造与初始化

priority_queue的构造初始化类似queue。

  • 默认构造函数创建一个新的对象
  • 接受一个容器的构造函数拷贝该容器来初始化适配器
1.5.3.2 priority_queue相关元素操作
  • pq.pop():删除priority_queue最高优先级元素但不返回此元素
  • pq.top():返回最高优先级元素,但不删除该元素
  • pq.push(item):在priority_queue恰当位置添加一个值为item的元素
  • pq.emplace(args):在priority_queue恰当位置添加一个由args创造的元素
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	int arr[10] = { 7,3,8,0,2,1,4,9,6,5};
	priority_queue<int> pq(arr, arr + 10);
	pq.push(22);
	pq.push(-3);
	cout << pq.size() << endl;//12

	while (!pq.empty()) {
		cout << pq.top() << " ";
		pq.pop();
	}//22 9 8 7 6 5 4 3 2 1 0 -3
	cout << endl;

	return 0;
}

二、关联式容器

  • 与顺序容器按照元素在容器中位置来进行顺序保存和访问不同,关联式容器的元素按照关键字来保存和访问,可以支持高效的关键字查找和访问。
  • 标准的STL关联式容器分为set(集合)与map(映射表)两大类,以及二者的衍生体multiset(多键集合)和multimap(多键映射表),这些容器底层均使用红黑树实现。
  • SGI STL还提供了基于hashtable实现的hash_set、hash_map、hash_multiset、hash_multimap。

关联容器类型:

按关键字有序保存元素描述
map关联数组,保存键值对(key-value)
set关键字即值,即只保存关键字的容器
multimap关键字可重复出现的map
multiset关键字可重复出现的set
无序集合描述
unordered_map用哈希函数组织的map
unordered_set用哈希函数组织的set
unordered_multimap哈希组织的map,关键字可重复出现
unordered_multiset哈希组织的set,关键字可重复出现

2.1 pair类型

一个pair可以保存两个数据成员

pair操作描述
pair<T1, T2> pp是一个pair,两个类型分别为T1和T2的成员都进行了值初始化
pair<T1, T2> p (v1, v2)p是一个类型成员为T1、T2的pair,first与second成员分别使用v1、v2进行初始化
pair<T1, T2> p = {v1, v2}等价pair<T1, T2> p (v1, v2)
make_pair(v1, v2)返回使用v1、v2进行初始化进行初始化的pair,pair的类型从v1、v2推断中获得
p.first返回p的first数据成员
p.second返回p的second数据成员
p1 == p2当first、second成员分别相等时两个pair相等
p1 != p2当first、second成员分别相等时两个pair相等,否则不等

2.2 set与multiset

set的特点是所有元素会根据元素的键值自动被排序,set元素的key就是value(或者说value就是key),且set不允许两个元素有相同的键值(key)。
当客户端对set进行元素新增或删除操作时,操作之前所有的迭代器操作完成后仍有效(被删除的元素的迭代器除外)
multiset的特性与用法与set完全相同,唯一的差别在于multiset允许键值重复。

#include <set>
#include <iostream>
using namespace std;
int main() {
	string arr[6] = { "Chandler","Monica","Joey","Rachel","Ross","Phoebe" };
	set<string> friends(arr, arr + 6);
	cout << friends.size() << " " << friends.count("Phoebe") << endl;//6  1
	//加入已经存在的key
	friends.insert("Phoebe");
	cout << friends.size() << " " << friends.count("Phoebe") << endl;//6  1

	//添加未存在的key
	cout << friends.count("Alan") << endl;//0
	friends.insert("Alan");
	cout << friends.size() << " " << friends.count("Alan") << endl;//7  1

	//删除某个key
	friends.erase("Alan");
	cout << friends.size() << " " << friends.count("Alan") << endl;//6  0

	//遍历set
	set<string>::iterator ite1 = friends.begin();
	set<string>::iterator ite2 = friends.end();
	for (; ite1 != ite2; ++ite1) {
		cout << *ite1 << " ";
	}//Chandler Joey Monica Phoebe Rachel Ross
	cout << endl;

	//搜索元素(使用STL算法find())
	set<string>::iterator ite = find(friends.begin(), friends.end(), "Rachel");
	if (ite != friends.end()) {
		cout << *ite << endl;//Rachel
	}
	ite = find(friends.begin(), friends.end(), "Bob");
	if (ite != friends.end()) {
		cout << *ite << endl;
	}//未找到

	//搜索元素(使用关联式容器提供的find(),优于使用STL算法find())
	ite = friends.find("Rachel");
	if (ite != friends.end()) {
		cout << *ite << endl;//Rachel
	}

	//不可以使用迭代器来改变set元素
	//*ite = "Bob";//error
	
	return 0;
}

2.3 map与multimap

  • map的特点是所有元素会根据元素的键值自动被排序。map中的所有元素都是pair——键值对,同时拥有键值key与实值value。map不允许两个元素有相同的键值key。
  • 可以对map中元素的value进行修改但不能对key值进行修改,因为修改key值会严重破坏map组织,而value不影响map元素的排列。
  • 客户端对map进行元素新增或删除操作时,操作前所有迭代器操作完成后仍有效(被删除的元素的迭代器会失效)。
  • multimap的特性与用法与map完全相同,唯一的差别在于multimap允许出现键值的重复。
#include <map>
#include <iostream>
using namespace std;

int main() {
	map<string, int> friends;
	friends[string("Chandler")] = 1;
	friends[string("Monica")] = 2;
	friends[string("Joey")] = 3;
	friends[string("Rachel")] = 4;
	friends[string("Ross")] = 5;
	friends[string("Phoebe")] = 6;

	pair<string, int> value1(string("Cindy"), 7);
	pair<string, int> value2(string("jjhou"), 8);
	//插入元素
	friends.insert(value1);
	friends.insert(value2);

	//遍历map元素
	map<string, int>::iterator ite = friends.begin();
	for (; ite != friends.end(); ite++) {
		cout << ite->first << ": " << ite->second << "; ";
	}//Chandler: 1; Cindy: 7; Joey: 3; Monica: 2; Phoebe: 6; Rachel: 4; Ross: 5; jjhou: 8;
	cout << endl;

	//使用关联式容器的find函数搜索元素
	ite = friends.find("cxj");
	if (ite != friends.end()) {
		cout << ite->first << ": " << ite->second << endl;
	}//未找到

	ite = friends.find("Ross");
	if (ite != friends.end()) {
		cout << ite->first << ": " << ite->second << endl;
	}//Ross: 5

	//修改元素的value
	ite->second = 222;
	friends[string("Chandler")] = 333;

	return 0;
}

2.4 无序关联容器

无序关联容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符。无序容器中元素不具有自动排序功能,使用方式方面与上述容器一致。


总结

最近在阅读侯捷老师的《STL源码剖析》,于是参考着《C++ Primer》对书中容器部分的使用做了一些整理归纳。

注:代码块中注释C代表容器(container)构造函数、A代表容器适配器(container adapter)构造函数

ps:这篇文章是本人第一次尝试用博客的形式记录一些知识点,文章内容、结构方面可能存在较多问题,还望大家批评指正!

参考书籍

1. 《STL源码剖析》
2. 《C++ Primer》

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值