C++泛型算法2——谓词,lambda表达式

发布于:2024-05-04 ⋅ 阅读:(28) ⋅ 点赞:(0)

定制操作

很多算法都会比较输入序列中的元素。

默认情况下,这类算法使用元素类型的<或==运算符完成比较。

标准库还为这些算法定义了额外的版本,允许我们提供自己定义的操作来代替默认运算符。

例如,sort 算法默认使用元素类型的<运算符。但可能我们希望的排序顺序与<所定义的顺序不同,或是我们的序列可能保存的是未定义<运算符的元素类型。在这两种情况下,都需要重载 sort 的默认行为。

谓词

谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。

标准库算法所使用的谓词分为两类:一元谓词(unary predicate,意味着它们只接受单一参数)和二元谓词(binary predicate,意味着它们有两个参数)。

接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换为谓词的参数类型。

在C++中,谓词(Predicate)是用于描述某种特定条件或判断的函数对象或可调用对象。根据谓词所接受的参数个数,可以分为一元谓词和二元谓词。

一元谓词(Unary Predicate):

一元谓词是指只接受一个参数的谓词。

在使用一元谓词时,通常会将一个参数传递给该谓词,然后谓词会根据这个参数进行判断并返回相应的结果。

例如,一个判断整数是否大于5的一元谓词可以这样定义:

class GreaterThanFive {  
public:  
    bool operator()(int val) {  
        return val > 5;  
    }  
};

在这个例子中,GreaterThanFive是一个一元谓词类,它的operator()接受一个整数参数并返回一个布尔值。 

二元谓词(Binary Predicate):

二元谓词是指接受两个参数的谓词。

在使用二元谓词时,通常会将两个参数传递给该谓词,然后谓词会根据这两个参数进行判断并返回相应的结果。

例如,一个比较两个整数大小的二元谓词可以这样定义:

class CompareIntegers {  
public:  
    bool operator()(int a, int b) {  
        return a < b;  
    }  
};

在这个例子中,CompareIntegers是一个二元谓词,它的operator()接受两个整数参数并返回一个布尔值。

一元谓词和二元谓词在C++的STL(Standard Template Library)算法中广泛使用,用于定义元素之间的比较或判断条件。例如,在std::sort算法中,你可以通过提供一个二元谓词来定义元素的排序规则。在std::remove_if算法中,你可以提供一个一元谓词来定义要移除的元素的条件。

注意:

 bool bigger5(int val) {  
 return val > 5;  
 }  
 

在C++的标准库和算法库中,通常所说的“谓词”(Predicate)是一个可调用实体,它接受一定数量的参数并返回一个布尔值。这个可调用实体可以是函数、函数对象(具有operator()的重载的类)、lambda表达式、函数指针或任何可转换为std::function的对象。

虽然bool bigger5(int val)这个函数在语义上符合谓词的定义(它接受一个参数并返回一个布尔值),但在C++的STL(标准模板库)中,当我们提到“谓词”时,我们通常指的是那些能够作为函数对象使用的实体,即它们可以像对象一样被传递和存储。

lambda表达式

根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须严格接受一个或两个参数。

但是,有时我们希望进行的操作需要更多参数,超出了算法对谓词的限制。

可调用对象

我们可以向一个算法传递任何类别的可调用对象。

对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的。

即,如果e是一个可调用的表达式,则我们可以编写代码e(args),其中args是一个逗号分隔的一个或多个参数的列表。

在C++中,可调用对象(Callable Objects)是可以像函数那样被调用的实体。这些实体可以是函数、函数指针、成员函数指针、lambda表达式、函数对象(也称为仿函数,functor)等。它们共同的特点是可以使用圆括号()进行调用,并可能接受参数和返回结果。

以下是C++中几种常见的可调用对象:

普通函数:

 int add(int a, int b) {  
 return a + b;  
 }  
 // 调用:int result = add(2, 3); 

函数指针:

 int subtract(int a, int b) {  
 return a - b;  
 }  
 int (*funcPtr)(int, int) = subtract;  
 // 调用:int result = funcPtr(5, 2); 

成员函数指针(仅针对类的成员函数):

 class MyClass {  
 public:  
 int getValue() const { return value_; }  
 private:  
 int value_ = 10;  
 };  
   int (MyClass::*getMemberPtr)() const = &MyClass::getValue;  
 MyClass obj;  
 // 调用:int result = (obj.*getMemberPtr)(); 

Lambda表达式:

 auto lambda = [](int a, int b) { return a * b; };  
 // 调用:int result = lambda(4, 5); 

函数对象(仿函数):

 struct Multiplier {  
 int operator()(int a, int b) const {  
 return a * b;  
 }  
 };  
 Multiplier multiplier;  
 // 调用:int result = multiplier(6, 7); 

std::function:

std::function是一个通用、多态的函数封装器,可以存储、复制和调用任何可调用目标——函数、lambda表达式、bind表达式或其他函数对象。

 #include <functional>  
   std::function<int(int, int)> func = [](int a, int b) { return a + b; };  
 // 调用:int result = func(2, 3); 

可调用对象在C++ STL(标准模板库)的算法中特别有用,因为它们可以作为谓词(predicates)和函数对象(function objects)传递给算法,如std::sort、std::find_if等。

介绍 lambda

一个lambda 表达式表示一个可调用的代码单元。

我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda 具有一个返回类型、一个参数列表和一个函数体。

但与函数不同,lambda可能定义在函数内部。

一个lambda表达式具有如下形式

[capture list] (parameter list) -> return type ( function body )

其中,capture_list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type、parameter list和 function body与任何普通函数一样,分别表示返回类型、参数列表和函数体。但是,与普通函数不同,lambda 必须使用尾置返回来指定返回类型。

我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体

auto f= [] { return 42;};

此例中,我们定义了一个可调用对象f,它不接受参数,返回42。

lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符:

cout << f() << endl;//打印42

在lambda 中忽略括号和参数列表等价于指定一个空参数列表。

在此例中,当调用f时,参数列表是空的。如果忽略返回类型,lambda 根据函数体中的代码推断出返回类型。

如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来。否则,返回类型为void。

如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void。

向lambda传递参数

与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。

通常,实参和形参的类型必须匹配。但与普通函数不同,lambda不能有默认参数。

因此,一个lambda 调用的实参数目永远与形参数目相等。一旦形参初始化完毕,就可以执行函数体了。

下面是一个简单的例子,展示了lambda表达式的定义和调用:

#include <iostream>  
  
int main() {  
    // 定义一个lambda,它接受一个int类型的参数并打印它  
    auto printNumber = [](int num) {  
        std::cout << "The number is: " << num << std::endl;  
    };  
  
    // 调用lambda,为形参num提供一个实参  
    printNumber(42); // 输出: The number is: 42  
  
    // 尝试调用lambda但不提供实参会导致编译错误  
    // printNumber(); // 错误:缺少实参  
  
    return 0;  
}

空捕获列表表明此lambda 不使用它所在函数中的任何局部变量。

在上面的例子中,我们定义了一个lambda printNumber,它接受一个int类型的参数num。当我们调用printNumber(42)时,我们为形参num提供了一个实参42如果我们尝试调用printNumber()而不提供任何实参,编译器会报错,因为lambda没有默认参数

使用捕获列表

我们现在已经准备好解决原来的问题了——编写一个可以传递给find_if的可调用表达式。

我们希望这个表达式能将输入序列中每个string的长度与biggies函数中的sz参数的值进行比较。

虽然一个lambda 可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指明的变量。一个lambda 通过将局部变量包含在其捕获列表中来指出将会使用这些变量。捕获列表指引lambda在其内部包含访问局部变量所需的信息。

在本例中,我们的lambda 会捕获sz,并只有单一的 string 参数。其函数体会将string 的大小与捕获的 sz的值进行比较:

[sz](const string &a)
{ return a.size() >= sz;};

lambda以一个[ ]开始,我们可以在其中提供一个以逗号分隔的名字列表,这些名字都是它所在函数中定义的。

由于此 lambda 捕获sz,因此 lambda的函数体可以使用sz。

lambda不捕获words,因此不能访问此变量。

如果我们给lambda 提供一个空捕获列表,则代码会编译错误:

//错误:sz未捕获
[](const string &a)
{return a.size()>=sz; };

一个lambda 只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量

捕获列表只用于局部非 static 变量,lambda 可以直接使用局部static变量和在它所在函教之外声明的名字

lambda 捕获和返回

当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。

目前,可以这样理解,当向一个函数传递一个 lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名对象。类似的,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。

默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员类似任何普通类的数据成员,lambda的数据成员也在lambda对象创建时被初始化。

lambda 捕获列表
[ ] 空捕获列表。lambda不能使用所在函数中的变量。一个lambda只有捕获变量后才能使用它们
[names] names 是一个逗号分隔的名字列表,这些名字都是lambda所在函数的局部变量。默认情况下,捕获列表中的变量都被拷贝。名字前如果使用了&,则采用引用捕获方式
[&] 隐式捕获列表,采用引用捕获方式。lambda体中所使用的来自所在函数的实体都采用引用方式使用
[=] 隐式捕获列表,采用值捕获方式。lambda 体将拷贝所使用的来自所在函数的实体的值
[&, identifier_list] identifier_list 是一个逗号分隔的列表,包含0个或多个来自所在函数的变量。这些变量采用值捕获方式,而任何隐式捕获的变量都采用引用方式捕获。identifier_list中的名字前面不能使用&
[=, identifier_list] identifier_list中的变量都采用引用方式捕获,而任何隐式捕获的变量都采用值方式捕获。identifier _list中的名字不能包括this,且这些名字之前必须使用&

值捕获

类似参数传递,变量的捕获方式也可以是值或引用。

到目前为止,我们的lambda 采用值捕获的方式。

与传值参数类似,采用值捕获的前提是变量可以拷贝。

与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝:

voia Ecnl()
{
size_t v1 =42;//局部变量

//将v1拷贝到名为f的可调用对象
auto f= [v1] { return v1; };

v1 = 0;
auto j=f();//j为42;f保存了我们创建它时v1的拷贝
}

由于被捕获变量的值是在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值。

引用捕获

们定义lambda时可以采用引用方式捕获变量。例如:

void fcn2()
{
size_t vl =42;//局部变量

//对象f2包含v1的引用
auto f2 = [&vl] { return vl; };

v1 = 0;
auto j=f2();//j为0;f2保存v1的引用,而非拷贝
}

v1之前的&指出v1应该以引用方式捕获。

一个以引用方式捕获的变量与其他任何类型的引用的行为类似。当我们在lambda 函数体内使用此变量时,实际上使用的是引用所绑定的对象。

在本例中,当lambda返回v1时,它返回的是v1指向的对象的值。

引用捕获与返回引用有着相同的问题和限制。

如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda 执行的时候是存在的。

lambda 捕获的都是局部变量,这些变量在函数结束后就不复存在了。

如果lambda可能在函数结束后执行,捕获的引用指向的局部变量已经消失。

引用捕获有时是必要的。

例如,我们可能希望函数接受一个 ostream的引用,用来输出数据:

#include <iostream>  
  
void useLambdaWithOutputStream() {  
    // 通过引用捕获std::cout  
    auto lambda = [&](const std::string& message) {  
        std::cout << message << std::endl;  
    };  
  
    // 使用lambda输出信息  
    lambda("Hello from lambda!");  
}  
  
int main() {  
    useLambdaWithOutputStream();  
    return 0;  
}

在这个例子中,lambda通过引用捕获了std::cout(尽管我们没有在捕获列表中明确写出std::cout,但因为我们使用了[&],它捕获了所有外部作用域中可访问的自动存储期变量的引用,包括std::cout)。然后,lambda内部就可以安全地使用std::cout了。 

我们不能拷贝ostream对象,因此捕获os的唯一方法就是捕获其引用(或指向os的指针)。

当我们向一个函数传递一个lambda时,lambda会立即执行。

在此情况下,以引用方式捕获os没有问题,因为当for_each 执行时,biggies中的变量是存在的。

我们也可以从一个函数返回lambda。

函数可以直接返回一个可调用对象,或者返回一个类对象,该类含有可调用对象的数据成员。如果函数返回一个 lambda,则与函数不能返回一个局部变量的引用类似,此 lambda 也不能包含引用捕获。

当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。

建议:尽量保持 lambda的变量捕获简单化

一个lambda捕获从lambda被创建(即,定义lambda的代码执行时)到lambda自身执行(可能有多次执行)这段时间内保存的相关信息。确保lambda 每次执行的时候这些信息都有预期的意义,是程序员的责任。

捕获一个普通变量,如int、string或其他非指针类型,通常可以采用简单的值捕获方式。在此情况下,只需关注变量在捕获时是否有我们所需的值就可以了。

如果我们捕获一个指针或迭代器,或采用引用捕获方式,就必须确保在lambda执行时,绑定到迭代器、指针或引用的对象仍然存在。而且,需要保证对象具有预期的值。在lambda从创建到它执行的这段时间内,可能有代码改变绑定的对象的值。也就是说,在指针(或引用)被捕获的时刻,绑定的对象的值是我们所期望的,但在lambda执行时,该对象的值可能已经完全不同了。

一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。而且,如果可能的话,应该避免捕获指针或引用。

隐式捕获

除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。

当提到“隐式捕获”时,是在说[&][=]这两种捕获方式,它们分别表示按引用捕获和按值捕获所有外部变量,但这实际上是隐式指定了捕获方式,而不是隐式捕获了所有变量。

  • [&](按引用捕获):捕获所有外部作用域中的自动存储期变量(包括this指针,如果lambda在类成员函数内部定义),并作为引用在lambda体内使用。
  • [=](按值捕获):捕获所有外部作用域中的自动存储期变量(包括this指针,如果lambda在类成员函数内部定义),并作为副本在lambda体内使用。

为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。

&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。

我们举个例子

#include <iostream>  
  
void example() {  
    int x = 10;  
  
    // 按引用捕获所有外部变量  
    auto lambda_ref = [&]() {  
        std::cout << "x by reference: " << x << std::endl;  
        x = 20; // 可以修改x的值,因为它是按引用捕获的  
    };  
  
    lambda_ref(); // 输出: x by reference: 10  
    std::cout << "x after lambda: " << x << std::endl; // 输出: x after lambda: 20  
  
    // 按值捕获所有外部变量  
    auto lambda_val = [=]() {  
        std::cout << "x by value: " << x << std::endl;  
        // x = 20; // 编译错误,因为x是按值捕获的副本,无法修改原始变量  
    };  
  
    lambda_val(); // 输出: x by value: 10  
    std::cout << "x after lambda (again): " << x << std::endl; // 输出: x after lambda (again): 20  
}  
  
int main() {  
    example();  
    return 0;  
}

在这个例子中,lambda_ref按引用捕获了x,因此它可以在lambda体内修改x的值。而lambda_val按值捕获了x,它得到的是x的一个副本,因此无法修改原始变量x的值(尝试修改会导致编译错误)。 

如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获:

第一种情况

#include <iostream>  
  
void example() {  
    int x = 10;  
    int y = 20;  
  
    // 显式捕获:x按值捕获,y按引用捕获  
    auto lambda = [x, &y]() {  
        std::cout << "x by value: " << x << std::endl;  
        std::cout << "y by reference: " << y << std::endl;  
        y = 30; // 可以修改y的值,因为它是按引用捕获的  
    };  
  
    lambda(); // 输出x和y的当前值  
    std::cout << "y after lambda: " << y << std::endl; // 输出修改后的y的值  
}  
  
int main() {  
    example();  
    return 0;  
}
x by value: 10  
y by reference: 20  
y after lambda: 30

 第二种情况

#include <iostream>  
  
void example() {  
    int x = 10;  
    int y = 20;  
  
    // 只使用显式捕获:x按值捕获,y按引用捕获  
    auto lambda = [=, &y]() {  
        // 注意:[=] 是多余的,因为已经显式捕获了所有需要的变量  
        // 但这里为了展示目的,我们保留它  
  
        // 试图修改x会失败,因为x是按值捕获的  
        // x = 20; // 这会导致编译错误  
  
        std::cout << "x by value: " << x << std::endl;  
        std::cout << "y by reference: " << y << std::endl;  
        y = 30; // 可以修改y的值,因为它是按引用捕获的  
    };  
  
    lambda(); // 输出x和y的当前值  
    std::cout << "y after lambda: " << y << std::endl; // 输出修改后的y的值  
}  
  
int main() {  
    example();  
    return 0;  
}
x by value: 10  
y by reference: 20  
y after lambda: 30

 但是,在这个例子中,[=] 是多余的,因为我们已经显式地捕获了x(按值)和y(按引用)。在实际编写代码时,我们通常不会同时使用[=]和显式捕获,除非有特定的原因需要这样做。 

当我们混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个&或=。此符
号指定了默认捕获方式为引用或值。

当混合使用隐式捕获和显式捕获时,显式捕获的变量必须使用与隐式捕获不同的方式。

即,如果隐式捕获是引用方式(使用了&),则显式捕获命名变量必须采用值方式,因此不能在其名字前使用&。

类似的,如果隐式捕获采用的是值方式(使用了=),则显式捕获命名变量必须采用引用方式,即,在名字前使用&。

可变 lambda

默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。

如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。

因此,可变lambda能省略参数列表:

void fcn3()
{
size_t vl= 42;//局部变量
    //f可以改变它所捕获的变量的值
auto f = [vl] () mutable { return ++vl; };

v1 =0;
auto j =f();//j为43

一个引用捕获的变量是否(如往常一样)可以修改依赖于此引用指向的是一个const类型还是一个非const类型

void fcn4()
{
size_t v1=42;
//局部变量

//v1是一个非const变量的引用
//可以通过f2中的引用来改变它
auto f2 = [&vl] { return ++v1;}.

v1 = 0;
auto j=f2();//j为1
}

指定 lambda 返回类型

到目前为止,我们所编写的lambda 都只包含单一的return语句。

因此,我们还未遇到必须指定返回类型的情况。

默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。

与其他返回void的函数类似,被推断返回void的lambda不能返回值。

下面给出了一个简单的例子,我们可以使用标准库transform 算法和一个 lambda来将一个序列中的每个负数替换为其绝对值:

transform(vi.begin(),vi.end(),vi.begin(),
                [](int i) { return i <0?-i : i;});

函数transform 接受三个迭代器和一个可调用对象。前两个迭代器表示输入序列,第三个迭代器表示目的位置。

算法对输入序列中每个元素调用可调用对象,并将结果写到目的位置。

如本例所示,目的位置迭代器与表示输入序列开始位置的迭代器可以是相同的。

当输入迭代器和目的迭代器相同时,transform 将输入序列中每个元素替换为可调用对象操作该元素得到的结果。

在本例中,我们传递给transform一个 lambda,它返回其参数的绝对值。lambda体是单一的return语句,返回一个条件表达式的结果。

我们无须指定返回类型,因为可以根据条件运算符的类型推断出来。

但是,如果我们将程序改写为看起来是等价的if语句,就会产生编译错误:

// 错误:不能推断lambda的返回类型
transform(vi.begin(), vi.end(),vi.begin(),
            [](int i) { if (i <0) return -i; else return i; });

默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。 

所以编译器推断这个版本的lambda 返回类型为void,但它返回了一个int值。

当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型:

transform(vi.begin(), vi.end(), vi.begin(),
        [](int i)->int{ if (i < 0) return -i; else return i;});

在此例中,传递给transform的第四个参数是一个lambda,它的捕获列表是空的,接受单一int参数,返回一个int 值。它的函数体是一个返回其参数的绝对值的if语句。
 

参数绑定

对于那种只在一两个地方使用的简单操作,lambda表达式是最有用的。

如果我们需要在很多地方使用相同的操作,通常应该定义一个函数,而不是多次编写相同的lambda 表达式。

类似的,如果一个操作需要很多语句才能完成,通常使用函数更好。

如果lambda 的捕获列表为空,通常可以用函数来代替它。

当lambda的捕获列表为空时,它实际上并没有捕获任何外部作用域中的变量。这意味着lambda只能访问其内部定义的变量或者全局变量。在这种情况下,lambda的用途与普通的函数相似,因为它不依赖于外部状态。

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
// 使用lambda的例子,捕获列表为空  
void processVectorWithLambda() {  
    std::vector<int> vec = {1, 2, 3, 4, 5};  
  
    // 捕获列表为空,因为lambda不依赖于外部变量  
    std::for_each(vec.begin(), vec.end(), [](int num) {  
        std::cout << num << ' ';  
    });  
    std::cout << std::endl;  
}  
  
// 使用普通函数的例子  
void printNumber(int num) {  
    std::cout << num << ' ';  
}  
  
void processVectorWithFunction() {  
    std::vector<int> vec = {1, 2, 3, 4, 5};  
  
    // 使用普通函数代替lambda  
    std::for_each(vec.begin(), vec.end(), printNumber);  
    std::cout << std::endl;  
}  
  
int main() {  
    processVectorWithLambda(); // 输出: 1 2 3 4 5  
    processVectorWithFunction(); // 输出: 1 2 3 4 5  
    return 0;  
}

但是,当lambda捕获了局部变量时,使用普通函数来替换它就变得不那么容易了因为普通函数无法直接访问lambda所在作用域中的局部变量。Lambda表达式的一个关键特性就是它们能够捕获并封闭其定义环境中的变量,这些变量在lambda被调用时仍然可用,即使原始作用域已经退出。

#include <iostream>  
  
void withLambda() {  
    int localVariable = 42;  
  
    // Lambda捕获了局部变量localVariable  
    auto lambda = [localVariable]() {  
        std::cout << "Lambda captured localVariable: " << localVariable << std::endl;  
    };  
  
    // 在原始作用域外部调用lambda  
    lambda();  
}  
  
// 尝试用普通函数模拟捕获局部变量的lambda  
// 但这不可能,因为普通函数不能直接访问外部局部变量  
// 下面的代码只是为了说明普通函数无法直接替代捕获局部变量的lambda  
void printCapturedValue(/* 无法直接传递局部变量 */) {  
    // 假设这里能访问到localVariable,但这是不可能的  
    // std::cout << "Impossible to access localVariable from a regular function" << std::endl;  
}  
  
// 一个可能的替代方案是使用函数对象(或称为仿函数)  
// 但这仍然需要显式地传递局部变量的副本或引用  
struct FunctionObject {  
    int value;  
    FunctionObject(int v) : value(v) {}  
    void operator()() const {  
        std::cout << "FunctionObject holds a copy of the value: " << value << std::endl;  
    }  
};  
  
void withFunctionObject() {  
    int localVariable = 42;  
    FunctionObject obj(localVariable); // 显式传递localVariable的副本  
    obj(); // 调用函数对象  
}  
  
int main() {  
    withLambda(); // 输出: Lambda captured localVariable: 42  
    withFunctionObject(); // 输出: FunctionObject holds a copy of the value: 42  
    return 0;  
}

在上面的代码中,withLambda函数定义了一个捕获局部变量的lambda表达式。

printCapturedValue函数试图模拟这个功能,但失败了,因为它无法直接访问外部作用域中的localVariable。 


网站公告

今日签到

点亮在社区的每一天
去签到