C++ 的标准模板库(Standard Template Library,STL)是泛型程序设计最成功应用的实例。STL 是一些常用数据结构(如链表、可变长数组、排序二叉树)和算法(如排序、查找)的模板的集合,主要由 Alex Stepanov 主持开发,于 1998 年被加入 C++ 标准。
c++容器
容器(container)用于存放数据的类模板。可变长数组、链表、平衡二叉树等数据结构在 STL 中都被实现为容器.
顺序容器
元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置(尾部、头部或中间某处)插入,元素就会位于什么位置。
主要包括:可变长动态数组vector,双端队列 deque、双向链表 list。
关联容器
关联容器内的元素是排序的。插入元素时,容器会按一定的排序规则将元素放到适当的位置上,因此插入元素时不能指定位置。
主要包括:set、multiset、map、multimap。
容器适配器
STL在两类容器的基础上屏蔽一部分功能,突出或增加另一部分功能,实现了三种容器适配器:栈(stack)队列(queue)、优先级队列 (priority_queue)。
通用操作
容器函数
所有容器都可以使用的成员函数
- int size():返回容器对象中元素的个数。
- bool empty():判断容器对象是否为空。
顺序容器和关联容器共有成员函数
- begin():返回指向容器中第一个元素的迭代器。
- end():返回指向容器中最后一个元素后面的位置的迭代器。
- rbegin():返回指向容器中最后一个元素的反向迭代器。
- rend():返回指向容器中第一个元素前面的位置的反向迭代器。
- erase(…):从容器中删除一个或几个元素。
- clear():从容器中删除所有元素。
顺序容器专享成员函数
- front():返回容器中第一个元素的引用。
- back():返回容器中最后一个元素的引用。
- push_back():在容器末尾增加新元素。
- pop_back():删除容器末尾的元素。
- insert(…):插入一个或多个元素。
迭代器(Iterator)
迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似。
迭代器定义方式
- 正向迭代器
容器类名::iterator 迭代器名;
example:vector::iteartor i;
- 常量正向迭代器
容器类名::const_iterator 迭代器名;
- 反向迭代器
容器类名::reverse_iterator 迭代器名;
- 常量反向迭代器
容器类名::const_reverse_iterator 迭代器名;
###迭代器用法示例
迭代器都可以进行++操作
对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素。
而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v; //v是存放int类型变量的可变长数组,初始为空
for (int n = 0; n<5; ++n)
v.push_back(n); //push_back成员函数在vector容器尾部添加一个元素
vector<int>::iterator i; //定义正向迭代器
for (i = v.begin(); i != v.end(); ++i) { //用迭代器遍历容器
cout << *i << " "; //*i 就是迭代器i指向的元素
*i += 2; //每个元素变为原来的2倍
}
cout << endl;
//用反向迭代器遍历容器
for (vector<int>::reverse_iterator j = v.rbegin(); j != v.rend(); ++j)
cout << *j << " ";
system("pause");
return 0;
}
运行结果:
0 1 2 3 4
6 5 4 3 2》
- 注意定义为常量迭代器时第12行’*i+=2;'是会报错的,迭代器的值不能更改。
- 容器适配器 stack、queue 和 priority_queue 没有迭代器。
迭代器功能分类
不同容器的迭代器,其功能强度是不同的
4. 正向迭代器
迭代器p是正向迭代器,则支持++p,p++,*p,==,!=
5. 双向迭代器(list,set/multiset,map/multimap)
包含正向迭代器的所有功能,还支持–p,p–
6. 随机访问迭代器(vector,deque)
包含前者功能,还支持随机访问
* p+=i:使得 p 往后移动 i 个元素。
* p-=i:使得 p 往前移动 i 个元素。
* p+i:返回 p 后面第 i 个元素的迭代器。
* p-i:返回 p 前面第 i 个元素的迭代器。
* p[i]:返回 p 后面第 i 个元素的引用。
- 两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。p1<p2的含义是:p1 经过若干次(至少一次)++操作后,就会等于 p2。其他比较方式的含义与此类似。
- 对于两个随机访问迭代器 p1、p2,表达式p2-p1也是有定义的,其返回值是 p2 所指向元素和 p1 所指向元素的序号之差(也可以说是 p2 和 p1 之间的元素个数减一
示例代码
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(100); //v被初始化成有100个元素
for(int i = 0;i < v.size() ; ++i) //size返回元素个数
cout << v[i]; //像普通数组一样使用vector容器
vector<int>::iterator i;
for(i = v.begin(); i != v.end (); ++i) //用 != 比较两个迭代器
cout << * i;
for(i = v.begin(); i < v.end ();++i) //用 < 比较两个迭代器
cout << * i;
i = v.begin();
while(i < v.end()) { //间隔一个输出
cout << * i;
i += 2; // 随机访问迭代器支持 "+= 整数" 的操作
}
}
迭代器辅助函数
- advance(p, n):使迭代器 p 向前或向后移动 n 个元素。
- distance(p, q):计算两个迭代器之间的距离,即迭代器 p 经过多少次 + + 操作后和迭代器 q 相等。如果调用时 p 已经指向 q 的后面,则这个函数会陷入死循环。
- iter_swap(p, q):用于交换两个迭代器 p、q 指向的值。
使用时需要引入algorithm库
#include <list>
#include <iostream>
#include <algorithm> //要使用操作迭代器的函数模板,需要包含此文件
using namespace std;
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
list <int> lst(a, a+5);
list <int>::iterator p = lst.begin();
advance(p, 1); //p向后移动1个元素,指向2
cout << "1)" << *p << endl; //输出 1)2
advance(p, -1); //p向前移动一个元素,指向1
cout << "2)" << *p << endl; //输出 2)1
list<int>::iterator q = lst.end();
q--; //q 指向 5
cout << "3)" << distance(p, q) << endl; //输出 3)4
iter_swap(p, q); //交换 1 和 5
cout << "4)";
for (p = lst.begin(); p != lst.end(); ++p)
cout << *p << " ";
return 0;
}
STL算法:
- copy:将一个容器的内容复制到另一个容器。
- remove:在容器中删除一个元素。
- random_shuffle:随机打乱容器中的元素。
- fill:用某个值填充容器。
- find:在容器中查找元素。
- count_if:统计容器中符合某种条件的元素的个数。
- sort:排序
vector
顺序容器,支持STL中的所有算法,使用vector,需要包含vector头文件
随机访问元素的开销是常数,尾部添加元素的消耗时间也是常数,中间插入和删除涉及到元素的移动,消耗时间与元素个数有关
vector采用动态分配数组来存储元素,默认大小是32个元素存储空间,超过这个数时,需要把之前的元素复制到另外开辟的空间上,此时添加元素的时间开销不是常数。
成员函数 | 解释 |
---|---|
vector() | 无参构造函数,将容器初始化为空 |
vector(int n) | 将容器初始化为有 n 个元素 |
vector(int n, const T & val) | 假定元素的类型是 T,此构造函数将容器初始化为有 n 个元素,每 个元素的值都是 val |
vector(iterator first, iterator last) | first 和 last 可以是其他容器的迭代器。一般来说,本构造函数初始化的结果就是将 vector 容器的内容变成与其他容器上的区间 [first, last) —致 |
void clear() | 删除所有元素 |
bool empty() | 判断容器是否为空 |
void pop_back() | 删除容器末尾的元素 |
void push_back( const T & val) | 将 val 添加到容器末尾 |
int size() | 返回容器中元素的个数 |
T & front() | 返回容器中第一个元素的引用 |
T & back() | 返回容器中最后一个元素的引用 |
iterator insert(iterator i, const T & val) | 将 val 插入迭代器 i 指向的位置,返回 i |
iterator insert( iterator i, iterator first, iterator last) | 将其他容器上的区间 [first, last) 中的元素插入迭代器 i 指向的位置 |
iterator erase(iterator i) | 删除迭代器 i 指向的元素,返回值是被删元素后面的元素的迭代器 |
iterator erase(iterator first, iterator last) | 删除容器中的区间 [first, last) |
void swap( vector & v) | 将容器自身的内容和另一个同类型的容器 v 互换 |
list
顺序容器,双向链表
使用时需要引入list库文件
定位到元素位置之后,增删元素时间为常数
成员函数 | 解释 |
---|---|
void push_front(const T & val) | 将 val 插入链表最前面 |
void pop_front() | 删除链表最前面的元素 |
void sort() | 将链表从小到大排序 |
void remove (const T & val) | 删除和 val 相等的元素 |
remove_if | 删除符合某种条件的元素 |
void unique() | 删除所有和前一个元素相等的元素 |
void merge(list & x) | 将链表 x 合并进来并清空 x。要求链表自身和 x 都是有序的 |
void splice(iterator i, list & x, iterator first, iterator last) | 在位置 i 前面插入链表 x 中的区间 [first, last),并在链表 x 中删除该区间。链表自身和链表 x 可以是同一个链表,只要 i 不在 [first, last) 中即可 |
STL 中的算法 sort 可以用来对 vector 和 deque 排序,它需要随机访问迭代器的支持。因为 list 不支持随机访问迭代器,所以不能用算法 sort 对 list 容器排序。因此,list 容器引入了 sort 成员函数以完成排序。
#include <list> //使用 list 需要包含此头文件
#include <iostream>
#include <algorithm> //使用STL中的算法需要包含此头文件
using namespace std;
class A {
private: int n;
public:
A(int n_) { n = n_; }
friend bool operator < (const A & a1, const A & a2);
friend bool operator == (const A & a1, const A & a2);
friend ostream & operator << (ostream & o, const A & a);
};
bool operator < (const A & a1, const A & a2) {
return a1.n < a2.n;
}
bool operator == (const A & a1, const A & a2) {
return a1.n == a2.n;
}
ostream & operator << (ostream & o, const A & a) {
o << a.n;
return o;
}
template <class T>
void Print(T first, T last)
{
for (; first != last; ++first)
cout << *first << " ";
cout << endl;
}
int main()
{
A a[5] = { 1, 3, 2, 4, 2 };
A b[7] = { 10, 30, 20, 30, 30, 40, 40 };
list<A> lst1(a, a + 5), lst2(b, b + 7);
lst1.sort();
cout << "1)"; Print(lst1.begin(), lst1.end()); //输出:1)1 2 2 3 4
lst1.remove(2); //删除所有和A(2)相等的元素
cout << "2)"; Print(lst1.begin(), lst1.end()); //输出:2)1 3 4
lst2.pop_front(); //删除第一个元素
cout << "3)"; Print(lst2.begin(), lst2.end()); //输出:3)30 20 30 30 40 40
lst2.unique(); //删除所有和前一个元素相等的元素
cout << "4)"; Print(lst2.begin(), lst2.end()); //输出:4)30 20 30 40
lst2.sort();
lst1.merge(lst2); //合并 lst2 到 lst1 并清空 lst2
cout << "5)"; Print(lst1.begin(), lst1.end()); //输出:5)1 3 4 20 30 30 40
cout << "6)"; Print(lst2.begin(), lst2.end()); //lst2是空的,输出:6)
lst1.reverse(); //将 lst1 前后颠倒
cout << "7)"; Print(lst1.begin(), lst1.end()); //输出 7)40 30 30 20 4 3 1
lst2.insert(lst2.begin(), a + 1, a + 4); //在 lst2 中插入 3,2,4 三个元素
list <A>::iterator p1, p2, p3;
p1 = find(lst1.begin(), lst1.end(), 30);
p2 = find(lst2.begin(), lst2.end(), 2);
p3 = find(lst2.begin(), lst2.end(), 4);
lst1.splice(p1, lst2, p2, p3); //将[p2, p3)插入p1之前,并从 lst2 中删除[p2,p3)
cout << "8)"; Print(lst1.begin(), lst1.end()); //输出:8)40 2 30 30 20 4 3 1
cout << "9)"; Print(lst2.begin(), lst2.end()); //输出:9)3 4
return 0;
}
deque
顺序容器,可变长数组,所以vector的成员函数,deque都有
deque 在头尾增删元素都具有较好的性能
相比于vector多了下面两个成员函数
void push_front (const T & val); //将 val 插入容器的头部
void pop_front(); //删除容器头部的元素
关联容器
关联容器内部的元素都是排好序的,有以下四种:
set:排好序的集合,不允许有相同元素。
multiset:排好序的集合,允许有相同元素。
map:每个元素都分为关键字和值两部分,容器中的元素是按关键字排序的。不允许有多个元素的关键字相同。
multimap:和 map 类似,差别在于元素的关键字可以相同。
学前了解:pair模板
{% note success %}
由于关联容器的一些成员函数的返回值是 pair 对象,而且 map 和 multimap 容器中的元素都是 pair 对象,所以在学习之前,首先认识一下pair对象
{% endnote %}
pair对象模板简介
set
set 是关联容器的一种,是排序好的集合(元素已经进行了排序)。set 中不能有重复的元素。
不能直接修改set容器中元素的值。因为元素被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。因此,如果要修改set容器中某个元素的值,正确的做法是先删除该元素,再插入新元素。
成员函数 | 解释 |
---|---|
iterator find (const T & val); | 在容器中查找值为 val 的元素,返回其迭代器。如果找不到,返 回 end() |
pair<iterator, bool> insert(const T & val); | 将 val 插入容器中并返回其迭代器和bool对象bool为true表示插入成功 |
void insert(iterator first, iterator last); | 将区间 [first, last) 中的元素插人容器 |
int count( const T & val); | 统计有多少个元素的值和 val 相等 |
iterator lower_bound( const T & val); | 查找一个最大的位置 it,使得 [begin(), it) 中所有的元素者比 val 小 |
iterator upper_bound( const T & val); | 查找一个最小的位置 it,使得 [it, end()) 中所有的元素都比 val 大 |
pair <iterator, iterator > equal_range (const T & val); | 同时求得 lower_bound 和 upper_bound |
iterator erase(iterator it); | 删除 it 指向的元素,返回其后面的元素的迭代器(Visual Studio 2010 中如此,但是在 C++ 标准和 Dev C++ 中,返回值不是这样) |
iterator erase(iterator first, iterator last); | 删除区间 [first, last),返回 last(Visual Studio 2010 中如此,但是在 C++ 标准和 Dev C++ 中,返回值不是这样) |
使用小测试
#include <iostream>
#include <set> //使用set须包含此文件
using namespace std;
int main()
{
typedef set<int>::iterator IT;
int a[5] = { 3,4,6,1,2 };
set<int> st(a,a+5); // st里是 1 2 3 4 6
pair< IT,bool> result;
result = st.insert(5); // st变成 1 2 3 4 5 6
if(result.second) //插入成功则输出被插入元素
cout << * result.first << " inserted" << endl; //输出: 5 inserted
if(st.insert(5).second)
cout << * result.first << endl;
else
cout << * result.first << " already exists" << endl;
//输出 5 already exists
pair<IT,IT> bounds = st.equal_range(4);
cout << * bounds.first << "," << * bounds.second ; //输出:4,5
return 0;
}
multiset
multiset 是关联容器的一种,是排序好的集合(元素已经进行了排序),并且允许有相同的元素。它和 et的差别在于set中不能有重复的元素。 set的成员函数 multiset 中都有。
map
map 是关联容器的一种,map 的每个元素都分为关键字和值两部分,容器中的元素是按关键字排序的,并且不允许有多个元素的关键字相同。
{% note warning %}
不能直接修改 map 容器中的关键字。因为 map 中的元素是按照关键字排序的,当关键字被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。
{% endnote %}
成员函数和set大同小异,使用方法也类似
成员函数 | 解释 |
---|---|
iterator find( const Key & val); | 在容器中查找关键字等于 val 的元素,返回其迭代器;如果找不到,返回 end() |
iterator insert (pair <Key, T> const &p); | 将 pair 对象 p 插入容器中并返回其迭代器 |
void insert(iterator first, iterator last); | 将区间 [first, last) 插入容器 |
int count( const Key & val); | 统计有多少个元素的关键字和 val 相等 |
iterator lower_bound( const Key & val); | 查找一个最大的位置 it,使得 [begin( ), it) 中所有的元素的关键字都比 val 小 |
iterator upper_bound(const Key & val); | 查找一个最小的位置 it,使得 [it, end()) 中所有的元素的关键字都比 val 大 |
pair < iterator, iterator > equal_range (const Key & val); | 同时求得 lower_bound 和 upper_bound |
iterator erase(iterator it); | 删除 it 指向的元素,返回其后面的元素的迭代器(Visual Studio 2010 中如此,但是在 C++ 标准和 Dev C++ 中,返回值不是这样) |
iterator erase(iterator first, iterator last); | 删除区间 [first, last),返回 last(Visual Studio 2010 中如此,但是在 C++ 标准和 Dev C++ 中,返回值不是这样) |
multimap
与map操作基本相同,主要区别时key值允许重复
容器适配器
STL 中的容器适配器有 stack、queue、priority_queue 三种。它们都是在顺序容器的基础上实现的,屏蔽了顺序容器的一部分功能,突出或增加了另外一些功能。
容器适配器都有以下三个成员函数:
- push:添加一个元素。
- top:返回顶部(对 stack 而言)或队头(对 queue,priority_queue 而言)的元素的引用。
- pop:删除一个元素。
stack
stack就是“栈”。栈是一种后进先出的元素序列,访问和删除都只能对栈顶的元素(即最后一个被加入栈的元素)进行,并且元素也只能被添加到栈顶。栈内的元素不能访问。如果一定要访问栈内的元素,只能将其上方的元素全部从栈中删除,使之变成栈顶元素才可以。
{% note primary %}
使用时包含stack头文件
在默认情况下,stack 就是用 deque 实现的。当然,也可以指定用 vector 或 list 实现。
{% endnote %}
成员函数 | 解释 |
---|---|
void pop(); | 弹出(即删除)栈顶元素 |
T & top(); | 返回栈顶元素的引用。通过此函数可以读取栈顶元素的值,也可以修改栈顶元素 |
void push (const T & x); | 将 x 压入栈顶 |
queue
queue 就是“队列”。队列是先进先出的,和排队类似。队头的访问和删除操作只能在队头进行,添加操作只能在队尾进行。不能访问队列中间的元素。
使用时包含queue头文件
queue 可以用 list 和 deque 实现,默认情况下用 deque 实现。
queue 同样也有和 stack 类似的 push、pop、top 函数。区别在于,queue 的 push 发生在队尾,pop 和 top 发生在队头。
priority_queue
priority_queue 是“优先队列”。它和普通队列的区别在于,优先队列的队头元素总是最大的——即执行 pop 操作时,删除的总是最大的元素;执行 top 操作时,返回的是最大元素的引用。
- 使用时包含queue头文件
- queue 可以用 list 和 deque 实现,默认情况下用 deque 实现。
- queue 同样也有和 stack 类似的 push、pop、top 函数。区别在于,queue 的 push 发生在队尾,pop 和 top 发生在队头。
priority_queue 可以用 vector 和 deque 实现,默认情况下用 vector 实现。 - priority_queue 默认的元素比较器是 less 。也就是说,在默认情况下,要放入 priority_queue 的元素必须是能用“<”运算符进行比较的,而且 priority _queue 保证以下条件总是成立:对于队头的元素 x 和任意非队头的元素 y,表达式“x<y”必为 false。priority_queue 的第三个类型参数可以用来指定排序规则。
string类
- 创建string
string s();//s=""
string s1("123");//s1="123"
string s2(2,'a');//s2="aa"
string s3("12345670",1,3);//s4="234"
- 赋值
string s1,s2;
s1 = "Hello,world";
s2 = 'a';//
string的成员函数assig()也可以用来赋值,约等于’=’;
- 求长度
s.length()或者s.size()
- 字符串连接
s1.append(s2);
s1.append(s2,1,2);
s1.append(3,'a');
- string对象比较
可以用< <= == != >= >
可以用int compare()函数,返回值:0->相等;
string s1("hello"), s2("hello, world");
int n = s1.compare(s2);
n = s1.compare(1, 2, s2, 0, 3); //比较s1的子串 (1,2) 和s2的子串 (0,3)
n = s1.compare(0, 2, s2); // 比较s1的子串 (0,2) 和 s2
n = s1.compare("Hello");
n = s1.compare(1, 2, "Hello"); //比较 s1 的子串(1,2)和"Hello”
n = s1.compare(1, 2, "Hello", 1, 2); //比较 s1 的子串(1,2)和 "Hello" 的子串(1,2)
- 求子串
string substr()函数
string s1 = "hello";
string s2 = s1.substr(2, 4); // s2 = "llo"
s2 = s1.substr(1); // s2 = "ello"
- 交换
swap()
string s1("123"),s2("456");
s1.swap(s2);
- 查找
- find:从前往后查找子串或字符出现的位置。
- rfind:从后往前查找子串或字符出现的位置。
- find_first_of:从前往后查找何处出现另一个字符串中包含的字符。例如:s1.find_first_of(“ab”); //查找s1中第一次出现"ab"中任一字符的位置
- find_last_of:从后往前查找何处出现另一个字符串中包含的字符。
- find_first_not_of:从前往后查找何处出现另一个字符串中没有包含的字符。
- find_last_not_of:从后往前查找何处出现另一个字符串中没有包含的字符
- 替换子串
replace()
string s1("hahahaha");
s1.replace(1, 3, "123456", 2, 4); //用 "123456" 的子串从2开始的4个元素 替换 s1 的子串(1,3)
cout << s1 << endl; //输出 h3456haha
string s2("xixixi");
s2.replace(2, 3, 5, '0'); //用 5 个 '0' 替换子串(2,3)
cout << s2 << endl; //输出 xi00000xi
int n = s2.find("OOOOO"); //查找子串 "00000" 的位置,n=2
s2.replace(n, 5, "111"); //将子串(n,5)替换为"XXX"
cout << s2 < < endl; //输出 xi1110xi
- 删除字串
erase()成员函数可以删除 string 对象中的子串,返回值为对象自身的引用。
s1.erase(1,3);//范围删除
s1.erase(2);//删除索引2之后的所有元素
- 插入字符串
string s1("hihihi"), s2("00");
s1.insert(2, "123"); //在下标 2 处插入字符串"123",s1 = "hi123hihi"
s1.insert(3, s2); //在下标 2 处插入 s2 , s1 = "hi00123hihi"
s1.insert(3, 5, '1'); //在下标 3 处插入 5 个 'X',s1 = "hi0111110123hihi"
- 流转换
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
string s("hi 99 5.21 hello k");
istringstream istrStream(s); //建立s到istrStream的联系
string s1, s2;
int n; double d; char c;
istrStream >> s1 >> n >> d >> s2 >> c; //把s的内容当做输入流进行读取
ostringstream ostrStream;
ostrStream << s1 << endl << s2 << endl << n << endl << d << endl << c <<endl;//此时所有的值都已经按空格分配
cout << ostrStream.str();//将ostrStream中的所有值显示出来
system("pause");
return 0;
}
- STL操作字符串
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main()
{
string s("afgcbed");
string::iterator p = find(s.begin(), s.end(), 'c');
if (p!= s.end())
cout << p - s.begin() << endl; //输出 3
sort(s.begin(), s.end());
cout << s << endl; //输出 abcdefg
system("pause");
return 0;
}