如何寫C++而不是C with Class?
如何讓自己的代碼更像是C++那樣優美,而不僅僅是寫有Class的C?
我覺得寫代碼不是為了追求優美的,而是追求最優解。
一些問題適合用 procedural, 一些問題適合 object-oriented,一些問題適合 functional,一些問題適合 meta programming。這些 C++ 都直接支持,但你要按情況選擇。不要因為有了槌子找釘子,也不要 over-engineering。
首先,要明確一個概念,想要用C++寫出優美的代碼,難度很大,迄今為止,方方面面備受好評的通用基礎庫遲遲未曾出現,83年c++面世到現在30多年,所謂的優美代碼的樣子,業界一直也就在探索之中。千萬不要說stl里的那一套就是優美的c++代碼,垃圾allocator,垃圾string,垃圾iostream,垃圾find,find_if,remove,remove_if等等醜醜。更別說boost裡面鋪天蓋地的大倒胃口的template的變態使用,每一次的代碼編譯真要急死人了。功能自然強大,但是難以使用,還代碼冗長,實在談不上優美。標準的准標準的官方庫都這麼垃圾,其他的庫就更不必提了。這裡面跟C++語言本身有很大的原因,另一方面可能也是猿猴的抽象能力跟不上來。template、exception等好東西已經這麼多年了,還有人在擁抱c with class,甚至,完全就拒絕c++的。其他的語言可能有優美一說,更有甚者,java有龐大體系的jdk,C#有.NET。就算是c,也存在很多很多的優美代碼,因為C的抽象能力就是這樣,庫的代碼(printf,scanf,fopen,qsort等等)寫成那樣子,也就是盡頭了,很難再能寫得更好了,換另一種寫法,想要達到同樣的效果(靈活、高性能、通用),都很難很難。可是,C系的標準函數,到了C++的世界,馬上就備受指責,比如,printf,1、類型不安全;2、難以擴展,只有預先定義的數據類型才有資格參與格式化;3、輸出目標也被限制死了,所以不同的輸出目的必須用不同的格式化函數,fprintf用以文件格式化,sprintf用以字元串,有日誌的格式化函數。所以說,C的優美到了C++的世界,馬上就變得很醜陋。問題是,C++提供的一套IO函數(還出自C++老父之手),實在很難讓人滿意,不靈活(更逞論優美),易用倒是易用,也類型安全。當然,C++也有類似printf的格式化操作的第三方庫,但都有這樣那樣的缺陷,綜合下來,好像猿猴都更願意用原生的printf系列函數。
有段時間,本座也很反感C++的繁瑣,複雜,龜速編譯,直到有天,自己也實現了一套類似於printf格式化的IO函數,類型安全,支持擴展(原生類型,複合類型,數組,鏈表,哈希表……),支持不同目的的格式化操作(文件、控制台、字元串、日誌等),支持iostream的&<&<和&>&>操作符重載,都統一在一兩個函數下面,這算不算Facade模式呢,性能還不遜色於原生的sprintf、printf。比如以下代碼,
vector&
AString str = "hello";
double val = 3.14;
Fmt(stdout, "%s %s %s", nums, str, val);
AString abuf;
Fmt(abuf, "%s %s %s", nums, str, val);
WString wbuf;
Fmt(wbuf, "%s %s %s", nums, str, val) &<&< 100; //在格式化後的wbuf後面添加「100」
明白到,C++其實就是一怪胎,很難說它僅僅是語言。更確切地說,本質上,C++就是一台編譯期的解釋器,輸入可見的C++代碼,輸出不可見的C++代碼,比如那些vector&
1、既然是解釋器,就難免會有部分代碼存在類型錯誤或者其他錯誤,但是不要緊,只要錯誤代碼不進入運行期,也即是sfine,而且最後也不會出現在二進位代碼中,而一旦代碼要參與到運行時,必然就觸發編譯錯誤了。所以,極大程度上發揮靜態強類型語言的威力,這一點上,所有的動態語言就自愧不如了。須知,優美的代碼並不是一開始就是優美的樣子,必須歷經成長,一再重構,才慢慢接近優美,有時候的重構,是程序的主體框架都做大修改,連同測試用例。不可否認,靜態強類型語言更適合做快速重構;
2、對底層的操作,對內存最精細的控制,各種各樣豐富多彩的花樣作死能力,比如說將函數指針cast為double類型。很多其他地方必須語言層面提供的數據類型,在C++下,都可以用庫的方式來提供,好比string,好比數組,好比Object,好比tuple等等,當然,這個問題就見仁見智,有些人就堅持這些基本組件必須語言層面上來實現,用庫怎麼搞都吃力不討好。但是,一直以來,C++都堅持不搞新的數據類型,只搞相應的機制來讓用戶創造新的數據類型,比如操作符重載、析構函數、move等,這些機制,除了造已有語言的輪子,更能造不存在的輪子,比如操作虛函數表,操作類的元數據等,這裡面一切皆有可能;
3、堅持零懲罰抽象,這一點就不展開了。二三點也是C++區別於其他編譯期解釋器能力語言的地方。很難說這裡是在追求性能,又很難說不是在追求性能。因為,不管什麼樣子的輪子,性能無論如何都不能差。其實,造string、tuple等等這樣各方各面要求極高的輪子,技術難度其實很大的,更何況C++中又缺乏垃圾回收,還有異常作怪。語言哲學自然有問題,但是,輪子師的水平也要承擔一定的責任,不完全是語言的問題,甚至有人還把語言提供的操作符重載、自動類型轉換、異常等等視為原罪,語言中不提供這些功能,反而是大優點,對此,真不知道該說什麼,謝天謝地,他們還保留了對class的肯定。C++下,很容易做性能優化,但是,C++下,最不應該關心的首先恰恰是性能;最須關心的是開發效率,根本的最重要目的,是以最快的方式把符合需求的代碼寫出來,別瞎考慮性能、通用、擴展等鬼東西。另外,原生提供的new delete,綜合性能已經很好了,以大多數人寫代碼的應用場景,還輪不到拼new delete,搞內存池的必要。
4、預處理,這又是編譯前的解釋器,偽圖靈完備(可以做條件選擇,有限的遞歸或者循環,完全滿足需求了)。批量生成名字相似的變數,批量生成名字相似的函數,大大彌補template的不足。用得好,極大程度上減少重複代碼,創造新的語法糖。你看看,C++03時候,boost硬是用宏實現了foreach的語法形式,就可見其威力。
5、全局變數和靜態變數,溝通編譯器和運行時的橋樑。靜態變數確保了代碼之間的依賴順序,而全局變數保證了對象的構造函數必將在main之前運行,這點強有力的機制的保證,就可以隨時隨地給類型添加新的元數據,修改反射信息。相比之下,C#、java等的反射能力,就顯得功能弱了很多。
但是,這也造就了C++學習曲線的漫長。更因為C++缺乏垃圾回收,堅持零懲罰抽象而導致很薄弱得可憐的運行時信息的登記,這兩者即是優點又是弱點,所以想用C++寫出優美代碼,千難萬難,就算是老手,也要經歷幾十次的大重構,大量的單元測試,才能逐漸接近優美的樣子。其實,能用好C With Class,也就是在原來的c語言要素上再加上一條class,先不玩構造函數和析構函數,其實也就是C++版的object pascal語言,能寫出優美代碼,也都難能可貴了。要用好class也非易事,後面再慢慢嘗試加上RAII,操作符重載,異常,多繼承,template,甚至偽圖靈完備的預處理。總之,標準就是代碼越來越簡潔,越來越好理解,越來越可擴展,類型越來越安全,差不多就開始感覺可以往優美的方向靠了。在此過程中,一定要堅持做單元測試。
本人對於C++的熱愛,早就不再是其運行性能了,而是其無限的世界以及無拘無束的隨心所欲(鄙視rust),當然,在團隊中大規模使用,是大災難!
蟹妖。
1/ C++最重要的是要用好RAII,這樣寫出來的程序就已經很地道了。
2/ OO不濫用的話是個好東西。繼承虛函數之類的,用途明確且有限,你用到的時候自然會用,別沒事像C一樣用什麼函數指針甚至函數指針數組跳來跳去,或者寫什麼if (a.typecode == A) { blahblahblah(); } 這樣的代碼,記得用虛函數適當用用繼承即可。
3/ 模板可以很大程度上減輕你的代碼量,特別是當你有大量不同類型、相似操作的對象的時候。學一學長點記性,等到用的那一天,終於會回想起來被尖括弧戳到死的恐懼。當需要對類型本身做出判斷和篩選以選出合適的模板的時候,自然會掌握到高級的元編程技巧。
4/ Lambda這樣的控制流語法糖,你學一點FP自然就會用的很熟練了。
其它的奇技淫巧比如什麼在C++裡面硬造一個DSL之類的,不懂也就不懂了,沒什麼大不了的。
有空不如多學學Best Practices,就是什麼Effective C++之類,相信我你一定看了後面的就忘了前面,完全掌握的時間基本等於正無窮,Scott Meyers靠講解它們就能活半輩子。
從模仿STL開始,那就是正確的C++使用方法——當你想用虛函數的時候,先想想能不能用模板代替,能你就C++,不能你就C with Class。
不知道題主覺得哪些特性、以及為啥覺得用這些特性寫的C++才優美,class也是很重要的一部分,光C with class已經很不錯了吧
先學 C++,再學 Rust,再重新學一遍 C++,就可以了。
在語言範式之前,先考慮封裝性思路的差異。CwithClass如果能把實現塞到合適的位置問題也不大,本身也不和模板多態衝突。
如果感覺自己在寫CwithClass,推薦一本書:&<C++沉思錄&> Andrew Koenig / Barbara E.Moo。對比一下自己的抽象思路和書里最簡單的基於對象部分的封裝思路。
個人觀點謝絕爭論:我們大多數人都是在C++誕生完善並且有了很多元素以後開始學的,而很多大佬可能是從C一路過來的。所以我們覺得C++很豐富也很難,而很多大佬卻嗤之以鼻。從C過來的都不可避免經歷C with class的過程,畢竟C最顯而易見的缺陷就是對象模型和對應的語言包裝。所以只寫帶Class的C並不算有問題,但是隨著你的深入,C++提供的其他元素就會一點點的浮現出來,有了類,那可以用類包裝數據,然後數據對象可能需要操作符重載,然後寫了某些共性的類,發現有需要操作他們,此時模板就要用上了,虛函數定義介面也得用,如果開始寫點大點的項目就會用STL或者Boost,你就會不可避免的採用更多的C++手法,如果你還需要調用別的庫,如果是個C++的庫,你就會越來越多的採用C++的東西。我的意思是,其實隨著你對C++的了解深入和項目的規模,慢慢會用到越來越多,但是如果你可以做到一直不用,估計你就是高手了。。。
我維護過一個商業C++編譯器的STL庫,而且早期也是模版的愛好者。不過現在我認為C with Classes是比模版更好,而且是好得多的編程模式。除了最關鍵路徑上的對性能最敏感的演算法也許有那麼一點點可能需要用模版,其餘的場合應該只用C with Classes
我非常喜歡這個問題,這說明題主對C++有很好的思考。
在計算機中,從低級語言到高級語言,是一層比一層抽象,一層比一層更接近自然語言。想要從C語言轉到C++,最先要明白的就是C和C++的區別,一個面向過程,一個面向對象。
面向過程比較好理解,在遇到問題時,我們首先想到的是如何解決問題,然後根據我們的經驗,按照既定的步驟一一處理,直到最後將問題解決。例如,元旦快要到了,我想要給老家的爸媽買件衣服。面向過程大致的步驟是這樣的:商場挑選衣服=》買車票=》坐車=》到達目的地=》送給爸媽。如果是面向對象則是:買衣服=》寄件=》快遞到達=》簽收。
從這個例子中,有個很明顯的區別,如果面向過程,你自己會全程參與到問題的解決方案中。如果面向對象,你只需要將自己的需求澄清清楚,然後交給專業的人,等待問題解決,確認即可。而這個專業人,則是你創建的一個個類。
記住:每個類都是一個有「生命」的。
從上面一個簡單的例子看出,其實在我們的生活中一直都是採用面向對象的生活方式的,每個人協同合作,完成社會的大生產。每一個人或物體都為我提供專業服務,飯店為我提供美味的食物,電視機為我提供有趣的節目,汽車幫我更快的到達目的地,每一個事物都可以被看做是一個類,每個類都做著自己最專業的事情,如果把這些類設計並串聯起來,用它們來解決問題,那這就是面向對象編程。
現在,我們再來看類的內部應該如何做,電視機內部會處理各種電信號,視頻音頻信息,圖像處理等,這是內部實現,而內部實現則是面向過程的。
所以,類通過它的介面對外表現為它是一個怎樣的類,一個手機可以充當照相機,因為它提供了照相功能,也可以是音樂播放器,因為它提供了播放功能。所以在類的設計中,要充分考慮這個類用來幹什麼,對外提供怎樣的介面,當我設計完這個類後,我就可以把它放在任何需要它的地方,而不需要我親自去處理每一個環節。當一個類變得「有生命」時,那你的設計就是成功的。
而類的內部,其實是更趨向於面向過程的,正是因為這是在類內部實現的,所以如果介面不改變,那麼你的內部實現可以做各種的優化,這對代碼的維護是非常高效的。
所以就我個人而言,當我設計一個類的功能時,我採用面向對象的思維方式,當我實現它的介面時,更趨向於面向過程。面向過程與面向對象並不衝突,而是兩者互為補充,所以我覺得沒必要必須就只能面向對象編程而對面向過程大加批判,C和C++的思想是互補的,只有兩者結合才會發揮最大的優勢。
最後,不要因為C whith Class而感到困惑,這需要你不斷的在項目實戰中體驗,總結,重構,而最基本的思想恰好相反:Class With C(Class的設計還包括繼承,多態等特性)。
當楊過和小龍女練到玉女心經的最高境界時才發現,原來只有二人情投意合,將全真劍法和古墓劍法合二為一才能發揮其最高威力,即使強如法王這樣的人物也只能是退避三舍。
廣告一下我的專欄,跟小豆君學Qt
更多編程分享可關注公眾號:小豆君,關注後還可加入我的C++/Qt交流群,與更多小夥伴們共同學習。
1.想清楚要不要學好C++的特性並且花上5年以上的精力做項目練手,這個問題比具體怎麼做更重要,因為如果你不是造大型的追求接近榨乾性能的輪子,比如IDE或者某熱門技術的特殊框架這樣的級別的底層建築項目,請考慮生產效率更高的語言,事實上現在在生產效率方面作為賣點,各種語言都在分走c++原有的份額。2.如果編程語言能看做一把劍,那麼帶有特性的C++就是一把以主人的精元和心血來餵養的一把魔劍,一把未傷敵人先傷己的魔劍,要用劍,請先自備好腎和心,毫不誇張的說,C++的性能和靈活就是用編程者的精血換來的,其他語言一樣也吃主人心血,但它最為明顯突出。3.C++委員會那幫人表示:想精通C++?先擼個編譯器先,不然就磨50萬行以上的有效代碼吧,那樣即便不能精通,也能達到一流水平。4.如果看完依然要堅持學好他的特性,答案就在其他的答主的答案中。
我覺得你不如寫CPPwithoutClass,
代碼會更優美一些
首先,寫 C with class 並不可恥。C++ 是一個多範式的語言,C with class、template、meta programming、面向過程、面向對象都是 C++。
編程風格經歷了 C with class =&> STL、template、RAII、funcitonal、lambda =&> C with class STL funcitonal 的過程。
最初寫 C with class 是因為除了這個不會別的。
後來接觸到 STL、template、RAII、funcitonal、lambda 打開了新世界的大門。
再後來為了團隊合作代碼易讀,嚴格限制自己對 C++ 高級技巧的使用。
回答你的問題,如何寫 C with class 之外的 C++?答案是學習。學 STL、template、RAII、funcitonal、lambda 不光學語法,學背後的編程思想,這樣有一天切換到別的語言也能受益於此。
多看看別人寫的代碼比如,你可以考慮入手一本《STL源碼剖析》
請務必忘掉傳統的大學數據結構課上學到的代碼風格。我見過的最樸實的C++代碼,用到的C語言以外唯一特性是struct成員函數。數據結構的初始化是C風格的init()。使用的標準庫函數全部來自C。我似乎看到了這位代碼編寫者的大學之路。於是我浪費了一下午時間,把代碼改成了純C。
優雅是個含混的標準,而強硬區分C++和CwithClass也是一個含糊的標準
寫代碼是為了完成功能,業務需求是第一位的
滿足業務需求/滿足性能要求(不是最高性能)/代碼可讀性/可維護性/可擴展性,這些都比「優雅」的評價要好
當用「滿足業務需求/性能/代碼可讀性/可維護性/可擴展性」來評判C++寫的好不好的時候,是Pure C++還是CwithClass,就看需求了
一個模板可以搞定的多類型函數,寫了很多重複代碼,這就是不C++
同樣,一個簡單的函數需求,非得套個模板class重載一坨,你說這很C++,我說這得TJJTDS,PHP都救不了你
單單一個with class怎麼夠,那當然是再with一個STL啦。
學著走走template metaprogramming這條不歸路
c with class 不就是c++嗎?
c不是c++的真子集嗎?(錯誤)
…………
看知友了回答,我上網查了一下,我的回答錯,c的確有一小部分c++是不兼容的,
那麼,問題的答案就出來了,
替換掉c不被c++兼容的那一小部分,不就是c++了嘛?
這樣,也可也說是,能通過c++編譯器都是在寫c++了,
所以能通過c++編譯器的c with class的代碼本身就是C++了,
這時候如果你覺得不優美,那就是你寫的不優美,原因可能是沒按照規範來寫或者代碼的實現的邏輯不夠好,
( ??? ??)請舉例啥叫像c++一樣優美...
說實話哈,我就從來沒見過生產環境里「優美」的代碼,尤其是c++。幾乎所有的生產代碼,尤其是c++,都滿足以下特徵1. 一點點不多的核心邏輯堆在一個或者幾個大函數里2. 所有地方的邏輯都淹沒在無數debug,log,exception hanlding,霍霍os的代碼里3. 取決於製作者的癖好,不多的一點點邏輯都散布在一大堆不知道為了什麼原因製作出來的,名字上完全看不出來的class hierachy里。或者模版參數上,which is,更恐怖。讀代碼的時候滿眼看到的都是委屈,憤怒,驚恐和無奈。
優美?
所以,孩子你走偏了。我們寫代碼是為了完成任務,升職加薪,優美神馬的,又不能吃,又不可能...你就別想了推薦閱讀:
※編程人常說「精通 C,你想幹啥就幹啥 」這個 C 是指 C++ 還是最原始的沒有類的 C ?
※作為非計算機專業的學生,覺得 C 語言遠比其他語言易於上手,正常嗎?
※怎樣理解C語言是才是代碼的精髓,可以讓你領略不一樣的世界這句話?(其實就是怎麼翻譯成人話-_-#
※C語言C11為什麼選擇`thrd_create`這麼奇怪的命名?
※C和Python我該先學什麼?