1)什么是可调用对象?
Callable 可调用对象 支持函数调用运算符()
在C++中, Callable可调用对象有如下:
(1) 函数
(2) 函数指针
(3) 函数对象 functor "仿函数"
只要该对象所属的类,实现了 函数调用运算符重载 operator()
(4) lambda表达式
例子:
template<typename T>
void Swap( T& a, T& b )
{
T tmp = a;
a = b;
b = tmp;
}
//冒泡排序
template<typename T, typename Caller>
void MPsort( T *begin, T *end, Caller less )
//less类似于一个函数 bool (T& a, T& b)
{
T *p = end-1;
for( ; begin < end ; begin++ )
{
for( p=end-1; p>begin; p-- )
{
//if( *p < *(p-1) )
if( less(*p, *(p-1)) )
{
//交换位置
Swap( *p, *(p-1) );
}
}
}
}
bool pk( const int& a, const int& b)
{
return a<b;
}
class Less
{
public:
// () 函数调用运算符重载
bool operator()( const int& a, const int& b )
{
return a<b;
}
};
int main()
{
int a[10] = {1,3,5,7,9,2,4,6,8,0};
MPsort( a, a+10 ); // [ , )
//1.函数
//pk是一个函数,pk后面可以接(), 因此pk就是一个Callable
MPsort( a, a+10, pk );
//2.函数指针
bool (*p)(const int& , const int&);
p = pk;
MPsort( a, a+10, p ); //函数指针p 也是一个Callable
//3.函数对象 functor "仿函数"
Less less; //函数对象, 也是一个Callable
MPsort( a, a+10, less );
//4.lambda表达式,也是一个Callable
//...
for( auto &s : a )
{
cout << s << " ";
}
cout << endl;
}
lambda表达式 被用来表示一种匿名的函数
所谓的匿名函数,简单的理解就是 没有名字的函数, 也常称为: lambda表达式 / lambda函数
lambda匿名函数 的定义语法:
[外部变量的访问方式的说明符] (形式参数) mutable noexcept/throw() -> 返回值类型
{
//... 函数体/语句
}
(1) [外部变量的访问方式的说明符]
[]不能省略
[]中括号 用于向编译器表明是一个lambda表达式
[]中括号内部 可以注明当前lambda函数的函数体中,可以使用哪些"外部变量"
"外部变量",指的是 和 lambda表达式 位于同一个作用域内的所有的局部变量
[] 用于向编译器表明,该匿名函数需要"捕获"哪些外部变量
lambda引入"外部变量"的方式
[] 空, 表示当前的lambda匿名函数不导入任何的外部变量
[=] 只有一个=等于号,表示是以值传递的方式导入所有的外部变量
[&] 只有&引用符号,表示以 引用传递的方式导入所有的外部变量
[val1, val2, ...] 表示以值传递的方式导入val1,val2等指定的多个外部变量,顺序无关
值传递的方式捕获外部变量,会在定义lambda表达式时,生成它的一个拷贝
[&val1, &val2, &...] 表示以引用传递的方式导入val1,val2等指定的多个外部变量,
[ val1, &val2, ... ] 表示以值传递的方式导入val1, 以引用传递的方式导入val2
[=, &val1] 除了val1是引用传递的方式导入以外,其他的都是值传递的方式导入
[this] 表示以值传递的方式导入当前的this指针// [=, val1] //error 单个变量不允许以相同的传递方式导入多次
(2) (形式参数)
和普通函数的定义一样, lambda函数也可以接收外部传递过来的多个参数
和普通函数不同的是:
如果不需要传递参数, 连同()小括号一起省略(3) mutable
"可变的", 此关键字可以省略, 如果使用了 则之前的()就不能省略了
默认情况下:
对于以值传递的方式导入的外部变量,不运行来lambda函数体内修改它们的值
(可以理解为 这部分变量都是 const常量 )
如果想修改它们,则必须使用 mutable这个关键字
注意:
对于以值传递的方式引入的外部变量,lambda表达式修改的只是拷贝的那一份
并不会修改真正的外部变量(4) noexcept/throw()
可省略的, 如果使用了 则之前的()就不能省略了noexcept 表示匿名函数体内不会抛出异常, 默认情况下(不加noexcept) 匿名函数体内可以抛出异常的
throw() 用来指定函数体内抛出的异常的类型(5) -> 返回值类型
指定匿名函数的返回值类型
如果lambda函数体内只有一个return语句,或者该函数返回值为void
则编译器可以自行推断返回值类型,在这种情况下 可以直接省略(6) 函数体
和普通函数一样, lambda匿名函数包含的代码都必须放在函数体内
该函数体内 除了可以使用自己定义的内部变量(包括参数), 还可以直接使用引入的外部变量
当然也可以使用全局变量lambda表达式可以 形成一个 "闭包" ,可以捕获外部变量或对象
例子:
// 4.lambda表达式 , 也是一个Callable可调用对象
MPsort( a, a+10, [](const int& a, const int& b) -> bool { return a < b; } );
MPsort( a, a+10, [](const int& a, const int& b) { return a < b; } );
C++11开始引入的可调用对象的其他形式,如std::bind
的结果和std::function
的实例。
5. std::bind
std::bind用于绑定一个可调用对象的参数,并返回一个新的可调用对象。
#include <iostream>
#include <functional>
// 普通函数
void print_sum(int a, int b) {
std::cout << a + b << std::endl;
}
int main() {
// 使用std::bind绑定print_sum的第一个参数为10
auto bound_print_sum = std::bind(print_sum, 10, std::placeholders::_1);
// 调用bound_print_sum,只需提供一个参数
bound_print_sum(5); // 输出15
return 0;
}
6. std::function
std::function是一个通用的、多态的函数封装器。它可以存储、复制和调用任何可调用对象,如函数、lambda表达式、函数对象或其他std::function对象。
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用std::function来存储任何可调用对象
std::function<int(int)> operation;
// 赋值一个lambda表达式给operation
operation = [](intn) { return n * n; };
// 使用std::transform和std::function来将每个数平方
std::transform(numbers.begin(), numbers.end(), numbers.begin(), operation);
// 输出结果
for (int n : numbers) {
std::cout << n << ' ';
}
std::cout << std::endl;
// 更改operation以指向另一个可调用对象
operation = [](int n) { return n * 3; };
// 再次使用std::transform来将每个数乘以3
std::transform(numbers.begin(), numbers.end(), numbers.begin(), operation);
// 输出结果
for (int n : numbers) {
std::cout << n << ' ';
}
std::cout << std::endl;
return 0;
}
在这个例子中,`std::function<int(int)>`表示一个接受一个`int`参数并返回`int`的可调用对象。我们首先将一个lambda表达式赋值给`operation`,然后使用它来计算每个数的平方。接着,我们更改`operation`以指向另一个lambda表达式,这次我们将每个数乘以3。 每种可调用对象都有其适用的场景。函数和函数指针简单直接,适用于简单的函数调用。Lambda表达式提供了匿名函数和捕获局部变量的能力,非常灵活。函数对象(仿函数)允许你封装更复杂的逻辑,并可以像使用对象一样使用它们。
`std::bind`和`std::function`提供了更高级的功能,如参数绑定和可调用对象的类型擦除,使得代码更加通用和灵活。