假設我們現在有個任務,要做一個程式,將一個數組中的每一個元素乘上100,然後賦值回去。為此,我寫下了這樣的C#代碼:int[] ai=new int[10];//初始化ai。foreach(int i in ai){ i*=100;}我又寫了C++代碼:vector<int> ai(10);//初始化ai。for(vector<int>::iterator i=ai.begin(); i!=ai.end(); ++i){ (*i)*=100;}唔,代碼比C#長多了。C++真複雜。這個或許好點:…for(int i=0; i<ai.size(); ++i){ ai[i]*=100;}現在任務升級了,有三個數組,把一個數組的元素乘上第二個數組相應元素,放入第三個數組中。我想寫這樣的代碼(C#):int[] ai1=new int[10],ai2=new int[10], ai3=new int[10];//初始化ai1和ai2foreach(int i in ai1, int j in ai2, int k in ai3) //語法錯誤, 要能這麼寫{ // 多好,可惜 i=j*k;}沒有這種文法,得用for:…for(int i=0; i<0; ++i){ ai1[i]=ai2[i]*ai3[i];}C++版的是這樣:vector<int> ai1(10), ai2(10), ai3(10);//初始化ai1和ai2for(int i=0; i<0; ++i){ ai1[i]=ai2[i]*ai3[i];}代碼一樣。但是,C++有個transform演算法,可以不用迴圈:…int mul_them(int v,int u) { return v*u;}transform(ai1.begin(), ai1.end(), ai2.begin(), ai3.begin(), mul_them);代碼沒有簡單,可晦澀多了。不熟悉標準庫的初學者,會覺得這像是天書。我們一會兒來討論學習問題。再加點難度,三個容器,第一個是隊列,第二個是數組,第三個是鏈表。把隊列裡的元素乘上數組裡的元素,放到鏈表裡。C#隊列和鏈表沒有隨機訪問操作符[],必須利用IEnumerable<>泛型介面訪問:Queue<int> ai1=new stack<int>(10);int[] ai2=new int[10]LinkedList<int> ai3=new queue<int>(10);//初始化ai1和ai2IEnumerator<int> eai1=ai1.GetEnumerator();IEnumerator<int> eai2=ai2.GetEnumerator();IEnumerator<int> eai3=ai3.GetEnumerator(); while(eai1.MoveNext() && eai2.MoveNext() && eai3.MoveNext()){ eai3.Current=eai1.Current*eai2.Current;}下面該C++代碼了。C++標準庫的隊列和鏈表也沒有隨即操作符[],必須使用迭代器:deque<int> ai1(10);vector<int> ai2(10);list<int> ai3(10);//初始化ai1和ai2int mul_them(int v,int u) { return v*u;}transform(ai1.begin(), ai1.end(), ai2.begin(), ai3.begin(), mul_them);C++也可以用手工逐一查看迭代器,就像C#那樣。當然,代碼會複雜得多,比C#的還複雜。再提高難度,我發現這種運算在很多地方出現,想把它做成一個演算法。但是,每次出現的三種容器的類型不一定,元素的類型也不同,我必須作一個泛型演算法。先做C#的版本:public class MyAlogrithm{ public static void Caculate_Contain<C1,T1,C2,T2,C3,T3>(C1 c1, C2 c2, C3 c3) where C1: IEnumerator<T1> where C2: IEnumerator<T2> where C3: IEnumerator<T3> { IEnumerator<T1> eai1 = c1.GetEnumerator(); IEnumerator<T2> eai2 = c2.GetEnumerator(); IEnumerator<T3> eai3 = c3.GetEnumerator(); while (eai1.MoveNext() && eai2.MoveNext() && eai3.MoveNext()) { eai3.Current = eai1.Current * eai2.Current;//編譯錯誤 } }}有編譯錯誤。eai1.Current和eai2.Current分別返回T1和T2類型的執行個體,因為C#編譯器不知道T1和T2的類型,所以不能確定兩者是否能夠相乘。此路走不通,我正在努力解決這個問題。剩下一種辦法,就只能採用RTTI(RunTime Type Info)解決:public class MyAlogrithm{ public static void Caculate_Contain<C1,C2,C3>(C1 c1, C2 c2, C3 c3) where C1 : IEnumerable where C2 : IEnumerable where C3 : IEnumerable { IEnumerator eai1 = c1.GetEnumerator(); IEnumerator eai2 = c2.GetEnumerator(); IEnumerator eai3 = c3.GetEnumerator(); while (eai1.MoveNext() && eai2.MoveNext() && eai3.MoveNext()) { if(eai3.Current.type ==typeof(int) && eai1.Current.type ==typeof(int) && eai2.Current.type ==typeof(int)) { (int)eai3.Current = (int)eai1.Current * (int)eai2.Current; } else if(eai3.Current.type==typeof(float) && eai1.Current.type==typeof(int) && eai2.Current.type ==typeof(int)) { (float)eai3.Current = (int)eai1.Current * (int)eai2.Current; } else if(eai3.Current.type ==typeof(double) && eai1.Current.type ==typeof(float) && eai2.Current.type ==typeof(int)) { (double)eai3.Current = (float)eai1.Current * (int)eai2.Current; } … } }}如果不喜歡大量的if…else…,那麼可以用Dictionary做一個指派器,根據容器元素的類型指派操作。這種做法有個限制性要求:必須事先約定容器的元素類型,比如int、long、double等等,可以是使用者定義的類型(重載過*操作符)。代碼的缺點麼,都應該看到了:如果約定的容器元素的類型有n個,那麼if…else…的數量將是n的立方。回過頭來在看C++的實現:template<typename T1, typename T2, typename R>R mul_them(T1 v,T2 u) { return v*u;}template<typename C1, typename C2, typename C3>Caculate_Container(const C1& c1, const C2& c2, C3& c3){ transform(c1.begin(), c1.end(), c2.begin(), c3.begin(), mul_them<C1::value_type, C2::value_type, C3::value_type>);}就這麼簡單。由於模板是編譯期的機制,所有未確定類型(模板參數),是否包含所需的成員,如begin()、end()等,以及是否能夠相乘,編譯時間便會立刻明了。而C#的泛型(確切地說,是.net的泛型),是跨語言的機制,泛型演算法可能會被其他語言使用,無法在編譯期確定類型的特徵,所以必須要求類型在編譯前明確所應具備的條件,即類型約束(where字句)。但問題還不在這裡。問題在於,C#的泛型的類型約束依賴於繼承,即類型必須從…類處繼承而來。而不是像C++09中的concept那樣直接描述類型所需的特徵。所以,C#版的泛型演算法也就是因此而失敗。最後再做一個小小的擴充,要求Caculate_Catainer<>()演算法可以接受一個演算法,該演算法指示了如何對這些容器進行操作。此時,我們會發現,C#的泛型演算法反而能夠實現了:public delegate void alg<T1, T2, R>(T1 v1, T2 v2, R r); public static void Caculate_Contain<C1, T1, C2, T2, C3, T3> (C1 c1, C2 c2, C3 c3, alg<T1, T2, T3> a ) where C1: IEnumerable<T1> where C2 : IEnumerable<T2> where C3 : IEnumerable<T3>{ IEnumerator<T1> eai1 = c1.GetEnumerator(); IEnumerator<T2> eai2 = c2.GetEnumerator(); IEnumerator<T3> eai3 = c3.GetEnumerator(); while (eai1.MoveNext() && eai2.MoveNext() && eai3.MoveNext()) { a(eai1.Current, eai2.Current,eai3.Current); }}//使用public static void CaculThem(int v1, int v2,int r) { r=v1*v2;}Caculate_Contain(ai1, ai2, ai3, new alg<int, int, int>(CaculThem));public static void CaculThem2(float v1, int v2,double r) { r=v1*v2;}Caculate_Contain(af1, ai2, ad3, new alg<float, int, double>(CaculThem2));我使用了一個委託,作為傳遞處理容器元素的演算法的載體。使用時,用具體的演算法建立委託的執行個體。但具體的演算法CaculThem()必須同相應的容器元素類型一致。下面輪到C++:template<typename C1, typename C2, typename C3, typename Alg>Caculate_Container(const C1& c1, const C2& c2, C3& c3, Alg a){ transform(c1.begin(), c1.end(), c2.begin(), c3.begin(), a);}//使用template<typename T1, typename T2, typename R>R mul_them(T1 v,T2 u) { return v*u;}Caculate_Container(ai1, ai2, ai3, mul_them<int, int, int>);Caculate_Container(af1, ad2, ad3, mul_them<float, double, double>);如果容器元素有所變化,C#代碼必須重寫演算法CaculThem()。但C++不需要,由於mul_them<>()本身是個函數模板,那麼只需將這個函數模板用新的類型執行個體化一下即可。C++的代碼相對簡單些,靈活性也更高些。但這還不是全部,C++還有一個最終極的解法,不需要迴圈,不需要建立模板演算法:transform(c1.begin(), c1.end(), c2.begin(), c3.begin(), _1*_2);沒看明白?我一開始也看不明白。這裡用到了boost庫的Lambda運算式。_1預留位置對應c1的元素,_2的預留位置對應c2的元素,_1*_2表示才c1的元素乘上c2的元素,其結果放在c3裡。運算式可以寫得更複雜,比如(_1*_2+3*_1)/(_1-_2)。Lambda運算式可以用在所有需要操作的演算法中,比如我要去掉字串中的“-”,可以這樣寫:remove_if(s.begin(), s.end(), _1==’-’);
Lambda運算式基於一種叫做“模板運算式”的技術,通過操作符重載,將一個運算式一層一層地展開,構成一個解析樹。然後作為一個函數對象傳遞給演算法,演算法在迴圈內調用函數對象,執行相應的計算。