C++ 11 Lambda表达式、auto、function、bind、final、override
接触了cocos2dx 3.0,就必须得看C++ 11了。有分享过帖子:【转帖】漫话C++0x(四) —- function, bind和lambda。其实最后的Lambda没太怎么看懂。
看不懂没关系,会用就行。可惜是连用都要思考半天。其实,查找根源是定义没有搞明白。
以后买东西,用之前,先看说明书才是必要的。
---------------------------------开始正文粘贴-----------------------------------------
一、Lambda表达式
C++ 11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。Lambda的语法形式如下:
如上图所示Lambda分为以下几个部分:1、 [函数对象参数] ,2、(操作符重载函数参数), 3、mutable, 4、exception声明, 5、->返回值类型, 6、{函数体}。
当然也可以认为Lambda主要分为五个部分:[函数对象参数]、(操作符重载函数参数)、mutable或exception声明、->返回值类型、{函数体}。
下面分别进行介绍。
1、[函数对象参数],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
1、空。没有使用任何函数对象参数。
2、=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
3、&。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
4、this。函数体内可以使用Lambda所在类中的成员变量。
5、a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
6、&a。将a按引用进行传递。
7、a, &b。将a按值进行传递,b按引用进行传递。
8、=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
9、&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
2、(操作符重载函数参数),标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
3、mutable或exception声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)。
4、->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
5、{函数体},标识函数的实现,这部分不能省略,但函数体可以为空。
其实学习Lambda最好的资料还是MSDN,传送门:Lambda 表达式语法http://msdn.microsoft.com/zh-cn/library/dd293603.aspx
注意文中有一句:
Visual Studio 通过添加以下特性来增添 C++11 lambda 功能:
-
无状态 lambda 可通过任意调用约定完全转换为函数指针。
-
只要所有返回语句具有相同的类型,会自动推导比 { return expression; } 更复杂的 lambda 主体的返回类型。(这一特性现包含在拟建的 C++14 标准中。)
二、auto 关键字
C++ 11中引入的auto主要有两种用途:自动类型推断和返回值占位。
auto自动类型推断,用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推断,可以大大简化我们的编程工作。
因此,Lambda表达式和auto关键字合体之后,就可以非常方便的用类似如下的代码了:
auto a; // 错误,没有初始化表达式,无法推断出a的类型 auto int a = 10 // 错误,auto临时变量的语义在C++ 11中已不存在 auto a = 10 auto c = 'A' auto s("hello"); vector<int> vctTemp; auto it = vctTemp.begin(); auto ptr = [](){ cout << "hello world" << endl; };
1 template <typename T1, typename T2> 2 auto compose(T1 t1, T2 t2) -> decltype(t1 + t2) 3 { 4 return t1+t2; 5 } 6 auto v = compose(2, 3.14); // v's type is double
注意:auto并不能作为函数的返回类型,但是能用auto去代替函数的返回类型,当然,在这种情况下,函数必须有返回值才可以。auto不会告诉编译器去推断返回值的实际类型,它会通知编译器在函数的末段去寻找返回值类型。在上面的那个例子中,函数返回值的构成是由T1类型和T2类型的值,经过+操作符之后决定的。
关于 decltype
是一个操作符,其可以评估括号内表达式的类型,其规则如下:
- 如果表达式e是一个变量,那么就是这个变量的类型。
- 如果表达式e是一个函数,那么就是这个函数返回值的类型。
- 如果不符合1和2,如果e是左值,类型为T,那么decltype(e)是T&;如果是右值,则是T。
原文给出的示例如下,我们可以看到,这个让的确我们的定义变量省了很多事。
decltype(&myfunc) pfunc = 0; typedef decltype(&A::func1) type;
三、std::function
类模版 std::function是一种通用、多态的函数封装。std::function的实例可以对任何可以调用的目标进行存储、复制、和调用操作,这些目标包括函数、lambda表达式、绑定表达式、以及其它函数对象等。
用法示例:
①保存自由函数
1 void printA(int a) 2 { 3 cout<<a<<endl; 4 } 5 6 std::function<void(int a)> func; 7 func = printA; 8 func(2);
②保存lambda表达式
运行输出:hello world
③保存成员函数
1 struct Foo { 2 Foo(int num) : num_(num) {} 3 void print_add(int i) const { cout << num_+i << '\n'; } 4 int num_; 5 }; 6 7 // 保存成员函数 8 std::function<void(const Foo&, int)> f_add_display = &Foo::print_add; 9 Foo foo(2); 10 f_add_display(foo, 1);
运行输出: 3
四、bind
bind是一组用于函数绑定的模板。在对某个函数进行绑定时,可以指定部分参数或全部参数,也可以不指定任何参数,还可以调整各个参数间的顺序。对于未指定的参数,可以使用占位符_1、_2、_3来表示。_1表示绑定后的函数的第1个参数,_2表示绑定后的函数的第2个参数,其他依次类推。
下面通过程序例子了解一下用法:
1 #include <iostream> 2 using namespace std; 3 class A 4 { 5 public: 6 void fun_3(int k,int m) 7 { 8 cout<<k<<" "<<m<<endl; 9 } 10 }; 11 12 void fun(int x,int y,int z) 13 { 14 cout<<x<<" "<<y<<" "<<z<<endl; 15 } 16 17 void fun_2(int &a,int &b) 18 { 19 a++; 20 b++; 21 cout<<a<<" "<<b<<endl; 22 } 23 24 int main(int argc, const char * argv[]) 25 { 26 auto f1 = bind(fun,1,2,3); //表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3 27 f1(); //print:1 2 3 28 29 auto f2 = bind(fun, placeholders::_1,placeholders::_2,3); 30 //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别有调用 f2 的第一,二个参数指定 31 f2(1,2);//print:1 2 3 32 33 auto f3 = bind(fun,placeholders::_2,placeholders::_1,3); 34 //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别有调用 f3 的第二,一个参数指定 35 //注意: f2 和 f3 的区别。 36 f3(1,2);//print:2 1 3 37 38 39 int n = 2; 40 int m = 3; 41 42 auto f4 = bind(fun_2, n,placeholders::_1); 43 f4(m); //print:3 4 44 45 cout<<m<<endl;//print:4 说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的 46 cout<<n<<endl;//print:2 说明:bind对于预先绑定的函数参数是通过值传递的 47 48 49 A a; 50 auto f5 = bind(&A::fun_3, a,placeholders::_1,placeholders::_2); 51 f5(10,20);//print:10 20 52 53 std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2); 54 fc(10,20);//print:10 20 55 56 return 0; 57 }
五、nullptr -- 空指针标识
空指针标识(nullptr)(其本质是一个内定的常量)是一个表示空指针的标识,它不是一个整数。(译注:这里应该与我们常用的NULL宏相区别,虽然它们都是用来表示空置针,但NULL只是一个定义为常整数0的宏,而nullptr是C++0x的一个关键字,一个内建的标识符。下面我们还将看到nullptr与NULL之间更多的区别。)
1 char* p = nullptr; 2 int* q = nullptr; 3 char* p2 = 0; //这里0的赋值还是有效的,并且p=p2 4 5 void f(int); 6 void f(char*); 7 8 f(0); //调用f(int) 9 f(nullptr); //调用f(char*) 10 11 void g(int); 12 g(nullptr); //错误:nullptr并不是一个整型常量 13 int i = nullptr; //错误:nullptr并不是一个整型常量 14 (译注:实际上,我们这里可以看到nullptr和NULL两者本质的差别,NULL是一个整型数0,而nullptr可以看成是一个空指针。)
六、final 和 override
两者都是用在对于继承体系的控制。
final
用来标明被这个修饰符修饰的class/struct和虚函数已经是最终版本,无法被进一步继承.
override
override关键字用来表示在子类的函数一定重载自基类的同名同性质的虚函数或者纯虚函数,否则无法被编译.
1 class Base 2 { 3 public: 4 virtual void test(){} 5 virtual void test2(int i) {} 6 virtual void test3() const {} 7 }; 8 9 class D1:public Base 10 { 11 void test() override {} //编译正确 12 void test2(float i) override {} //编译错误,参数不一致 13 void test3() override {} //编译错误,函数常量性不一致 14 void test4() override {} //编译错误,并不是重载父类虚函数 15 };
需要注意的一点是,final和override两者很有可能在C++ 98的代码里面被程序员大量的用在其他地方命名,因此C++ 11为了保持和之前代码的兼容性,所以这两个标记只有在修饰class/struct和函数的时候,才会被当成关键字。也就是说,在其他地方依然可以使用这两个字符命名成变量/函数/类/结构体.
比如:
1 class Base 2 { 3 public: 4 virtual void test() 5 { 6 int final = 1; 7 } 8 virtual void test2(int i) {} 9 virtual void test3() const {} 10 virtual void override(); 11 };