精华:
什么是可调用对象
就是可以对它使用调用运算符()的对象或者表达式
在c++ 中有以下几种可调用的对象
(1)函数
(2)函数指针 调用(*fun)();
(3)lambda表达式
(4)bind 创建的对象
(5)重载了函数调用符的类
函数指针声明
函数返回值类型 (* 指针变量名) (函数参数列表);
eg int(*p)(int, int);
p就是一个指向接收两个int返回一个int的函数的函数指针,调用 p(a,b)
int Func(int x); /*声明一个函数*/
int (*p) (int x); /*定义一个函数指针*/
p = Func; /*将Func函数的首地址赋给指针变量p*/
调用(*p)(6); == Func(6);
函数指针的调用
double y = cal(5); // 通过函数调用
double y = (*pf)(5); // 通过指针调用 推荐的写法
double y = pf(5); // 这样也对, 但是不推荐这样写
函数指针做参数
void estimate(int lines, double (*pf)(int)); // 函数指针作为参数传递
bool (*pf)(string &,string&)
表示pf是一个指向函数的指针,函数接收两个string引用,返回bool值
使用 bool a=pf(“dd”,“ss”);
有时候这个可调用对象你可以把他们看做一个函数地址
什么是lambda
简单来说,Lambda是一个可调用的代码单元。可以理解为一个未命名的内联函数(匿名函数);它的语法定义如下:
[capture](parameters) mutable ->return-type{statement}
可以忽略参数列表和返回类型也就是也已表示为[] {}
的形式,当语句块中只有一条==return xxx;
语句的时候返回类型会自动进行推导,而有别的语句的时候返回类型都会被推导为void
这时候如果你需要返回类型就需要手动进行指定 []()->bool {}
==
1.[capture]:捕捉列表。捕捉列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数。捕捉列表能够捕捉上下文中的变量以供Lambda函数使用;
2.(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略;
3.mutable:mutable修饰符。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);
4.->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;
5.{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,**还可以使用所有捕获的变量。**
与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式:
1.[var]表示值传递方式捕捉变量var;
2.[=]表示值传递方式捕捉所有父作用域的变量(包括this);
3.[&var]表示引用传递捕捉变量var;
4.[&]表示引用传递方式捕捉所有父作用域的变量(包括this);
5.[this]表示值传递方式捕捉当前的this指针。
上面提到了一个父作用域,也就是包含Lambda函数的语句块,说通俗点就是包含Lambda的“{}”代码块。上面的捕捉列表还可以进行组合,例如:
1.[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;
2.[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量
不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。例如:
erro:
3.[=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;
4.[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。
lambada的函数体未指定返回类型,默认为void
lambda只有在其捕获列表中捕获一个它所在函数的局部变量才能在该函数体中使用
捕获列表只用于局部非static变量,可以直接使用局部static变量和在它所在函数之外声明的名字
#include<iostream>
using namespace std;
int main()
{
int j = 10;
auto by_val_lambda = [=]{ return j + 1; };
auto by_ref_lambda = [&]{ return j + 1; };
cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
++j;
cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
return 0;
}
output
by_val_lambda: 11
by_ref_lambda: 11
by_val_lambda: 11
by_ref_lambda: 12
你想到了么???那这又是为什么呢?为什么第三个输出不是12呢?
在by_val_lambda中,j被视为一个常量,一旦初始化后不会再改变(可以认为之后只是一个跟父作用域中j同名的常量),而在by_ref_lambda中,j仍然在使用父作用域中的值。所以,在使用Lambda函数的时候,如果需要捕捉的值成为Lambda函数的常量,我们通常会使用按值传递的方式捕捉;相反的,如果需要捕捉的值成成为Lambda函数运行时的变量,则应该采用按引用方式进行捕捉。
#include<iostream> 对于const的成员函数,修改非静态的成员变量,所以就出错了。而对于引用的传递方式,并不会改变引用本身,而只会改变引用的值,因此就不会报错了。都是一些纠结的规则。慢慢理解吧。
using namespace std;
int main()
{
int val = 0;
// auto const_val_lambda = [=](){ val = 3; }; wrong!!!
auto mutable_val_lambda = [=]() mutable{ val = 3; };
mutable_val_lambda();
cout<<val<<endl; // 0
auto const_ref_lambda = [&]() { val = 4; };
const_ref_lambda();
cout<<val<<endl; // 4
auto mutable_ref_lambda = [&]() mutable{ val = 5; };
mutable_ref_lambda();
cout<<val<<endl; // 5
对于const的成员函数,修改非静态的成员变量,所以就出错了。而对于引用的传递方式,并不会改变引用本身,而只会改变引用的值,因此就不会报错了。都是一些纠结的规则。慢慢理解吧。
return 0;
}
这段代码主要是用来理解Lambda表达式中的mutable关键字的。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。按照规定,一个const的成员函数是不能在函数体内修改非静态成员变量的值。例如上面的Lambda表达式可以看成以下仿函数代码:
class const_val_lambda
{
public:
const_val_lambda(int v) : val(v) {}
void operator()() const { val = 3; } // 常量成员函数
private:
int val;
};
对于const的成员函数,修改非静态的成员变量,所以就出错了。而对于引用的传递方式,并不会改变引用本身,而只会改变引用的值,因此就不会报错了。都是一些纠结的规则。慢慢理解吧。
lambda也可以捕获this
struct Foo {
void f() {
std::unique_ptr<int> p;
auto f = [=] { cout << p.get() << endl; };
f();
}
};
捕获只能针对于在创建lambda式的作用域内可见的非静态局部变量(包括形参)。
也就是说,在函数体外的类成员变量(这里的p)是无法被捕获的,那么为何可以直接访问p呢?
其实是捕获了this指针。
每一个非静态成员函数都持有一个this指针,然后每当提及该类的成员变量时都会用到这个指针。
那么这里也解释通了,其实[=]捕获了this指针,然后编译器看到p.get()时,把它翻译成了this->p.get()。把代码中的[=]改成[this],仍然成功编译。
也就是说,如果this指向的对象已经被析构,捕获this指针的lambda式就可能出现问题,比如下面代码
using namespace std;
struct Foo {
std::unique_ptr<int> p;
std::function<void()> f() {
p.reset(new int(1));
return [=] { cout << *p << endl; };
}
};
int main() {
auto foo = new Foo();
auto f = foo->f();
delete foo;
f();
}
运行结果为0而非1,而且这里输出0是未定义行为,因为访问的实际上是被回收的空间,只是因为编译器的delete并没有对回收的空间做额外的操作,所以p指向的仍然是原来那块,只不过那块已经被unique_ptr的析构函数自动清除了,只不过将清除的部分全部置为0而已。
由于[=]很容易让人忽略掉this也被捕获了,所以很容易让人忽视这个问题,所以不如[this]直观。
bind
bind就是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表
auto newcallable=bind(callable,arglist)
newcallable就是一个可调用对象:函数,函数指针,lambda表达式,bind,重载()的类;
当我们调用newcallable时候,newcallable会调用callable并传递给它arglist中的参数.
arglist中可能会有_n这种出现,这种叫做占位符,数值n表示可调用对象参数的位置,例如_1代表newcallable的第一个参数
eg
auto wc=bind(check,_1,6);
wc(5);
相当于
check(5,6);
通过pointers to members使用bind
bind将传入的成员(数据成员和成员函数)指针作为第一个参数,其行为如同使用boost::mem_fn将成员指针转换为一个函数对象,即:
bind(&X::f, args); 等价于bind<R>(mem_fn(&X::f), args),其中R为X::f的返回类型(成员函数)或类型(数据成员)。
1 struct X
2 {
3 bool f(int a);
4 };
5
6 X x;
7 shared_ptr<X> p(new X);
8 int i = 5;
9
10 bind(&X::f, ref(x), _1)(i); // x.f(i)
11 bind(&X::f, &x, _1)(i); // (&x)->f(i)
12 bind(&X::f, x, _1)(i); // x.f(i)
13 bind(&X::f, p, _1)(i); // p->f(i) p是类对象,实例化后的
bind(&X::F,*this,_1)
相当于
(*this)->F();
所以: 在使用std::bind绑定类成员函数时,一定要注意,不能有要绑定成员函数的重载成员函数,它分不清到底是哪一个
但是可以进行显示的调用
#include <functional>
#include <iostream>
#include <string>
class test {
public:
test() {}
~test() {}
void diaplay(std::string value) { std::cout << "string" << value; }
void diaplay(int value) { std::cout << "int" << value; }
};
int main() {
test test_;
auto fun = std::bind((void (test::*)(std::string)) & test::diaplay, &test_,
std::placeholders::_1);
fun("ss");
}