本专栏目的
- 更新C/C++的基础语法,包括C++的一些新特性
前言
- STL无疑是C++史上一个重要的发明,未来我将更新STL有关的知识点,入门绝对够了(看目录就知道了👀)
- 这是第一篇,讲仿函数
- C语言后面也会继续更新知识点,如内联汇编;
- 欢迎收藏 + 关注,本人将会持续更新。
文章目录
- 仿函数
- 可调用对象(Callable object)
- 仿函数
- 为什么要有仿函数?
- 仿函数优点
- 仿函数作用
- 函数对象
- 绑定函数
- bind
- not1
- 一元函数又叫一元谓词
- not
- ref、cref
- mem_fun
- 示例
- 总结
- 包装类
- funtion
仿函数
可调用对象(Callable object)
函数调用需要使用"()",这个“()”叫做函数调用用运算符
。
C++中的可调用对象有以下几种:
-
函数(function)
-
函数指针(function pointer)
-
仿函数(Functor)
-
lambda表达式
-
bind 函数封装的函数对象
仿函数
仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类,仿函数是定义了一个含有operator()成员函数的对象,可以视为一个一般的函数,只不过这个函数是通过类重载()
所实现的。
为什么要有仿函数?
1,假如客户有一个需求摆在我们的面前,编写一个函数:
- 函数可以获得斐波拉契数列每项的值;
- 每调用一次便返回一个值;
- 函数可根据需要重复使用。
普通函数实现:
int fibonacci()
{static int a0 = 0; //第一项static int a1 = 1; //第二项int ret = a1; //保存a1 = a0 + a1;a0 = ret;return ret;
}
int main()
{for (size_t i = 0; i < 5; i++){cout << fibonacci() << " "; //1 1 2 3 5}cout << endl;for (size_t i = 0; i < 5; i++){cout << fibonacci() << " "; //8 13 21 34 55}return 0;
}
缺点 :无法根据需求使用,如:无法想要重复获取一项值。
解决: 每一次从将初始项从新赋值为0。
int a0 = 0; //第一项
int a1 = 1; //第二项
int fibonacci()
{int ret = a1;a1 = a0 + a1;a0 = ret;return ret;
}int main()
{for (size_t i = 0; i < 5; i++){cout << fibonacci() << " "; //1 1 2 3 5 8}cout << endl;a0 = 0;a1 = 1;for (size_t i = 0; i < 5; i++){cout << fibonacci() << " "; //1 1 2 3 5 8}return 0;
}
缺点: 无法直接获取一项值,如直接获取第9项值
,而且每一次获取的时候还需要将初始值手动赋值为0;
解决: 函数对象
结合本案例,说明函数对象特点:
a> 使用具体的类对象取代函数;
b> 该类的对象具备函数调用的行为;
d>多个对象相互独立的求解数列项。
总结:函数对象利用运算符重载,使得对象可以想函数一样调用,但是对于类来说,他可以封装很多方法,很多变量,这个就可以实现普通函数完不成的东西,比如说:用成员变量储存过程数据。
解决:
class Fibonacci
{
public:Fibonacci() :_a0(0), _a1(1) {}Fibonacci(int n) :_a0(0), _a1(1) {for (int i = 0; i < n; i++){int ret = _a1;_a1 = _a0 + _a1;_a0 = ret;}}int operator()(){int ret = _a1;_a1 = _a0 + _a1;_a0 = ret;return ret;}
private:int _a0;int _a1;
};int main()
{Fibonacci fib;for (size_t i = 0; i < 5; i++){cout << fib() << " "; //1 1 2 3 5 8}cout << endl;Fibonacci fib1(9);for (size_t i = 0; i < 5; i++){cout << fib1() << " "; //55 89 144 233 377}return 0;
}
我们看到已经实现了所有需求,并且随时想从哪个数开始都行。
仿函数优点
如果可以用仿函数实现,那么你应该用仿函数,而不要用CallBack。原因在于:
- 仿函数可以不带痕迹地传递上下文参数。而CallBack技术通常使用一个额外的void*参数传递。这也是多数人认为CallBack技术丑陋的原因。
- 仿函数技术可以获得更好的性能,这点直观来讲比较难以理解。
仿函数作用
仿函数通常有下面四个作用:
- 作为排序规则,在一些特殊情况下排序是不能直接使用运算符<或者>时,可以使用仿函数。
- 作为判别式使用,即返回值为bool类型。
- 同时拥有多种内部状态,比如返回一个值得同时并累加。
- 作为算法for_each的返回值使用。
函数对象
头文件
函数对象是专门设计用于使用类似于函数的语法的对象。在 C++ 中,这是通过operator()
在其类中定义成员函数来实现的,它们通常用作函数的参数,例如传递给标准算法的谓词或比较函数,这个提出来个人感觉是为了替代函数指针,函数指针写起来确实比较麻烦。
绑定函数
这些函数根据其参数创建包装类的对象
bind
-
它的作用是将一个可调用对象(比如函数、函数指针、成员函数、成员函数指针等等)以及若干个参数绑定到一个新的函数对象上,形成一个新的可调用对象。
-
还可以用来绑定函数调用的某些参数,包括可以绑定参数顺序,可以将bind函数看作一个通用的函数包装器,它接受一个可调用对象,并返回函数对象。
-
☄ 常用,这个的出现和
std::function
,其实就相当于替代了C的函数指针,使用起来更方便了。
1. 绑定普通函数
void show(int number, const std::string& str)
{cout << number << " " << str << endl;
}
-
顺序绑定参数
auto bind_show = std::bind(show, placeholders::_1, placeholders::_2); bind_show(2,"world");
-
交换参数位置
auto bind_show1 = std::bind(show, placeholders::_2, placeholders::_1); bind_show1("world",1314520);
-
绑定固定参数
auto bind_show3 = std::bind(show, 888,placeholders::_1); //在函数调用的时候传递一个参数,一个在绑定的时候加 bind_show3("wy");
2. 绑定成员函数
😜 注意: 需要指明对象
struct Plus
{int plus(int a, int b){return a * b;}
};
{Plus plus;auto func1 = std::bind(&Plus::plus, &plus, placeholders::_1, placeholders::_2); //绑定对象指针auto func2 = std::bind(&Plus::plus, plus, placeholders::_1, placeholders::_2); //绑定对象或引用cout << func1(2, 4) << endl;
}
3. 绑定函数对象
struct Sub
{int operator()(int a, int b){return a * b;}
};
{auto func2 = std::bind(Sub(), placeholders::_1, placeholders::_2);cout << func2(3, 4) << endl;
}
4. 绑定lambda表达式
{ auto func3 = std::bind([](int a, int b) {return a / b; }, placeholders::_1, placeholders::_2);cout << func3(6 ,2) << endl;
}
not1
对一元函数对象的否定,只能对仿函数进行否定,普通函数不行,这个有时候在看一些API的源码我们会看到有这个参数,其实这个就相当于bool函数,判断是否可行。
一元函数又叫一元谓词
谓词( predicate )是指普通函数或重载的operator()返回值是bool类型的函数对象(仿函数)。如果operator()接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓词,谓词可作为一个判断式。
struct Greater5 :public unary_function<int, bool> //必须继承自一元函数类
{bool operator()(int val) const //必须加上const{return val > 5;}
};int main()
{cout << boolalpha << Greater5()(10) << endl;auto _less5 = not1(Greater5());cout << boolalpha << _less5(10) << endl;return 0;
}
not
和上面不同的是,对二元函数对象的否定,还是只能对仿函数进行否定,普通函数不行。
struct Greater:public binary_function<int,int,bool> //必须继承自二元函数类
{bool operator()(int a, int b) const //必须加上const{return a > b;}
};int main()
{cout << boolalpha << Greater()(3, 1) << endl;;auto _less = not2(Greater());cout << boolalpha << _less(3,1) << endl;;return 0;
}
ref、cref
上面提到,std::bind
可以绑定函数参数,但是想要绑定引用参数,那如何做呢? ref
和cref
这两个就是为了解决这个问题的。
- ref 普通引用
- cref 常引用
struct Inc
{mutable int number = 0;void operator()()const{cout << number << endl;number++;}
};int main()
{Inc inc;auto func = bind(std::cref(inc));func();inc();return 0;
}
mem_fun
把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定
class Foo
{
public:int a{ 100 };void print(){cout << a << endl;}void print2(int val){cout << a << " val:" << val << endl;}
};int main()
{Foo f;//把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定auto func = mem_fn(&Foo::print);func(f); //把对象传进去func(&f); //对象指针也行func(Foo()); //临时对象也行//把成员函数转为函数对象,使用对象指针进行绑定auto func1 = mem_fun(&Foo::print2);func1(&f,666);//把成员函数转为函数对象,使用对象(引用)进行绑定auto func2 = mem_fun_ref(&Foo::print);func2(f);return 0;
}
示例
struct Foo
{int v;Foo(int val = -1):v(val) {}void print(){cout <<v << endl;}
};int main()
{//让每个对象都调用指定的成员函数std::vector<Foo> vec(5); //存对象for_each(vec.begin(), vec.end(), mem_fn(&Foo::print));cout << endl;//让每个对象都调用指定的成员函数std::vector<Foo*> vec_ptr; //存指针for (int i = 0; i < 5; i++){vec_ptr.push_back(new Foo(i*3));} for_each(vec_ptr.begin(), vec_ptr.end(), mem_fn(&Foo::print));return 0;
}
总结
函数 | 作用 |
---|---|
mem_fun | 把成员函数转为函数对象,使用对象指针进行绑定 |
mem_fun_ref | 把成员函数转为函数对象,使用对象(引用)进行绑定 |
mem_fn | 把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定 |
bind | 包括但不限于mem_fn的功能,更为通用的解决方案 |
包装类
包装类是使不同的函数(普通函数、仿函数、bind绑定的一些函数…………)打包成具有统一接口一个类型,其中最常用的是function。
funtion
函数包装器
概念: C++11 中引入了 std::function
类型,它是一个多态函数封装类,可以用来存储和调用任意可调用对象,包括函数指针、函数对象、Lambda 表达式等等。
原因:因为不管是函数名、函数指针、仿函数还是lambda表达式,它们都是一个“函数”,function的作用是能够统一类型,从而提高效率。
与bind区别:
- bind更多的是将函数和他的参数相互绑定在一起,可以使函数作为参数;
- function主要是为“不同形状的函数”包装成统一类型,可以看下面的一些案例:
-
包装普通函数
int add(int a, int b) {return a + b; }{// 包装std::function<int(int, int)> fun_add(add);cout<<fun_add(2, 3); }
-
包装成员函数(通过bind绑定)
class Wy { public:int add(int a, int b){return a + b;} };{Wy wy;std::function<int(int, int)> fun_maye_add(std::bind(&Wy::add, &wy,placeholders::_1,placeholders::_2));cout << fun_maye_add(3, 5); }
-
包装lambda表达式(刷算法题常用)
{std::function<int(int, int)> fun_lambda_add([](int a, int b)->int {return a + b; });cout << fun_lambda_add(7, 8) << endl; // 包装 }
-
包装函数对象
class Maye { public:int operator()(int a, int b){return a * b;} };{ Maye obj;std::function<int(int, int)> fun_functor(obj);cout << fun_functor(2,4); }