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 表達式的結構化元素

      如上圖所示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

關於 decltype 是一個操作符,其可以評估括號內表達式的類型,其規則如下:

  1. 如果表達式e是一個變量,那么就是這個變量的類型。
  2. 如果表達式e是一個函數,那么就是這個函數返回值的類型。
  3. 如果不符合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表達式

 1 std::function<void()> func_1 = [](){cout<<"hello world"<<endl;}; 2 func_1(); 

運行輸出: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 };

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM