我用C++的理由——關於C和C++的選擇


 

摘自:http://www.xue163.com/32/6/325715.html,作者:王可。整理的很好!


首先,我不會使用Java或C#,能力上不會,主觀上也不會,因為兩點原因:1,他們都屬於解釋型的語言,這有很多問題是我無法容忍的,程序的速度和封裝的安全性;2,他們都不夠底層,沒有指針,卻加載了內存管理器,對我來說這些都是麻煩和束縛,對我而言他們都不是足夠自由的語言。或者說,我無法接受他們對使用者的理念,似乎他們認為使用他們的程序員都是懶惰和容易犯錯誤的,而他們的高級之處都是依賴各種類庫,但對底層的操作卻有很多限制,如類型轉換,非常羅嗦。C和C++就不同了,他們能夠給我充分的自由,由程序員來承擔自己邏輯錯誤的后果,和開發需要的類庫,這更符合我的個性。 

我現在對C和C++都能熟練地使用,我的工作主要維護着以往的C代碼,對這部分代碼的修改與補充,我仍然使用C語言;對新的開發任務我都使用C++,如數字圖象處理的類庫,數據遷移的模塊等等。我首先是一名熟練的C程序員,而后逐步掌握C++,最初使用C++是認為他可能比C更方便和高級,不排除有一種追求時髦的情節。現在我同時是這兩種語言的熟練掌握者,也曾經猶豫過到底是最終使用C還是C++,主要是擔心C++的速度不如C。 


最近看到了關於C和C++的爭論,堅持C語言的論調對我有一定的觸動,因為我現在基本都是使用C++開發,他們提出的一些C++的弊端正是C++為之驕傲的東西,我從來都沒有考慮過這些,這些觀點的確非常新穎,且非常有見地;比如,所謂的面向對象和設計模式,我們是否真的需要或只是為追求一些時髦的概念,這引發了我很深的思考。為此我又特意去嘗試強制使用C來寫已經用C++寫好的類庫,發現不行,那太羅嗦;我又嘗試僅使用C++基本的內容,而不使用高級特性,如繼承、虛函數、模板等等,以及直接實現功能類,而不使用復雜的各種類關系,比如我的模板動態數組,需要使用到內存分配器類,對象構造器類,對象分配器類和模板算法集合,發現也不行,我沒有辦法改變這種設計。 所以,經過這次再嘗試,我決定還是選擇C++作為我的主要語言工具,我知道C和C++都不能相互替代,但對於我個人,我還是選擇C++。我使用C++,完全可以僅使用它和C語言的交集,我可以選擇不使用任何高級特性,或選擇我需要的那部分高級特性,在適當的位置;但選擇C就意味着幾乎不可能,雖然C++的一些特性可以在C里將就或有等價實現方法,但那會另你的代碼看上去比較累。 

名字空間,我喜歡用名字空間,雖然這不是大問題,但我覺得使用名字空間更舒服。如果是C語言,我就會選擇加一串前綴來防止重名或表示這個函數是此函數庫的一部分,如: 
lib_func(); 

而C++則是: 
lib::func(); 

或: 
using namespace lib; 
func(); 

我更樂於接受C++的形式,尤其是當你的函數名也很長的時候,C會令它更長。 

在結構體中使用方法,我可以將從屬於結構體的函數作為它的成員方法和它聲明在一起,這起到了給函數歸類的作用,如果是C,或許作者自己是清楚的,但作為調用者就要在一大堆函數中找出操作這個結構體的函數。而且,按照我的習慣,會使函數名更復雜: 

struct StructA a;

lib_StructA_func(&a); 
而C++的形式是: 
lib::StructA a; 
a.func(); 


構造函數,我喜歡用構造函數來初始化結構體,這樣會避免使用者忘記使用memset,或者有的時候結構體的初始化值並不是全為0,如句柄應該是-1,或者C語言需要單獨為初試化寫個函數,如: 
struct StructA a; 
memset(&a, 0, sizeof(a)); 

或者: 
struct StructA a; 
lib_StructA_init(&a); 

而C++只有一句: 
lib::StructA a; 

拷貝構造函數、賦值運算符、析構函數,同樣的道理,重載這些會另使用者考慮更少的問題,代碼也更簡潔。 

類的作用域控制,它可以很簡單地控制哪些成員和方法是供使用者訪問的,哪些是內部的成員和方法,而C你想確保函數肯定不會被訪問到,就只能寫在源文件里,但這樣又不能在自己的庫內共享,所以只有利用函數名來告戒使用者,這是內部的函數,最好不要直接調用它: 
void _lib_func(); 

通常使用下划線起頭來代表此類函數,但你仍不能完全消除這種隱患。 

虛函數,我認為它的確有可能非常影響性能,我也是很少使用它,但它作為回調的實現方式的確很好,或者是我個人更樂於接受這種形式吧。在父類中實現算法,然后提供一組虛函數,只要子類繼承它並實現那些需要父類在算法過程中來回調的方法,就可以實現回調的目的,這是在C++中一種非常典型的虛函數用法。而作為C,就必須傳遞函數指針,和書寫設置這些函數指針的函數。雖然從本質道理上說,他們是一樣的,但C++的形式更讓人樂於接受。 

對於虛函數在教課書中描述的用法,以虛類指針去操作子類,可以簡化代碼的說法,我倒不喜歡用,而且這樣的情況其實比較少,而且極度影響性能。 

利用模板類來實現數據結構,是一種對使用者來說非常直觀的事情,而傳統的C方法,即定義一個void *指針,實現和使用起來是很羅嗦的,如: 
struct Array 

void *buffer; /* 數據地址 */ 
int bufsize; /* 內存的總字節數 */ 
int typesize; /* 元素的字節長度 */ 
int count; /* 元素的有效個數 */ 
}; 

這種形式有兩個問題:1,訪問元素需要做指針類型轉換,並且沒有了類型檢查,容易出錯;2,如果將數據結構的方法都封裝起來,那么肯定沒有C++的模板類庫快,因為后者大部分函數都是內聯的。 
那么就只有第二種形式,即完全用宏來做,這就是所謂的用宏來實現與C++模板類等價的功能,相信很多人都見過那種代碼,它幾乎可以達到和C++模板類一樣的形式:(注意,這些都是宏) 
lib_DeclareArray(a, int); /* 聲明一個int數組 */ 
lib_Array_Element(a, 0) = 100; /* 訪問第一個元素 */ 

C++模板類的形式:(在編譯后是實例化的真正函數) 
lib::Array<int> a; 
a.Element(0) = 100; 

它甚至可能比C++更快,因為它全是宏,但是它的實現代碼可讀性太差,而且非常難以調試。 
C++的這一優點在於使用模板的直觀和有內聯函數的支持,如果沒有內聯函數,我想我不會選擇使用模板類來實現數據結構。 
 
如果說名字空間,成員函數,自動初始化和摧毀,虛函數的回調,這些其實都不是大問題的話,因為只是C++的形式更好一些,那么對我來說,模板和模板類就是不可或缺的。如果要用C實現和C++一樣的模板機制,那對程序員的技巧要求太高了,而且產生的代碼不僅多而且亂,對於我習慣了用C++的模板,就不會再樂意去使用C的宏技巧。而且C只能使用宏,然后這些宏所代表的代碼大段大段地嵌入調用者的代碼里,使它的函數變得十分龐大,這就好比一個模板類的所有成員函數都必須是內聯函數,而C++是可以選擇的。 
模板還有一個好處,就是只需要為算法寫一份代碼,尤其當算法不太關心或完全獨立於參數的類型時,你不使用這項技術,就要寫很多份代碼。如果類型是有限的,那么還好,你最多就寫那么幾份雷同的函數,你還可以利用宏的技巧,但如果你的函數需要接受不確定的參數類型,或對無限種可能的類型都有效,那么就不是寫多少份代碼的問題,而是你根本就不能完美或完全地實現。 
比如copy一個變量到另一個變量,就這么簡單的算法,用C++只要實現copy構造函數或賦值運算符,就可以做到: 
template<class t> 
void copy(t &a, const t &b) 

a.~t(); 
new (&a) t(b); 
}

如果有利用賦值運算符就更簡單: 
template<class t> 
void copy(t &a, const t &b) 

a = b; 

但如果是C,你就必須指定一個賦值函數的地址: 
void copy(void *pa, const void *pb, void (* func)(void *, const void *)) 

(*func)(pa, pb); 
}

那這個函數就沒有意義了,在C++中寫copy構造函數和賦值運算符是一種默認習慣,但在C里寫一個獨立的復制函數,就不是習慣了。如果沒有模板,大量的此類操作都要通過函數指針傳遞。 
 
異常,如果充分體會返回錯誤代碼和使用異常的區別,那么你就會認為這不是誰替代誰的問題,至少你在C++里就多了一種方式,而在C里你只能: 
int ret = 0; 
if(SUCCESS != (ret = func1())) 
return ret; 
else if(SUCCESS != (ret = func2())) 
return ret; 
else if(SUCCESS != (ret = func3())) 
return ret; 
else 
return SUCCESS; 

看上去很累,而且程序的邏輯看上去很不清晰。 
而C++的形式: 
try 

func1(); 
func2(); 
func3(); 

catch(Exception &e) 

return ERROR; 

你可以選擇catch或不管,讓更上層的函數去捕獲,也可以處理一部分后繼續向上拋,或完全將異常處理掉。而你仍然可以選擇返回錯誤代碼的方式。 
對我來說,這是兩種錯誤處理的方式,我會根據需要而選擇不同的方式,並不是用異常代替所有的返回錯誤代碼。而C語言中也有類似的東西,就是longjmp,所謂的超級goto,它和異常很像,但如果要像C++那樣處理多級異常就會很累,因為longjmp必須在錯誤發生時指定跳轉到哪里去的位置,那么就不能像C++那樣在任何層次上都可以捕獲。不過它也有一個功能是C++的異常做不到的,就是可以跳到更遠的地方去,可以轉到之前的調用順序上,而不僅僅是層次,如: 
func1(); 
func2(); /* 可以再跳回func1 */ 

而C++中func1不可能捕獲func2的異常。因此,C++的異常傳遞順序,是根據函數堆棧的深度,而C的longjmp是根據過程,它可以回到之前走過的任何位置。但我想自己永遠也不可能使用到這個功能。 
 
更重要的,我寫的是庫,是要給人用的,如果一個變長字符串都是一大堆宏,那還怎么叫人用。開發庫就要考慮到使用者的體驗和為使用者節約他的代碼,而不是為他的程序降低可讀性,讓他滿地都是你的宏名,而他自己的邏輯都被你的代碼給淹沒了。我認為在這個方面C++做的很好,他是以現代人的理解去展現你的接口的,在C++中簡潔的東西性能都是快的,而C做不到C++的簡潔,如果要做到最大的性能,卻又一定是復雜的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM