函数对象

重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象。又称仿函数。以下内容主要包括`lambda和function’(函数包装器)。

仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。

lambda表达式

Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

匿名函数

所谓匿名函数,其实类似于python中的lambda函数,其实就是没有名字的函数。使用匿名函数,可以免去函数的声明和定义。这样匿名函数仅在调用函数的时候才会创建函数对象,而调用结束后立即释放,所以匿名函数比非匿名函数更节省空间

必包

闭包就是能够读取其他函数内部变量的函数。例如在C++中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

lambda表达式的几种写法

1.值捕获

值捕获,使用[=]让编译器自动推倒捕获列表,lambda表达式定义前的函数变量都可以捕获。[variable_name]只捕获具体的变量。

1
2
3
4
5
6
7
8
9
10
11
void test_lambada(){
int tmp_1 = 0,tmp_2 = 1,tmp_3 = 2;
auto lambada_1 = [tmp_1](){//这里可以输入具体的变量名或者[=],这时传入的varible(变量)为const 类型不可修改
tmp_1 = 10;
cout<<"tmp_1 = "<<tmp_1<<endl;
return tmp_1;
};
auto lambada = lambada_1();//调用
cout<<"lambada = "<<lambada<<endl;

}

引用捕获

引用捕获,使用[&]开启引用捕获,捕获范围与值捕获一致。[variable_name]只捕获具体的变量,此时传入的变量值是可以修改的。

1
2
3
4
5
6
7
8
9
10
11
12
/*[&]开启引用捕获,&后面无参数表示全局引用捕获
*[&name]捕获特定的变量
*
*/
void Lambda_2(){
int num_1 = 0, num_2 = 1;
auto change = [&num_1,&num_2]{
num_1 = num_2;
};
change();
cout<<"num_1 = "<<num_1<<endl;
}

泛型lambda

使用auto类型来完成lambda初始化,[]不能有auto,其与模版产生有冲突,无法推倒出正确的模板类型。正确的写法是将auto放入parma列表中

1
2
3
4
5
6
7
8
void Lambda_2(){
int num_1 = 0, num_2 = 1,sum = 0;
auto add = [](auto a,auto b){
sum = a + b;
};
int res = add(num_1,num_2);
cout<<"num_1 = "<<res<<endl;
}

万能引用配合lambda

使用auto&&配合lambda实现lambda中的函数调用

1
2
3
4
5
6
7
8
9
void Lambda_4(){
auto lambda = [](){
cout<<"the lambda!"<<endl;
};
auto lambda_1 = [](auto&& f){
f();//调用lambda
};
lambda_1(lambda);//传入lambda
}

函数对象包装器

在 C++11 中统一了这些概念,将能够被调用的对象的类型,统一称之为可调用类型,而这种类型,便是通过std::function 引入的。

C++11 std::function 是一种通用、多态的函数封装,可以对任何可调用目标实体进行存储、复制和调用操作。
它也是对 C++中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的),换句话说,就是函数的容器。

当我们有了函数的容器之后便能够更加方便的将函数、函数指针作为对象进行处理。

普通函数封装成函数对象

1
2
3
4
5
6
7
8
//functional<return_type(parma_type_1,parma_type_2)> name = function_name;//普通函数封装
void no_parma_test(){
cout<<"the no_parma_test"<<endl;
}
void function_1(){
function<void(void)> f = no_parma_test;//<返回值类型,(参数列表类型);
f();
}

匿名函数对象封装成函数对象

1
2
3
4
5
6
7
8
9
10
11
//functional<return_type()> name = lambda();//匿名函数
void function_2(){
int num = 1,sum = 5;
auto lambda = [&](int tmp){
sum += tmp;
return sum;
};
function<int(int)> f = lambda;
sum = f(num);
cout<<"the sum value = "<<sum<<endl;
}

类成员函数和重载函数封装成函数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//class_member:funtional<return_type(class *,parma_type) name = class::member_name;
//调用class ,(&class,member_name);
//仿函数(函数对象)functional<return_type(class<int(functor*)> name = &functor::class
class Tmp_class{
public:
int test(int num){
cout<<"the Tmp_class test"<<endl;
return num+num;
}
int operator()(int num){
cout<<"the tmp_class operator"<<endl;
return num+num;
}
};

void function_3(){
Tmp_class *T = new Tmp_class;
function<int(Tmp_class*,int)> f = &Tmp_class::test;
auto res = f(T,20);
cout<<"the Tmp_class::test return value = "<<res<<endl;
function<int(Tmp_class*,int)> fo = &Tmp_class::operator();
res = fo(T,20);
cout<<"the Tmp_class::operator return value = "<<res<<endl;
}

Std::bind/std::placeholder

std::bind 是用来绑定函数调用参数的,它解决的需求是:
我们有时候可能并不一定能够一次性获得调用某个函数的全部参数。
通过这个函数,我们可以将部分调用参数提前绑定到函数身上成为一个新的对象,然后在参数齐全后,完成调用。

好处是降低耦合封装接口,屏蔽底层,使用延时调用。

1
2
3
4
5
6
7
8
9
10
int foo(int a, int b, int c) {
;
}
int main() {
// 将参数1,2绑定到函数 foo 上,
// 但是使用 std::placeholders::_1 来对第一个参数进行占位
auto bindFoo = std::bind(foo, std::placeholders::_1, 1, 2);
// 这时调用 bindFoo 时,只需要提供第一个参数即可
bindFoo(1);
}

函数对象作为参数使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void test_1(function<int(int,int)> tmp){
int res = tmp(10,20);
cout<<"the res value = "<<res<<endl;
}

void test_funciton(){
int num_1 = 10, num_2 = 20, sum = 0;
auto lambda = [&](auto a,auto b){
sum = num_1+num_2+a+b;
return sum;
};
function<int(int,int)> f = lambda;
test_1(f);
}