一、STL概述。
1、基本概念
STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件统称。
STL从广义上讲分为三类:algorithm(算法)、container(容器)和 iterator(迭代器)。容器和算法可以通过迭代器无缝地连接。几乎所有代码都采用了模板类和模板函数的方式。相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。在C++标准中,STL被组织为下面13个头文件中:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack>、<utility>。
老师这张图太形象了,必须放上来,感觉当初没学到精髓。容器就是存数据的地方,不同的数据根据其特点选用不同的容器,算法就是处理数据的地方。为了使得算法和存数据的地方足够通用,所以使用上了模板。为了使得算法能够对数据的处理,于是有了迭代器。
(1)STL六大组件
容器(Container)、算法(Algorithm)、迭代器(Iterator)、仿函数(Function object)、适配器(Adaptor)、空间配置器(allocator)。
(2)STL的优点
STL是C++的一部分,因此不用额外安装什么,它被内建在你的编译器之内。
STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但是这种分离使得STL变得非常通用。例如:STL的容器中,可以放入元素、基础数据类型变量、元素的地址等。STL中的sort()函数可以用来操作vector、list等容器。
程序员可以不用思考STL的具体实现过程,只要能够熟练掌握使用STL就可以。
STL具有高可重用性,高性能,高移植性,跨平台的优点。高重用性:STL几乎所有的代码都采用了模板类和模板函数的方式实现;高性能:如map可以高效地从十几万条记录里面查找出指定的记录,因为map指定的时采用红黑树的变体实现的。(红黑树是平衡二叉树的一种)
2、容器container
在实际的开发过程中,当程序存在着对时间要求很高的部分时,数据结构的选择就显得很重要。STL容器为我们提供特定类型下的数据结构,通过设置一些模板,STL容器对常用的数据结构提供了支持,这些模板的参数允许我们制定容器中元素的数据类型。容器部分主要头文件<vector>、<list>、<deque>、<set>、<map>、<stack>和<queue>组成。
(1)容器的概念
用来管理一组数据。
(2)容器的分类
序列式容器(Sequence containers)
每个容器有一个固定位置——取决于插入时机和地点,和元素值无关。如vector、deque、list。
关联式容器(Associated containers)
元素位置取决于特定的排序准则,和插入顺序无关。如set、multiset、map、multimap。
3、迭代器iterator
迭代器在STL中用来将算法和容器联系起来,起着一种黏合剂的作用,几乎所有的STL提供的所有算法都是通过迭代器存取元素序列进行工作的。每一个容器都定义了其本身所专有的迭代器,用以存取容器中的元素。迭代器部分主要由头文件<utility>、<iterator>和<memory>组成。
4、算法alogrithm
C++利用模板的机制允许推迟某些类型的选择,STL利用这一点提供了相当多的有用算法,在一个有效的框架中完成这些算法——将所有的数据类型划分为少数的几类,就可以在模板参数中使用一种类型代替同一种类的其他类型。STL实现了大约100个实现算法的模板函数。算法部分主要由头文件<algorithm> <numeric>和<functional>组成。
5、C++标准库
太多太多了,我可以抽空再好好理吗。。。。。
二、容器
1、string
(1)string的概念
string是STL的字符串类型,通常用来表示字符串。string和char *的比较:string是一个类,char *是一个指向字符的指针;string中封装了char *,管理这个字符串,是一个char *型的容器;string不用考虑内存释放和越界问题,string管理char *所分配的内存,每一个string复制,取值都由string类维护;string提供了一系列的字符串操作函数:查找find、拷贝copy、删除erase、替换replace、插入insert。
(2)string的构造函数
string(); //默认构造函数,构造一个空的字符串string s1
string(const string &str); //拷贝构造函数,构造一个和str一样的string
string(const char *s); //带参构造函数,用字符串s初始化
string(int n, char c); //带参构造函数,用n个字符c初始化
(3)string类的存取操作
connst char &operator [](int n) const; // [ ]在刚好越界的时会返回char(0) zai
const char & at(int n) const; //at()在越界时抛出异常
(4)从string取得const char *的操作
const char * c_str() const; //返回一个以'\0'结尾的字符串的首地址
(5)从stirng拷贝到指向char *指向的内存空间的操作
int copy(char *s, int n, int pos = 0) const; //把当前串中以pos开始的n个字符拷贝到以s为起始位置的字符数组中,返回实际拷 贝的数目。保证s所指向的空间足够大以容纳当前字符串。
(7)清空字符串
int length() const; //返回当前字符串的长度。长度不包括字符串结尾的‘\0’
bool empty() const; //当前字符串是否为空
(8)字符串赋值
string & operator = (const string &s); //把字符串s赋给当前的字符串
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赋给当前字符串
stirng &assign(const string &s,int start, int n); //把字符串s中从start开始的n个字符赋给当前字符串
(9)字符串连接
string &operator +=(const string &s); //把字符串s连接到当前字符串结尾
string &operator+=(const char *s); //把字符串s连接到当前字符串结尾
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
(10)字符串比较
int compare(const string &s) const; //与字符串s比较
int compare(const char *s) const; //与字符串s比较
(11)查找
int find(char c,int pos=0) const; //从pos开始查找字符c在当前字符串的位置
int find(const char *s, int pos=0) const; //从pos开始查找字符串s在当前字符串的位置
int find(const string &s, int pos=0) const; //从pos开始查找字符串s在当前字符串中的位置;find函数如果查找不到,就返回-1
int rfind(char c, int pos=npos) const; //从pos开始从后向前查找字符c在当前字符串中的位置
int rfind(const char *s, int pos=npos) const;
int rfind(const string &s, int pos=npos) const;//rfind是反向查找的意思,如果查找不到, 返回-1
(12)区间删除
string &insert(int pos, const char *s);
string &insert(int pos, const string &s); //前两个函数在pos位置插入字符串s
string &insert(int pos, int n, char c); //在pos位置 插入n个字符c
string &erase(int pos=0, int n=npos); //删除pos开始的n个字符,返回修改后的字符串
2、vector
vector是将元素置于一个动态数组中加以管理的容器。vector可以随机存取元素,支持索引值直接存取,用at()方法或者[]操作符。vector尾部添加或移除元素非常迅速,但是在中部或头部插入元素或移除元素比较费时。
(1)vector的默认构造函数
vector使用模板类实现,vector对象的默认构造形式:
vector<T> vecT; //T可以是自定义类型,当时自定义类型时,必须提供拷贝构造函数,以保证拷贝正常
(2)vector对象的带参构造函数
vector(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。注意该区间是左闭右开的区间。
vector(n, elem); //构造函数将n个elem拷贝给本身。
vector(const vector &vec); //拷贝构造函数
(3)vector的大小
vector.size(); //返回容器中元素的个数
vector.empty(); //判断容器是否为空
vector.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
vector.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
(4)vector的数据存储
ec.at(idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。
vec[idx]; //返回索引idx所指的数据,越界时,运行直接报错
deqInt.push_back( T ); //插入数据
(5)vector的迭代器
(a)迭代器基本原理
迭代器是一个可遍历STL容器内全部或部分元素的对象。
迭代器指出容器中的一个特定位置。
迭代器就如同一个指针。
迭代器提供对一个容器中的对象的访问方法,并且可以定义了容器中对象的范围。
迭代器的种类:输入迭代器、输出迭代器、正向迭代器、双向迭代器
(b)双向迭代器与随机访问迭代器
list,set,multiset,map,multimap支持双向迭代器,it++, it--
其中vector,deque支持随机访问迭代器 it += i, it-= i;
(c)vector和迭代器的配合使用
vector<int>::iterator it; //声明容器vector<int>的迭代器。
it = vecInt.begin(); // *it == 1
++it; //或者it++; *it == 3 ,前++的效率比后++的效率高,前++返回引用,后++返回值。
(6)vector的插入
vector.insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置。
vector.insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值。
vector.insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值
(7)vector的删除
vector.clear(); //移除容器的所有数据
vec.erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置
vec.erase(pos); //删除pos位置的数据,返回下一个数据的位置
3、deque
和vector一样是STL容器,deque是双端数组,vector是单端的。除此之外,基本与vector相同。
deque与vector的基本操作没啥区别,不同就是它支持双端数据处理,多了俩操作,从头读,从头写。
push_front(); pop_front();
4、stack
stack是堆容器,是一种“先进后出”容器。它是deque的简单装饰。与deque的区别在于,stack只允许从头进,从头出。
5、queue
queue是队列容器,是一种“先进先出”的容器,它时deque的简单装饰。与deque的区别在于,queue只允许头进尾出。
satck和queue都是deque的改进,只支持尾端push,一个只支持头部pop,一个只尾部pop。
7、list
list是一个双向链表容器,它高效的进行插入删除元素,不支持随机存储,不支持at.(pos)函数和[]操作符
除了不支持随机访问,没什么说法,内部实现是双向链表。
8、priority_queue
最大值优先级队列、最小值优先级队列, (默认是最大值优先级队列)说白了就是带排序的队列。
说白了就是带自动排序的queue,内部实现还是数组。
9、set/multiset
set是一个集合容器,其中所包含的元素是唯一的,集合中的元素按一定的顺序排列。
元素插入过程是按排序规则插入,所以不能指定插入位置。
set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树。在插入操作和删除操作上比vector快。
set不可以直接存取元素,它不可以使用at.()和[]。
multiset与set区别:
set中唯一键值只能出现一次,而multiset中同一值可以出现多次。
不可以修改set或multiset容器中的元素值,因为该容器自动排序。如果想要修改元素的值,必须先删除原有值,再插入新的元素。
#include<set>
(1)初始化
set<int> s; == set<int, greater<int>> s; //默认是从大到小排序
当数据类型时自定义类型时,需要重载-或者+来满足排序所需。
(2)插入和pair对组
s.insert(12);
s.insert(12); 只插入一个12,同一键值只能出现1次。
pair对组,可以构建一对数据,第一个元素使用first,第二个元素使用second
insert的返回值是一个pair对组,第一个指向插入位置的迭代器,如果插入失败,返回end()第二个表示插入是否成功。
(3)lower_bound()和upper_bound()
lower_bound()返回第一一个>=要查找元素的迭代器,upper_bound()返回第一个>要查找元素的迭代器。
(4)std::pair<set<int>::iterator, set<int>::iterator> ret = s.equal_range(3)
相当于上述两个函数的合集,第一个迭代器指向第一个等于3的元素,第二个迭代器指向第一个大于3的元素。
返回值是一个对组,fisrt是第一个等于3的元素,第二个是第一个大于3的元素。
9、map/multimap
map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对。它提供基于key的快速检索能力。
map中key值是唯一的,集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入的,所以不能指定插入位置。
map的具体实现采用红黑树变体的平衡二叉树数据结构。在插入操作和删除操作上比vector快。
map可以直接存取key所对应的value,支持[]操作符,如map[key] = value。
mutimap与map的区别:map支持唯一键值,每个键只能出现一次;而multimap中相同键可以出现多次。mulitmap不支持[]操作符。
#include<map>
(1)初始化
map<int, string> m; //第一项是键值,第二项是内容
可以使用m[8]进行数据插入,[]内是map的键值,不能当做数组下标。
10、容器的共性机制
(1)容器共同能力
所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝。(必须提供拷贝构造函数)
除了queue和stack外,每个容器都提供可返回迭代器的函数,运用返回的迭代器就可以访问元素。
通常STL不会丢出异常。要求使用者确保传入正确的参数。
每个容器都提供一个默认构造函数和一个默认的拷贝构造函数。
与大小相关的操作方法,c.size()和c.empty()。
(2)各个容器的使用时机
(a)deque使用场景:排队购票使用可以用,支持头端快速移除,尾端快速添加,如果用vector,则头端移除时,移动大量数据,速度较慢。 vector与deque的比较:
vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却是不固定的。
如果有大量释放,vector花的时间更少。这与二者内部实现有关。
deque支持头部的快速插入和快速移除,这是deque的优点。
(b)list使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
(c)set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
(d)map的使用场景:比如ID号存储十万个用户,想要快速通过ID查找对应的用户,二叉树效率高。
三、算法
1、算法概述
算法部分主要由头文件<algorithm>,<numeric>和<functional>组成。STL提供了大量实现算法的模版函数,只要我们熟悉了STL之后,许多代码可以被大大的化简,只需要通过调用一两个算法模板,就可以完成所需要的功能,从而大大地提升效率。
2、算法分类:
按操作对象:
直接改变容器内容。
将原容器内容复制一份,修改其副本,然后传回该副本。
按功能:
非可变序列算法 指不直接修改其所操作的容器内容的算法
计数算法 count、count_if
搜索算法 search、find、find_if、find_first_of、…
比较算法 equal、mismatch、lexicographical_compare
可变序列算法 指可以修改它们所操作的容器内容的算法
删除算法 remove、remove_if、remove_copy、…
修改算法 for_each、transform
排序算法 sort、stable_sort、partial_sort、
排序算法 包括对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作
数值算法 对容器内容进行数值计算
3、函数对象和谓词
(1)函数对象非和谓语定义
实质是一个对象,但是可以像函数一样使用。——重载了()运算符。
根据函数对象参数个数不同,分为:一元函数对象和二元函数对象。
例:
class Show{public:
void operator ()(int a) {
cout << a <<endl;}
}; Show show; show(n);
谓词:函数返回值是bool类型,用于判断,谓词可以是以个仿函数,也可以是一个回调函数。
(2)预定义函数对象
标准模板库中提前定义很多预定义好的函数对象。
例如,类模板plus<>实现了不同类型数据的加法运算。
减法:minus<Types>
乘法:multiplies<Types>
除法:divides<Tpye>
求余:modulus<Tpye>
取反:negate<Type>
大于:greater<Type>
小于: less<Type>
(3)函数适配器
STL中已经定义了大量的函数对象,但有时候需要对函数返回值进一步的简单计算,或者填上多余的参数,不能直接代入算法。函数适配器实现了这一功能,将一种函数对象转化为另一种符合要求的函数对象。
分为四大类:绑定适配器,组合适配器,指针函数适配器,成员函数适配器。
绑定适配器:将数值绑定到函数的数,bin1st、 bind2nd //绑定器
组合适配器:将谓词返回值取反。not1、not2 //取反器
指针函数适配器:将普通函数指针适配成函数基类类型
成员函数适配器:添加函数参数。
(4)STL的容器算法迭代器的设计理念
STL容器通过类模板技术,实现数据类型和容器模型分离。
STL的迭代器技术实现了遍历容器的统一方法;也为STL的算法提供了统一性
STL的函数对象实现了自定义数据类型的算法运算。
4、常用算法
(1)遍历算法
for_each() //用指定函数一次对指定范围内所有元素进行迭代访问,该函数不得修改序列中的元素
transform() //与for_each类似,遍历所有元素,但可对容器的元素进行修改
例: transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), Mul()); 二元函数对象Mul()
取v1和v2数据操作,结果放在v3
(2)查找算法
int count(数据开始,数据结束,数); //计数等于的数
bool count_if(数据开始,数据结束,执行的函数); //判断是否有函数返回真的
find(数据开始,数据结束,数); //返回第一个相等的数的迭代器
iterator find_if(数据开始,数据结束,执行的函数); //返回第一个代入函数返回真的迭代器
(3)排序算法
sotr(数据开始,数据结束,执行的函数); //按照执行的函数的规则进行排序
random_shuffle(数据开始,数据结束,执行的函数); //对指定范围内的元素随机调整次序
reverse(vecInt.begin(), vecInt.end()); //反序
(4)拷贝和替换算法
swap(vecIntA, vecIntB); //交换两个容器的元素
replace(数据开始,数据结束,oldValue,newValue); 将指定范围内的所有等于oldValue的元素替换成newValue
replace_if (数据开始,数据结束,执行的函数, newValue); //将指定范围内所有操作结果为true的元素用新值替换。