C++之父談關于C++的五個需要被重新認識的觀點(上)
概述:學習和使用過C++的人幾乎都曾經聽說過下面的五個關于C++的觀點,并且對這些話篤信不已,那么真實的情況是怎么樣的呢?本文的作者——C++之父Bjarne Stroustrup將會對這些觀點作逐一回擊。</blockquote> </div>
- C++之父談關于C++的五個需要被重新認識的觀點(上)
- C++之父談關于C++的五個需要被重新認識的觀點(中)
</ul> </div>以下的這五個觀點盛行于C++多年:
- “要了解C++,你必須先學習C語言。”
- “C++是一門面向對象的語言。”
- “對于可靠的軟件,垃圾回收機制必不可少。”
- “為了提高效率,你必須編寫底層代碼。”
- “C++只對大型復雜的項目有用。”
如果你還對這些觀點深信不已,那么這篇文章可以給你一些重新認識。這些觀點在特定的時間對于某些人、某些工作來說是正確的。但是對于今天的C++,隨著ISO C++11標準的編譯器和工具的廣泛使用,這些觀點都需要被重新認識。
接下來,我們將會對這些觀點進行逐一反駁。
觀點一:“要了解C++,你必須先學習C語言。”
這不對,事實上對于基礎編程的學習來說C++比C語言容易的多。
C語言雖然幾乎可以認為是C++的子集,但對初學者來說卻不是最容易學習的。因為C語言缺乏標記支持和類型安全,并且對于簡化簡單任務來說C++的標準庫更加易于使用。
比如,對于一個非常簡單的用于描述郵件地址格式的函數:
![]()
它可以被這樣使用:
![]()
而C語言中需要明確的字符操作和明確的內存管理:
![]()
然后,它需要被這樣使用:
![]()
相比之下,哪種版本更加容易學習?哪種語言更加有效率?很顯然是C++了,因為它不需要計算參數字符,不需要為簡短的字符串分配動態內存。
對于C++的學習關于“C語言優先學習”的觀點并非來自少部分人的認識。傳授這種典型觀點的老師主要有以下幾個方面原因:
- 因為這是他們在這方面有豐富經驗。
- 因為這是課程需求。
- 因為這是老師年輕時的學習方式。
- 因為C比C++要小,所以更容易學習。
- 因為學生遲早都必須學習C語言或者C++的C語言子集。
然而,C語言并不是作為優先學習的最簡單和有用的C++子集。當你知道足夠多的C++知識后學習C語言則會非常容易。這種學習方式可以有效減輕從C到C++學習時在認識和技術上的困難。
對于現代C++的教學方法,可以參見我的著作:Programming: Principles and Practice Using C++。它甚至在有一章的結尾處展示了如何學習使用C語言。這種教學方法在幾所大學的數以萬計學生中使用,非常的成功。它的第二版是使用C++11和 C++14來讓學習變得更加容易。
C++11標準使C++更容易被初學者接受,例如,這里是一個元素序列已初始化的vector標準庫:
![]()
在C++98中,我們只能初始化數組和列表。在C++11中,我們可以定義一個包含有{}和需要的任何類型的初始化列表的構造函數。
我們可以通過for循環的范圍來遍歷vector:
![]()
對于v的任何一個元素都會調用一次test()。
for循環的范圍可以遍歷任何序列,因此我們可以通過直接使用初始化列表來簡化示例。
![]()
C++11的目的是使簡單的事情變得簡單。代碼的簡單化并沒有以性能降低為代價。
觀點二:“C++是一門面向對象的語言。”
不對。C++支持面向對象和其它編程風格,它并不僅限于“面向對象”這個狹隘的觀點。它支持一個綜合的編程技術,包括面向對象和泛型編程。通常一個問題的最佳方式需要比較多種類型。最佳,在這里指的是時間最短、最易于理解、最有效率和最易于維護等等。
“C++是一門面向對象的語言”的觀點使人們在除非需要擁有許多虛擬(多態運行)函數的巨大類層次結構時才會考慮使用它。而這種用法對于許多問題來 說是不合適的。這個觀點也會導致另外一些人指責C++的面向對象并不純粹。畢竟,如果把“好”和“面向對象”劃上等號的話,C++還包含了其它被認為是 “不好”的非面向對象的東西。這種觀點產生的兩種認識都會導致人們放棄學習C++。(譯者注:作者表達的意思就是把C++比作是一個賣包子和賣米線的餐 館。將C++認作是包子鋪會讓人產生2種誤會,其一,路過的人會以為這里只賣包子,不賣其它的;其二,愛吃包子的人會認為包子鋪還賣米線,這包子一定做得 不專業)
舉個例子:
![]()
它面向對象嗎?當然,它嚴重依賴包含虛函數的類層次結構。它是泛型編程嗎?當然是,它嚴重依賴于參數化容器(vector)和泛型函數for_each。它是函數式編程嗎?在一定程度上是,它使用了匿名函數(由[]構造)。那么它到底是什么?它是現代C++:C++11。
我同時使用了for循環和標準庫算法for_each只是為了展示其特性。在實際代碼中,我只會使用其中的一個循環。
泛型編程你想讓上面那段代碼更通用嗎?因為畢竟它只適用于vector指針的Shape基類。那么對于列表和內置數組呢?對于象shared_ptr和 unique_ptr這樣的“智能指針”(資源管理指針)呢?對于沒有調用Shape類的對象能夠使用draw()和rotate()么?可以這樣來做:
![]()
你可以使用這段程序對任何序列從頭到尾進行遍歷。這是一個C++風格的標準庫算法。我使用了auto來避免必須為“象Shape類這樣的對象”的接 口類型命名。這是C++11的特性,它的含義是“使用被用于初始化的表達式的類型”。所以由for循環中p的類型就能決定這是什么類型的對象。這種使用 auto表示匿名函數參數類型的方法是現已廣泛使用的一個C++14新特性。
如下圖所示:
![]()
在這里我假定Blob是包含了操作函數draw()和rotate()的圖形化類型,而Container是容器類型。標準庫 list(std::list)擁有成員函數begin()和end(),用于幫助用戶遍歷元素的序列。這是很好很經典的面向對象編程。但是,假如容器不 支持C++標準關于遍歷半開序列[b:e)的概念呢?假如庫里面沒有begin()和end()成員函數呢?或者,由于沒有容器一類的東西因此無法遍歷。 對于這些情況,我們可以用適當的語義來定義獨立的begin()和end()。標準庫提供了C語言風格的數組,因此如果容器是C語言風格的數組,問題就迎 刃而解了——而C語言風格的數組非常常見。
改寫來看看一個更難點的例子,假如容器保留了對象的指針,并且有一個用于訪問和遍歷的不同模型呢?比如,你會訪問到象下面的這個容器:
![]()
這種風格并不少見,我們可以將其映射到[b,e)這樣的一個序列:
![]()
注意,這種修改是無關緊要的:我并沒有修改容器或者某些由C++標準庫支持的將容器映射到模型進行遍歷的容器類的層次結構。這是改寫的一種形式而不是重構。
我選擇這個例子是為了說明這些泛型編程技術并不局限于流行的標準庫。它們也符合常見的“面向對象”的定義,但是它們卻不是面向對象的。
關于C++的代碼一定是面向對象(意味著在每個地方都會使用層次結構和虛函數)的觀點深深地影響了人們對C++性能的評價。還有一些人認為當需要解 決多種類型的運行的問題只有面向對象才是最好的。在以前,我也是這么想的。但是事實上,它也有死板的一面(比如并不是所有相關類型都屬于同一層次結構)并 且虛函數無法作為內聯函數(這就使得處理許多簡單而重要的任務時會多花費大量的時間)。
下一篇將會圍繞“對于可靠的軟件,垃圾回收機制必不可少。”的觀點進行說明……
本文翻譯自Five Popular Myths about C++, Part 1,作者為:C++之父Bjarne Stroustrup
本文譯者為慧都控件網——回憶和感動,轉載請注明:本文轉載自慧都控件網
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!