從 STL 出現(xiàn)到現(xiàn)在已經(jīng)這么多年了,泛型算法是它的重要組成,也是其中最“看起來(lái)很美”的東西之一。然而在真實(shí)的程序設(shè)計(jì)中,它往往成為程序員的心頭一痛,因?yàn)橐坏┮?for_each ,accumulate 之類的算法做一些稍微復(fù)雜一點(diǎn)的事情,你就會(huì)發(fā)現(xiàn)自己一個(gè)頭變成兩個(gè)大。
從 STL 出現(xiàn)到現(xiàn)在已經(jīng)這么多年了,泛型算法是它的重要組成,也是其中最“看起來(lái)很美”的東西之一。然而在真實(shí)的程序設(shè)計(jì)中,它往往成為程序員的心頭一痛,因?yàn)橐坏┮?for_each ,accumulate 之類的算法做一些稍微復(fù)雜一點(diǎn)的事情,你就會(huì)發(fā)現(xiàn)自己一個(gè)頭變成兩個(gè)大。即便是有了 boost::bind 和 boost.lambda 的幫助,事情也仍然是撲朔迷離,求助于 comp.lang.c++ 雖然有用,但是又何嘗不是一種無(wú)奈。好了,現(xiàn)在我開(kāi)始收集一些來(lái)自 comp.lang.c++(.moderated) 的解答,希望日后對(duì)自己和他人有用。
=================================================================================
預(yù)備問(wèn)題(算是第0個(gè)問(wèn)題吧):如何對(duì)一個(gè) STL 容器內(nèi)的所有元素做某件事情?
這取決于你要做什么,以及容器是什么。如果是 vector, list 這樣的容器,而你要把它們?nèi)?cout 出來(lái),當(dāng)前的標(biāo)準(zhǔn) 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) );
}
基本上,這算是一個(gè)“過(guò)得去”的方案,但是有點(diǎn)問(wèn)題:
1. 對(duì)于不熟悉這個(gè)固定用法的人,cout 所有元素所首要考慮算法是 for_each,而不是 copy ,事實(shí)上,for_each 也是最符合我們慣常邏輯的算法,因?yàn)樵诓皇褂?STL 算法的時(shí)候,我們使用 for 循環(huán)來(lái)干這件事。
2. 可讀性不太良好,ostream_iterator 的使用有點(diǎn) tricky ,而且也不能用于做其他的事情。
我想熟悉 boost? 的人已經(jīng)知道我下面要說(shuō)什么了,因?yàn)橛?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! 。直觀、優(yōu)雅而且容易修改,例如,如果你不是想要輸出它們的值,而是想要輸出它們的長(zhǎng)度,只需要做很少的修改:
std::for_each( vect.begin(), vect.end(), std::cout << bind(&std::string::length, _1) << "\n" );
輸出
5
2
6
bind 的作用是把 lambda 表達(dá)式綁定到一個(gè)函數(shù)或者一個(gè)數(shù)據(jù)成員,在這里的意思,就是對(duì)于每一個(gè) string ,都調(diào)用其 length() 方法。_1 同樣也可以成為賦值的對(duì)象,例如,先執(zhí)行
std::for_each( vect[0].begin(), vect[0].end(), _1 = bind(&toupper, _1) );
然后再把這些 string 輸出,你會(huì)得到
HELLO, world!
因?yàn)槟且痪鋵?duì) "Hello" 中的每一個(gè)字母調(diào)用 toupper ,并把結(jié)果寫回。
=================================================================================
第一個(gè)問(wèn)題:如何對(duì)一個(gè) map 中所有的 key 或者 value 做某件事情?
當(dāng)然,這還是取決于你要做的是什么。手寫 for 循環(huán)當(dāng)然是萬(wàn)能的,但是現(xiàn)在有了那么多的泛型算法,我們可以考慮其他的方案了(這也是眾多 C++ Gurus 推薦的思維方式)如果是把所有的 value 全部 cout 出來(lái),用 boost.lambda 配合 for_each 還是比較優(yōu)雅的(雖然沒(méi)有像 vector 和 list 那樣的優(yōu)雅):
#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'
? );
其結(jié)果是:
Hello?? 0
,??????????? 1
world?? 2
!??????????? 3
因此,對(duì)于一個(gè) map 中的 value 的操作往往可以依法炮制,如果我們想要在其中尋找 value 為 “world" 的那一個(gè)元素,并且輸出它的 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 接受的第三個(gè)參數(shù)是一個(gè) prediate ,而生成這種臨時(shí)的 functor 正是 lambda 的拿手好戲。上面的這句話也可以用 boost::bind 來(lái)做,只需要更改 include 和 using namespace ,代碼本身無(wú)需更改。而如果你不借助于它們,你只有手寫循環(huán)或者自己寫一個(gè) predicate 。
當(dāng)情況變得復(fù)雜一些的時(shí)候,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 元素不再是一個(gè)單純的 string,我們要輸出的是這個(gè) value 的 Name() ,幸好 lambda 的綁定可以級(jí)聯(lián),所以我們?nèi)匀豢梢杂?STL 算法在一個(gè)表達(dá)式之內(nèi)搞定這些任務(wù):for_each 輸出 key 和 value 的 Name(),而 find_if 找到 value 的 Name() 為 "Ralph" 的那一個(gè)元素,輸出是這樣的:
123???? Amy
234???? Ralph
345???? Simon
456???? Maggie
Ralph's Id is: 234