什么是泛型算法
软件分数据和计算两部分,前面我们学的容器是对数据的封装,被称为数据结构。关于这些数据结构的计算在容器内部包含了一部分,如sort用来排序,但是没有提供更多,每一个容器内的的sort的实现方法不同,因为他们面对的是不同的容器,泛型算法是独立于容器类的一些操作方法,可以通用于多种容器,所以叫泛型,泛型算法有更高的抽象,实现起来难度更大,这也是STL的核心技术。使用STL算法库进行升序排序,示例代码如下。
vectorbb{4,2,1,3,5}; sort( bb.begin(), bb.end(), greater () ); //传入迭代器和谓词,使其降序排列 for(auto x : bb) { cout << x <<" "; }
谓词predicate引入
谓词就是可以做谓语的词,例如“我吃饭”,吃就是一个谓词,语义上含有动作。我们程序中的函数就是一个典型的谓词。如上例中,调用的greater
函数对象引入
函数对象 function object,也叫仿函数,是一个长得像函数的对象,他不是真的函数,只是在调用形式上像是个函数,实际上他是一个类,而在类中做了小括号“()”的运算符重载,如上例中的greater
template( typename T ) class greater
{
public:
T operator()(T a) //()运算符重载
{
if(a>0) return 1; return 0;
}
};
greater func; //定义一个对象
int b = func(5); //调用类中运算符重载函数。
自定义谓词
如下示例,定义了一个aa类,在类中写了一个括号的运算符重载函数,在内部实现字符串比较,再调用STL的sort方法来对数据排序,同时把我们自己写的类传进去,即可实现按照我们自己定义的对比方法来排序,STL算法库中大多函数只负责做自己的功能,但是关键的判断,需要外部提供,如sort只负责排序,到底从大到小还是由多变少这些判断都由谓词来提供sort只根据谓词返回值来做排序,简单说就是怎么对比由我们提供的谓词决定。谓词的应用和函数回调非常类似,都是向一个函数传入一个函数。
class aa
{
public:
bool operator()(const string &a,const string &b) //二元谓词
{
return (a.size()>b.size());
}
};
array bb{"hu","dai","zhou"};
sort( bb.begin(), bb.end(), aa() );
return 返回值扩展
之前一直以为return只可以返回定值,从没考虑过后面还可以有逻辑判断,如下示例,当调用func(4)返回值是1,func(5)返回值是0,末尾涉及到表达式,返回值适合用bool类型。
int func(int i)
{
return i%2==0;
}
STL算法库all_of
all_of是检查传入的数据判定都为turn,则返回turn,否则返回flase,如下示例,定义了一个谓词mult,执行all_of(table.begin(),table.end(),mult(2))传入谓词,背后解析步骤如下:
- 创建一个mult类型的对象,同时调用构造函数base的值为2;
- 遍历begin和end之间的数据,分别每次传1个给mult中的“( )”运算符重载函数。
- 运算符重载函数对数据进行判读,结果为0就返回true,否则返回false。
- 重复读取容器的值,并传给( )运算符重载去处理,直到遍历完成。
- 最后判断,如果返回的都是true那么就返回true。
class mult
{
public:
mult(int i):base(i){};
bool operator()(int &i)
{
return i % 2 == 0 ;
}
private:
int base;
};
array table={2,2,2,2,2};
bool c = all_of(table.begin(),table.end(),mult(2));
cout << c << endl;
在all_of(table.begin(),table.end(),mult(2))表达式中,all_of在遍历数据的时候mult(2)并不是直接被调用的,这只是一个传参,all_of内部会创建对象后,调用基于对象的运算符重载函数。
lambda表达式
bool operator()(int &i)
{
return i % 2 == 0 ;
}
[ ](int &i){return i % 2 == 0;};
lambda表达式就是一个匿名函数,匿名也就是没有名字,既然没有名字,在其他地方也就自然无法调用,相当于一次性使用的,就像被原地展开的函数或者说是函数对象,lambda还有一个名字叫“闭包”,闭包就是在别处不能调用的一个封包。
lambda表达式其实就是一个函数对象,在内部创建了一个( )重载函数符的类,如上例,可以把lambda表达式看成是函数对象的简写,lambda中的(int i)也就是就是运算符重载的传参(int &i),上例中用黄色来标识,其中绿色部分也是相对应的,我们如果写了上例中的lambda表达式,那么编译器就会自动为我们生成上部分的函数对象,只不过这个函数对象的名字只有编译器知道,我们不知道,所以我们无法调用。
lambda的意义其实就是简写函数对象。
lambda表达式格式
[ 参数捕获] (操作符重载函形参) mutable或exception声明 -> 返回值 {函数体;};
完整的格式分5部分,其中参数捕获内容可以省略,mutable或excep声明和返回值可以省略,而[ 参数捕获] 、(操作符重载函数参数)、函数体不能被省略,即使没有内容,空着也得要有占位符,上面是函数定义,函数调用时需在后面加括号,没有函数名肯定不能调用,但是我们可以使用auto关键字来定义一个变量,来接收函数指针,实现访问如下示例,注意大括号内的语句以分号“;”结束。
lambda表达式逐渐变形如下:
- [ ]( ){ }(); //空lambda表达式调用
- [ ]( ){cout <
- [ ](int i){cout << i << endl;}(5); //带传参的lambda表达式
- auto func = [ ](int i){cout << i << endl;}; //定义一个lambda表达式,并func指向
func(4); //调用指向的函数,并传参。
auto func = [ ](int i) ->int {return i;}; //带返回值
cout << func(3)<
参数捕获
前面提到lambda是一个闭包,意思是不对外有任何交换,有时候lambda表达式需要访问外部变量,由于闭包特性,导致无法访问,所以就设计了参数捕获机制,来实现外部变量访问。如下示例,参数捕获只能是在lambda表达式的同代码块,代码块以外如全局变量,等都不能访问,下例中在执行func(3)时,打印出 3 5,在这之前并不能打印出东西,说明编译器将其变更为函数对象了,但是该定义并未被调用,如果要使用可以在后面加小括号。
int a=5;
atuo func = [ a](int i){cout << I << a << endl;}
func(3);
捕获表达式
[ ] 空:完全不捕获,只占位。
[=] 传值,捕获代码块所有变量和this指针,传值自动变cosnt内部不能改。
[&] 引用方式,捕获代码块内所有的变量,包含this指针。内部可修改捕获变量。
[this] 当前指针捕获,主要出现在class中,用来捕获当前class的this。
[a] 传值方式,指定访问a变量。
[&a] 引用方式,指定访问a变量。
[a,&b] 传值访问a,引用访问b;
[=,&a] 引用访问a,其余的传值访问。
[&, a] 传值访问a,其余的引用访问。
cpp函数适配器
适配器,平时我们听得最多的就是电源适配器,其作用就是电压转换后和用电设备进行电压适配。而函数适配的作用也是同样的道理,是将输入函数进行转换后输出,以适配接收函数。如下列,func1的实现需要回调可以接收1个参数的函数,但是func2有2个形参,所以不能直接回调,所以我们再写1个func3做为中间环节,在func3内部调用func2的时候填充一个变量,这个就是函数的适配原理。简单说,函数适配器就是在不同传参个数的函数间进行适配的技术。
func1(viod (*func) (int a) ) //该函数需要传入有2个int形参的函数
func2(int a, int b) //定义1个形参的函数
func3(int a)
{
func2(a, 5);
}
适配器bind
前面我们为了参数适配,写了一个func3来做中间转换,实际上在c++的标准库中已经为我们实现了这样一个适配函数就是bind。在c++98中就引入了bind1s和bind2nd,而在c++11中使用bind来替代前面的两个函数。
bind的使用如下示例,bind函数的第一个传参是要适配的函数名,placeholders占位符关键字在后面添加::_1表示占位print函数的第一个形参,::_2表示占位print函数的第2个传参,后面的数字就是直接传给函数的第3个参数的。placeholders::_x在bind函数的排序,与在调用func函数时传入参数的位置有关,下例中‘A’是func第一个参数,传给bind除了前面的适配函数名外的第1个参数即placeholders::_2,而这个placeholders::_2指向的是print函数的第2个参数b,那么最终就是将‘A’传给了print函数形参b。bind转换完成后返回的是一个匿名函数。
auto func = bind(print,placeholders::_2, placeholders::_1, 33.1);
func('A',5);
void print(int a,char b,double c) //定义一个函数需要传3个参
{
cout << "1. " << a <
less
less释义:更少的,是一个函数对象,用来大小比较,返回值为bool的()运算符重载,使用如下,比较方式类似于在传入的两个数据之间加一个小于符号“<”,成立返回true,否则false
less()(10,20) //10<20,返回true
bind1st与bind2nd
bind1st和bind2nd只能用来适配2个形参的函数,如bind1st(less(),10); 就是将10绑定在“<”符号的左边,bind2nd(less(),10)就是将10绑定在<符号的右边,返回只有1个形参的匿名函数。使用示例如下:
array aa{11,12,9,8,7};
auto func = bind1st(less(),10);
for(auto x:aa)
{
cout << func(x)<
for_each
- 头文件:
- 作用:用来对容器做遍历,是STL算法,传入起始和结束迭代器,依次遍历容器元素,并逐个传给f;
- 原型:UnaryFunction for_each( Input first, Input last, UnaryFunction f );
- 示例:
vector aa{5,4,6,7,8,10,15};
auto print = [](int &tige){cout<<" "<
相似的还有for_each_n,功能类似,只不过是传入起始迭代器和往后的数量。
count
- 作用:用来查找给入容器中与设定值相同的数量
- 原型:count( InputIt first, InputIt last, const T &value );
- 示例:
vector aa{5,4,6,7,8,10,15,5};
int i = count( aa.begin(), aa.end(), n); //n表示要比较的值
count_if
- 功能:查找给如容器中的元素,符合要求的数量。
- 原型:count_if( InputIt first, InputIt last, UnaryPredicate p );
- 示例:
i= count_if(aa.begin(),aa.end(),[](int &data){return data%2==0;});
cout <



