牛刀小试5

lambda表达式需要捕获引用时怎么写?需要捕获引用或值时怎么写?

  • 什么是lambda表达式

  • 百度百科:
          Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(闭包: lambda表达式也叫闭包。闭就是封闭的意思(封闭就是其他地方都不调用它),包就是函数。
    lambda表达式 其实就是一个函数对象,他内部创建了一个重载()操作符的类。).

  • 个人理解:
            lambda表达式 就是一个函数(匿名函数),也就是一个没有函数名的函数。为什么不需要函数名呢,因为我们直接(一次性的)用它,嵌入式用的它,不需要其他地方调用它。 lambda表达式 其实就是一个函数对象,他内部创建了一个重载()操作符的类。

  • Lambda表达式的使用

  • Lambda表达式完整语法如下:

[capture list] (params list) mutable exception-> return type { function body }    

各项参数解释如下:

1.[capture]:捕捉列表。捕捉列表总是出现在Lambda函数的开始处。实际上,
[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数。
捕捉列表能够捕捉上下文中的变量以供Lambda函数使用;

2.(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,
则可以连同括号“()”一起省略;

3.mutable:mutable修饰符。默认情况下,Lambda函数总是一个const函数,
mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);

4.0  exception:异常设定;

4.1 ->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。
我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,
在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;

5.{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,
还可以使用所有捕获的变量。

与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过
捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以
被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。
语法上,在“[]”包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。
捕捉列表有以下几种形式:

1.[var]表示值传递方式捕捉变量var;
2.[=]表示值传递方式捕捉所有父作用域的变量(包括this);
3.[&var]表示引用传递捕捉变量var;
4.[&]表示引用传递方式捕捉所有父作用域的变量(包括this);
5.[this]表示值传递方式捕捉当前的this指针。

上面提到了一个父作用域,也就是包含Lambda函数的语句块,
说通俗点就是包含Lambda的“{}”代码块。上面的捕捉列表还可以进行组合,例如:

1.[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;
2.[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。

不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,
会导致编译时期的错误。例如:

3.[=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;
4.[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。

此外,我们还可以省略其中的某些成分来声明“不完整”的Lambda表达式,常见的有以下几种:

序号格式含义
1[capture list] (params list) -> return type {function body}省略mutable,默认表达式为const类型,这种类型的表达式不能修改捕获列表中的值
2[capture list] (params list) {function body}省略了返回值类型,但编译器可以根据以下规则推断出Lambda表达式的返回类型:
(1):如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定;
(2):如果function body中没有return语句,则返回值为void类型
1[capture list] {function body}省略了参数列表,类似普通函数中的无参函数。
  • Lambda表达式的实例

  • 1、最简单的例子

    #include <iostream>
    using namespace std;
    int main()
    {
        auto func = [] () { cout << "Hello world"; };
        func(); 
    }
    

           上面的 lambda 表达式 func 没有传入任何参数,也没有返回值,甚至我们可以对其简写成:auto func = [] { cout << “Hello world”; } 。并且配合 C++11标准加入的 auto 自动类型判断,省去了以前定义函数指针冗杂繁琐的过程,程序看上去如何优雅、简洁。

  • 2、更加深入的示范:

    class AddressBook
    {
    public:
        template<typename Func>
        std::vector<std::string> findMatchingAddresses (Func func)
        { 
            std::vector<std::string> results;
            for ( auto itr = _addresses.begin(), end = _addresses.end(); itr != end; ++itr )
            {    
                if ( func( *itr ) )
                {
                    results.push_back( *itr );
                }
            }
            return results;
        }
    private:
        std::vector<std::string> _addresses;
    };
    /*类 AddressBook 封装了 findMatchingAddresses 函数,返回满足我们需要的书目,下面我们看看 lambda 表达式如何实现这一过程:*/
    AddressBook global_address_book;
    vector<string> findAddressesFromOrgs ()
    {
    		return global_address_book.findMatchingAddresses(
    		[] (const string& addr) { return addr.find( ".org" ) != string::npos; }
    		);
    }
    /*上面函数返回满足地址中带有 ".org" 字样的书籍条目,lambda 表达式虽然没有定义返回类型,但是编译器可以根据我们的 return 语句自动判断返回值是 boolean 类型。我们的 lambda 表达式中 [] 并没有 capture 任何变量,再下面的例子中将展示 [&]:*/
    string name;
    cin >> name;
    return global_address_book.findMatchingAddresses(
    		[&] (const string& addr) { return addr.find( name ) != string::npos; }
    );
    

      再次注意到,类 global_address_book 竟然能够访问到我们定义的局部变量 name 字符串,这正是 lambda 表达式的强大之处,[&] 代表 lambda body 中用到的变量都以“reference”的方式使用,还有更多的 capture 用法这里就不再叙述,有兴趣进一步了解的同学可以自行搜索。

  • 3、Lambda 表达式使 STL 更加强大:
传统的情况下,我们会用下面的方式去访问容器里面的数据:

vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ )
{
    cout << *itr;
}
但是当我们有了 Lambda 之后,利用 STL 里面的 for_each ,将会变成下面的代码:

vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for_each( v.begin(), v.end(), [] (int val)
{
    cout << val;
});

你可能会想,上面的 for_each 循环,会不会使我们的程序有性能上的损耗?答案是否定的:for_each 的效率和迭代的效率是一致的,甚至加上 Lambda 之后,for_each 会利用 “loop unrolling” 机制使程序运行的更快。

Lambda 的引入给我们带来了一种全新的编程体验,它可以让我们把 “function” 当做是 “data” 一样传递,并且使我们从繁琐的语法中解放出来,更加关注于 “算法” 本身。我们也称 Lambda 为 Closure(闭包),顾名思义,这使我们的函数变得更加私有,所以限制了别人的访问,同时我们也可以更加方便的编程。

  • 4、Lambda 与 资源管理:
前面在我的 「理解智能指针」一文中提到,智能指针可以利用 C++RAII(Resource acquisition is initialization) 特性,在类型(class)的析构函数时来完成自动释放指针所指向对象的目的。同样,在 Lambda 中,又把 RAII 这一特性体现的淋漓尽致:

class ScopeGuard
{
public:
    explicit ScopeGuard(std::function<void()> onExitScope)
        : onExitScope_(onExitScope)
    { }
    ~ScopeGuard()
    {
            onExitScope_();
    }
private:
    std::function<void()> onExitScope_;
private: // noncopyable
    ScopeGuard(ScopeGuard const&);
    ScopeGuard& operator=(ScopeGuard const&);
};
int main() {
    HANDLE h = CreateFile(...);
    ScopeGuard onExit([&] { CloseHandle(h); });
    ...
    return 0;
}
  • 5、Lambda 到底是什么类型:
auto func = [] () { cout << "hello world"; };
std::function<void ()> func = [] () { cout << "hello world"; };

auto func = [] (int val) { cout << val; return false; };
std::function<bool (int)> func = [] (int val) { cout << val; return false; };
 上面的上下 2 行代码效果是等效的,看到这里是否有种似曾相识的感觉?那 Lambda 和 我们定义的函数指针有什么区别呢:

typedef int (*func)();
func f = [] () -> int { return 2; };
f();
没错,这段代码是可以正常运行的,因为 Lambda 表达式中并没有 capture 任何本地变量,因此会被编译成普通的函数指针。最后采用 coolshell 里面 Lambda 的 2 点总结: 1)可以定义匿名函数,2)编译器会把其转成函数对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值