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;
}
自定义数据类型
重写比较符号即可

                
            
        
浙公网安备 33010602011771号