C++复习之路(五)——STL标准模板库

一、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的元素用新值替换。

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/iotflh/article/details/86570907