前言
学习这本书已经两三年了,平时在写C++程序的时候,都会时不时的翻阅STL相关的API,已经将这本很适合初学者使用的书籍当作了一本工具书使用。同时,每次在翻看的时候都会得到新的启发和学习。
为方便以后更快,更高效的查阅到相关的知识,特写此学习笔记,将平时经常用到的内容进行简要记录。学习笔记,如有错误,还望指正。
一、STL概述
常用STL包含头文件
- #include <iterator> 迭代器
- <iostream> 标准输入输出流
- <fstream> 文件输入输出流
- <sstream> 字符串输入输出流
- <string> 字符串
- <functional> 函数对象
- <vector> 向量容器
- <deque> 双端队列
- <list> 链表容器
- <queue> 队列,优先队列
- <stack> 堆栈
- <set> 集合、多集合、位集合
- <map> 映射、多映射
- <algorithm> 通用算法
- <numeric> 数值算法
命名空间
标准命名空间
- using namespace std;
自定义命名空间
// 自定义命名空间示例程序
#include <iostream>
using namespace std;
// 此处定义自己的命名空间
namespace mynamespace{
void func(){
cout<<"this is mynamespace!"<<endl;
}
};
int main(){
// 使用自定义命名空间,也可像std一样,先进行using namespace后,直接进行函数调用。
mynamespace::func();
return 0;
}
二、模板
三、迭代器
1、输入迭代器(Input Iterator)
按顺序只读一次。能进行构造和默认构造。能被复制或赋值,能进行相等性比较,能进行逐步向前移动,能进行读取值。
istream_iterator(): 默认构造器,创建一个流结束的迭代器
istream_iterator(istream &): 参数是输入流。含义是从输入流中读数据,当遇到流结束符时停止。
输入迭代器重载主要操作符
operate * 访问迭代元素值
operate ++() 前置迭代指针++
operate ++(int) 后置迭代指针++
operate == 迭代元素相等比较
operate != 迭代元素不等比较
利用istream_iterator迭代器迭代标准输入流示例代码
#include <iostream>
#include <iterator>
using namespace std;
int main(){
// 建立键盘输入流,并用istream_iterator枚举整型数据
istream_iterator<int> a(cin);
// 建立输入流结束迭代器
istream_iterator<int> b;
while(1){
// 调用operator*() 输出数据
cout<<*a<<endl;
// 调用operator++(int) 迭代器指针指向下一个元素
a++;
// 判断结束
if(a==b){
break;
}
}
return 0;
}
2、输出迭代器(Output Iterator)
只写一次。能进行构造或默认构造,能被复制或赋值,能进行相等性比较,能进行逐步向前移动,能进行写入值(*p = x, 但不能读出)。
ostream_iterator(ostream & out): 创建流输出迭代器,用来迭代out输出流。
ostream_iterator(ostream & out, const char * delim): 创建流输出迭代器,用来向out输出流输出数据,输出的数据之间用delim字符串分隔。即每向输出流输出一个数据后,就向输出流输出一个分隔符delim。
输出迭代器重载主要操作符
operate * 分配迭代元素值空间
operate = 写入元素值
operate ++() 前置迭代指针++
operate ++(int) 后置迭代指针++
利用ostream_iterator向屏幕输出数据
#include <iostream>
#include <iterator>
using namespace std;
int main(){
// 创建标准输出迭代器
ostream_iterator<int> myout(cout, "\t");
* myout = 1;
* myout++;
return 0;
}
3、前向迭代器(Forward Iterator)
具有输入迭代器、输出迭代器的功能。只能向前移动。
4、双向迭代器(Bidirectional Iterator)
具有前向迭代器的全部功能,另外还可以利用自减操作符 operator-- 向后一次移动一个位置
5、随机访问迭代器(Random Access Iterator)
具有双向迭代器的所有功能,再加上一个指针的所有功能。包括使用操作符operator[] 进行索引,加某个整数值到一个指针就可以向前向后移动若干个位置,或者使用比较运算符在迭代器之间进行比较。
四、输入输出流
1、标准输入输出流
2、文件输入输出流
3、字符串输入输出流
字符串输入输出流类直接对内存而不是对文件和标准输出进行操作,它使用与cin及cout相同的读取和格式化函数进行操纵内存中的数据,头文件<sstream>
- istringstream: 字符串输入流,提供读string功能
- ostringstream: 字符串输出流,提供写string功能
- stringstream: 字符串输入输出流,提供读写string功能
stringstream进行类型转换示例:
#include <iostream>
#include <sstream>
using namespace std;
int main(){
string data = "123 456 789";
stringstream ss;
ss<<data;
for(int i=0;i<3;i++){
int tmp;
ss>>tmp;
cout<<tmp<<endl;
}
return 0;
}
五、字符串
六、函数对象
七、通用容器
1、概述
容器分类:
1)序列性容器
按照线性排列来存储某类型值的集合,每个元素都有自己特定的位置,顺序容器主要有vector, deque, list
vector:动态数组。它是在堆中分配内存,元素连续存放,有保留内存,如果减少大小后内存也不会释放。新值大于当前大小时才会再分配内存。对最后元素操作最快(在后面添加删除最快),此时一般不需要移动内存,如果元素是结构或是类,移动的同时还会进行构造和析构操作。vector的一大特点是可直接访问任何元素。
deque:与vector类似,支持随机访问和快速插入删除,它在容器中的某一位置上的操作所花费的是线性时间。与vector不同的是,deque还支持从开始端插入、删除数据。由于它主要对前端、后端进行操作,因此也叫做双端队列。
list:又叫链表,是一种双线性列表,只能顺序访问(从前向后或者从后向前),与前面两种容器类有一个明显的区别就是它不支持随机访问。要访问表中某个下标处的项需要从头或表尾处(接近该下标的一端)开始循环。
2)关联式容器
与前面的顺序性容器相比,关联式容器更注重快速和高效地检索数据的能力。这些容器是根据键值来检索数据的,键可以是值也可以是容器中的某一成员。这一类的成员在初始化后都是按一定的顺序排好序的。关联式容器主要有set, multiset, map, multimap。
set: 快速查找,不允许重复值
multiset: 快速查找,允许重复值
map:一对一映射,基于关键字快速查找,不允许重复值。
multimap:一对多映射,基于关键字快速查找,允许重复值。
3)容器适配器
对已有的容器进行某些特性的再封装,不是一个真正的新容器。主要有stack、queue。
stack:堆栈类,特点是后进先出。
queue:队列类,特点是先进先出。
容器共性:
容器一般来说都有下列函数
- 默认构造函数:提供容器默认初始化的构造函数
- 复制构造函数:将容器初始化为现有同类容器副本的构造函数
- 析构函数:不在需要容器时进行内存整理的析构函数。
- empty:容器中没有元素时返回true,否则返回false。
- max_size:返回容器中最大元素个数。
- size:返回容器中当前元素个数。
- operator = :将一个容器赋给另一个容器。
- operator < : 如果第一个容器小于第二个容器,返回true,否则返回false。
- operator <= : 如果第一个容器小于或等于第二个容器,返回true,否则返回false。
- operator > : 如果第一个容器大于第二个容器,返回true,否则返回false。
- operator >= : 如果第一个容器大于或等于第二个容器,返回true,否则返回false。
- operator == : 如果第一个容器等于第二个容器,返回true,否则返回false。
- operator != : 如果第一个容器不等于第二个容器,返回true,否则返回false。
- swap: 交换两个容器的元素
顺序容器和关联容器共有函数
- begin:该函数有两个版本,返回iterator或const_iterator,返回容器第一个元素迭代器指针。
- end:该函数有两个版本,返回iterator或const_iterator,返回容器最后一个元素迭代器指针。
- rbegin:该函数有两个版本,返回reverse_iterator或const_reverse_iterator,返回容器最后一个元素迭代器指针。
- rend:该函数有两个版本,返回reverse_iterator或const_reverse_iterator,返回容器首个元素前面一位的迭代器指针。
- erase: 从容器中清楚一个或多个元素。
- clear: 清楚容器中所有元素。
容器比较
vector:(连续的空间存储,可以使用[]操作符)快速地访问随机的元素,快速地在末尾插入元素,但是在序列中随机插入、删除元素比较慢。而且如果一开始分配的空间不够的话,有一个重新分配更大空间的过程。
deque:(小片的连续,小片间用链表相连,实际上内部有一个map指针,因为知道类型,所以还是可以使用[],只是速度没有vector快)快速地访问随机的元素,快速地在开始和末尾插入元素,随机地插入、删除数据要慢,空间的重新分配要比vector快,重新分配空间后,原有的元素不需要备份。对deque的排序操作,可将deque先赋值到vector,排序后再复制回vector。
list:(每个元素间用链表相连)访问随机元素不如vector快,随机地插入元素比vector快,对每个元素分配空间,所以不存在空间不足,重新分配的情况。
set: 内部元素唯一,用一颗平衡树结构来存储,因此遍历的时候就排序了,查找也比较快。
map:一对一地映射结合,key不能重复。
2、vector容器
vector类称作向量类,它实现了动态的数组,用于元素数量变化的对象数组。像数组一样,vector类也用从0开始的下标表示元素的位置;但和数组不同的是,当vector对象创建后,数组的元素个数会随着vector对象元素个数的增大和缩小而自动变化。
vector常用函数:
1)构造函数
- vector():创建一个空vector
- vector(int nSize):创建一个vector,元素个数为nSize
- vector(int nSize, const T & t):创建一个vector,元素个数为nSize,且值均为t
- vetor(const vector& ):复制构造函数
2)增加函数
- void push_back(const T & x):向量尾部增加一个元素x
- iterator insert(iterator it, const T& x):向量中某一个元素前增加一个元素x
- void insert(iterator it, int n, const T& x):向量中某一个元素前增加n个相同元素x
- void insert(iterator it, const_iterator first, const_iterator last):向量中某一个元素前插入另一个相同类型向量的[first, last)间的数据
3)删除函数
- iterator erase(iterator it):删除向量中某一个元素
- iterator erase(iterator first, iterator last):删除向量中[first,last)中元素
- void pop_back():删除向量中最后一个元素
- void clear():删除向量中所有元素
4)遍历函数
- reference at(int pos):返回pos位置元素的引用
- reference front():返回首元素的引用
- reference back():返回尾元素的引用
- iterator begin():返回向量头指针,指向第一个元素
- iterator end():返回向量尾指针,不包括最后一个元素,在其下面
- reverse_iterator rbegin():反向迭代器,最后一个元素迭代指针
- reverse_iterator rend():反向迭代器,第一个元素之前的迭代指针
5)判断函数
- bool empty() const:向量是否为空,若true,则向量中无元素
6)大小函数
- int size() const:返回向量中元素个数
- int capacity() const:返回当前向量所能容纳的最大元素值
- int max_size() const:返回最大可允许的vector元素数量值
7)其他函数
- void swap(vector &):交换两个同类型向量的数据
- void assign(int n, const T& x):设置容器大小为n个元素每个元素值为x
- void assign(const_iterator first, const_iterator last):容器中[first,last)中元素设置为当前向量元素
vector使用示例:
#include <iostream>
// 引入头文件
#include <vector>
using namespace std;
int main(){
// 初始化
vector<int> vec;
// 新增元素
vec.push_back(1);
vec.push_back(2); // vec:1,2
for(int i=0; i<10; i++){
vec.push_back(i);
}
// 获取向量元素数
int nSize = vec.size();
// 通过数组方式访问
for(int i=0; i<nSize; i++){
cout<<vec[i]<<endl;
}
// 通过获取引用访问
for(int i=0; i<nSize; i++){
int &nValue = vec.at(i);
cout<<nValue<<endl;
}
// 通过迭代器访问
vector<int>::iterator vec_it = vec.begin();
while(vec_it != vec.end()){
cout<< *vec_it <<"\t";
vec_it++;
}
// 通过数组下标方式修改
vec[0] = 10;
// 通过引用修改
int& m = vec.at(0);
m = 20;
// 通过迭代器修改
vector<int>::iterator vec_it_1 = vec.begin();
*vec_it_1 = 30;
// 删除第一个元素
vec.erase(vec.begin());
// 删除第2~5个元素,[vec.begin()=1, vec.begin()+5) 左闭右开
vec.erase(vec.begin()+1, vec.begin()+5);
// 删除尾部元素
vec.pop_back();
return 0;
}
3、deque容器
deque容器为一个给定类型的元素进行线性处理,像向量一样,它能够快速地随机访问任一个元素,并且能够高效地插入和删除容器的尾部元素。但它又与vector不同,deque支持高效插入和删除容器的头部元素,因此也叫做双端队列。
常用函数:
1)构造函数
- deque():创建一个空deque
- deque(int nSize):创建一个deque,元素个数为nSize
- deque(int nSize, const T& t):创建一个deque,元素个数为nSize,且值均为t
- deque(const deque& ):复制构造函数
2)增加函数
- void push_front(const T& x):双端队列头部增加一个元素x
- void push_back(const T& x):双端队列尾部增加一个元素x
- iterator insert(iterator it, const T& x):双端队列中某一元素前增加一个元素x
- void insert(iterator it, int n, const T& x):双端队列中某一元素前增加n个相同元素x
- void insert(iterator it, const_iterator first, const_iterator last):双端队列中某一元素前插入另一个相同类型向量的[first, last)间的数据
3)删除函数
- iterator erase(iterator it):删除双端队列中某一个元素
- iterator erase(iterator first, iterator last):删除双端队列中[first, last)中元素
- void pop_front():删除双端队列中最前一个元素
- void pop_back():删除双端队列中最后一个元素
- void clear():删除双端队列中所有元素
4)遍历函数
- reference at(int pos):返回pos位置元素的引用
- reference front():返回首元素的引用
- reference back():返回尾元素的引用
- iterator begin():返回向量头指针,指向第一个元素
- iterator end():返回向量尾指针,不包括最后一个严肃,在其下面
- reverse_iterator rbegin():反向迭代器,最后一个元素的迭代指针
- reverse_iterator rend():反向迭代器,第一个元素前的迭代指针
5)判断函数
- bool empty() const:向量是否为空,若true,则向量中无元素
6)大小函数
- int size() const:返回向量元素个数
- int max_size() const:返回最大可允许的双端队列元素数量值
7)其他函数
- void swap(vector &):交换两个同类型向量的数据
- void assign(int n, const T& x):设置容器大小为n个元素,每个元素值为x
- void assign(const_iterator first, const_iterator last):容器中[first, last)中元素设置成当前双端队列元素
deque使用示例:
// deque容器与vector容器许多动能相似,此处代码主要示例不同之处。
#include <iostream>
// 引入头文件
#include <deque>
using namespace std;
int main(){
deque<int> dq;
// 尾部增加元素
dq.push_back(1);
// 头部增加元素
dq.push_front(2); // 2,1
// 头部删除元素
dq.pop_front(); //1
return 0;
}
在建立vector容器时,一般来说伴随着建立空间——填充数据——重建更大空间——复制原空间数据——删除原空间——添加新数据,如此反复,保证vector始终是一块独立的连续内存空间
在建立deque容器时,一般伴随着建立空间——填充数据——建立新空间——填充新数据,如此反复,没有原空间数据的复制和删除,是由多个分段连续的内存空间组成的。
4、list容器
相对于vector的连续线性空间,list是一个双向链表,他有一个重要性质:插入操作和删除操作都不会造成原有的list迭代器失效,每次插入和删除一个元素就配置或释放一个元素空间。也就是说,对于任何位置的元素插入和删除,list永远是常数时间。
常用函数
1)构造函数
- list<Elem> c:创建一个空的list
- list<Elem> c1(c2):复制另一个同类型元素的list
- list<Elem> c(n):创建n个元素的list,每个元素值由默认构造函数确定
- list<Elem> c(n,elem):创建n个元素的list,每个元素值为elem
- list<Elem> c(begin,end):由迭代器创建list,迭代区间为[begin, end)
2)大小、判断空函数
- int size() const:返回容器元素个数
- bool empty() const:判断容器是否空,若返回true,表名容器已空
3)增加、删除函数
- void push_back(const T& x):list容器尾元素后增加一个元素x
- void push_front(const T& x):list容器首元素前增加一个元素x
- void pop_back():删除容器尾元素,当且仅当容器不为空
- void pop_front():删除容器首元素,当且仅当容器不为空
- void remove(const T& x):删除容器中所有元素值等于x的元素
- void clear():删除容器中所有元素
- iterator insert(iterator it, const T& x=T()):迭代器指针it前插入元素x,返回x迭代器指针
- void insert(iterator it, size_type n, const T& x):在迭代器指针it前插入n个相同元素x
- void insert(iterator it, const_iterator first, const_iterator last):把[first, last)间的元素插入迭代指针it前
- iterator erase(iterator it):删除迭代器指针it对应的元素
- iterator erase(iterator first, iterator last):删除迭代器指针[first, last)间的元素
4)遍历函数
- iterator begin():返回首元素的迭代器指针
- iterator end():返回尾元素后的迭代器指针,而不是尾元素的迭代器指针
- reverse_iterator rbegin():返回尾元素的逆向迭代器指针,用于逆序遍历容器
- reverse_iterator rend():返回首元素前的逆向迭代器指针,用于逆序遍历容器
- reference front():返回首元素的引用
- reference back():返回尾元素的引用
5)操作函数
- void sort():容器内所有元素排序,默认是升序
- template<class Pred> void sort(Pred pr):容器内所有元素根据预判定函数pr排序
- void swap(list& str):两list容器交换功能
- void unique():容器内相邻元素若有重复的,则仅保留一个
- void splice(iterator it, list& x):队列合并函数,队列x所有元素插入迭代器指针it前,x变成空队列
- void splice(iterator it, list& x, iterator first):队列x中移走[first, end)间元素插入迭代指针it前
- void splice(iterator it, list& x, iterator first, iterator last):队列x中移走[first, last)间元素插入迭代指针it前
- void reverse():反转容器中元素顺序
list使用示例
#include <iostream>
#include <string>
// 引入头文件
#include <list>
using namespace std;
int main(){
list<string> test;
// 尾部添加元素
test.push_back("back");
// 首部添加元素
test.push_front("front");
// 访问首部元素
cout<<test.front()<<endl;
cout<<*test.begin()<<endl;
// 访问尾部元素
cout<<test.back()<<endl;
cout<<*(test.rbegin())<<endl;
// 删除首部元素
test.pop_front();
// 删除尾部元素
test.pop_back();
list<int> list_i;
for(int i=0; i<5; i++){
list_i.push_back(10 * i);
}
// 迭代器遍历
list<int>::iterator it = list_i.begin();
for(; it!=list_i.end(); it++){
cout<<*it<<endl;
}
// 逆序迭代器遍历
list<int>::reverse_iterator rit = list_i.rbegin();
for(; rit!=list_i.rend(); rit++){
cout<<*rit<<endl;
}
// 排序
list_i.sort();
return 0;
}
注:当list的每个元素为string的时候,采用迭代器进行遍历,it++并不是跳到下一个string,而是string的下一个字符。
5、队列和堆栈
队列和堆栈是常用和重要的数据结构。队列只允许在表的一端插入,在另一端删除,允许插入的一端叫做队尾,允许删除的一端叫做队头,是一种先进先出线性表。栈只允许在表的一端进行插入和删除操作,是一种后进先出的线性表。
常用函数:
1)构造函数
- queue(class T, class Container=deque<T>):创建元素类型为T的空队列,默认容器是deque
- stack(class T, class Container=deque<T>):创建元素类型为T的空堆栈,默认容器是deque
2)操作函数
队列和堆栈共有函数:
- bool empty():如果队列(堆栈)为空返回true,否则返回false
- int size():返回队列(堆栈)中元素数量
- void push(const T& t):把t元素压入队尾(栈顶)
- void pop():当队列(栈)非空情况下,删除队头(栈顶)元素
队列独有函数:
- T& front():当队列非空情况下,返回队头元素引用
- T& back():当队列非空情况下,返回队尾元素引用
堆栈独有函数:
- T& top():当栈非空情况下,返回栈顶元素的引用
操作代码示例
#include <iostream>
// 引入堆栈头文件
#include <stack>
// 引入队列头文件
#include <queue>
#include <vector>
#include <deque>
using namespace std;
int main(){
// 底层容器采用vector
stack<int, vector<int> > s;
// 插入操作
for(int i=0; i<10; i++){
s.push(i);
}
// 取栈顶
cout<<s.top()<<endl;
// 出栈
s.pop();
// 底层容器采用deque
queue<int, deque<int> > dq;
for(int i=0; i<10; i++){
// 入队列
dq.push(i);
}
//取队头
cout<<dq.front()<<endl;
// 取队尾
cout<<dq.back()<<endl;
// 出队
dq.pop();
return 0;
}
6、优先队列
优先队列即priority_queue类,带优先权的队列,优先权高的元素优先出队。与普通队列相比,共同点都是对队头做删除操作,队尾做插入操作,但不一定遵循先进先出原则,也可能先进后出。prority_queue是一个基于某个基本序列容器进行构建的适配器,默认的序列容器是vector。
常用函数:
1)构造函数
- priority_queue(const Pred& pr=Pred(), const allocator_type& al=allocator_type()):创建元素类型为T的空优先队列,Pred是二元比较函数,默认是class<T>
- prority_queue(const value_type*first, const value_type * last, const Pred& pr=Pred(), const allocator_type& al=allocator_type()):以迭代器[first, last)指向元素,创建元素类型为T的优先队列,Pred是二元比较函数,默认是less<T>
2)操作函数
- bool empty():如果优先队列为空返回true,否则返回false
- int size():返回优先队列中元素数量
- void push(const T& t):把t元素压入优先队列
- void pop():优先队列非空的情况下,删除优先级最高元素
- T& top():优先队列非空情况下,返回优先级最高元素的引用
使用代码示例
#include <iostream>
//引入头文件
#include <queue>
using namespace std;
int main(){
int a[] = {1,2,3,4,5,6,7,8,9,10};
// 通过构造函数将数组a的元素送入优先队列
priority_queue<int> pr(a,a+10);
// 入队列
pr.push(11);
while(!pr.empty()){
// 取队头
cout<<pr.top()<<endl;
// 出队
pr.pop();
}
return 0;
}
如果是自定义的比较函数,通常定义class,然后重载operator < 。
或者进行书写二元比较函数,在进行定义的时候,传入其中。