《C++探幽:仿函数与lambda表达式》

仿函数

  1. 定义
    • 仿函数(Functor)是一种重载了函数调用运算符(operator())的类的对象。它看起来像函数,但又具有对象的特性。本质上,仿函数是一个类,通过重载operator()来实现类似函数的行为。
  2. 特点
    • 状态保存:仿函数可以有自己的成员变量,这意味着它可以保存状态。例如,在一个累加的仿函数中,可以保存当前的累加值。而普通的函数无法保存状态,每次调用都是独立的。
    • 类型安全:仿函数的参数类型和返回类型在类定义时就已经确定,这使得它在类型检查方面更加严格。编译器可以在编译时检查参数类型是否匹配,减少运行时错误。
    • 可定制性高:通过继承和修改仿函数类,可以很容易地扩展其功能。比如,可以继承一个基本的比较仿函数类,然后添加新的比较逻辑。
  3. 使用场景
    • 算法库:在C++标准库的算法中,仿函数经常被用作参数。例如,在std::sort函数中,可以传入一个自定义的比较仿函数来指定排序规则。如果要对一个整数数组按照绝对值的大小进行排序,就可以定义一个仿函数来实现这种特殊的比较逻辑。
    • 事件处理:在一些事件驱动的程序中,可以使用仿函数来处理事件。例如,在图形用户界面(GUI)程序中,当用户点击按钮时,可以将一个仿函数绑定到按钮的点击事件上,这个仿函数可以包含处理按钮点击的逻辑,同时还可以保存一些与按钮相关的信息。

二、 lambda表达式

  1. 定义

    Lambda表达式是一种匿名函数对象。它允许在代码中直接定义一个简单的函数,而不需要像定义普通函数那样写一个完整的函数声明和定义。Lambda表达式是从C++11标准开始引入的。

  2. 语法结构

    一个典型的lambda表达式语法如下:

    • body(函数体):包含要执行的代码。
    • return_type(返回类型):在C++11中,返回类型是可选的,如果省略,编译器会根据函数体中的返回语句自动推导。从C++14开始,可以使用auto来让编译器自动推导返回类型。
    • parameters(参数列表):和普通函数的参数列表类似,可以为空,也可以有多个参数。
    • capture(捕获列表):用于捕获外部变量。捕获方式有值捕获(用[]表示,如[x]表示捕获变量x的值)和引用捕获(用[&]表示,如[&x]表示捕获变量x的引用)。还可以混合捕获,如[this, &x]表示捕获当前类的this指针和变量x的引用。
    • [capture](parameters)->return_type{body}
  3. 特点

    • 简洁性:对于一些简单的函数逻辑,使用lambda表达式可以减少代码量。例如,在调用std::sort时,如果只是简单的比较逻辑,使用lambda表达式可以直接在调用处定义比较规则,而不需要单独定义一个仿函数类。
    • 局部性:lambda表达式定义在需要使用它的地方,这使得代码更加紧凑,逻辑更加清晰。它只在定义它的作用域内有效,不会像全局函数那样可能引起命名冲突。
    • 捕获变量:可以方便地捕获外部变量,这使得lambda表达式在处理一些需要使用外部变量的场景时非常方便。例如,在遍历一个容器并对每个元素进行操作时,可能需要使用外部的一些变量来控制操作的逻辑。
  4. 使用场景

    • 标准库算法:和仿函数一样,lambda表达式也可以作为标准库算法的参数。例如,在std::for_each中,可以使用lambda表达式来对容器中的每个元素执行操作。如std::for_each(vec.begin(), vec.end(), [](int &x){x *= 2;});,这个lambda表达式将容器vec中的每个整数元素乘以2。
    • 线程创建:在C++多线程编程中,可以使用lambda表达式来创建线程。例如,std::thread t([](){std::cout << "Hello from thread" << std::endl;});,这个lambda表达式定义了线程要执行的函数体,使得线程的创建和任务的定义更加简洁。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐