stl标准库

发布时间 2023-11-27 15:09:27作者: 常羲和

STL标准库

1. STL概念

为了建立数据结构和算法的一套标准,并且降低他们之间的耦合关系,以提升各自的独立性、弹性、交互操作性(相互合作性,interoperability),诞生了STL

​ STL(Standard Template Library,标准模板库),是惠普实验室开发的一系列软件的统称。现在主要出现在 c++中,但是在引入 c++之前该技术已经存在很长时间了。

​ STL(Standard Template Library)标准模板库,在我们 c++标准程序库中隶属于 STL 的占到了 80%以上。

六大组件:

容器:数据结构,用于存放数据【类模板】
算法:操作数据的各种功能,如删除、排序、查询等【函数模板】
迭代器:算法借助迭代器操作数据(主要读数据) 【各种运算符重载】
仿函数:算法的某种策略,增强算法的功能。【()运算符重载】
适配器:用于扩展容器、算法、迭代器的接口
空间管理器:负责空间的配置与管理 【类模板】

​ STL优点:

1)STL是C++的一部分,不需要安装外部库
2)STL将数据和操作分离
3)STL具有高可重用性,高性能,高移植性,跨平台的优点
   高可重用性:采用了模板类和模板函数
   高性能:可以高效地从大量的数据中快速查找,如map采用红黑树的结构
   高移植性:只要存在C++的编译环境的操作系统,都可以编译和运行STL模块。

STL 之父 Alex Stepanov 亚历山大·斯特潘诺夫(STL 创建者)。

2. STL三大组件的基本用法

容器(数据结构,vector,set,map,queue等)、算法(插入数据、删除数据、修改数据、排序等)、迭代器(算法操作容器)

2.1 容器

容器:用于存放数据

常用的数据结构

数组(array) 链表(list) 树(tree) 栈(stack) 队列(queue) 集合(set) 映射表(map)

根据数据在容器中的排列特性:序列式容器、关联式容器

序列式容器:
     强调值的排序,每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。如:vector,deque/queue,list;
关联式容器:
     非线性,更准确的说是二叉树结构,各元素之间没有严格的物理上的顺序关系;在数据中选择一个关键字key,这个key对数据起到索引的作用,方便查找。如:set/multiset,map/multimap容器

【注】容器可以嵌套容器

2.2 算法

算法(Algorithm):用于解决问题

STL收录的算法经过了数学上的效能分析与证明,是极具复用价值的,包括常用的排序,查找等。特定的算法往往搭配特定的数据结构,算法与数据结构相辅相成。

算法分为:质变算法和非质变算法

质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝、替换、删除等
非质变算法:是指运算过程中不会更改区间内的元素内容。如查找、统计、求极值等。

2.3 迭代器

迭代器(iterator)是一种抽象的设计概念,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式

简之,迭代器是依次遍历容器中所有的元素

迭代器的种类:

输入迭代器:只读数据,支持++、==、!=
输出迭代器:只写数据,支持++
向前迭代器:读写数据(向前),支持++、==、!=
双向迭代器:读写数据(向前、向后),支持++、--
随机迭代器:提供读写操作(跳跃式访问任意位置),支持++、--、[]、-n、<、<=、>、>=

3. STL常见容器的应用

3.1 string 容器

string也是字符串的类型,可以理解它是用于存放字符的容器

3.1.1 string和c风格字符串对比

1)char*是一个指针,string是一个类
2)string封装了很多实用的成员方法
  查找find,拷贝copy,删除delete,替换replace,插入insert
3)不用考虑内存释放和越界
  string管理char*所分配的内存,每一次string的赋值,取值都由string类负责维护,不用担心赋值越界和取值越界等

3.1.2 string容器api

//构造函数
string(); //创建一个空的字符串 string str;
string(const string& str); //使用一个string对象初始化另一个string对象
string(const char* s); //使用字符串s初始化
string(int n,char c); //使用n个字符c初始化

//赋值
string& operator=(const char* s); //char*类型字符串 赋值给当前的字符串
string& operator=(const string &s); //把字符串s赋值给当前的字符串
string& operator=(char c); //字符赋值给当前的字符串
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赋给当前字符串
string& assign(const string &s,int start,int n);//将s从start开始n个字符赋值给字符串

//取值
char& operator[](int n);//通过[]方式取字符
char& at(int n);//通过at方法取字符

//字符串拼接
string& operator+=(const string& str); //重载+=操作符
string& operator+=(const char* str); //重载+=操作符
string& operator+=(const char c);//重载+=操作符
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

//查找与替换
int find(const string& str,int pos=0)const;//查找str第一次出现位置,从pos开始查找,如果未查找到返回-1
int find(const char* s,int pos=0)const;//查找s第一次出现位置,从pos开始找
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;//查找str最后一次位置,从pos开始查找
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;//查找字符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

//比较,返回值 0;相等,1;大于s,-1;小于s
int compare(const string &s)const;//与字符串s比较
int compare(const char *s)const;//与字符串s比较

//截取子字符串
string substr(int pos=0,int n=npos)const;//返回由pos开始的n个字符组成的字符串

//插入与删除
string& insert(int pos,const char* s);//插入字符串
string& insert(int pos,const string& str);//插入字符串
string& insert(int pos,int n,char c);//在指定位置插入n个字符c
string& erase(int pos,int n=npos);//删除从pos开始的n个

//转成char*
const char* c_str()  将当前字符串转成char*

//获取字符串的长度
int size();

3.2 vector容器

3.2.1 vector与数组的区别

vector的结构类同于数组,数组是静态的,在定义时确定的数组的大小;而vector是动态的,添加元素时如果空间不足时,则会自动扩容器(2^n);整体来说,vector比较灵活的,而且vector是类模板,可以存放任意类型的元素

image-20231124111701011

3.2.2 vector迭代器

vector维护一个线性空间(线性连续空间),vector迭代器所需要的操作行为是(*,->,++,--,+,-,+=,-=)运算符重载等。vector支持随机存取,vector提供的是随机访问迭代器(Random Access Iterator),迭代器中的元素即为vector容器中的元素。

【注】vector扩容之后,原迭代器则无效,需要重新获取迭代器

所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空间),而是一块更大的内存空间,然后将原数据拷贝新空间,并释放空间。
因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就都失效了。

3.2.3 vector容器api

//构造函数
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(),v.end());//将v[begin,end())区间中的元素拷贝给本身
vector(n,elem);//构造函数将n个elem拷贝给本身
vector(const vector &vec);//拷贝构造函数

//赋值操作
assign(beg,end);//将[beg,end)区间中的数据拷贝赋值给本身
assign(n,elem); //将n个elem拷贝赋值给本身
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);//将vec与本身的元素互换

//大小操作
int size(); //返回容器中元素的个数
bool empty();//判断容器是否为空,返回bool值(0,1)
void resize(int num);//重新指定容器的长度为num,若容器边长,则以默认值填充新位置。若容器变短,则末尾超出容器长度的元素被删除
void resize(int num,elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除
int capacity(); //容器的容量
void reserve(int len); //容器预留len个元素长度,预留位置不初始化,元素不可访问。

//取值操作
at(int idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常
operator[](int idx);//返回索引idx所指的数据,越界时,运行直接报错
front(); //返回容器中第一个数据元素
back(); //返回容器中最后一个数据元素

//插入和删除
insert(const_iterator pos,int count,T ele);//迭代器指向位置pos插入count个元素ele
push_back(ele); //尾部插入元素ele
pop_back(); //删除最后一个元素
erase(const_iterator start,const_iterator end);//删除迭代器从start到end之间的元素,删除[start,end)区间的所有元素
erase(const_iterator pos);//删除迭代器指向的元素
clear(); //删除容器中所有元素

巧用swap收缩内存空间

resize()+swap()实现vector收缩内存空间

resize(n);
vector<T>(v).swap(v);

3.3 deque容器

3.3.1 deque基本概念

vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间。所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。

image-20231124115631599

deque容器和vector容器最大的差异:

1)在于deque允许使用常数项时间对头端进行元素的插入和删除操作
2)在于deque没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来
3)deque没有必要要提供所谓的空间保留(reserve)功能
4)虽然deque容器也提供了Random Access Iterator,但是它的迭代器并不是普通的指针,其复杂度和vector不是一个量级,这当然影响各个运算的层面。因此,除非有必要,我们应该尽可能的使用vector,而不是deque
5)对deque进行的排序操作,为了最高效率,可将deque先完整的复制到一个vector中,对vector容器进行排序,再复制回deque。

3.3.2 deque实现原理

逻辑上,deque容器是连续的空间,是由一段一段的定量的连续空间构成。一旦有必要在deque前断或者尾端增加新的空间,便配置一端连续定量的空间,串接在deque的头端或者尾端。

deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口。

既然deque是分段连续内存空间,那么就必须有中央控制(map实现的),维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐

中央控制:连续小空间,由map实现,存放是地址,地址指向的另一个连续空间为缓冲区。

缓冲区:是deque的存储空间的主体。

image-20231124141345255

3.3.3 常用API

//构造函数
deque<T> deqT;//默认构造形式
deque(beg,end);//构造函数将[beg,end)区间中的元素拷贝给本身
deque(n,elem);//构造函数将n个elem拷贝给本身
deque(const deque &deq);//拷贝构造函数

//赋值操作
assign(beg,end);//将[beg,end)区间中的数据拷贝赋值给本身
assign(n,elem);//将n个elem拷贝赋值给本身
deque& operator=(const deque &deq);//重载等号运算符
swap(deq);//将deq与本身的元素互换

//大小操作
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除
deque.resize(num,elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除

//双端插入和删除
void push_back(elem);//在容器尾部添加一个数据
void push_front(elem);//在容器头部插入一个数据
void pop_back();//删除容器最后一个数据
void pop_front();//删除容器第一个数据

//数据存取
at(idx);//返回索引idx所指的数据,如果idx越界,抛出out_of_range
operator[];//返回索引idx所指的数据,如果idx越界,不抛出异常,会直接出错
front();//返回第一个数据
back();//返回最后一个

//插入操作
void insert(iterator pos,T elem);//在pos位置插入一个elem元素的拷贝,返回新数据的位置
void insert(iterator pos,int n,T elem);//在pos位置插入n个elem数据,无返回值
void insert(iterator pos,iter_beg,iter_end);//在pos位置插入[beg,end)区间的数据,无返回值

//删除操作
clear();//移除容器的所有数据
iterator erase(iterator beg,iterator end);//删除[beg,end)区间的数据,返回下一个数据的位置
iterator erase(iterator pos);//删除pos位置的数据,返回下一个数据的位置

3.4 stack容器

3.4.1 stack基本概念

stack是一种先进后出(First in Last Out,FILO)的数据结构,它只有一个出口,stack容器允许新增元素、移除元素、取得栈顶元素,但是除了最顶端外,没有任何其他方法可以存取stack的其他元素,换言之,stack不允许有遍历行为

image-20231124143443033

3.4.2 stack没有迭代器

stack 所有元素的进出都必须符合”先进后出”的条件,只有 stack 顶端的元素,才有机会被外界取用。Stack 不提供遍历功能,也不提供迭代器。

3.4.3 常用API

//构造函数
stack<T> stkT;//stack 采用模板类实现,stack对象的默认构造形式
stack(const stack &stk);//拷贝构造函数

//赋值操作
stack& operator=(const stack &stk);//重载等号操作符

//数据存取
void push(elem);//向栈顶添加元素
void pop();//从栈顶移除第一个元素
T pop();//返回栈顶元素

//大小操作
empty();//判断堆栈是否为空
size();//返回堆栈的大小

3.5 queue容器

3.5.1 queue基本概念

queue是一种先进先出(First in first out,FIFO)的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素

image-20231127101407440

3.5.2 queue没有迭代器

queue所有元素的进出都必须符合"先进先出"的条件,只有queue的顶端元素,才有机会被外界取用。queue不提供遍历功能,也不提供迭代器

3.5.3 常用API

//构造函数
queue<T> queT; //queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que); //拷贝构造函数

//存取、插入和删除
void push(elem); //往队尾添加元素
void pop(); //从队头移除第一个元素
T back(); //返回最后一个元素
T front(); //返回第一个元素

//赋值与大小
queue& operator=(const queue &que);//重载等号操作符
empty(); //判断队列是否为空
size(); //返回队列的大小

3.6 list容器

3.6.1 list概念

list(链表)是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成

每个结点包含两个部分:

1)存储数据元素的数据域
2)存储下一个结点地址的指针域

list与vector的比较:

1)相对于vector的连续线性空间,list就显得负责许多,每次插入或者删除一个元素,就是配置或者释放一个元素的空间,不浪费多余的空间,且插入与移除元素的操作是常数时间(稳定)。
2)list和vector是两个最常被使用的容器,但list是由双向链表实现的
3)list插入操作和删除操作都不会造成原有list迭代器的失效

image-20231127102346533

list特点:

1)采用动态存储分配,不会造成内存浪费和溢出
2)链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
3)链表灵活,但是空间和时间额外耗费较大

3.6.2 list的迭代器

list不能像vector一样以普通指针作为迭代器,因为其节点不能保证在同一块连续的内存空间上。list迭代器必须有能力指向list的节点,并有能力进行正确的递增、递减、取值、成员存取操作。递增时指向下一个节点,递减时指向上一个节点,取值时取的是节点的数据值,成员取用时取的是节点的成员。另外,list是一个双向链表,迭代器必须能够具备前移、后移的能力,所以list容器提供的是双向的迭代器(bidrectional iterators)

list有一个重要的性质,插入和删除都不会造成原有list迭代器失效

【注】list的迭代器,不支持+n操作

list容器不仅是一个双向链表,而且还是一个循环的双向链表

3.6.3 常用API

//构造函数
list<T> lstT; //list采用模板类实现,对象的默认构造形式
list(beg,end); //构造函数将[beg,end)区间中的元素拷贝给本身
list(n,elem); //构造函数将n个elem拷贝给本身
list(const list &lst); //拷贝构造函数

//插入和删除
push_back(elem); //在容器尾部加入一个元素
pop_back(); //删除容器中最后一个元素
push_front(elem); //在容器开头插入一个元素
pop_front(); //从容器开头移除第一个元素
iterator insert(pos,elem); //在pos位置插elem元素的拷贝,返回新数据的位置
void insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值
void insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值
clear(); //移除容器的所有数据
iterator erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。
iterator erase(pos); //删除pos位置的数据,返回下一个数据的位置
remove(elem); //删除容器中所有与elem值匹配的元素

//大小
size(); //返回容器中元素的个数
empty(); //判断容器是否为空
resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除
resize(num,elem);

//赋值
assign(beg,end); //将[beg,end)区间中的数据拷贝赋值给本身
assign(n,elem); //将n个elem拷贝赋值给本身
list& operator=(const list &lst); //重载等号操作符
swap(lst); //将lst与本身的元素互换

//读取
front(); //返回第一个元素
back(); //返回最后一个元素

//反转和排序
reverse(); //反转链表
sort(); //list排序

3.7 set/multiset容器

3.7.1 set概念

set(集合)的特性是所有元素都会根据元素的键值自动被排序

set的元素即是键值(key)又是实值(value),不允许两个元素有相同的键值

set的iterator是一种const_iterator,不允许修改set的键值

set拥有和list某些相同的性质,当对容器中的元素进行插入操作或者删除操作的时候,操作之前的迭代器,在操作完成之后依然有效,被删除的那个元素的迭代器必然是一个例外。

3.7.2 set数据结构

multiset的特性及用法和set完全相同,唯一的差别在于它允许键值重复。set和multiset的底层实现是红黑树。

3.7.3 常用API

//构造函数
set<T> st; //set默认构造函数
multiset<T> mst; //multiset默认构造函数
set(const set &st); //拷贝构造函数
set(begin,end); //复制[begin,end)区间的数据到当前的集合中

//赋值和大小
set& operator=(const set &st); //重载等号操作符
swap(st); //交换两个集合容器
size(); //返回容器中元素的数目
empty(); //判断容器是否为空

// 插入和删除
insert(elem); //在容器中插入元素
clear(); //清除所有元素
erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器
erase(beg,end); //删除区间[beg,end)的所有元素,返回下一个元素的迭代器
erase(elem); //删除容器中值为elem的元素

//查找
find(key); //查找键key是否存在,若存在,返回该键的元素的迭代器,若不存在,返回set.end();
count(key); //查找键key的元素个数,针对multiset
lower_bound(keyElem); //返回第一个key>=keyElem元素的迭代器
upper_bound(keyElem); //返回第一个key>keyElem元素的迭代器
equal_range(keyElem); //返回容器中key与keyElem相等的上下限的两个迭代器

set排序规则

set自动排序,但可以改变它的排序规则,默认从小到大

自动排序规则,可以使用struct或class,声明bool operator()(v1,v2)仿函数重载

在定义集合时,指定排序规则(类):

set<数据元素的泛型,排序规则类型> s;

3.7.5 对组(pair)

对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用pair的两个公有属性first和second访问

类模板:template <class T1,class T2> class pair

用法一:

pair<string,int> pair1(string("name"),20);
cout<<pair1.first<<endl;
cour<<pair1.second<<endl;

用法二:

pair<string,int> pair2=make_pair("name",30);
cout<<pair2.first<<endl;
cout<<pair2.second<<endl;

用法三:

pair<string,int> pair3=pair2; //拷贝构造函数
cout << pair3.first<<endl;
cout << pair3.second<<endl;

3.8 map/multimap容器

3.8.1 map概念

map的特性是所有元素都会根据元素的键值自动排序

map所有的元素都是pair,同时拥有实值和键值,pair的第一元素被视为键值,第二元素被视为实值,map不允许两个元素有相同的键值【键值是唯一的】

multimap和map的操作类型,唯一区别multimap键值可重复

map和multimap都是以红黑树为底层实现机制

【注】map迭代器的元素是pair对组,通过pair对象获取键值(first)和实值(second)

3.8.2 常用API

//构造函数
map<T1,T2> mapTT; //map默认构造函数
map(const map &mp); //拷贝构造函数
map(begin,end); //复制[begin,end)区间的pair对到当前map中

//赋值和大小
map& operator=(const map &mp); //重载等号操作符
swap(mp); //交换两个集合
size(); //返回容器中元素的数目
empty(); //判断容器是否为空

//插入
insert(pair<...>(...)); //往容器中插入元素,返回pair<iterator bool>
insert(make_pair(...))
insert(map<T1,T2>::value_type(...));
T2& operator[](T1 &key);

//删除
clear(); //删除所有元素
erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器
erase(beg,end); //删除区间[beg,end)的所有元素,返回下一个元素的迭代器。
erase(keyElem); //删除容器中key为keyElem的对

//查找
find(key); //查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
count(keyElem); //返回容器中key为keyElem的对组个数。对map来说,要么是0,要么是1,对multimap来说,值可能大于1
lower_bound(keyElem); //返回第一个key>=keyElem元素的迭代器
upper_bound(keyElem); //返回第一个key>keyElem元素的迭代器
euqal_range(keyElem); //返回容器中key与keyElem相等的上下限的两个迭代

3.9 STL容器使用时机

3.9.1 结构与操作比较

image-20231127112537795

3.9.2 使用场景

  • vector:如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述
  • deque:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢
  • list:比如公交车乘客的存储,随时有可能有乘客下车,支持频繁的不确定位置元素的移除插入
  • set:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列
  • map:比如按ID号存储十万个用户,想要快速通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。

4. STL算法

4.1 函数对象

重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象,也叫仿函数(functor),其实就是重载"()"操作符,使得类对象可以像函数那样调用

【注意】

1. 函数对象(仿函数)是一个类,不是一个函数
2. 函数对象(仿函数)重载了"()"操作符使得它可以像函数一样调用

函数对象分类:

一元仿函数(unary_functor),重载的operator()要求获取一个参数
二元仿函数(binary_functor),重载的operator()要求获取两个参数

函数对象的作用:

STL操作的算法往往都有两个版本,其中一个版本表现出最常用的某种运算,另一版本则允许用户通过template参数的形式来指定所要采取的策略

对于template 参数的形式 意思,如指定set的排序规则

set<A,sortA> s;

【总结】

1. 函数对象通常不定义构造函数和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调用的运行时问题
2. 函数对象超出普通函数的概念,函数对象可以有自己的状态
3. 函数对象可内联编译、性能好,用函数指针几乎不可能
4. 模板函数对象使函数对象具有通用性,这也是它的优势之一

4.2 谓词

谓词是指普通函数或重载的operator()返回值是bool类型的函数对象(即仿函数),依据函数接收的参数又分为一元谓词二元谓词等,谓词可作为一个判断式。

4.3 内建函数对象

引入头文件<algorithm>

STL内建了一些函数对象,分为算数函数对象、关系运算类函数对象、逻辑运算类仿函数。这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能

4.3.1 算数类函数对象

6个算数类函数对象,除了negate是一元运算,其他都是二元运算

template<class T> T plus<T>//加法仿函数
template<class T> T minus<T>//减法仿函数
template<class T> T multiplies<T>//乘法仿函数
template<class T> T divides<T>//除法仿函数
template<class T> T modulus<T>//取模仿函数
template<class T> T negate<T>//取反仿函数

4.3.2 关系运算类函数对象

6个关系运算类函数对象,每一种都是二元运算

template<class T> bool equal_to<T>//等于
template<class T> bool not_equal_to<T>//不等于
template<class T> bool greater<T>//大于
template<class T> bool greater_equal<T>//大于等于
template<class T> bool less<T>//小于
template<class T> bool less_equal<T>//小于等于

4.3.3 逻辑运算类运算函数

逻辑运算类运算函数,not为一元运算,其余为二元运算

template<class T> bool logical_and<T>//逻辑与
template<class T> bool logical_or<T>//逻辑或
template<class T> bool logical_not<T>//逻辑非

4.3.4 函数对象适配器

函数适配器bind1st,bind2nd(参数绑定)

1. 函数适配器

1)创建函数对象类,继承binary_function<T1,T2,T3>,声明()重载,其中T1、T2是重载函数的2个参数,T3为重载函数的返回值类型

【注】定义public的()重载时,必须由const修饰

2)应用函数适配器

for_each(v.begin(),v.end(),bind1st(函数对象类(),x));
for_each(v.begin(),v.end(),bind2nd(函数对象类(),x));

bind1st和bind2nd的区别

bind1st:将参数绑定为函数对象的第一个参数
bind2nd:将参数绑定为函数对象的第二个参数
bind1st和bind2nd 将二元函数对象转为一元函数对象
2. 取反适配器

1)定义unary_function<int,bool>的函数对象类

2)应用

find_if(v.begin(),v.end(),函数对象类());
find_if(v.begin(),v.end(),not1(函数对象类())); 
//动态给定条件
find_if(v.begin(),v.end(),nto1(bind2nd(greater<int>(),5)));
sort(v.begin(),v.end(),not2(less<int>()));
//匿名函数
for_each(v.begin(),v.end(),[](int val){cout<<cal<<" ";});

其中的 not1 对一元函数对象取反, not2 对二元函数对象取反。

3. 函数指针适配器

应用ptr_fun()

【注】仿函数的参数不能使用引用

4. 成员函数适配器

应用mem_fun_ref()

成员函数作为仿函数时,则通过容器成员调用它的仿函数

4.4 算法应用

算法中常用的功能涉及到比较、交换、查找、遍历、复制、修改、反转、排序、合并等

4.4.1 常用遍历算法

for_each(iterator beg,iterator end,_callback);
/*
遍历算法 遍历容器元素
@param beg 开始迭代器
@param end 结束迭代器
@param _callback 函数回调或者函数对象
@return 函数对象
*/

transfrom(iterator beg1,iterator end1,iterator beg2,_callback);
/*
@param beg1 源容器开始迭代器
@param end1 源容器结束迭代器
@param beg2 目标容器开始迭代器
@param _callback 回调函数或者函数对象
@return 返回目标容器迭代器
*/
将指定容器区间元素搬运到另一容器中
【注】不会给目标容器分配内存,所以需要我们提前分配好内存

4.4.2 常用查找算法

find(iterator beg,iterator end,value);
/*
find 算法 查找元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return 返回查找元素的位置
*/

find_if(iterator beg,iterator end,_callback);
/*
find_if 算法 条件查找
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回 bool 类型的函数对象)
@return 返回查找元素的位置
*/

adjacent_find(iterator beg,iterator end,_callback);
/*
adjacent_find 算法 查找相邻重复元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param _callback 回调函数或者谓词(返回 bool 类型的函数对象)
@return 返回相邻元素的第一个位置的迭代器
*/

bool binary_search(iterator beg,iterator end,value);
/*
binary_search 算法 二分查找法
注意: 在无序序列中不可用
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return bool 查找返回 true 否则 false
*/

count(iterator beg,iterator end,value);
/*
count 算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 回调函数或者谓词(返回 bool 类型的函数对象)
@return int 返回元素个数
*/

count_if(iterator beg,iterator end,value);
/*
count_if 算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回 bool 类型的函数对象)
@return int 返回元素个数
*/

4.4.3 常用排序算法

merge(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
/*
merge 算法 容器元素合并,并存储到另一容器中
注意:两个容器必须是有序的
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
*/

sort(iterator beg,iterator end,_callback);
/*
sort 算法 容器元素排序
@param beg 容器 1 开始迭代器
@param end 容器 1 结束迭代器
@param _callback 回调函数或者谓词(返回 bool 类型的函数对象)
*/

random_shuffle(iterator beg,iterator end);
/*
random_shuffle 算法 对指定范围内的元素随机调整次序
@param beg 容器开始迭代器
@param end 容器结束迭代器
*/

reverse(iterator beg,iterator end);
/*
reverse 算法 反转指定范围的元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
*/

4.4.4 常用拷贝和替换算法

copy(iterator beg,iterator end,iterator dest);
/*
copy 算法 将容器内指定范围的元素拷贝到另一容器中
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param dest 目标起始迭代器
*/

replace(iterator beg,iterator end,oldvalue,newvalue);
/*
replace 算法 将容器内指定范围的旧元素修改为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param oldvalue 旧元素
@param oldvalue 新元素
*/

replace_if(iterator beg,iterator end,_callback,newvalue);
/*
replace_if 算法 将容器内指定范围满足条件的元素替换为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 函数回调或者谓词(返回 Bool 类型的函数对象)
@param oldvalue 新元素
*/

swap(container c1,container c2);
/*
swap 算法 互换两个容器的元素
@param c1 容器 1
@param c2 容器 2
*/

4.4.5 常用算术生成算法

引入<numeric>头文件

accumlate(iterator beg,iterator end,value);
/*
accumulate 算法 计算容器元素累计总和
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 累加值, 额外加的值,可以为0
@return 累加后的数值
*/

fill(iterator beg,iterator end,value);
/*
fill 算法 向容器中添加元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value t 填充元素
*/

4.4.6 常用集合算法

set_intersection(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
/*
set_intersection 算法 求两个 set 集合的交集
注意:两个集合必须是有序序列
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/

set_union(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
/*
set_union 算法 求两个 set 集合的并集
注意:两个集合必须是有序序列
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/

set_difference(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
/*
set_difference 算法 求两个 set 集合的差集
注意:两个集合必须是有序序列
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/