可调用对象即可以像函数一样被调用的对象,有以下三种:
- 函数(指针)
- 仿函数对象
- lambda表达式
tips:调用函数时,既可以用函数名,也可以用函数地址,因为函数名和函数地址是一回事。
[捕捉列表](参数列表)mutable->返回值类型 {函数体}
- 捕捉列表不能省略,即使它为空
- 参数列表为空时可以省略,但是有mutable时不能省略
- mutable用法后面会讲
- 返回值类型可以省略
例如:
struct Good
{
Good(const string& name, double price, int id)
:_name(name)
,_price(price)
,_id(id)
{}
string _name;
double _price;
int _id;
};
int main()
{
vector v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2 ,3 }, { "菠萝", 1.5, 4 } };
auto priceLess = [](const Good& g1, const Good& g2)->bool
{
return g1._price < g2._price;
};
sort(v.begin(), v.end(), priceLess);
}
lambda表达式底层是一个仿函数对象,是一个重载了operator()的类的匿名对象。这个类的名称是
(uuid是一个很长的字符串,通过某种算法得到,同一台机器上基本不会重复),这个类对用户也是匿名的,不能直接使用。
捕捉列表用于捕捉父作用域的局部变量,供函数体中使用。父作用域指的是lambda表达式所在的语句块。捕捉列表内的东西实质上是该匿名对象的成员。
int main()
{
int x = 0, y = 1;
//捕捉lambda表达式函数体的父作用域,即main函数作用域内的变量x和变量y
auto f = [x, y]()mutable->void
{
int tmp = x;
x = y;
y = tmp;
};
f();
cout << "x = " << x << endl;
cout << "y = " << y << endl;
return 0;
}
lambda表达式是一个仿函数对象,它的operator()函数是const的,也即函数体内不能修改成员,使用mutalbe可以取消const。lambda表达式不建议取消const特性。
可以看出x和y的值并没有交换,因为捕捉列表中的x和y是main函数中的x,y的拷贝,二者互不影响。
int main()
{
int x = 0, y = 1;
auto f = [&x, &y]()->void //不用加mutable,因为引用可以修改
{
int tmp = x;
x = y;
y = tmp;
};
f();
cout << "x = " << x << endl;
cout << "y = " << y << endl;
return 0;
}
int main()
{
int x = 0, y = 1;
auto f = [=]()->void
{
cout << x << endl;
cout << y << endl;
};
f();
return 0;
}
如果lambda表达式在成员函数中,那么this指针也会捕捉过来
class AA
{
public:
void func()
{
auto f1 = [=]()->void
{
cout << _a;//this->_a
cout << _b;//this->_b
};
//这样写反而不行,因为_a和_b不在func作用域内
/*auto f1 = [_a, _b]()->void
{
cout << _a;
cout << _b;
};*/
}
private:
int _a;
int _b;
};
int main()
{
int x = 0, y = 1;
auto f = [&]()->void
{
cout << x << endl;
cout << y << endl;
};
f();
return 0;
}
同理,如果lambda表达式在成员函数中,this指针也会捕捉过来。
int main()
{
int x = 0, y = 1;
auto f1 = [&, x]()->void //传值捕捉x,其余全部引用捕捉,&或=必须在前
{
cout << x << endl;
cout << y << endl;
};
auto f2 = [x, &y]()->void //传值捕捉x,引用捕捉y
{
cout << x << endl;
cout << y << endl;
};
//auto f3 = [=, &]()->void //不允许重复捕捉,因为使用时会有歧义
//{
// cout << x << endl;
// cout << y << endl;
//};
return 0;
}
前文说到,捕捉列表中的东西实质上是匿名对象的成员,证明如下:
由结果可知,当捕捉父作用域的全部局部变量时,编译器并非憨憨地全部捕捉,而是看你在函数体内用到了哪些,没用到的就不捕获了。
- function是一个类模版,在
中。 - 不同的可调用对象虽然类型不同,但是却可能有相同的调用形式,即返回类型,参数类型相同。
- function包装器作用就是包装可调用对象,把调用形式相同的可调用对象的类型统一起来,便于书写它们的类型。
- function实际是一个类模版,实例化时的模版参数写可调用对象的调用形式,即返回值类型(参数类型列表),如void(int, double),这一点和普通模版不一样。
void swap_func(int& x, int& y)
{
int tmp = x;
x = y;
y = x;
cout << "函数指针" << endl;
}
class Swap_func
{
public:
void operator()(int& x, int& y)
{
int tmp = x;
x = y;
y = x;
cout << "仿函数对象" << endl;
}
};
int main()
{
int x = 1, y = 0;
function f1 = swap_func;
f1(x, y);
auto lambda_swap_func = [](int& x, int& y)->void
{
int tmp = x;
x = y;
y = x;
cout << "lambda表达式" << endl;
};
f1 = lambda_swap_func;
f1(x, y);
f1 = Swap_func();
f1(x, y);
return 0;
}
将具有同种调用形式的可调用对象类型统一起来,下面这段代码能体现其用处:
map> cmdOP = {
{"函数指针", swap_func},
{"lambda表达式", lambda_swap_func},
{"仿函数对象", Swap_func()}
};//列表初始化
cmdOP["函数指针"](x, y);
cmdOP["lambda表达式"](x, y);
cmdOP["仿函数对象"](x, y);
如果没有function包装器,map的第二个模版参数就只能用函数指针了,并且只能使用函数这一种可调用对象,缺乏灵活性。并且函数指针用起来很麻烦,这也是为什么C++仿函数被广泛应用的原因。
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
//包装静态成员函数
function f1 = Plus::plusi;
int ret = f1(1, 2);
return 0;
}
与普通全局函数唯一不同的一点,就是要指明类域。
function f2 = Plus::plusd;
//error C3867: “Plus::plusd”: 非标准语法;请使用 "&" 来创建指向成员的指针
非静态成员函数比较特殊,函数前还需加上取地址符号,一般情况下不管函数取不取地址,都代表函数地址,这里特殊情况记住就行。
function f2 = &Plus::plusd;
//error C2440: “初始化”: 无法从“double (__thiscall Plus::* )(double,double)”
//转换为“std::function”
还是有问题,因为成员函数都是隐式传了this指针的,不然函数体内就无法访问成员变量。
最终写法:
int main()
{
double x = 1.1;
double y = 2.2;
function f2 = &Plus::plusd;
double ret = f2(&p, x, y);
return 0;
}
还支持这样写:
function f2 = &Plus::plusd;
f2(Plus(), x, y);//传匿名对象
在底层上可以理解为前者是通过对象指针去调用plusd,后者通过对象调用plusd,二者是一样的。而且后者可以传匿名对象,更加方便。
但是,每次调用f2都好麻烦啊,需要传递对象或者对象指针。如果函数体内部根本不涉及成员变量的操作,也就是说第一个参数传什么根本不重要。有没有什么办法,每次自动传一个匿名对象,让我自己少传一个参数呢?有的,使用bind函数!!!
- bind是一个函数模版,在
中 - 它的作用是包装可调用对象,返回一个新的可调用对象,以达到调整调用形式的目的,即参数个数及顺序
一般形式:auto newCallable = bind(callable, arg_list)
callalbe是原来的可调用对象,newCallable是返回的新的可调用对象,arg_list是以逗号分隔的参数列表
int Sub(int x, int y)
{
return x - y;
}
int main()
{
//调整参数顺序
auto f1 = bind(Sub, placeholders::_2, placeholders::_1);
cout << f1(10, 5) << endl;
return 0;
}
其中_n(n=1, 2, 3……)是“占位符”,表示新的可调用对象的参数,它定义在命名空间placeholders中。实际上,bind返回的newCallable封装了Callable,当我们调用newCallable时,newCallable会调用Callable,并传递给它arg_list中的参数。
bind意为“绑定”,它真正的作用在于绑定某些参数,从而减少传参的个数。
int main()
{
//调整参数个数
auto lambda = [](int a, int b, int c)->void
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
};
function f = bind(lambda, placeholders::_1, 10, placeholders::_2);
f(1, 100);
}
注意观察,bind返回的是一个可调用对象,当然可以用function包装。并且function的模版参数就是新的可调用对象的调用形式。
同理,用function包装非静态成员函数时就可以使用bind来减少参数传递
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
function f = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);
double ret = f(1.1, 2.2);
cout << ret << endl;
}
可见,bind返回的可调用对象也是一个类的实例对象,这个类肯定封装了operator()。其实无论是lambda表达式,还是bind返回的对可调用象,都是用的仿函数技术,这不过外面做了一层精美的包装,使我们使用起来更加方便。