Skip to content

1. 函数对象

函数对象(Function Object)又称函数对象类、仿函数、高阶函数等,是指那些可以被传入其他函数或是从其他函数返回的函数。函数对象实际上是一个重载了operator()操作符的类,由于重载了这个操作符的类有和普通函数类似的使用方法,所以称其为函数对象。

  • 举例
cpp
void fun()
{
    cout << "hello world" << endl;
}
int main()
{
    fun();
}
  • 功能
    让一个对象可以像函数一样去使用

  • 特点
    对参数的个数,参数类型,返回值类型没有限制,看具体要实现的功能

  • 实现方式
    只能以成员函数方式

  • 成员函数方式
    operator()(形参表)的表达式可以被编译器翻译成对象.operator()(形参表)。

cpp
#include<iostream>
using namespace std;

class Func
{
    public:
    double operator()(double d)
    {
    return d * d;
    }
};

int main()
{
    Func func;
    //func.operator()(3.14)
    cout << func(3.14) << endl;
    return 0;
}

函数对象的使用非常灵活,可以像普通函数那样调用,有参数,也可以有返回值。此外,函数对象还可以有自己的状态,这使其超出普通函数的概念。它们可以作为参数传递给其他函数。

2. lambda匿名函数

有些时候,对于一些比较简单且使用次数少的的函数,我们没必要再特地去定义一个函数去实现该功能。例如,求一个数的平方

cpp
double square(int x)
{
    return x * x;
}

此时我们可以使用lambda匿名函数来完成上述操作

2.1 lambda函数的定义

lambda被用来表示一种匿名函数。所谓匿名函数简单地理解就是没有名称的函数。又常被称为lambda函数或者lambda表达式。

lambda表达式创建一个匿名函数对象,通常被称为lambda函数或lambda闭包。当你定义一个lambda表达式时,你实际上创建了一个可以像函数一样调用的对象。这个对象有一个调用运算符(operator()),这使得它可以像函数那样被调用。

因此,当你写下一个lambda表达式并赋值给一个变量时,这个变量的类型是一个函数对象,而不是一个返回值。lambda表达式本身没有“返回值”的概念,因为它不是一个函数调用,而是一个对象定义。

2.2 lambda函数的语法

  • 定义
    [外部变量访问方式说明符] (参数) mutable noexcept/throw() -> 返回值类型 { 函数体; };

  • 各部分的含义分别为

    • [外部变量访问方式说明符]
      []用于向编译器表明当前是一个lambda表达式,其不能被省略。在方括号内都可以注明当前lambda函数的函数体中可以使用哪些“外部变量”。所谓外部变量指的是和当前lambda表达式位于同一作用域内的所有局部变量。

比如,如下就定义了一个最简单的lambda匿名函数:
auto d = [ ]{ }; // 一个空的lambda表达式

  • (参数)
    和普通函数的定义一样,lambda匿名函数也可以接收外部传递的多个参数。和普通函数不同的是,如果不需要传递参数,可以连同小括号一起省略
cpp
auto c = []/*()*/{
    int a = 1;
    int b = 2;
    return a / b;
};

cout << c() << endl;

auto square = [](int x)/* -> int*/ {
    return x;
};

cout << square(1) << endl;
  • mutable
    此关键字可以省略,如果使用则之前的()小括号将不能省略(参数个数可以为0)。默认情况下,对于以值传递方式引入的外部变量,不允许在lambda表达式内部修改它们的值(可以理解为这部分变量都是const常量)。而如果想修改它们,就必须使用mutable关键字。

注意:对于以值传递方式引入的外部变量,lambda表达式修改的是拷贝的那一份,并不会修改真正的外部变量。

外部变量的定义方式

外部变量格式功能
[ ]空方括号表示当前lambda匿名函数中不导入任何外部变量。
[=]只有一个等号,表示以值传递的方式导入所有外部变量。
cpp
auto d = [=](int i) mutable {
    int x = 3;
    int y = 4;
    a = 10;//lambda表达式修改的是拷贝的那一份,并不会修改真正的外部变量
    return a + b + i + x + y;
};

//默认是值传递
auto c = [=](int i) /*-> float*/ {
    int x = 3;
    int y = 4;
    //a = 10;//error 以值传递方式引入的外部变量,不允许在lambda表达式内部修改它们的值
    return a + b + i + x + y;
};

cout << c(5) << endl;
cout << d(5) << endl;
外部变量格式功能
[&]只有一个&符号,表示以引用传递的方式导入所有外部变量。
cpp
int a = 1;
int b = 2;

auto c = [&](int i) /*-> float*/ {
    int x = 3;
    int y = 4;
    a = 10; //引用传递可以修改
    return a + b + i + x + y;
};

cout << c(5) << endl;
外部变量格式功能
[val1, val2,...]表示以值传递的方式导入val1, val2等指定的外部变量,同时多个变量之间没有先后次序
cpp
int a = 1;
int b = 2;
int c = 3;

auto d = [a, b](int i) /*-> float*/ {
    int x = 3;
    int y = 4;
    //return a + b + c + i + x + y; //error c没有传入,不能使用
    return a + b + i + x + y;
};

cout << d(5) << endl;
外部变量格式功能
[&val1,&val2,...]表示以引用传递的方式导入val1, val2等指定的外部变量,多个变量之间没有前后次序
cpp
int a = 1;
int b = 2;
int c = 3;

auto d = [&b, &a](int i) /*-> float*/ {
    int x = 3;
    int y = 4;
    //return a + b + c + i + x + y; //error c没有传入,不能使用
    return a + b + i + x + y;
};

cout << d(5) << endl;
外部变量格式功能
[val1,&val2,...]以上2种方式还可以混合使用, 变量之间没有前后次序。
cpp
int a = 1;
int b = 2;
int c = 3;

auto d = [&b, a](int i) /*-> float*/ {
    int x = 3;
    int y = 4;
    //return a + b + c + i + x + y;//error_c没有传入,不能使用
    return a + b + i + x + y;
};

cout << d(5) << endl;
外部变量格式功能
[=,&val1,...]表示除val1以引用传递的方式导入外,其它外部变量都以值传递的方式导入
cpp
int a = 1;
int b = 2;
int c = 3;

auto d = [=, &b](int i) /*-> float*/ {
    int x = 3;
    int y = 4;
    return a + b + i + x + y;
};
外部变量格式功能
[this]表示以值传递的方式导入当前的this指针。
cpp
#include <iostream>

class MyClass {
public:
    void fun()
    {
    // 使用 [this] 捕获列表访问和修改成员变量和成员函数
    auto lambda = [this]()
    {
    // 访问局部变量
    std::cout << "Local variable: " << this->memberVar << std::endl;
    };
    lambda();
    }
private:
    int memberVar = 5;
};

int main() {
    MyClass obj;
    obj.fun();
    return 0;
}
  • noexcept/throw()
    可以省略,如果使用,则之前的()小括号将不能省略(参数个数可以为0)。默认情况下,lambda函数的函数体中可以抛出任何类型的异常,而标注noexcept关键字,则表示函数体内不会抛出任何异常。使用throw()可以指定lambda函数内部可以抛出的异常类型。如果lambda函数有noexcept而函数体内抛出了异常,又或者使用throw()限定了异常类型而函数体内抛出了非指定类型的异常,这些异常无法使用try-catch捕获会导致程序执行失败。
cpp
#include <iostream>
using namespace std;

int main()
{
    auto d = []{/*int x, int y*/}throw()/*noexcept*/
    {
    int x = 1;
    int y = 0;
    if (y == 0)
    {
    throw(-1);
    //throw string("除数不能为0");
    //throw(1.1);
    }
    return x / y;
    };
    try
    {
    cout << d({/*1, 0*/}) << endl;
    }
    catch (int n)
    {
    cout << "除数不能为0" << endl;
    }
    catch (const string& str)
    {
    cout << str << endl;
    }
    auto e = []{/*int x, int y*/}noexcept
    {
    int x = 1;
    int y = 2;
    return x / y;
    };
    cout << e() << endl;
    return 0;
}
  • 返回值类型
    指明lambda匿名函数的返回值类型:大部分情况下可以直接省略->返回值类型。
cpp
#include<iostream>
using namespace std;

class A
{
public:
    double m_data;
};

int main()
{
    A a1;
    auto c = [&]() /*-> A*/ 
    {
    double a = 3.0;
    double b = 2.0;
    double res = a / b;
    a1.m_data = res;
    return a1;
    };
    cout << c().m_data << endl;

    return 0;
}
  • 函数体
    和普通函数一样,lambda匿名函数包含的内部代码都放置在函数体中。该函数体内除了可以使用指定传递进来的参数之外,还可以使用指定的外部变量以及全局范围内的所有全局变量。需要注意的是,外部变量会受到以值传递还是以引用传递方式引入的影响,而全局变量则不会。换句话说,在lambda表达式内可以使用任意一个全局变量,必要时还可以直接修改它们的值。

全局变量的值可以修改

cpp
#include<iostream>
using namespace std;

double d = 5.0;

int main()
{
    auto c = []() /*-> A*/ 
    {
    double b = 2.0;
    d = 6.0;
    double res = d / b;
    return res;
    };
    cout << c() << endl;
    cout << d << endl;

    return 0;
}
  • 问题1: lambda函数中的=表示lambda函数的返回值吗
cpp
auto add = [ ] (int x, int y) { return x + y; };
int a = add(3, 4);

在这个lambda表达式中,其实是创建一个函数对象,它封装了一个可以调用(即执行)的操作。当你调用add(3, 4)时,你实际上是调用这个函数对象的调用运算符,并且这个调用会返回7。这个7是add(3, 4)这个函数调用表达式的返回值,而不是add本身的“返回值”。

为了澄清,这里有两个概念需要区分:

  1. Lambda表达式的返回值:当你调用一个lambda函数对象时,它可能会返回一个值。这个值是由lambda函数体中的返回语句确定的。
  2. Lambda表达式创建的对象:lambda表达式创建一个函数对象,这个对象可以被存储、传递和调用。函数对象本身不是一个返回值,而是一个可调用实体。
  • 问题2: C++ lambda函数的作用
    C++中的lambda函数(也被称为lambda表达式或匿名函数)是一种非常强大的工具,它允许你定义一个可以在需要时立即使用的匿名函数对象。lambda函数的主要作用包括:

    1. 代码简洁与可读性:lambda函数允许你在需要的地方直接定义并使用一个小函数,而无需事先声明一个单独的函数或函数对象。这有助于减少代码冗余,使代码更加紧凑和易于理解。
    2. 就地定义函数逻辑:有时,你可能需要在某个特定的作用域上上下文中执行一段逻辑,而这段逻辑只在这个地方使用一次。在这种情况下,使用lambda函数可以就地定义这段逻辑,而无需在全局作用域或类的成员函数中定义它。
    3. 算法和容器的配合:C++标准库中的许多算法(如std::find_if、std::transform等)都接受一个函数对象作为参数。lambda函数可以很方便地作为这些算法的参数,用于定义算法的行为。
    4. 闭包特性:lambda函数可以捕获其所在作用域中的局部变量,并在其函数体内使用这些变量。这种捕获变量的能力使得lambda函数可以访问和操作外部作用域的变量。
    5. 状态封装与传递:lambda函数可以封装状态信息,并将其传递给其他函数或对象。这在需要传递函数逻辑和相关数据时非常有用。
  • 问题3: C++ lambda函数和普通函数的区别?
    C++中的lambda函数和普通函数(非lambda函数)之间存在一些关键的区别。这些区别主要体现在定义方式、作用域、生命周期以及使用场景上。以下是它们之间的主要区别:

    1. 定义方式
      • 普通函数:通常在类的外部或内部定义,具有显式的函数名、返回类型和参数列表。
      • Lambda函数:是匿名函数,直接在代码中使用lambda表达式定义,没有显式的函数名,但可以有返回类型和参数列表。
    2. 作用域和捕获
      • 普通函数:其作用域通常跨越整个文件或类,并可以通过其名称在代码中多次调用。普通函数不直接捕获外部变量。
      • Lambda函数:具有局部作用域,只在定义它的代码块内可见。它们可以捕获其所在作用域中的局部变量,使得这些变量在lambda函数体内可用。捕获可以是按值或按引用。
    3. 生命周期
      • 普通函数:它们的生命周期通常与程序或类实例的生命周期相同。
      • Lambda函数:它们的生命周期取决于它们被创建和使用的上下文。如果Lambda函数被赋值给一个对象或作为参数传递给其他函数,则其生命周期可能会与这些对象的生命周期相关联。
    4. 语法简洁性
      • 普通函数:需要显式声明函数名和返回类型,语法上可能较为繁琐。
      • Lambda函数:可以就地定义,无需显式函数名,语法简洁,特别适用于需要一次性使用的函数逻辑。
    5. 使用场景
      • 普通函数:适用于需要多次调用和跨作用域使用的函数逻辑。
      • Lambda函数:特别适用于需要就地定义函数逻辑的场景,如算法库中的回调函数、排序比较函数、事件处理等。它们使得代码更加紧凑和模块化。
    6. 性能
      在性能方面,普通函数和Lambda函数之间没有本质的区别。它们都被编译器转换为机器代码并执行。然而,由于Lambda函数可能涉及捕获外部变量,这可能会增加一些内存开销。

知识如风,常伴吾身