C++STL的基本学习(三)——函数对象、谓词、适配器
前言
c++的STL这边,还有一点点的内容
函数对象
函数对象的概念
重载函数调用操作符的类,其对象常称为函数对象(function object),即他们是行为类似函数的对象,也叫仿函数(functor),没错,上一篇文章里set容器和map容器自定义排序规则时,我们就用过了它。
其实就是重载“( )”运算符,使得类对象可以像函数那样调用。
注意:
- 函数对象(仿函数)是一个类,不是一个函数
- 函数对象重载了“( )”操作符,使它可以像函数那样调用
假设某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,我们就将这个类称为“一元仿函数(unary functor)”;相反,如果重载的operator()要求获取两个参数,就称这个类为“二元仿函数(binary functor)”
使用案例
class MyPrint {
public:
void operator()(string val) {
cout << val<<endl;
}
};
int main(void) {
MyPrint p1;
p1("I'm batman!");
return 0;
}
函数对象可以像普通函数一样接收参数调用,函数对象可以保证函数调用的状态,可以说函数对象超越了函数的概念。
优越性体现
那么仿函数比普通函数的优越性体现在哪里呢?
如果我们有一个vector,我们对vector中元素处理,并且我们要记录调用了几次处理的函数,那我们应该怎么做呢?
int num = 0;
void print(string str) {
cout << str << endl;
num++;
}
void test() {
vector<string> v;
v.push_back("去年今日此门中,");
v.push_back("人面桃花相映红。");
v.push_back("人面不知何处去,");
v.push_back("桃花依旧笑春风。");
for_each(v.begin(), v.end(), print);
}
int main(void) {
test();
cout << num;
return 0;
}
我们定义了全局变量来记录,使用全局变量的话,当项目越做越大会造成命名混乱等情况,在使用多线程的时候还要去加锁减锁从而避免值的不固定,这会使得代码非常混乱。
如果使用仿函数的话:
class MyPrint {
public:
void operator()(string val) {
cout << val<<endl;
num++;
}
public:
int num = 0;
};
void test() {
vector<string> v;
v.push_back("去年今日此门中,");
v.push_back("人面桃花相映红。");
v.push_back("人面不知何处去,");
v.push_back("桃花依旧笑春风。");
MyPrint mp;
for (vector<string>::iterator it = v.begin();
it != v.end(); it++) {
mp(*it);
}
cout << mp.num << endl;
}
int main(void) {
test();
return 0;
}
两次的输出都是:
去年今日此门中,
人面桃花相映红。
人面不知何处去,
桃花依旧笑春风。
4
后者我们使用了仿函数之后,在每个类中都有自己的状态变量,这样就不必去创造全局变量了。
注意:为什么函数对象的实验不用for_each而用了老老实实的for循环
如果要调用你会这么做吧?
void test() { vector<string> v; v.push_back("去年今日此门中,"); v.push_back("人面桃花相映红。"); v.push_back("人面不知何处去,"); v.push_back("桃花依旧笑春风。"); MyPrint mp; for_each(v.begin(), v.end(), mp); cout << mp.num << endl; }
我们可以进入for_each这个函数的底层:
template<class _InIt, class _Fn> inline _Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last) _Adl_verify_range(_First, _Last); auto _UFirst = _Get_unwrapped(_First); const auto _ULast = _Get_unwrapped(_Last); for (; _UFirst != _ULast; ++_UFirst) { _Func(*_UFirst); } return (_Func); }
看不懂没关系,重点我们发现,形参的第三个参数_Fn并不是引用传递(&),而是值传递,所以如果你把类对象传进来,底层运作是对你传进来的类对象的拷贝对象去运作,所以如果你就按照上面那么写,最后的num的值是0。
怎么解决呢?for_each的底层虽然是值传递创建了拷贝,但是最后它将这个拷贝对象返回了,我们可以接收一下,就得到这个拷贝出来的真正被调用的对象了,最后代码如下:
void test() { vector<string> v; v.push_back("去年今日此门中,"); v.push_back("人面桃花相映红。"); v.push_back("人面不知何处去,"); v.push_back("桃花依旧笑春风。"); MyPrint mp; MyPrint mp2=for_each(v.begin(), v.end(), mp); cout << mp2.num << endl; }
这样最后输出才是正确的4次。
谓词
谓词就是普通函数或重载的operator()返回值是bool类型的函数对象(仿函数)。如果operator接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓词。谓词可以作为一个判断式。
内建函数对象
STL内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。
这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。使用内建函数对象,需要印入头文件 #include<functional>
6个算数类函数对象
除了negate是一元运算,其他都是二元运算
template<class T> T plus<T> //加法仿函数
template<class T> T minute<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> //取反仿函数
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> //小于等于
逻辑运算类运算函数
not为一元运算,其余为二元运算
template<class T> bool logical_and<T> //逻辑与
template<class T> bool logical_or<T> //逻辑或
template<class T> bool logical_not<T> //逻辑非
使用示例
int main(void) {
plus<int> myPlus;
cout<<myPlus(10,25)<<endl;
return 0;
}
输出:35
函数对象适配器(function adapter)
概念
函数对象适配器是完成一些配接工作,这些配接包括绑定(bind)、否定(negate),以及对一般函数或成员函数的修饰,使其成为函数对象,重点掌握函数对象适配器(红色字体)
适配器adapter这个词,学过java的朋友应该也眼熟,没错,java中有个适配器的概念是说一种类,对相应接口进行了处理的类
绑定适配器
仿函数适配器 bind1st bind2nd 绑定适配器
我们来实现一个需求,给一个函数对象传如一个值后,再输出这个值:
struct MyPrint{
void operator()(int v) {
cout << v << endl;
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
for_each(v.begin(),
v.end(),
MyPrint()); //仿函数匿名对象
}
这代码已经没有难度了,但是如果需求变了,现在要求输出的值必须是传入的值加上100,那应该怎么做呢?再operator()的输出中加上100吗?那如果我要改成两百,那岂不是还得修改这个类?
这样并不是聪明的做法,如果给重载的()再加一个形参呢?我们再for_each中转到实现,然后再转到内部for_each的实现,会发现底层的调用只有一个形参位置,所以我们也不能添加形参,那该怎么做呢?
这里我们就可以使用适配器绑定了
struct MyPrint:public binary_function<int,int,void>{
//函数后加const:该函数对成员变量只读
void operator()(int v,int val) const {
cout << v +val<< endl;
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
int num = 100;
for_each(v.begin(),
v.end(),
bind2nd(MyPrint(), num)); //仿函数匿名对象
}
然后就输出了100、101……
这里我们来介绍一下这俩的区别:
- bind1st:将num绑定为函数对象的第一个参数
- bind2nd:将num绑定为函数对象的第二个参数
这两的作用都是将二元函数对象转变成了一元函数对象。
取反适配器
我们来看一个新的案例,我们产生0~99的随机数,然后将这些随机数放到vector容器中,然后我们对vector容器中的元素进行排序。
struct MyCompare{
bool operator()(int v1,int v2) {
return v1 > v2;
}
};
struct MyPrint {
void operator()(int v) {
cout << v<<" ";
}
};
void test02() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(rand()%100);
}
for_each(v.begin(), v.end(), MyPrint());
cout << endl;
sort(v.begin(), v.end(), MyCompare()); //匿名对象
for_each(v.begin(), v.end(), MyPrint());
}
如果我们想要对结果取反,应该怎么做呢?
struct MyCompare:public binary_function<int,int,bool>{
bool operator()(int v1,int v2) const {
return v1 > v2;
}
};
struct MyPrint {
void operator()(int v) {
cout << v<<" ";
}
};
void test02() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(rand()%100);
}
for_each(v.begin(), v.end(), MyPrint());
cout << endl;
sort(v.begin(), v.end(),not2 (MyCompare())); //匿名对象
for_each(v.begin(), v.end(), MyPrint());
}
对于not1和not2:
- not1:对一元谓词(谓词:返回值为bool的函数对象)进行取反
- not2:对二元谓词进行取反
不管你的谓词里面是怎么实现的,总之取反后,返回true的变成了返回false。返回false的变成了true;
仿函数适配器
ptr_fun:将一个普通函数转成一个伪函数
//仿函数适配器 ptr_fun
void MyPrint03(int val,int val2) {
cout << val << " "<<val2<<endl;
}
void test02() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//把普通函数MyPrint03转成函数对象
for_each(v.begin(), v.end(), bind2nd(ptr_fun(MyPrint03),10));
}
int main(void) {
test02();
return 0;
}
输出:
0 10
1 10
2 10
3 10
4 10
5 10
6 10
7 10
8 10
9 10
成员函数适配器
成员函数适配器意在将一个类的内置函数传给for_each这种方法
class Person {
public:
Person(int age, int id) :age(age), id(id) {}
void show() {
cout << "age:" << age << " id:" << id << endl;
}
public:
int age;
int id;
};
void test() {
/* 如果容器中存放的对象或者对象指针,我们for_each算法打印
的时候,调用类自己提供的打印函数
*/
vector<Person> v;
Person p1(10, 1),p2(30,2),p3(50,3);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
//格式:&类名::函数名
for_each(v.begin(), v.end(),mem_fun_ref(&Person::show ));
vector<Person*> v1;
v1.push_back(&p1);
v1.push_back(&p2);
v1.push_back(&p3);
for_each(v1.begin(), v1.end(), mem_fun(&Person::show));
}
mem_fun_ref 与 mem_fun:
- 如果存放的是对象,使用mem_fun_ref
- 如果存放的是对象指针,使用mem_fun
适配器解惑
- 为什么即将适配器处理的类/结构体必须继承binary_function?
我也不知道,底层代码太底层了,只知道他是一个结构体,对你的类模板传入的三种类型进行了类名重定义,其他完全看不懂原理,如有大佬请赐教。
- 为什么函数对象中的重载函数后面要有const
避免我们修改了父类binary_function的成员变量。
商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢