C++lambda表达式

在介绍lambda表达式之前,先来看一下什么是谓词。下面是一个例子

void elimDups(vector<string> &words)
{
         sort(words.begin(),words.end()); //按字典序排序words,以便查找重复单词
         //排列在范围的前部,返回指向不重复区域之后一个位置的迭代器
         auto end_unique=unique(words.begin(),words.end());//unique重排输入范围,使得每个单词只出现一次
         words.erase(end_unique,words.end());//使用向量操作erase删除重复单词
}

       假定希望在调用elimDups后打印vector的内容,此外还假定希望单词按其长度排序,大小相同的再按字典序排列。为了按长度重排vector,我们将使用sort的第二个版本,此版本是重载过的,它接受第三个参数,此参数是一个谓词。

谓词

      谓词是一个可调用的表达式,其返回结果是一个能用做条件的值。标准库算法所使用的谓词分为两类:一元谓词(只接受单一参数)和二元谓词(它们有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换为谓词的参数类型。

下面来看调用谓词的例子:

//比较函数,用来按长度排序单词
bool isShorter(const string &s1,const string &s2)
{
         return s1.size()<s2.size();
}
sort(words.begin(),words.end(),isShorter);//按长度由短至长排序words


lambda表达式

      根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须严格接受一个或两个参数。但是,有时我们希望进行的操作需要更多的参数,超出了算法对谓词的限制。下面看一个相关的例子:

void biggies(vector<string>&words,vector<string>::size_type sz)
{
         elimDups(words);//将words按字典排序,删除重复单词
         stable_sort(words.begin(),words.end(),isShorter);//按长度排序,长度相同的单词维持字典顺序
        //获取一个迭代器,指向第一个满足size()>=sz的元素
        //计算满足size>=sz的元素的数目
        //打印长度大于等于给定值的单词,每个单词后面接一个空格
}

       问题是在vector中寻找第一个大于等于给定长度的元素。一旦找到了这个元素,根据其位置,就可以计算出多少元素的长度大于等于给定值。
       可以使用标准库find_if算法来查找第一个具有特定大小的元素。编写一个函数,令其接受一个string和一个长度,并返回一个bool值表示该string的长度是否大于给定长度是一件容易的事情。但是,find_if接受一元谓词,我们传递给find_if的任何函数都必须严格接受一个参数,以便能用来自输入序列的一个元素调用它。没有任何办法能传递给它第二个参数来表示长度,因此考虑lambda表达式。

 

介绍lambda表达式

       可以向一个算法传递任何类别的可调用对象,可调用对象有四种:函数、函数指针、重载了函数调用运算符的类以及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};
cout<<f()<<endl;  //打印42

      此例中,我们定义了一个可调用对象f,它不接受参数,返回42。lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符。
      在lambda中忽略括号和参数列表等价于指定一个空参数列表。在此例中,当调用f时,参数列表是空的。如果忽略返回类型,lambda根据函数体重的代码推断出返回类型。如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断出来。否则。返回类型为void。

 

向lambda传递参数

       与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。但与普通函数不同的是lambda不能有默认参数。来看下面一个例子:

[](const string &a,const string &b){return a.size()<b.size();}

      空捕获列表表面此lambda不使用它所在函数中的任何局部变量。lambda的参数与isShorter的参数类似,是const string的引用。lambda的函数体也与isShorter类似,比较其两个参数的size(),并根据两者的大小返回一个bool值。
如下所示,可以使用此lambda来调用stable_sort:

stable_sort(words.begin(),words.end(),[](const string &a,const string &b){return a.size()<b.size();});

当stable_sort需要比较两个元素时,它就会调用给定的这个lambda表达式。

 

使用捕获列表

       虽然一个lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指明的变量。一个lambda通过将局部变量包含在其捕获列表中来指出将会使用这些变量。捕获列表指引lambda在其内部包含访问局部变量所需信息。
在本例中,我们的lambda表达式会捕获sz,并只有单一的string参数。其函数体会将string的大小与捕获的sz的值进行比较:

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

       lambda以一对[]开始,我们可以在其中提供一个以逗号分隔的名字列表,这些名字都是它所在的函数中定义的。
由于此lambda捕获sz,因此lambda的函数体可以使用sz。lambda不捕获words,因此不能访问此变量。如果我们给lambda提供一个空捕获列表,则代码会编译错误:

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

来看下面几个例子:

例1  

auto wc=find_if(words.begin(),words.end(),[sz](const string &a){return a.size()>=sz;});
auto count=words.end()-wc;

        这里对find_if的调用返回一个迭代器,指向第一个长度不小于给定参数sz的元素。如果这样的元素不存在,则返回words.end()的一个拷贝。还可以使用find_if返回的迭代器来计算它开始到words的末尾一共有多少个元素。

例2

//打印长度大于等于给定值的单词,每个单词后面接一个空格
    for_each(wc,words.end(),[](const string &s){cout<<s<<" ";})
    cout<<endl;

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

 

lambda捕获和返回

       当定义一个lambda时,编译器生成一个与lambda对应的新的类类型。当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名对象。类似的,当用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。
       默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。类似任何普通类的数据成员,lambda的数据成员也在lambda对象创建时被初始化。

 

值捕获

       类似参数传递,变量的捕获方式也可以是值或引用。与传值参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值在lambda创建时拷贝,而不是调用时拷贝:

void fcn1()
{
        size_t v1=42;//局部变量
        auto f=[v1]{return v1;};//将v1拷贝到名为f的可调用对象
        v1=0;
        auto j=f();//j为42;f保存了我们创建它时v1的拷贝
}

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

 

引用捕获

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

void fcn2()
{
        size_t v1=42;//局部变量
        auto f2=[&v1]{return v1;};//对象f2包含v1的引用
        v1=0;
        auto j=f2(); //j为0;f2保存v1的引用,而非拷贝
}

        v1之前的&指出v1应该以引用方式捕获。一个引用方式捕获的变量与其它任何类型的引用行为类似。当我们在lambda函数体内使用此变量时,实际上使用的是引用所绑定的对象。在本例中,当lambda返回v1时,它返回的的v1指向的对象的值。
       引用捕获与返回引用有着相同的问题和限制。如果采用引用方式捕获一个变量,就必须确保被应用的对象在lambda执行的时候是存在的。lambda捕获的是局部变量,这些变量在函数结束后就不复存在了。如果lambda可能在函数结束后执行,捕获的引用指向的局部变量已消失。
       引用捕获有时是必要的。例如,我们可能希望biggies函数接受一个ostream的引用,用来输出数据,并接受一个字符作为分隔符:

void biggies(vector<string>&words,vector<string>::size_type sz,ostream &os=cout,char c=' ')
{
        //打印cout的语句改为打印到os
        for_each(words.begin(),words.end(),[&os,c](const string &s){os<<s<<c;});
}

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

 

隐式捕获

       为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。例如:

wc=find_if(words.begin(),words.end(),[=](const string &s){return s.size()>=sz;});

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

void biggies(vector<string>&words,vector<string>::size_type sz,ostream &os=cout,char c=' ')
{
         for_each(words.begin(),words.end(),[&,c](const string &s){os<<s<<c;});
         for_each(words.begin(),words.end(),[=,&os](const string &s){os<<s<<c;});
}

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

 

可变lambda

      默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表:

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

一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型:

void fcn4()
{
         size_t v1=42;
         auto f2=[&v1]{return ++v1;};
         v1=0;
         auto j=f2();//j为1
}


指定lambda返回类型
       默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。与其他返回void的函数类似,被推断返回void的lambda不能返回值。
例如:

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

       本例中,我们传递给transform一个lambda,它返回其参数的绝对值。lambda体是单一的return语句,返回一个表达式的结果。我们无需指定返回类型,因为可以根据条件运算符的类型推断出来。
       但是,如果我们将程序改写为看起来是等价的if语句,就会产生编译错误:

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

 编译器推断这个版本的lambda返回类型为void,但它返回了一个int值。
        当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型:  

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

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

### C++ Lambda 表达式简介 C++11 引入了 lambda 表达式这一特性,使得开发者可以在需要函数的地方直接定义匿名函数对象,而无需单独声明函数。这种机制不仅简化了代码结构,还增强了程序的灵活性[^4]。 #### 基本语法 Lambda 表达式的通用形式如下: ```cpp [capture](parameters) -> return_type { body } ``` - **`capture`**: 定义如何捕获外部作用域中的变量(按值、按引用或其他方式)。 - **`parameters`**: 函数参数列表,类似于普通函数。 - **`return_type`**: 可选部分,默认情况下编译器会推导返回类型。 - **`body`**: 函数体,包含具体的逻辑实现。 --- ### 示例解析 以下是几个典型的 lambda 表达式使用场景及其对应的代码示例。 #### 1. 打印容器中的元素 通过 `std::for_each` 配合 lambda 表达式来遍历并打印向量中的数据。 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> vec = {1, 2, 3, 4, 5}; // 使用 Lambda 表达式打印每个元素 for_each(vec.begin(), vec.end(), [](int x) { cout << x << " "; }); cout << endl; return 0; } ``` 此代码展示了如何利用 lambda 表达式作为回调函数传递给标准库算法 `std::for_each`[^1]。 --- #### 2. 捕获局部变量 Lambda 表达式可以通过 `[ ]` 来捕获上下文中的变量。支持的方式有多种:按值 (`=`),按引用 (`&`) 或显式指定单个变量。 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int factor = 2; vector<int> vec = {1, 2, 3, 4, 5}; // 按值捕获变量 'factor' transform(vec.begin(), vec.end(), vec.begin(), [factor](int x) { return x * factor; }); for (auto num : vec) { cout << num << " "; } cout << endl; return 0; } ``` 在此示例中,lambda 表达式捕获了名为 `factor` 的局部变量,并将其用于计算新数组的值[^2]。 --- #### 3. 返回复杂类型的 lambda 表达式lambda 表达式的返回值较为复杂时,可以显式指定其返回类型。 ```cpp #include <functional> #include <iostream> using namespace std; int main() { auto adder = [] (int a, int b) -> int { return a + b; }; cout << adder(3, 4) << endl; // 输出 7 return 0; } ``` 上述代码显示了一个简单的加法操作,其中指定了返回类型为整数型。 --- ### 总结 Lambda 表达式极大地提高了 C++ 编程的便利性和可读性,尤其是在处理 STL 算法或者 GUI 应用开发时尤为有用。它们能够减少不必要的辅助函数定义,使代码更加紧凑高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值