基於C++ Lambda運算式的程式最佳化

來源:互聯網
上載者:User
基於C++ Lambda運算式的程式最佳化2012-06-21 11:59

這是一個關於C\C++程式員的一個小故事,關於C++11――剛剛通過的新標準的一個小故事…

請不要誤會,題目中所提及的“最佳化”並不是提升程式的效能――Lambda運算式幹不了這個。從本質上來說,它只是一種“文法糖”而已。不使用這種運算式,我們照樣可以寫出滿足需求的程式。正如放棄C而使用彙編,或者放棄彙編而使用機器語言一樣,你能控制的範圍就在那裡,不增不減。但如果有得選擇,我相信大部分人會選擇彙編而非機器語言,選擇C而非彙編,甚至選擇C++而非C語言……。如果你確實是這樣選擇的,那麼我有理由相信,你會選擇C++新標準中的Lambda運算式,因為它確實能夠簡化你的程式,讓你寫起程式來更容易;讓你的程式更易讀,更優美;同時也讓你有更多向同行炫耀的資本。

從一個實際的應用說起

讓我們還是看一個例子吧。

無論是C語言的使用者,還是C++的使用者,如果你從事PC程式的演算法開發,我有96.57%的把握認為你可能使用過C++標準模板庫STL(其中的string,vector之類)。畢竟,STL的抽象不錯,不用白不用,是不是。STL中有一大類是演算法,這些演算法的抽象同樣不錯,我們就拿排序演算法(sort)來說事吧。

假設現在有一個結構稱為Student,其中包含了ID與name兩項――分別表示學號與姓名。在某個應用中,使用者希望對一個Student的數組按照ID的從大到小排序,那麼程式可能寫成如下的形式(本文中的所有程式均在Visual Studio 2010下編譯通過):
#include <string>
#include <vector>
#include <iostream>
#include <iterator>
#include <algorithm>
using namespace std;

struct Student {
unsigned ID;
string name;
Student(unsigned i, string n) : ID(i), name(n){}
};

struct compareID {
bool operator ()(const Student& val1, const Student& val2)? ???const {
return val1.ID < val2.ID;
}
};

int main(int argc, char* argv[]) {
Student a[] = {Student(2, “John”), Student(0, “Tom”), Student(1, “Lily”)};
sort(a, a+3, compareID());
for(int i=0; i<3; ++i)
cout<<a[i].ID<<’ ‘<<a[i].name<<endl;
return 0
}

程式用sort進行排序,之後用一個for迴圈輸出結果。而之所以能完成這個排序,則是由於仿函數compardID的存在。

現在假設使用者的需求變了(或者是另一個需求),需要你按照學生的姓名進行排序,那麼你需要重新寫一個仿函數如下:

struct compareName {

bool operator ()(const Student& val1, const Student& val2) const? ? {
return val1.name < val2.name;
}
};

然後將sort的調用修改為:

sort(a, a+3, compareName());

問題出現了,你意識到了嗎?你只是想表達一個很簡單的排序方式,確不得不引入很多的程式碼來建相應的仿函數。如果這個函數在很多地方都會用到,那麼建立它的價值還相對較大。如果只是用在一個地方,你也不得不中段你流暢是思路,一邊罵娘一邊寫出這麼多行代碼。另一方面,程式的讀者在讀到相應部分的時候,也不得不中段他流暢的思路,在工程的某個地方苦苦求索――compareName或者compareID是怎麼乾的呢?
是的,是的,作為一個C++老鳥,你會說,這樣寫代碼太不專業了。完全可以有不建立仿函數的寫法,比如以ID排序時,完全可以通過引入boost庫中的bind來實現,比如這樣:

sort(a, a+3, bind(less<unsigned>(), bind(&Student::ID, _1), bind(&Student::ID, _2)));

如果你能寫出或是讀懂這段代碼,我承認你的C++水平確實說得過去(如果讀不懂,沒關係,它不是本文的重點)。但這段代碼真的好嗎?確實,這樣可以省略了仿函數。但問題是代碼的複雜性大大增加了――即使如此簡單的一個需求,bind運算式也要複雜如斯,更複雜一點的需求要寫成何等複雜的形式啊,這對於bind本身,寫程式的人,讀程式的人都是一種折磨――你hold住嗎?

如果用Lambda運算式呢,唔,這個sort語句可以這麼寫:

sort(a, a+3, [](const Student& val1, const Student& val2){ return val1.ID < val2.ID; });

那個看上去有點奇怪的,sort的第三個函數就是一個Lambda運算式了。如果我們除去開頭的“[]”不看,後面的部分很像一個函數――你可以很容易地看出這個函數是幹什麼的:給定兩個Student元素,比較兩個元素的ID值,並返回比較結果――這玩意兒比上面那個bind結果容易閱讀多了。

事實上,利用Lambda運算式,上述程式可以修改為如下的樣子(只列出了main函數):

int main(int argc, char* argv[]) {
Student a[] = {Student(2, “John”), Student(0, “Tom”), Student(1, “Lily”)};
sort(a, a+3, [](const Student& val1, const Student& val2){ return val1.ID < val2.ID; });
for_each(a, a+3, [](const Student& val){cout<<val.ID<<’ ‘<<val.name<<endl;});
return 0
}

其中的for_each句用於輸出――其中的Lambda運算式意味著:對於每一個val,輸出其ID與Name值――這樣我們連for迴圈也省了。

Lambda運算式的引入就是為了更方便地書寫程式,更容易地閱讀程式。如同STL一樣,有什麼理由不去用呢?

Lambda運算式的基本文法

有了感性的認識後,我們來分析一下Lambda運算式的文法。

我這裡無意把C++標準草案中Lambda運算式的有關章節翻譯過來(我也不佩這麼做)。只是在這裡希望以最通俗的方式將它的文法講解一二。從結構上說,Lambda運算式可以寫成如下的形式:

Lambda-introducer lambda-declarator(opt) compound-statement

其中的Lambda-introducer就是剛剛的那個“[]”它是不能省略的。中括弧中也可能出現變數。表示將局部變數傳入到Lambda運算式中。lambda-declaratoropt是可選擇的,包括了運算式的參數列表,傳回值資訊,mutable聲明(以及一些其它資訊,這裡不做討論)。而最後的compound-statement則是運算式的主要內容。

還是看一個例子吧:

int n = 10;
[n](int k) mutable -> int { return k + n; };

程式的第二行是一個lambda運算式,lambda裡能出現的東西幾乎全了(當然,正如我在前文說的,有一些其它資訊這裡不做討論,所以沒有加入其中)。讓我們對裡面的東西一一分析:

l[n]是Lambda-introducer,而n是一個變數,表明該運算式範圍中的變數n將被傳入這個運算式。以本程式為例,傳入的值是10。Lambda-introducer可以指定變數以值的方式傳入,也可以用其它的形式指定其以引用的方式傳入。其變型大家就baidu一下吧J

l(int k)表示了參數列表,屬於lambda-declarator的一部分。你可以把運算式看成一個仿函數(如上文的)。這裡指定了仿函數的參數列表。如果函數的參數列表為空白,這一部分可以省略。

lmutable表示仿函數中的變數能否改變。以前文中compareID這個仿函數為例,注意到其中的operator ()是const的。如果lambda運算式中引入了這個mutable,則對應的仿函數中operator()的定義將不包含這個const――這意味著仿函數中的變數值(Lambda-introducer傳入)可以改變。討論operator() const與operator()的區別已經超出了本文的範圍,想瞭解的話,看看C++相關教程吧J

l-> int表示傳回型別(這裡是int)。如果編譯器能從代碼中推斷出傳回型別,或者Lambda運算式的傳回型別為void,則該項可省略;

l{ return k+n; }是compound-statement:函數體。

通過分析可以看出,這個Lambda運算式相當於一個函數,該函數讀入一個int值k,將該值加上n返回。根據上述說明,這個運算式可以簡寫為:

[n](int k){ return k + n; };

Lambda運算式可以儲存在std::function<T>或std:: reference_closure<T>類型的變數中。其中的T表示了運算式對應函數的類型。以上述運算式為例,它輸入參數為int型變數,輸出為int,那麼為了儲存它,可以寫成如下的形式:

function<int(int)> g = [n](int k){ return k + n; };

另一個例子,前文所使用的Lambda運算式:

[](const Student& val1, const Student& val2){ return val1.ID < val2.ID; }

可以儲存於function<bool(constStudent&,constStudent&)>這個類型的變數中。

如果你嫌這麼寫麻煩,也可以利用C++新標準中另一個新特性:類型推導。即用auto作為變數的類型,讓編譯器自己推導運算式的類型:

auto g = [n](int k){ return k + n; };

沒問題,這樣寫g還是一個強型別的變數,只不過其類型是由編譯器推導的,好處是你不用寫太長的變數類型了J

Lambda運算式進階

作為結尾,我們來看一些C++ Lambda運算式進階的用法。

Lambda運算式被引入主要是用於函數式編程。有了Lambda運算式,我們也可以做一些函數式編程的東西。比如將一個函數作為傳回值的應用:

auto g = [](int n) -> function<void (int)> {
return [n](int k){ cout<<n+k<<’ ‘; };
};

它是一個Lambda運算式,輸入一個整型變數n,返回一個函數(lambda運算式),這個函數接收一個int值k,並列印出k+n。g的使用方法如下:

int a[]={1,2,3,4,5,6,7,8,9,0};
function<void (int)> f = g(2);
for_each(a, a+10, f);

它將輸出:3 4 5 6 7 8 9 10 11 2

有一點函數式編程的味道了J

至於其它的東西,比如如下的運算式:

[](){}();

是一個有效調用。其中“[](){}”表示一個Lambda運算式,其輸入參數為空白,返回void,什麼都不幹。而最後的()表示調用其求值――雖然什麼都不幹,但編譯能通過,很唬人喔J

好了,就寫到這裡吧。關於Lambda運算式想說的最後一件事是:它是新標準C++11中定義的。老的編譯器不支援(這也是我用VS2010的原因)。想要用它,以及其它新標準帶來的好處嗎?嘿,你的傢伙(指編譯器)該升級了J

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.