函数指针和函数对象/lambda表达式


我们承担ROS,FastDDS等通信中间件,C++,cmake等技术的项目开发和专业指导和培训,有10年+相关工作经验,质量有保证,如有需要请私信联系。

函数指针

定义一个函数指针:

  • 使用类型别名:using func_ptr = type(*) (param_type1, param_type2, ...)
  • typedef定义一个函数指针类型:typedef <return_type> (*func_ptr)(param_type1, param_type2, ...)
int add(int a, int b) { return a+b; }
typedef int (Func)(int, int);  //声明一个函数类型
typedef int (*pFunc)(int, int);  //声明一个函数指针类型
pFunc func0 = add;
Func *func1 = add;

函数指针作为参数传递给函数时,&是可选的。

函数对象

默认情况下函数对象是值传递而不是引用传递:

  • 好处是可以传递常量表达式或暂态表达式(在参数中之间传个值,如1)
  • 缺点就是无法改变function object的状态,有三个办法改变:
    1. 在外部持有状态,并让function object指向它
    2. 以by reference方式传递function object
    3. 利用for_each()算法的返回值

概念:函数对象(仿函数)是定义了一个operator()的类

  • 三大优点:
    • 函数对象比一般函数更灵巧,因为它可以拥有状态
    • 有类型,可以作为template的参数,或容器的参数
      1. 可以作为排序准则
    • 执行速度上,函数对象比函数指针更快
  • 特点:
    • 可以根据需要重载operator()()函数
    • 可以通过类名调用,也可通过实例名称调用
  • 缺点:——待补充
  • 判断式:返回bool的函数或函数对象
    预定义好的函数对象 #include<functional>

函数适配器

指能够将不同的函数对象结合起来的函数对象,头文件 #include<functional>

  • bind函数,, 一般形式为 auto newCallable = std::bind(callable, arg_list)
    • newCallable是一个可调用对象,类型为std::function
    • callbale:如果是类成员要加取地址符,如std::bind(&T::t, arg_list);它执行的还是callbale,只不过换个参数传递方式
    • arg_list: 逗号分割的参数列表,按顺序作为callbale的参数;可包含形如_n的名字,n为整数,占位符,表示可调用对象中参数的位置,_1为newCallable的第一个参数
      • _n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在命名空间std中,形如:using std::placeholders::_1

lambda表达式

常见形式

[capture list](parameter list) mutable constexpr noexcept_specifier attributes -> return type { function body } (C++20P586)

  • capture list: 必需。lambda所在函数中定义的局部变量的列表,只有在捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。但可以使用当前函数体之外的变量,通常为空;捕获列表有如下形式:
    • [var]:表示值传递捕获边变量var
    • [=]:以值传递方式捕获所有父作用域的变量(包括this)
    • [&var]:表示引用传递捕捉变量var
    • [&]:表示引用传递捕捉所有父作用域的变量(包括指针)
    • [this]:表示值传递捕捉当前的this指针
    • [=, &a, &b]:以引用方法捕捉a和b,值传递方式捕捉其他所有变量
    • [=, this]:按值捕获所有内容,并显式捕获this指针。在C++20之前,[=]会隐身捕获this指针,C++20之后不会隐式捕获this,如果需要需显式捕获
  • 关于捕获块的注意事项:
    • 如果默认指定了值捕获或者引用捕获,则不允许额外通过值或引用捕获特定的变量,例如[=,x][&, &x]都是无效的
    • 对象的数据成员不能被捕获
    • 当通过拷贝this指针[this],或拷贝当前对象[*this]来捕获this时,lambda表达式可以访问被捕获对象的所有公有、保护、私有数据成员和方法。
    • 全局变量总是通过引用捕获,即便要求值捕获!如下代码默认值捕获所有内容,单global全局变量仍然是通过引用捕获的。
int global {42};
int main() {
    int n1 = 1, n2 = 2, n3 = 3, n4 = 4, n5 = 5;
    std::cout << global << std::endl;  // global == 42;
    auto f01 {[=] {global=2;}};
//  auto f01 {[=] {global=2; n1 = 3;}}; 这种方式编译不过
    f01();
    std::cout << global << std::endl;  // global == 2;
}
  • (parameter list):参数列表,不能有默认参数。只有当不需要任何参数,不指定mutable,constexpr,noexcept说明符、属性、返回类型或可选条款时,才可以省略
  • mutable:可选。lambda函数总是一个默认的const函数,所以在lambda表达式中,按值捕获的非const变量也不能修改,mutable可以取消其常量属性
  • constexpr:将lambda表达式标记为constexpr,它就可以在编译期计算。即使省略,如果满足constexpr函数的约束条件,lambda表达式也会隐式成为constexpr
  • noexcept说明符:类似普通函数的noexcept
  • attributes 属性:指定lambda表达式的属性
  • ->return type:
    • 返回类型可以省略,这种情况下编译器根据与函数返回类型推导相同的规则来推导lambda表达式的返回类型。
    • 与普通函数不同的是,lambda必须使用尾置返回来指定返回类型,如果lambda函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void——这个是什么意思?
    • 返回类型推导会移除任何引用和const限定。可以将返回类型与delctype(auto)结合使用使推导出来的类型与真实类型相匹配。

泛型lambda表达式

可以为lambda表达式的参数使用自动类型推导,而不是显式地为它们指定具体类型。若要为参数指定自动类型推导,只需要将类型定为auto,类型推导规则与模板参数推导规则相同。

auto func1 { [] (const auto& val1, const auto& val2) {
  return val1 == val2; }
// 编译器为泛型lambda表达式生成的仿函数行为如下:
class xx {
public:
  template<typename T1, typename T2> auto operator()(const T1& val1, const T2& val2) const {
    return val1 == val2;  }
};

模板化lambda表达式

C++20开始支持
[capture list] <typename T> (parameter list) mutable constexpr noexcept_specifier attributes -> return type { function body }
使用lambda表达式的优势:

  • 相比较泛型lambda表达式,指定模板类型代码更加易懂。比如,为了获得val的类型:
auto lamb { [] (const auto& val) {
  using V = decay_t<decltype(val)>;
  using T = typename V::value_type;
} };
  • 第二个好处是,如果想对泛型lambda表达式添加某种限制,如以下代码:
[] (const auto& val1, const auto& val2) { /* ... */ }

如果想对两个参数加以限制,希望具有相同的类型,可以将其转换为一个模板化lambda表达式,并添加requires条款对模板类型设置约束

[] <typename T> (const T& val1, const T& val2) requires integral<T> { /* ... */ }

作为返回类型

function<int(void)> func(int x) { return [x] { return 2 * x; }; }
  • 当定义一个lambda时,编译器生成一个与lambda对应的新的类类型,当向一个函数传递lambda时,同时定义了一个新类型和该类型的一个对象
  • C++20中支持默认构造,拷贝和赋值无状态的lambda表达式。
auto lamb {[] (int a, int b) { return a+b; }};
decltype(lamb) lamb1;  // 默认构造
auto c { lamb };  // 拷贝构造
c = lamb;  // 拷贝

函数外覆器

std::function<T>( #include<functional>):可以存储,移动,赋值,执行。

  • 使用成员函数时,借以调用它们的那个对象必须被当作第一实参:
  • 异常:执行一个函数调用却没有标的物可调用,会抛出异常:std::bad_function_call
class C { public: void memfunc(int x, int y) const; };
std::function<void(const C&, int, int)>mf = &C::memfunc;    // 将成员函数赋值给std::function对象
mf(C(), 42, 77);       // 调用
  • 可以通过类模板参数推导,简化function对象的创建
void func(int a, string_view b) { ... }
function f1 { func };  // 简化f1创建,不需要指定function模板参数
auto f2 { func };    // 更加简化,但编译器推导出来的f1类型是个函数指针,即void(fp*)(int, string_view)而不是std::function

其他辅助函数

标准库functional中的其他辅助函数

  • std::ref 绑定非const引用,将类型T转换成&T
  • std::cref 绑定const引用,将类型T转换成const T&
  • std::invoke() 可用于调用任何带有一组参数的可调用对象
void func(string_view str) { ... }
int main() {
  std::invoke(func, "hello");  // 调用普通函数
  std::invoke([] (cosnt auto& msg) { ... }, "hello");
}
  • 哈希模板std::hash<T>#include<functional>
    • 定义了一个函数对象,实现了哈希函数,这个函数对象的实例定义了一个operator()
    • 接受一个参数的类型key,返回一个类型为size_t的值,表示参数的哈希值
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值