C++ lambda表达式权威指南

来源:互联网 发布:僵尸变脸软件下载 编辑:程序博客网 时间:2024/06/05 13:16

 最近经常看到lambda表达式这几个字,有的人说它使得C++可以写出更加简单易懂的代码,也有人说它是语法糖,我就不纠结这个问题了,一个国外技术大师写了一篇文章来描述的,我就果断翻译了下(哥英语各种无语,四级3次才过,6级一直无缘,果断使用百度翻译加上自己的理解翻译的,不要吐槽哥,谢谢!),如果有些地方写得比较模糊,可以参考原文:http://www.cprogramming.com/c++11/c++11-lambda-closures.html

C++11标准中加入了lambda表达式特性,可能这个听起来很玄乎,其实就是很多语言里面的“闭包”而已。这是什么意思呢?lambda表达式可以在源代码中编写内联(通常传递到另一个函数,类似于函数指针和仿函数)。快速创造功能变得非常简洁大方,这不仅意味着你可以使用lambda表达式去除一些你以前必须单独命令功能的苦恼,也可以是你开始写一写依赖于创建快速和容易实现功能的能力。这篇文章中,我将首次解释为什么lambda是伟大的,其中的 一些例子,都是我测试使用学习的,你可以试着试一下,相信你会喜欢它。

想一下,如果你要实现一个地址薄的类,并且你希望能提供一个搜索功能。你可以提供一个简单的搜索函数,传一个字符串参数并返回一些列匹配这个字符串的地址薄。有时候你可能会想,我要搜索地址中含有某些字符的信息或者以什么开始的地址,或以什么结束的地址等。然我们来看看,如果提供这么一个通用的函数来搜索。

[cpp] view plaincopy
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <string>  
  4.   
  5. class AddressBook  
  6. {  
  7. public:  
  8.     template <typename Func>  
  9.     std::vector<std::string> findMatchAddresses(Func func)  
  10.     {  
  11.         std::vector<std::string> results;  
  12.         for (auto iter = _addresses.begin(), end = _addresses.end(); iter != end; ++iter)  
  13.         {  
  14.             if (func(*iter))  
  15.             {  
  16.                 results.push_back(*iter);  
  17.             }  
  18.         }  
  19.         return results;  
  20.     }  
  21.   
  22. private:  
  23.     std::vector<std::string> _addresses;  
  24. };  

任何人都可以通过这个函数来实现自己想的逻辑来完成查询操作,如果函数返回不为空,表示查询出来结果了,在早期的C++中,只能在其他地方定义一个函数,它不太方便的创建一个函数,这就是lambda表达式引进来的一个原因

基本lambda表达式语法

在你使用lambda表达式解决问题之前,先来看看一个最基本的lambda表达式使用方法。

[cpp] view plaincopy
  1. #include <iostream>  
  2. int main(int argc, char* argv[])  
  3. {  
  4.     auto func = [] () { std::cout << "hello world"; };  
  5.     func();  
  6.     return 0;  
  7. }  

好吧,你已经发现了lambda表达式,从[]开始,这个符号成为捕获规范它告诉我们正在创建一个lambda函数编译,你会发现每一个lamda表达式都有这样的开头表示(有些会稍有不同,后面会介绍到)。接下来,像其他任何函数一样,需要一个参数列表,哪里是返回值的描述呢?事实证明我们不需要,因为在C++11标准中,编译器可以推断lambda函数的返回值,它会做而不是强迫你加上它。下一行func()中,我们称之为lambda函数,它看起来就像调用其他任何函数一样,顺便说一下,这样来,你就不需要定义一个函数,然后用一个函数指针指向它。

举个例子,使用我们开始写的那个地址薄类

让我们看看我们如何应用于我们的地址薄类,首先创建一个简单的函数,找到电子邮件中包含.org的所有地址薄信息。

[cpp] view plaincopy
  1. AddressBook global_address_book;  
  2. std::vector<std::string> findMatchAddresses()  
  3. {  
  4.     return global_address_book.findMatchAddresses(   
  5.         [] (const std::string& addr) { return addr.find( ".org" ) != std::string::npos; }   
  6.     );  
  7. }  

我们再次看到里面使用的lambda表达式,从[]开始,这次里面有一个参数addr,我们测试其中是否包含.org,换句话说,遍历所有地址信息过程中,每次循环都会通过findMatchAddresses,返回所有满足条件的地址信息。

lambda变量捕获

虽然这样使用lambda表达式很cool,但是lambda表达式真正强大的秘密在于变量捕获,假定你要用上面的地址薄类来查找包含特定名称的地址,用下面这个代码区实现,是不是看起来更加nice!

[cpp] view plaincopy
  1. // read in the name from a user, which we want to search  
  2. string name;  
  3. cin>> name;  
  4. return global_address_book.findMatchingAddresses(   
  5.     // notice that the lambda function uses the the variable 'name'  
  6.     [&] (const string& addr) { return addr.find( name ) != string::npos; }   
  7. );  
事实证明,这样是完全合法的,这表明我们能够以一个在lambda之外声明一个变量,然后在lambda里面直接使用,在lambda函数在执行的时候,这些变量已经获取,当然这些需要告诉编译器,比如[]表示不会获取任何变量,而[&]告诉编译器执行变量捕获。是不是太神奇呢?我们可以创建一个lambda表达式实现逻辑,其中逻辑中用到的变量采取lambda变量捕获,这样写看起来是不是更加简洁,仅需要几行代码即可完成。在C++11标准中,我们可以有一个简单的接口地址薄,可以支持任何一种过滤,而且实现起来十分简单。只是为了好玩,让我们来实现这样一个功能:找到唯一的地址邮件地址是不是一定数目的字符长。
[cpp] view plaincopy
  1. int min_len = 0;  
  2. cin >> min_len;  
  3. return global_address_book.find( [&] (const string& addr) { return addr.length() >= min_len; } );  

lambda表达式和STL

毫无疑问,一个lambda表达式的最大受益者是对标准库算法封装奠定基础。以前,使用类似于for_each是不可想象的,只能模拟,现在你可以使用for_each等STL算法几乎一样,如果你写一个正常的循环。比较如下: 

[cpp] view plaincopy
  1. vector<int> v;  
  2. v.push_back( 1 );  
  3. v.push_back( 2 );  
  4. //...  
  5. for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ )  
  6. {  
  7.     cout << *itr;  
  8. }  
[cpp] view plaincopy
  1. vector<int> v;  
  2. v.push_back( 1 );  
  3. v.push_back( 2 );  
  4. //...  
  5. for_each( v.begin(), v.end(), [] (int val)  
  6. {  
  7.     cout << val;  
  8. } );  

这是一段很cool的代码,如果你问我:它读取是结构话的,像一个正常的循环,但是你突然能够利用for_each提供了一个普通的for循环,例如保证你有权终止条件,现在你可能想,这样写会不会印象性能?嗯,重点就在这里,for_each拥有相同的性能,有时甚至比for循环更快。我希望你可以从这个例子看到lambda表达式和STL在一起的衣衣,lambda表达式不仅仅是一个更简单创建函数的方式,他们允许你以一种新的方式组织程序,在你的代码需要其他功能的时候,允许你抽象出来一个特定数据结构的处理逻辑。

新的lambda表达式

使用lambda表达式,参数列表,返回值等也是可选的,如果你想要一个函数,无参数,病无返回值,甚至都可以不添加逻辑,虽然是没有作用的,但是合法的,最短的lambda表达式:

[cpp] view plaincopy
  1. [] {}  
换一个更加令人折服的例子:
[cpp] view plaincopy
  1. using namespace std;  
  2. #include <iostream>  
  3.   
  4. int main()  
  5. {  
  6.     [] { cout << "Hello, my Greek friends"; }();  
  7. }  

 就我个人而已,我不缺省参数列表,我认为[]()结构趋向于表明一个语义,使得lambda表达式更加nice,这个就不多说,标准会衡量。

返回值

默认情况下,lambda表达式不写返回语句,它缺省值为空,如果你有一个简单的返回表达式,编译器也会推断返回值的类型。

[cpp] view plaincopy
  1. [] () { return 1; } // compiler knows this returns an integer  
如果你写一个更复杂的lambda函数,与一个以上的返回值,你必须指定返回类型(一些编译器,如GCC,会让你摆脱这样做,即使你有一个以上的返回语句,单标准并不能保证它)。
[cpp] view plaincopy
  1. [] () -> int { return 1; } // now we're telling the compiler what we want  

lambda表达式的闭包实现

lambda表达式如何像魔术一样的真正工作呢?原来,它是通过创建一个小类,这个类重载operator,所以它的行为就像一个函数。lambda函数式这类的一个实例,当类的构造时,周围环境中的变量传递到lambda函数的构造函数中,并保存为成员变量。C++最敏感的就是性能,实际上给你大量的灵活性来实现变量的捕获,以至于所有控制通过捕获规范。你已经看到了两个案例没有用[]。其实这里的捕获标准很多,如下就是完整的列表:

[] 不捕获

[&] 以引用的方式捕获

[=] 通过变量的一个拷贝捕获

[=, &foo] 通过变量的拷贝捕获,但是用foo变量的引用捕获

[bar] 通过复制捕获,不要复制其他

[this] 捕获this指针对应成员

注意最后的一个,你不需要包括它,但是实际上,你可以捕获这个指针的成员,你不需要显示地使用这个指针,看起是是不是很nice!

[cpp] view plaincopy
  1. class Foo  
  2. {  
  3. public:  
  4.     Foo () : _x( 3 ) {}  
  5.     void func ()  
  6.     {  
  7.         // a very silly, but illustrative way of printing out the value of _x  
  8.         [this] () { cout << _x; } ();  
  9.     }  
  10.   
  11. private:  
  12.         int _x;  
  13. };  
  14.   
  15. int main()  
  16. {  
  17.     Foo f;  
  18.     f.func();  
  19. }  

lambda表达式的应用方面

我们已经看到,使用模板以lambda作为参数,并自动保持在lambda函数作为一个局部变量,lambda函数式通过创建一个单独的类来实现的,正如你之前看到的,即使是单一的lambda函数,都是不同的类型,及时这两个函数具有相同的参数和返回值!但是C++11标准中,包括了存储任何类型的功能,即lambda函数,可以方便包装,或函数指针、std::function。

std::function

它是一个将lambda表达式作为参数非常优雅的一种方式,不仅包括传递参数的类型,也包括返回值。它允许你定义一个新的类型,继续给出开始使用的例子,不过这次用std::function作为参数。

[cpp] view plaincopy
  1. #include <functional>  
  2. #include <vector>  
  3.   
  4. class AddressBook  
  5. {  
  6. public:  
  7.     std::vector<string> findMatchingAddresses (std::function<bool (const string&)> func)  
  8.     {   
  9.         std::vector<string> results;  
  10.         for ( auto itr = _addresses.begin(), end = _addresses.end(); itr != end; ++itr )  
  11.         {  
  12.             // call the function passed into findMatchingAddresses and see if it matches  
  13.             if ( func( *itr ) )  
  14.             {  
  15.                 results.push_back( *itr );  
  16.             }  
  17.         }  
  18.         return results;  
  19.     }  
  20.   
  21. private:  
  22.     std::vector<string> _addresses;  
  23. };  
这样最大的一个优点是比较通用和灵活,使用时,如下即可。
[cpp] view plaincopy
  1. std::function<int ()> func;  
  2. // check if we have a function (we don't since we didn't provide one)  
  3. if ( func )   
  4. {  
  5.     // if we did have a function, call it  
  6.     func();  
  7. }  
关于函数指针

在最终的C++11标准制定上,如果你使用一个lambda表达式,并且没有捕获任何变量,那么它可以像一个普通的函数来处理和分配函数指针,如下所示:

[cpp] view plaincopy
  1. typedef int (*func)();  
  2. func f = [] () -> int { return 2; };  
  3. f();  

这样做,只是为了使得lambda表达式可以定位到一般函数层次上,使其在某些情况下,可以与函数指针共同使用。


使用lambda实现委托
从下面代码就可以看出来了:
[cpp] view plaincopy
  1. #include <functional>  
  2. #include <string>  
  3.   
  4. class EmailProcessor  
  5. {  
  6. public:  
  7.     void receiveMessage (const std::string& message)  
  8.     {  
  9.         if ( _handler_func )   
  10.         {  
  11.             _handler_func( message );  
  12.         }  
  13.         // other processing  
  14.     }  
  15.     void setHandlerFunc (std::function<void (const std::string&)> handler_func)  
  16.     {  
  17.         _handler_func = handler_func;  
  18.     }  
  19.   
  20. private:  
  21.         std::function<void (const std::string&)> _handler_func;  
  22. };  
这是一个非常标准的写法,允许类注册一个回调函数,但是我们需要另一个类,负责跟踪哪一个最最久的,无论如何,我们可以创建下面一个小类:
[cpp] view plaincopy
  1. #include <string>  
  2.   
  3. class MessageSizeStore  
  4. {  
  5.     MessageSizeStore () : _max_size( 0 ) {}  
  6.     void checkMessage (const std::string& message )   
  7.     {  
  8.         const int size = message.length();  
  9.         if ( size > _max_size )  
  10.         {  
  11.             _max_size = size;  
  12.         }  
  13.     }  
  14.     int getSize ()  
  15.     {  
  16.         return _max_size;  
  17.     }  
  18.   
  19. private:  
  20.     int _max_size;  
  21. };  
如果我们希望checkMessage在你接收消息的时候执行,那么就可以使用下面的代码,将其注册为回调函数(委托),如果我们这样写代码:
[cpp] view plaincopy
  1. EmailProcessor processor;  
  2. MessageSizeStore size_store;  
  3. processor.setHandlerFunc( checkMessage ); // this won't work  
它并不会向我们所设想的那么工作,我们需要这么实现:
[cpp] view plaincopy
  1. EmailProcessor processor;  
  2. MessageSizeStore size_store;  
  3. processor.setHandlerFunc(   
  4.         [&] (const std::string& message) { size_store.checkMessage( message ); }   
  5. );  

是不是很酷?我们使用lambda来实现委托时一种享受,希望你会喜欢,如果不是特别清楚,可以测试一下。

总结

             现在,我在开发一些具体项目的时候就经常用到lambda函数,他们开始出现在我的艺术品之中,有些情况下是为了缩短代码使其简单优雅,有的是为了单元测试的一些情况,在某些情况下,可以用它取代之前用宏实现的功能。所以我觉得lambda表达式功能是强大的,在gcc4.5之后也支持lambda表达式,msvc10和11版本的编译器也支持。

http://blog.csdn.net/u010732473/article/details/9019759

0 0
原创粉丝点击