《被誤解的C++——學習和使用》的案例

來源:互聯網
上載者:User
 

假設我們現在有個任務,要做一個程式,將一個數組中的每一個元素乘上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運算式基於一種叫做“模板運算式”的技術,通過操作符重載,將一個運算式一層一層地展開,構成一個解析樹。然後作為一個函數對象傳遞給演算法,演算法在迴圈內調用函數對象,執行相應的計算。 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.