從 STL 出現到現在已經這麼多年了,泛型演算法是它的重要組成,也是其中最“看起來很美”的東西之一。然而在真實的程式設計中,它往往成為程式員的心頭一痛,因為一旦要用 for_each ,accumulate 之類的演算法做一些稍微複雜一點的事情,你就會發現自己一個頭變成兩個大。
從 STL 出現到現在已經這麼多年了,泛型演算法是它的重要組成,也是其中最“看起來很美”的東西之一。然而在真實的程式設計中,它往往成為程式員的心頭一痛,因為一旦要用 for_each ,accumulate 之類的演算法做一些稍微複雜一點的事情,你就會發現自己一個頭變成兩個大。即便是有了 boost::bind 和 boost.lambda 的協助,事情也仍然是撲朔迷離,求助於 comp.lang.c++ 雖然有用,但是又何嘗不是一種無奈。好了,現在我開始收集一些來自 comp.lang.c++(.moderated) 的解答,希望日後對自己和他人有用。
=================================================================================
預備問題(算是第0個問題吧):如何對一個 STL 容器內的所有元素做某件事情?
這取決於你要做什麼,以及容器是什麼。如果是 vector, list 這樣的容器,而你要把它們全部 cout 出來,當前的標準 STL 解法是這樣的:
#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
#include <string>
int main()
{
std::vector<std::string> vect;
vect.push_back("Hello");
vect.push_back(", ");
vect.push_back("world!");
std::copy( vect.begin(), vect.end(),
std::ostream_iterator<std::string>(std::cout) );
}
基本上,這算是一個“過得去”的方案,但是有點問題:
1. 對於不熟悉這個固定用法的人,cout 所有元素所首要考慮演算法是 for_each,而不是 copy ,事實上,for_each 也是最符合我們慣常邏輯的演算法,因為在不使用 STL 演算法的時候,我們使用 for 迴圈來幹這件事。
2. 可讀性不太良好,ostream_iterator 的使用有點 tricky ,而且也不能用於做其他的事情。
我想熟悉 boost 的人已經知道我下面要說什麼了,因為用 boost.lambda 做這件事情的確非常漂亮:
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
using namespace boost::lambda;
int main()
{
std::vector<std::string> vect;
vect.push_back("Hello");
vect.push_back(", ");
vect.push_back("world!");
std::for_each( vect.begin(), vect.end(), std::cout << _1 );
}
這和前面的程式一樣,輸出我們熟悉的 Hello, world! 。直觀、優雅而且容易修改,例如,如果你不是想要輸出它們的值,而是想要輸出它們的長度,只需要做很少的修改:
std::for_each( vect.begin(), vect.end(), std::cout << bind(&std::string::length, _1) << "/n" );
輸出
5
2
6
bind 的作用是把 lambda 運算式綁定到一個函數或者一個資料成員,在這裡的意思,就是對於每一個 string ,都調用其 length() 方法。_1 同樣也可以成為賦值的對象,例如,先執行
std::for_each( vect[0].begin(), vect[0].end(), _1 = bind(&toupper, _1) );
然後再把這些 string 輸出,你會得到
HELLO, world!
因為那一句對 "Hello" 中的每一個字母調用 toupper ,並把結果寫回。
=================================================================================
第一個問題:如何對一個 map 中所有的 key 或者 value 做某件事情?
當然,這還是取決於你要做的是什麼。手寫 for 迴圈當然是萬能的,但是現在有了那麼多的泛型演算法,我們可以考慮其他的方案了(這也是眾多 C++ Gurus 推薦的思維方式)如果是把所有的 value 全部 cout 出來,用 boost.lambda 配合 for_each 還是比較優雅的(雖然沒有像 vector 和 list 那樣的優雅):
#include <iostream>
#include <algorithm>
#include <map>
#include <string>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
using namespace boost::lambda;
int main()
{
std::map<int, std::string> strs;
strs[0] = "Hello";
strs[1] = ", ";
strs[2] = "world";
strs[3] = "!";
std::for_each( strs.begin(), strs.end(),
std::cout << bind(&std::map<int, std::string>::value_type::second, _1) );
}
這樣的輸出如我們所料,就是 Hello, world! 。
如果想要把 key 也輸出,道理是一樣的,只需要這樣:
std::for_each( strs.begin(), strs.end(),
std::cout << bind(&std::map<int, std::string>::value_type::second, _1) << '/t'
<< bind(&std::map<int, std::string>::value_type::first, _1) << '/n'
);
其結果是:
Hello 0
, 1
world 2
! 3
因此,對於一個 map 中的 value 的操作往往可以依法炮製,如果我們想要在其中尋找 value 為 “world" 的那一個元素,並且輸出它的 key ,只需要一句話:
std::cout <<
std::find_if( strs.begin(), strs.end(),
bind(&std::map<int, std::string>::value_type::second, _1) == "world" )->first;
STL 演算法 find_if 接受的第三個參數是一個 prediate ,而產生這種臨時的 functor 正是 lambda 的拿手好戲。上面的這句話也可以用 boost::bind 來做,只需要更改 include 和 using namespace ,代碼本身無需更改。而如果你不藉助於它們,你只有手寫迴圈或者自己寫一個 predicate 。
當情況變得複雜一些的時候,lambda 的用法也變得更加有趣了:
#include <iostream>
#include <algorithm>
#include <map>
#include <string>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
using namespace boost::lambda;
class Person
{
public:
Person(){}
Person(const std::string& name) : name_(name){}
std::string Name()
{ return name_; }
private:
std::string name_;
};
int main()
{
std::map<int, Person> persons;
persons[123] = Person("Amy");
persons[234] = Person("Ralph");
persons[345] = Person("Simon");
persons[456] = Person("Maggie");
std::for_each( persons.begin(), persons.end(),
std::cout << bind(&std::map<int, Person>::value_type::first, _1) << '/t'
<< bind(&Person::Name,
bind(&std::map<int, Person>::value_type::second, _1)) << '/n'
);
std::cout << "Ralph's Id is: " <<
std::find_if( persons.begin(), persons.end(),
bind(&Person::Name,
bind(&std::map<int, Person>::value_type::second, _1)) == "Ralph" )->first;
}
這裡 map 的 value 元素不再是一個單純的 string,我們要輸出的是這個 value 的 Name() ,幸好 lambda 的綁定可以級聯,所以我們仍然可以用 STL 演算法在一個運算式之內搞定這些任務:for_each 輸出 key 和 value 的 Name(),而 find_if 找到 value 的 Name() 為 "Ralph" 的那一個元素,輸出是這樣的:
123 Amy
234 Ralph
345 Simon
456 Maggie
Ralph's Id is: 234