lambda表達式
C++ 語言中的lambda表達式在很多情況下提供了函數對象的另一種實現機制。Lambda表達式並不是STL所特有的,但它廣泛應用於這一環境中。Lambda是表達式是定義一個沒有名稱、也不需要顯示類定義的函數對象。Lambda表達式一般作為一種手段,用來將函數作為實參傳遞到另一個函數。相比於定義和創建一個常規的函數對象而言,lambda表達式非常容易使用和理解,而且需要的代碼也較少。當然,一般而言,lambda表達式並不會取代函數對象。
舉個例子,假設有個包含數值的矢量,我們計算此矢量的立方值。可以用transform()函數操作,簡單的用lambda表達式完成。
double values[] = {1,2,3,4,5,6}; vector<double> data(values,values+6); vector<double> cubes(data.size()); transform(values.begin(),values.end(),cubes.begin(),[](double x){ return x*x*x;});
最后這條語句用來計算data中的立方值,並存儲在cubes。這里簡單提一下transform()函數。它是algorithm頭文件中的函數,它有兩個版本。
第一個版本是將一個一元函數對象指定的操作應用到由一對迭代器指定的一個元素集合上,格式如下:
transform(InputIterator begin, InputIterator end, OutputIterator result,UnaryFuncton f);
transform()的這個版本將一元函數f應用到迭代器begin 和end指定的范圍中的所有元素,並從迭代器result指定的位置開始存儲結果。Result迭代器可以與begin迭代器相同,只是在這種情況下將會替換原有的內容。這個函數返回一個迭代器,指向存儲的最后一個結果的下一個位置。
舉例如下:
double values[] = {1,2,3,4,5,6}; vector<double> data(values,values+6); transform(values.begin(),values.end(),values.begin(),negate<double>);
transform()函數調用negate<double>函數對象應用到矢量data中的所有元素,結果存儲在data中,並重寫了原始值,執行完后data將包含 -1,-2,-3,-4,-5,-6。函數返回迭代器data.end()。
transform()第二個版本通過來自迭代器指定的兩個范圍內的操作數應用一個二元函數。格式為:
transform(InputIterator1 begin1, InputIterator 1end1, InputIterator2 begin2, OutputIterator result,BinaryFunction f);
由begin1和end1指定的范圍表示最后一個實參指定的二元函數f的左操作數集合。表示右操作的范圍從begin2迭代器指定的位置開始,這個范圍不需要提供end迭代器,因為這個范圍的元素數量必須與begin1和end1指定的范圍元素個數相同,結果從result迭代器位置開始存儲在這個范圍內。如果希望存回原范圍中,result迭代器可以與begin1相同。
舉例如下:
double values[]={2.5,-3.5,4.5,-5.5,6.5,-7.5}; vector<double> data(values, values + sizeof values / sizeof values[0]); vector<double> squares(data.size()); transform(data.begin(),data.end(),data.begin(),squares.begin(),multiplies<double>()); ostream_iterator<double> out(cout,” “); copy(squares.begin(),squares.end(),out);
Transform()函數通過multiplies<double>函數對象將自身相乘,結果存儲到squares中。最后兩句用一個輸出迭代器輸出內容。
現在回到上文:
transform(values.begin(),values.end(),cubes.begin(),[](double x){ return x*x*x;});
開始的方括號稱為lambda引導,它標志着lambda表達式的開始。后面的圓括號中的是lambda的參數列表,這與普通函數相同。此例中只有一個形參x。注意,lambda的參數列表不允許指定形參的默認值,並且參數列表的長度是不可變的。大括號中的是lambda的主體,此例只有一條return語句,當然可以包含多條語句。大家可能注意到這里沒有返回類型說明。當lambda表達式的主體是一條單一返回語句,而該語句在lambda表達式主體中返回一個值時,返回類型默認為返回值的類型。否則,返回void。當然可以指定返回類型,如下:
[](double x) ->double{ return x*x*x;} //指定返回double
Capture子句
lambda表達式引導可以包含一個捕獲子句,用來確定lambda主體如何訪問封閉作用域中的變量。前面lambda表達式方括號之間沒有內容,表面封閉作用域沒有可以再lambda表達式中訪問的變量。若要訪問,第一種是方括號之間是 = ,則lambda主體可以按值訪問封閉作用域的所有自動變量,但不會修改原始變量。另一中是方括號之間是 & ,則封閉作用域的所有自動變量按應用訪問,因此lambda表達式可以修改變量值。例如:
double index = 3.0; double values[] = {1,2,3,4,5,6}; vector<double> data(values,values+6); vector<double> cubes(data.size()); transform(values.begin(),values.end(),cubes.begin(), [=](double x){ return index*x*x*x;});
需要主要的是,這與按值傳遞實參根本不同,變量index的值可用在lambda中,但不能更新index的副本。如:
transform(values.begin(),values.end(),cubes.begin(), [=](double x) ->double{ index += 10; // error return index*x*x*x;});
以上是錯誤的,若要修改變量的臨時副本,則通過添加mutable關鍵字實現。如:
transform(values.begin(),values.end(),cubes.begin(), [=](double x)mutable ->double{ index += 10; // ok return index*x*x*x;});
現在可以修改作用域中的任意變量副本,而不會修改原始值。
transform(values.begin(),values.end(),cubes.begin(), [&](double x)mutable ->double{ index += 10; // change original value return index*x*x*x;});
現在采用按引用使用,則會改變index的原始值。
若要捕獲一個特定的變量,則:
transform(values.begin(),values.end(),cubes.begin(), [&index](double x)mutable ->double{ index += 10; // change original value return index*x*x*x;});
這樣,只捕獲index,如要捕獲多個變量,中間用逗號隔開即可。
Lambda也可以包含throw()異常說明,如:
transform(values.begin(),values.end(),cubes.begin(), [&index](double x)mutable throw()->double{ index += 10; // change original value return index*x*x*x;});
如果想要包含mutable說明和throw()說明,則中間必須用一個或多個空格隔開。
現在綜合看個實例,用以前說過的函數模板實現。
// Using lambda expressions #include <algorithm> #include <iostream> #include <iomanip> #include <vector> #include <ctime> #include <cstdlib> using namespace std; // Just to avoid a lot of using directives in the example... // Template function to return the average of the elements in a vector template <class T> T average(const vector<T>& vec) { T sum(0); for_each(vec.begin(), vec.end(), [&sum](const T& value){ sum += value; }); return sum/vec.size(); } // Template function to set a vector to values beginning with start and incremented by increment template <class T> void setValues(vector<T>& vec, T start, T increment) { T current(start); generate(vec.begin(), vec.end(), [increment, ¤t]()->T{T result(current); current += increment; return result;}); } // Template function to set a vector to random values between min and max template<class T> void randomValues(vector<T>& vec, T min, T max) { srand(static_cast<unsigned int>(time(0))); // Initialize random number generator generate(vec.begin(), vec.end(), [=](){ return static_cast<T>(static_cast<double>(rand())/RAND_MAX*(max-min)+min); }); } // Template function to list the values in a vector template<class T> void listVector(const vector<T>& vec) { int count = 0; // Used to control outputs per line for_each(vec.begin(), vec.end(), [&count](const T& n)->void{ cout << setw(10) << n; if(++count % 5) cout << " "; else cout << endl;}); } int main() { vector<int> integerData(50); randomValues(integerData, 10, 100); // Set random integer values cout << "Vector contains:" << endl; listVector(integerData); cout << "Average value is "<< average(integerData) << endl; vector<double> realData(20); setValues(realData, 5.0, 2.5); // Set real values starting at 5.0 ,increment by 2.5 cout << "Vector contains:" << endl; listVector(realData); cout << "Average value is "<< average(realData) << endl; return 0; }
Lambda表達式的包裝
Lambda表達式的包裝實際上是使用function< >模板賦予lambda表達式一個名字,這不僅提供了在Lambda表達式內遞歸的可能,而且可以再多條語句使用同樣的lambda表達式。如:
Function< int (double)> f = [](double x){ return static_cast<int>(x*x)};
這里具有一個double類型的形參,並返回一個為int類型的值,當然,此處只是舉例而已,因為該語句存在問題,將double賦給int時可能會丟失數據。
舉例:
#include <iostream> #include <functional> using std::function; using std::cout; using std::endl; int main() { // Wrap the lambda expression to compute the HCF function<int(int,int)> hcf = [&](int m, int n) mutable ->int{ if(m < n) return hcf(n,m); int remainder(m%n); if(0 == remainder) return n; return hcf(n, remainder);}; int a(17719), b(18879); cout << "For numbers " << a << " and " << b << " the HCF is " << hcf(a, b) << endl; a = 103*53*17*97; b = 3*29*103; cout << "For numbers " << a << " and " << b << " the HCF is " << hcf(a, b) << endl; return 0; }
該實例用歐幾里得法求兩個數的最大公約數,即所謂輾轉相除法,采用遞歸形式實現。