C++ 提高编程 第二章 STL之容器
一、STL基本概念
STL(Standard Template Library,标准模板库)
从广义上分为,容器(container)、算法(algorithm)、迭代器(iterator)
容器和算法之间通过迭代器进行无缝连接
STL几乎所有的代码都采用了模板类或者模板函数
STL六大组件
容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
- 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
- 算法:各种常用的算法,如sort、find、copy、for_each等
- 迭代器:扮演了容器与算法之间的胶合剂
- 仿函数:行为类似函数,可作为算法的某种策略
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
- 空间配置器:负责空间的配置与管理。
容器
STL容器就是将运用最广泛的一些数据结构实现出来
序列式容器:强调值的排序,序列式容器中每个元素均有固定的位置
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
算法
质变算法:运算过程中会更改区间的元素内容。如拷贝,替换,删除等
非质变算法:指运算过程中不会更改区间内的元素内容,例如查找,计数,遍历,寻找极值等
迭代器
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式
每个容器都有自己的专属迭代器,迭代器使用非常类似于指针
分类
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 对数据的只写访问 | 只写、支持++ |
前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 读写操作,并能向前和向后操作 | 读写、支持++、-- |
随机访问迭代器 | 读写操作,可以以跳跃的方式访问数据,功能最强的迭代器 | 读写支持++、--、[n]、-n、<、<=、>、>= |
常用的容器中迭代器种类分为双向迭代器,和随机访问迭代器
二、vector
容器:vector
算法:for_each
迭代器:vector<int>::iterator
1.构造函数
函数原型
vector<T>v;
:采用模板类实现,默认的构造函数
vector(v.begin(),v.end());
:将v[begin(),end())区间中的元素拷贝给本身
vector(n,elem);
:构造函数将n个elem拷贝给本身
vector(const vector &vec);
:拷贝构造函数
2.赋值操作
函数原型
vector& operator=(const vector &vec);
:重载等号运算符
assign(beg,end);
:将[beg,end)区间中的数据拷贝赋值给本身
assign(n,elem):
将n个elem拷贝赋值给本身
3.容量和大小
函数原型
empty()
:判空
capacity()
:容器的容量
size()
:元素个数
resize(int num)
:重新指定容器的长度为num,若容器变长,则以默认值填充新位置;若容器变短,则末尾超出的元素被删除
resize(int num,elem)
:重新指定容器的长度为num,若容器变长,则以elem填充新位置;若容器变短,则末尾超出的元素被删除
4.插入和删除
函数原型
push_back(ele)
pop_back()
insert(const_iterator pos,ele);
:迭代器指向位置pos插入元素ele
insert(const_iterator pos,int count,ele)
:迭代器指向位置pos插入count个元素ele
erase(const_iterator pos);
:删除迭代器指向的元素
erase(const_iterator start,const_iterator end);
:删除迭代器从start到end之间的元素
clear()
:清空容器
vector<int>v;
for (int i = 1; i <= 10; i++)
{
v.push_back(i);
}
v.insert(v.begin() + 3, -1);
v.erase(v.begin() + 3);
5.数据存取
at(int idx)
:返回索引idx下标的元素
operator[]
:同上
front()
:返回第一个元素
back()
:返回最后一个元素
6.互换容器
swap(vec)
:将vec与本身的元素互换
实际用途:巧用swap可以收缩内存空间
vector<int>v;
for (int i = 1; i <= 10; i++)
{
v.push_back(i);
}
v.resize(3);
cout << v.capacity()<<endl;
vector<int>(v).swap(v);//匿名对象
cout << v.capacity() << endl;
7.预留空间
减少vector在动态扩展容量时的扩展次数
函数原型
reserve(int len);
:容器预留len个元素空间,预留位置不初始化,元素不可访问
vector<int>v;
int cnt = 0;//统计开辟次数
int* p = NULL;
for (int i = 1; i <= 1e5; i++)
{
v.push_back(i);
if (p != &v[0])
{
p = &v[0];
cnt++;
}
}
cout << cnt << endl;
若不进行预留空间,向容器插入1e5个元素大约需要30次扩容操作
若提前预留空间,则只需1次扩容操作
三种遍历方式
void sum(int x)
{
s += x;
}
vector<int>v;
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
puts("");
for (auto it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
for_each(v.begin(), v.end(), sum);
puts("");
cout << s << endl;
三、string
string和char*的区别
char是一个指针
string是一个类,类内部封装了char,管理这个字符串,是一个char*的容器
1.构造函数
函数原型
string();
:创造一个空的字符串
string(const char* s);
:使用字符串s初始化
string(const string& str);
:使用一个string对象初始化另一个string对象
string(int n,char c);
:使用n个字符c初始化
2.赋值操作
函数原型
string& operator(const char* s);
:char字符串s赋值给当前字符串
string& operator=(const string& s);
:字符串s赋值给当前字符串
string& operator=(char c);
:字符赋值给当前字符串
string& assign(const char *s);
:char字符串s赋值给当前字符串
string& assign(const char *s,int n);
:char*字符串s的前n个字符赋值给当前字符串
string& assign(const string& s);
:把字符串s赋给当前字符串
string& assign(int n,char c);
:用n个字符c赋值给字符串
3.字符串拼接
实现在字符串末尾拼接字符串
函数原型
string& operator+=(const char* str)
:重载+=操作符
string& operator+=(const char c)
:重载+=操作符
string& operator+=(const string& str)
:重载+=操作符
string& append(const char* s)
:把字符串s连接到当前字符串末尾
string& append(const char *s,int n)
:把字符串s的前n个字符连接到末尾
string& append(const string &s)
:同operator+=(const string& str)
string& append(const string &s,int pos,int n)
:字符串s的第pos个字符开始的n个字符接到末尾
4.字符串查找与替换
函数原型
int find(const string& str,int pos = 0) const
:从pos开始查找str第一次出现位置
int find(const char* s,int pos = 0) const
:从pos开始查找s第一次出现位置
int find(const char* s,int pos,int n) const
:从pos开始查找s的前n个字符的第一次位置
int find(const char c,int pos = 0) const
:从pos开始查找字符c的第一次出现位置
int rfind(const string& str,int pos = npos) const
:从pos开始查找str的最后一次出现位置
int rfind(const char* s,int pos = npos) const
:从pos开始查找s的最后一次出现位置
int rfind(const char* s,int pos,int n)const
:从pos开始查找s的前n个字符最后一次位置
int rfind(const char c,int pos = 0) const
:从pos开始查找字符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
5.字符串比较
字符串比较是按ASCII码进行对比
=返回0 >返回1 <返回-1
函数原型
int compare(const string &s) const
:与字符串s比较
int compare(const char* s) const
:与字符串s比较
6.string字符存取
- 通过[]访问单个字符
- 通过at()访问单个字符
函数原型
char& operator[](int n)
:通过[]访问单个字符
char& at(int n)
:通过at()访问单个字符
7.字符串插入和删除
函数原型
string& insert(int pos,const char* s);
:在pos位置插入字符串
string& insert(int pos,const string& str)
:在pos位置插入字符串
string& insert(int pos,int n,char c)
:在pos位置插入n个字符c
string& erase(int pos,int n = npos)
:删除从Pos开始的n个字符
8.子串获取
函数原型
string& substr(int pos = 0,int n = npos);
:返回由pos开始的n个字符组成的字符串
实战演示:从邮箱地址提取出用户名
string email = "yxc@qq.com";
int pos = email.find('@');
string name = email.substr(0, pos);
cout << name << endl;
return 0;
四、deque容器
1.基本概念
功能
双端数组,可以实现对两端进行插入删除操作
与vector的区别
- vector对于头部的插入删除效率低,数据量越大,效率越低
- deque相对而言,对头部的插入删除速度比vector快
- vector访问元素时的速度比deque快,这和内部实现有关
内部工作原理
deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据
中控器维护的是每个缓冲区的地址,使得使用deque像一片连续的内存空间
deque迭代器支持随机访问
2.构造函数
函数原型
deque<T>deq
:默认构造形式
deque(beg,end);
:构造函数将[beg,end)区间中的元素拷贝给本身
deque(n,elem);
:构造函数将n个elem拷贝给本身
deque(const deque &deq);
:拷贝构造函数
3.赋值操作
函数原型
deque& operator(const deque &deq);
:重载等号操作符
assign(beg,end)
:将[beg,end)区间中的数据拷贝赋值给本身
assign(n,elem)
:将n个elem拷贝赋值给本身
4.大小操作
deque没有容量的概念
empty()
size()
resize(num)
resize(num,elem)
5.插入和删除
函数原型
两端插入操作:
push_back(elem)
:在容器尾部添加一个数据
push_front
:在容器头部插入一个数据
pop_back()
:删除容器最后一个数据
pop_front()
:删除容器第一个数据
指定位置操作:
insert(iterator pos,elem)
:pos位置插入一个元素elem,返回新数据的位置
insert(iterator pos,n,elem)
:pos位置插入n个elem数据,无返回值
insert(pos,beg,end)
:pos位置插入[beg,end)区间的位置,无返回值
clear()
:清空
erase(beg,end)
:删除区间的数据,返回下一个数据的位置
erase(pos)
:删除pos位置的数据,返回下一个数据的位置
6.数据存取
函数原型
at(int idx)
:返回索引idx所指的数据
operator[]
:同上
front()
:容器第一个元素
back()
:容器最后一个元素
7.排序
sort(iterator beg,iterator end)
五、stack容器
构造函数
stack<T> stk
stack(const stack& stk)
:拷贝构造函数
赋值操作
stack& operator=(const stack &stk)
数据存取
push(elem)
pop()
top()
empty()
size()
六、queue容器
构造函数
queue<T> que
queue(const queue& que)
:拷贝构造函数
赋值操作
queue& operator=(const queue &queue)
数据存取
push(elem)
pop()
front()
back()
empty()
size()
七、list容器
list是一个双向循环链表
由于链表的存储方式并不是连续的内存空间,因此list中的迭代器只支持前移和后移,属于双向迭代器
list的优点
- 采用动态存储分配,不会浪费空间
- 插入和删除效率高,无需移动大量元素
- 插入和删除操作都不会使原有list迭代器的失效,这在vector是不成立的
list的缺点
- 空间(指针域)和时间(遍历)额外耗费较大
构造函数
类似于vector
赋值和交换
类似于vector
大小操作
类似于deque
插入和删除
两端插入操作:
push_back(elem)
:在容器尾部添加一个数据
push_front
:在容器头部插入一个数据
pop_back()
:删除容器最后一个数据
pop_front()
:删除容器第一个数据
指定位置操作:
insert(iterator pos,elem)
:pos位置插入一个元素elem,返回新数据的位置
insert(iterator pos,n,elem)
:pos位置插入n个elem数据,无返回值
insert(pos,beg,end)
:pos位置插入[beg,end)区间的位置,无返回值
clear()
:清空
erase(beg,end)
:删除区间的数据,返回下一个数据的位置
erase(pos)
:删除pos位置的数据,返回下一个数据的位置
remove(elem)
:删除容器中所有与elem值匹配的元素
数据存取
front()
back()
不可以用[]/at()的方式随机访问
反转和排序
reverse()
sort()
list<double>l;
for (int i = 10; i >= 0; i--)
{
l.push_back(i);
}
//sort(l.begin(), l.end());//错误:所有不支持随机访问迭代器的容器,不能使用标准算法
l.sort();//默认升序,可传入bool函数改变排序规则
l.reverse();
for (auto x : l)
{
cout << x << " ";
}
return 0;
七、set/multiset容器
简介
所有元素都会在插入时自动被排序
set/multiset容器属于关联式容器,底层结构是用二叉树实现的
set和multiset的区别
set容器不允许容器中有重复的元素,multiset允许容器中有重复的元素
set插入数据的同时会返回插入结果,表示插入是否成功;multiset不会检测,因为可以插入重复元素
1.构造和赋值
set<T> st
:默认构造函数
set(const set &st)
:拷贝构造函数
set& operator=(const set &st)
:重载等号赋值运算符
2.大小和交换
size()
:返回容器中元素的数目
empty()
:判空
swap(st)
:交换两个集合容器
3.插入和删除
insert(elem)
:插入元素,set返回pair<iterator,bool>表示是否插入成功
erase(pos)
:删除迭代器指定的元素,返回下一个元素的迭代器
erase(beg,end)
:删除区间[beg,end)的所有元素,返回下一个元素的迭代器
erase(elem)
:删除容器中值为elem的元素
clear()
:清空
set<int>s;
for (int i = 0; i < 10; i++)
{
cout << s.insert(i).second << endl;
}
cout << s.insert(9).second << endl;
return 0;
4.查找和统计
find(key)
:查找key是否存在,返回key的迭代器,若不存在,返回set.end()
count(key)
:统计key的元素个数
5.排序规则的改变
利用仿函数,可以改变排序规则
class Mycom
{
public:
bool operator()(int a,int b) const //此处必须定义为常函数,否则报错
{
return a > b;
}
};
int main()
{
set<int,Mycom>s;
for (int i = 0; i < 10; i++)
{
s.insert(i);
}
for (auto x : s)
{
cout << x << " ";
}
return 0;
}
自定义数据类型
重写比较符号即可
八、map/multimap容器
1.简介
map中所有元素都是pair
pair第一个元素是key键值,起到索引作用,第二个元素为value(实值)
所有元素都会根据元素的键值自动排序
本质
map/multimap属于关联式容器,底层结构是用二叉树实现。
优点
可以根据key值快速找到value值
map与multimap的区别
map不允许容器中有重复key值元素
multimap允许容器中有重复key值元素
2.构造和赋值
函数原型
map<T1,T2>mp
:默认构造函数
map(const map &mp)
:拷贝构造函数
map& operator=(const map &mp)
:重载等号运算符
3.大小和交换
函数原型
size()
:返回容器中键值对个数
empty()
:判空
swap(mp)
:交换
4.插入和删除
insert(elem)
erase(iterator pos)
:删除迭代器所指的元素,返回下一个元素的迭代器
erase(beg,end)
:删除[beg,end)的所有元素,返回下一个元素的迭代器
erase(key)
:删除容器中值为key的元素
clear()
:清空
5.查找和统计
find(key)
:查找key是否存在,若是,返回该键元素的迭代器,否则返回set.end()
count(key)
:统计key元素的个数
6.改变排序规则
利用仿函数,可以改变排序规则
class com
{
public:
bool operator()(int a, int b) const
{
return a > b;
}
};
int main() {
map<int, int,com>mp;
mp.insert({ 3,2 });
mp.insert({ 1,0 });
for (auto& [key, value] : mp)
{
cout << key << ":" << value << endl;
}
return 0;
}
自定义数据类型
重写比较符号即可
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具