如今科學工程項目越來越大、越來越復雜,許多項目都采用C 編程語言來完成。本書深入介紹了基于C 編程語言高級功能的復雜方法,旨在幫助您快速入門,實現如表達式模板之類的高級技術。您還將學習如何使用C 編程語言的強大類庫:標準模板庫(STL)以及用于算法、線性代數、微分方程、圖形的科學類庫。書中演示了如何使用面向對象、泛型編程、元編程和過程技術來編寫清晰明了、富有表達力的軟件。當您學完本書,將掌握如何使用C 編程語言來編寫高質量、高性能的軟件。
前言
世界由C (以及它的C子集)構建。
Herb Sutter
Google、Amazon和Facebook的基礎架構很多都由C 構建。此外,相當一部分底層技術也是由C 實現的。在電信領域,幾乎所有固定電話和手機的連接都由C 軟件驅動。最重要的是,德國所有主要傳輸節點都是用C 處理的,這意味著筆者的家庭也依賴于C 軟件。
即便是由其他語言撰寫的軟件也會依賴于C ,因為最流行的編譯器如Visual Studio、Clang、Gnu編譯器包和Intel編譯器都是用C 實現的。Windows平臺上的程序也多由C 實現,如Microsoft Office套件。可以說C 是無所不在的。甚至您的手機和汽車也會包含由C 開發的組件。C 的發明者Bjarne Stroustrup制作了一個網頁,列出了由C 開發的應用清單,上文的例子也多源自于這個網頁。
在科學和工程中,許多高質量的軟件包都是用C 實現的。當項目超過一定大小且數據結構非常復雜的情況下更能凸顯C 的強大能力。這也是為什么大量科學和工程模擬軟件都 使用C 的原因。隨便舉幾個例子,比如該領域的領頭羊Abaqus、deal.II、FEniCS以及OpenFOAM。知名CAD軟件CATIA也由C 開發。得益于更強大的處理器和改進的編譯器, C 在嵌入式系統上的應用也越來越多。當然,并不是所有的現代語言特性和庫都可以用在這些平臺上。
最后,如果一個項目可以重新開發,我們不知道有多少項目會使用C 而不是C實現。例如,作者的好友Matt Knepley是非常成功的科學計算程序庫PETSc的作者之一,如果可以重寫這個庫的話,他會選擇C 來實現它。
學習C 的理由
和其他語言不同,從充分貼近硬件的開發,到高級抽象的開發,C 幾乎都能勝任。底層開發比如用戶定制的內存管理(User defined memory management)可以讓程序員了解程序執行過程中的細節,也能幫助您理解由其他語言開發的程序的行為。利用C 可以編寫執行效率非常高的程序,僅僅可能比使用機器語言編寫的程序慢上一點點,但是后者要付出更多的努力。然而,在死磕性能調優之前(Hardcore Performance Tuning),您應該首先關注如何開發邏輯清晰、表達準確的程序。
而這正是C 高層特性的用武之地。C 直接支持多種編程范式(Programming Paradigms):面向對象編程(Object Oriented Programming)(第6章)、泛型編程(Generic Programming)(第3章)、元編程(Metaprogramming)(第5章)、并發編程(Concurrent Programming)(見4.6節)和過程化編程(Procedural Programming)(見1.5節),等等。C 還發明了好幾種編程技術,例如RAII(見2.4.2.1節)和表達式模板(Expression templates)(見5.3節)。C 的表達能力如此之強,故而經常可以在不改變語言的情況下發明新的技術。也許有一天您也會發明一種新的技術呢。
閱讀本書的理由
本書素材已經經過了多人的檢驗。作者執教課程C for Scientist已有三年,每年兩個學期。這門課的學生多來自于數學系,也有一些來自于物理和工程專業。在課程學習之前,他們通常沒有C 基礎,但在學完這門課程后就可以實現如表達式模板(見5.3節)之類的高級技術。您可以按照自己的節奏閱讀本書:直接跟著本書路線完成學習,或者通過閱讀附錄A,了解更多示例和背景知識。
美女與野獸
編寫C 程序有多種方式,本書將循序漸進地指導讀者使用更加高級的風格。這需要使用到C 的高級特性。這些特性看起來挺嚇人,但是習慣之后,它們不僅應用更加廣泛,也更為高效且易讀。
我們用下面這個例子作為您對C 的第一印象:恒定步長的梯度下降法。原理非常簡單,用其梯度函數例如g(x)來計算f(x)的最陡下降,并且用固定大小的步長追隨這個梯度方向到相鄰的局部最小值。 算法的偽代碼也非常簡單:
算法1:梯度下降算法
輸入:初始值x,步長s,終止條件epsilon,函數f,梯度函數g
輸出:局部最小值x
這個簡單算法我們提供了兩種實現。我們看一下下面代碼并思考一下,不考慮它們的技術細節。
它們看起來非常相似,但我們會告訴您哪一個是我們喜歡的。第一個版本差不多是純C語言,您可以用C編譯器編譯。這個版本的好處就是看起來很直觀,使用double類型的2D函數。但是我們更喜歡第二個版本,因為它用途更廣,具有任意值類型的任意維度函數。令人驚奇的是這個多功能的版本并沒有降低效率。相反,函數F和G可以內聯(見1.5.3節)節省了函數調用開銷,而在左邊版本中顯式的(丑陋的)函數指針使得這種優化變得更困難。
如果您是一個有耐心的讀者,就可以在附錄A中(A.1)找個一個比較新舊樣式的更詳細的例子。附錄中的案例更能體現使用現代風格編程的好處。當然,我們不希望您為這些新舊樣式的小沖突折騰太久。
科學和工程領域的計算機語言
如果所有的數值類軟件都在可以用C 編寫而不損失效率,那自然很好。但在我們找到一種既能高效計算,又不違反C 類型系統的方法之前,還是用Fortran、匯編,或者針對體系結構優化過的方法更好。
Bjarne Stroustrup
科學計算和工程軟件可以使用不同的編程語言編寫,而哪種語言最合適則主要取決于我們的目標和可用的資源。
?當我們僅使用現有算法時,那么最好選擇數學工具軟件如MATLAB、Mathematica、R等。但如果希望在它們的基礎上通過一些細粒度的運算(如標量計算)實現自己的算法,那么我們會發現它的性能將顯著下降。當然如果問題本身很小,或者用戶非常有耐心,這自然不是問題。否則,就需要尋找其他語言來實現我們的問題。
?Python非常適合快速開發。它也包含了大量科學計算的庫,比如scipy和numpy。使用這些庫(這些庫本身通常由C或者C 實現),我們也能開發出很高效的應用程序。和數學工具軟件一樣,細粒度實現的用戶定義算法也會犧牲性能。如果要快速實現中小型任務,Python非常適合。但如果項目足夠大,編譯器的重要性就會逐漸體現出來(例如當參數不匹配時拒絕賦值)。
?Fortran也會適用于工程和科學計算,因為Fortran上擁有大量經過充分優化的操作,如密集矩陣運算。它非常適合完成那些老派教授的作業(這些作業本身很適合Fortran)。而依據作者的經驗,在Fortran中引入新的數據結構非常麻煩,因此在Fortran中編寫一個大規模的模擬程序是個相當大的挑戰在今天,這樣的選題只有少數人能勉為其難地接受。
?C語言有著很好的性能,用C編寫的軟件數量非常多。C的語言核心小,易于學習。使用C語言開發的挑戰在于,需要使用簡陋而危險的語言特性例如指針(見1.8.2節)和宏(見1.9.2.1節)去實現大型的、無Bug的軟件。
?當應用程序的主要組件Web或圖形界面組件沒有特別密集的運算時,Java、C#、PHP這一類的語言是非常好的選擇。
?開發大型、高質量、高性能的應用程序是C 尤為擅長的。并且開發過程也不會十分漫長而痛苦。通過正確的抽象,可以飛快地編寫C 程序。在未來會有更多的科學計算庫出現在C 標準中。
顯然,我們所了解的語言越多,選擇的余地也就越大;對語言了解的越深入,我們做出的選擇也越明智。此外,大型項目也常包含多個用不同語言開發的組件,多數情況下起碼性能關鍵的內核部分都是由C或者C 實現的。總而言之,學習C 一定是一段有趣的過程,深入理解它,一定能幫助您成為一名出色的程序員。
體例
新術語使用斜體(italic)。C 源代碼使用等寬字符(monospace)。一些重要的細節被標記為粗體(boldface)。
類、函數、變量與常量一律使用小寫字母,也可能會用到下劃線。矩陣使用特殊的標記,我們一般使用單個大寫字母來代表矩陣。模板參數和Concepts以大寫開頭,大寫分割(CamelCase)。程序輸出和命令行,使用打字機字體(typewriter font)。
如果程序需要C 3、C 11或者C 14的特性,會用相應的邊框來表示。一部分程序僅使用了少量C 11的特性,且很容易替換成C 3的實現,對于它們我們不再另行標注。
? directory/source_code.cpp。
除了一些特別短小的演示代碼,所有編程實例都至少在一個編譯器上測試過。在段落或小節的開頭,我們會用箭頭指示出所討論的代碼的路徑。
所有程序都作為公有代碼倉庫置放在Github上:
https://github.com/ petergottschling/discovering_modern_cpp。您可以使用以下命令來克隆代碼倉庫:
git clone
https://github.com/petergottschling/discovering_modern_cpp.git。
在Windows上還可以使用更方便的工具TortoiseGit(tortoisegit.org)