目录
- 1. 容器简介
- 2. String
- 2.1 构造操作
- 2.2 赋值操作
- 2.3 常用操作(存取字符串、拼接、 查找、替换、比较、插入、删除)
- 3. Vector
- 3.1 构造操作
- 3.2 赋值操作
- 3.3 常用操作(大小、 查找、替换、比较、插入、删除)
- 4. Deque
- 4.1 构造操作
- 4.2 赋值操作
- 4.3 常用操作(大小、插入、删除、存取)
- 5. Stack
- 5.1 构造操作
- 5.2 赋值操作
- 5.3 存取操作
- 5.4 大小操作
- 6. Queue
- 6.1 构造操作
- 6.2 赋值操作
- 6.3 存取操作
- 6.4 大小操作
- 7. List
- 7.1 构造操作
- 7.2 赋值操作
- 7.3 存取操作
- 7.4 常用操作(插入、删除、大小、排序)
- 8. Set/Multiset
- 8.1 构造操作
- 8.2 赋值操作
- 8.3 常用操作(大小、插入、删除、查找)
- 9. Map/Multimap
- 9.1 构造操作
- 9.2 赋值操作
- 9.3 常用操作(大小、插入、删除、查找)
- 10. unordered_set/multiset、unordered_map/multimap
- 11. 容器使用时机
1. 容器简介
容器就是数据结构,用来将数据元素按照一定的规则进行排列,不同的容器拥有不同的排列规则,不同的排列规则可以达到不同的数据操作特点。容器只需要提供迭代器 算法只需要拿到迭代器就可以完成容器和算法之间的关联和操作
- 顺序容器:数组(array),动态数组(vector),双向队列(deque),双向链表(list),单向链表(forward_list),字符串(string)
- 关联容器:map、set、multimap、multiset、unordered_set、unordered_map、unordered_multiset、unordered_multimap
- 容器适配器:单向队列(queue),栈(stack)
2. String
string 封装了 char*,管理这个字符串,是一个 char*型的容器。string 管理 char*所分配的内存。每一次 string 的复制,取值都由 string 类负责维护,不用担心复制越界和取值越界等。
2.1 构造操作
string();//创建一个空的字符串 例如: string str;
string(const string& str);//使用一个 string 对象初始化另一个 string 对象
string(const char* s);//使用字符串 s 初始化
string(int n, char c);//使用 n 个字符 c 初始化
//例子:
//默认构造函数
string s1;
//拷贝构造函数
string s2(s1);
string s2 = s1;
//带参数构造函数
char* str = "itcast";
string s3(str);
string s4(10, 'a');
2.2 赋值操作
string& operator=(const char* s);//char*类型字符串 赋值给当前的字符串
string& operator=(const string &s);//把字符串 s 赋给当前的字符串
string& operator=(char c);//字符赋值给当前的字符串
string& assign(const char *s);//把字符串 s 赋给当前的字符串
string& assign(const char *s, int n);//把字符串 s 的前 n 个字符赋给当前的字符串
string& assign(const string &s);//把字符串 s 赋给当前字符串
string& assign(int n, char c);//用 n 个字符 c 赋给当前字符串
string& assign(const string &s, int start, int n);//将 s 从 start 开始 n 个字符赋值给字符串
2.3 常用操作(存取字符串、拼接、 查找、替换、比较、插入、删除)
//存取字符串
char& operator[](int n);//通过[]方式取字符,越界程序挂掉
char& at(int n);//通过 at 方法获取字符,越界会抛出异常
string s = "itcast";
char c = s[1];
c = s.at(1);
//拼接
string& operator+=(const string& str);//重载+=操作符
string& operator+=(const char* str);//重载+=操作符
string& operator+=(const char c);//重载+=操作符
string& append(const char *s);//把字符串 s 连接到当前字符串结尾
string& append(const char *s, int n);//把字符串 s 的前 n 个字符连接到当前字符串结尾
string& append(const string &s);//同 operator+=()
string& append(const string &s, int pos, int n);//把字符串 s 中从 pos 开始的 n 个字符连接到当前字符串结尾
string& append(int n, char c);//在当前字符串结尾添加 n 个字符 c
//查找和替换
int find(const string& str, int pos = 0) const; //查找 str 第一次出现位置,从 pos 开始查找
int find(const char* s, int pos = 0) const; //查找 s 第一次出现位置,从 pos 开始查找
int find(const char* s, int pos, int n) const; //从 pos 位置查找 s 的前 n 个字符第一次位置
int find(const char c, int pos = 0) const; //查找字符 c 第一次出现位置
int rfind(const string& str, int pos = npos) const;//查找 str 最后一次位置,从 pos 开始查找
int rfind(const char* s, int pos = npos) const;//查找 s 最后一次出现位置,从 pos 开始查找
int rfind(const char* s, int pos, int n) const;//从 pos 查找 s 的前 n 个字符最后一次位置
int rfind(const char c, int pos = 0) const; //查找字符 c 最后一次出现位置
string& replace(int pos, int n, const string& str); //替换从 pos 开始 n 个字符为字符串 str
string& replace(int pos, int n, const char* s); //替换从 pos 开始的 n 个字符为字符串 s
//比较
int compare(const string &s) const;//与字符串 s 比较,等于时返回0
int compare(const char *s) const;//与字符串 s 比较,等于时返回0
//切割子串
string substr(int pos = 0, int n = npos) const;//返回由 pos 开始的 n 个字符组成的字符串
//插入删除
string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入 n 个字符 c
string& erase(int pos, int n = npos);//删除从 Pos 开始的 n 个字符
3. Vector
vector 是个动态数组,连续内存空间,随机存取效率高。当空间不足的时候插入新元素,vector 会重新申请一块更大的内存空间(每次增长一倍),将旧空间数据拷贝到新空间,然后释放旧空间。vector 是单口容器,所以在尾端插入和删除元素效率较高,在指定位置插入,势必会引起数据元素移动,效率较低。
3.1 构造操作
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将 v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将 n 个 elem 拷贝给本身。
vector(const vector &vec);//拷贝构造函数。
//例子 使用第二个构造函数 我们可以...
int arr[] = {2,3,4,1,9};
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
3.2 赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);// 将 vec 与本身的元素互换,实际交换彼此的指针
//第一个赋值函数,可以这么写:
int arr[] = { 0, 1, 2, 3, 4 };
assign(arr, arr + 5);//使用数组初始化 vector
3.3 常用操作(大小、 查找、替换、比较、插入、删除)
//大小
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量,因为当容空间器不够时会⾃动扩充空间 ,但不⼀定都会填满,所以可能与size()不等
reserve(int len);//容器预留 len 个元素长度,预留位置不初始化,元素不可访问。
//reserve 是容器预留空间,但在空间内不真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素
//resize 是改变容器的大小,且在创建对象,因此,调用这个函数之后,就可以引用容器内的对象了.
//存取
at(int idx); //返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range 异常。
operator[];//返回索引 idx 所指的数据,越界时,运行直接报错
front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素
//插入和删除
insert(const_iterator pos, int count,ele);//迭代器指向位置 pos 插入 count 个元素 ele.
push_back(ele); //尾部插入元素 ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从 start 到 end 之间的元素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素
4. Deque
deque 则是一种双向开口的连续性空间,所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,vector 当然也可以在头尾两端进行插入和删除操作,但是头部插入和删除操作效率奇差,无法被接受。deque是动态的以分段的连续空间组合而成,随时可以增加一段新的空间并链接起来,由中控器来维持连续的假象。
4.1 构造操作
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将 n 个 elem 拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
4.2 赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将 deq 与本身的元素互换
4.3 常用操作(大小、插入、删除、存取)
//大小
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
//插入和删除
push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据
insert(pos,elem);//在 pos 位置插入一个 elem 元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在 pos 位置插入 n 个 elem 数据,无返回值。
insert(pos,beg,end);//在 pos 位置插入[beg,end)区间的数据,无返回值。
at(idx);//返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range。
operator[];//返回索引 idx 所指的数据,如果 idx 越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据
//存取
at(idx);//返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range。
operator[];//返回索引 idx 所指的数据,如果 idx 越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据
5. Stack
stack是一种先进后出(first in last out,FILO)的数据结构,它只有一个出口,stack 只允许在栈顶新增元素,移除元素,获得顶端元素,但是除了顶端之外,其他地方不允许存取元素,只有栈顶元素可以被外界使用,也就是说stack不具有遍历行为,没有迭代器。
5.1 构造操作
stack<T> stkT;//stack 采用模板类实现, stack 对象的默认构造形式:
stack(const stack &stk);//拷贝构造函数
5.2 赋值操作
stack& operator=(const stack &stk);//重载等号操作符
5.3 存取操作
push(elem);//向栈顶添加元素
pop();//从栈顶移除第一个元素
top();//返回栈顶元素
5.4 大小操作
empty();//判断堆栈是否为空
size();//返回堆栈的大小
6. Queue
queue 是一种先进先出(first in first out, FIFO)的数据类型,他有两个口,数据元素只能从一个口进,从另一个口出。队列只允许从队尾加入元素,队头删除元素,必须符合先进先出的原则,queue 和 stack 一样不具有遍历行为。
6.1 构造操作
queue<T> queT;//queue 采用模板类实现,queue 对象的默认构造形式:
queue(const queue &que);//拷贝构造函数
6.2 赋值操作
queue& operator=(const queue &que);//重载等号操作符
6.3 存取操作
push(elem);//往队尾添加元素
pop();//从队头移除第一个元素
back();//返回最后一个元素
front();//返回第一个元素
6.4 大小操作
empty();//判断队列是否为空
size();//返回队列的大小
7. List
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。STL中的list为双向链表,索引时迭代器可前进或后退。
- 采用动态存储分配,不会造成内存浪费和溢出
- 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
- 链表灵活,但是空间和时间额外耗费较大
7.1 构造操作
list<T> lstT;//list 采用采用模板类实现,对象的默认构造形式:
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将 n 个 elem 拷贝给本身。
list(const list &lst);//拷贝构造函数。
7.2 赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将 lst 与本身的元素互换。
7.3 存取操作
front();//返回第一个元素。
back();//返回最后一个元素。
7.4 常用操作(插入、删除、大小、排序)
//插入和删除
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在 pos 位置插 elem 元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在 pos 位置插入 n 个 elem 数据,无返回值。
insert(pos,beg,end);//在 pos 位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除 pos 位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与 elem 值匹配的元素。
//大小
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。
//反转排列排序
reverse();//反转链表,比如 lst 包含 1,3,5 元素,运行此方法后,lst 就包含 5,3,1 元素。
sort(); //list 排序
8. Set/Multiset
set/multiset 的特性是所有元素会根据元素的值自动进行排序(默认从小到大排序)。set 是以 RB-tree(红黑树,平衡二叉树的一种)为底层机制,其查找效率非常好。set 容器中不允许重复元素,multiset 允许重复元素。set 集合是根据元素值进行排序,关系到 set 的排序规则,如果任意改变 set 的元素值,会严重破坏 set 组织。
8.1 构造操作
set<T> st;//set 默认构造函数:
set<T,func> st;//当T为对象时,需要指定针对于该对象的排序规则,即传入仿函数func
mulitset<T> mst; //multiset 默认构造函数:
set(const set &st);//拷贝构造函数
8.2 赋值操作
set& operator=(const set &st);//重载等号操作符
swap(st);//交换两个集合容器
8.3 常用操作(大小、插入、删除、查找)
//大小
size();//返回容器中元素的数目
empty();//判断容器是否为空
//插入和删除
insert(elem);//在容器中插入元素。
clear();//清除所有元素
erase(pos);//删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为 elem 的元素。
//查找
find(key);//查找键 key 是否存在,若存在,返回该键的元素的迭代器;若不存在,返回 map.end();
lower_bound(keyElem);//返回第一个 key>=keyElem 元素的迭代器。
upper_bound(keyElem);//返回第一个 key>keyElem 元素的迭代器。
equal_range(keyElem);//返回容器中 key 与 keyElem 相等的上下限的两个迭代器。
pair<set<int>::iterator,set<int>::iterator> ret = equal_range(2);
ret.first//第一个迭代器
ret.second//第二个迭代器
9. Map/Multimap
map 相对于 set 区别,map 具有键值和实值,所有元素根据键值自动排序。pair 的第一元素被称为键值,第二元素被称为实值。map 也是以红黑树为底层实现机制。
9.1 构造操作
map<T1, T2> mapTT;//map 默认构造函数,key:value
map(const map &mp);//拷贝构造函数
9.2 赋值操作
map& operator=(const map &mp);//重载等号操作符
swap(mp);//交换两个集合容器
9.3 常用操作(大小、插入、删除、查找)
//大小
size();//返回容器中元素的数目
empty();//判断容器是否为空
//插入和删除
map.insert(...); //往容器插入元素,返回 pair<iterator,bool>
map<int, string> mapStu;
// 第一种 通过 pair 的方式插入对象
mapStu.insert(pair<int, string>(3, "小张"));
// 第二种 通过 pair 的方式插入对象
mapStu.inset(make_pair(-1, "校长"));
// 第三种 通过 value_type 的方式插入对象
mapStu.insert(map<int, string>::value_type(1, "小李"));
// 第四种 通过数组的方式插入值
mapStu[3] = "小刘";
mapStu[5] = "小王";
clear();//删除所有元素
erase(pos);//删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中 key 为 keyElem 的对组。
//查找
find(key);//查找键 key 是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回 map.end();
count(keyElem);//返回容器中 key 为 keyElem 的对组个数。对 map 来说,要么是 0,要么是 1。对
multimap 来说,值可能大于 1。
lower_bound(keyElem);//返回第一个 key<=keyElem 元素的迭代器。
upper_bound(keyElem);//返回第一个 key>keyElem 元素的迭代器。
equal_range(keyElem);//返回容器中 key 与 keyElem 相等的上下限的两个迭代器。
10. unordered_set/multiset、unordered_map/multimap
unordered_set/multiset、unordered_map/multimap是无序的,底层实现结构为哈希表(hash table),使用方法与8,9相似。唯独需要注意的就是unordered_set/multiset的元素是非C++内建类型时、unordered_map/multimap的key是非C++内建类型时,需要传入一个计算hash值的仿函数(hash function)。
//unordered_map模板定义
template<//key:value
class Key,//键值,key
class T,//实值,value,
class Hash = std::hash<Key>,//计算hash值,当key为C++内建类型时,有默认计算hash值得函数对象使用;否则需要自己传入计算hash值的新规则函数对象(仿函数)
class KeyEqual = std::equal_to<Key>,//判断索引值相等的函数对象,与hash值类似,可能需要自定义
class Allocator = std::allocator<std::pair<const Key, T>>
>
class unordered_map;
#include <iostream>
#include <string>
#include <unordered_map>
int main()
{
// 创建包含三个字符串的(映射到字符串的) unordered_map
std::unordered_map<std::string, std::string> u =
{
{"红色", "#FF0000"},
{"绿色", "#00FF00"},
{"蓝色", "#0000FF"}
};
// 用于打印键值对的辅助 lambda 函数
auto print_key_value = [](const auto& key, const auto& value)
{
std::cout << "键:[" << key << "] 值:[" << value << "]\n";
};
std::cout << "遍历并打印 unordered_map 的键值对,显式指定类型:\n";
for (const std::pair<const std::string, std::string>& n : u)
print_key_value(n.first, n.second);
std::cout << "\n通过 C++17 的结构化绑定来遍历并打印 unordered_map 的键值对:\n";
for (const auto& [key, value] : u)
print_key_value(key, value);
// 向 unordered_map 中添加两项
u["黑色"] = "#000000";
u["白色"] = "#FFFFFF";
std::cout << "\n通过键输出值:\n"
"红色的十六进制表示:[" << u["红色"] << "]\n"
"黑色的十六进制表示:[" << u["黑色"] << "]\n\n";
std::cout << "通过以先前不存在的键使用 operator[] 来插入新的键值对:\n";
print_key_value("新键", u["新键"]);
std::cout << "\n通过使用 auto 来遍历并打印键值对;\n"
"现在`新键`是映射中的一个键:\n";
for (const auto& n : u)
print_key_value(n.first, n.second);
}
//unordered_set模板定义
template<
class Key,//元素的类型
class Hash = std::hash<Key>,//计算hash值,当key为C++内建类型时,有默认计算hash值得函数对象使用;否则需要自己传入计算hash值的新规则函数对象(仿函数)
class KeyEqual = std::equal_to<Key>,//判断索引值相等的函数对象,与hash值类似,可能需要自定义
class Allocator = std::allocator<Key>//空间分配器,一般情况下无需修改
>
class unordered_set;
//unordered_set示例
#include <iostream>
#include <unordered_set>
void print(const auto& set)
{
for (const auto& elem : set)
std::cout << elem << ' ';
std::cout << '\n';
}
int main()
{
std::unordered_set<int> mySet{2, 7, 1, 8, 2, 8}; // 创建 int 的集合
print(mySet);
mySet.insert(5); // 将元素 5 加入集合
print(mySet);
if (auto iter = mySet.find(5); iter != mySet.end())
mySet.erase(iter); // 移除 iter 指向的元素
print(mySet);
mySet.erase(7); // 移除元素 7
print(mySet);
}
11. 容器使用时机
- vector 的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。
- deque 的使用场景:比如排队购票系统,对排队者的存储可以采用 deque,支持头端的快速移除,尾端的快速添加。如果采用 vector,则头端移除时,会移动大量的数据,速度慢。
- vector 与 deque 的比较:
(1)vector.at()比 deque.at()效率高,比如 vector.at(0)是固定的,deque 的开始位置
却是不固定的。
(2)如果有大量释放操作的话,vector 花的时间更少,这跟二者的内部实现有关。
(3)deque 支持头部的快速插入与快速移除,这是 deque 的优点。 - list 的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
- set 的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
- map 的使用场景:比如按 ID 号存储十万个用户,想要快速要通过 ID 查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是 vector 容器,最坏的情况下可能要遍历完整个容器才能找到该用户。