什麼時候用C而不用C++?

前兩天不是有一個問題是「什麼時候用C++而不用C」,我一直覺得問錯了,難道不是「能用C++就不用C」么?那麼當然就要討論什麼時候用C而不用C++啦。

一直以來都嚴格遵循OO的原則來進行開發(用的工具是C#和Qt),直到最近,開始接手某同事的代碼,整個項目20多個小工程(代碼量並不多),除了界面部分用了MFC這種不倫不類的OO以外,所有的代碼都是C寫的。但是模塊化做的非常好。後來跟他討論為何不用C++,他說其實沒有什麼特別的,就是習慣和愛好而已,後又補充:

如果不用多態的話,其實不管怎麼寫,不管用那種語言寫,都算不上真正的OO

忽然覺得很有道理……


1:不能容忍C++的ABI相關問題的時候

2:目標平台上沒有C++編譯器


當你期望把你做的庫移植到很多其他語言的時候,C更具優勢。你也可以底層用C++實現,不過做一層C包裝也是有必要的。

要問為什麼,一門語言實現C FFI的難度和C++ FFI的難度顯然不在一個數量級。。


沒C++可用的時候。

==============================================================

對於還停留在C with class那個年代的人來說,C++也就只能玩玩OOP。如果再學一下設計模式那這一生就徹底毀了,只有Java能拯救。


當你能用一門語言寫好代碼的時候,換用另外一門語言也能寫好代碼,無非是對這門新語言有個熟悉踩坑的過程。多少叫囂著 C++ 是爛語言的人寫出來的 C 代碼不堪入目,只是覺得隨大流的黑一下 C++ 是政治正確(非知乎,知乎上大神太多)。

能寫好 C++ 代碼的人去寫 C 代碼一樣能寫出好代碼,只不過在踩 C 坑的時候會想起 C++ 的改進。同理,寫出一手好 C 代碼的人去寫 C++ 代碼,花上時間熟悉 C++ 的語法,踩了 C++ 的坑之後一樣能寫好代碼,只不過在這個過程中會想起 C 的簡單語法。

至於什麼時候用 C 什麼時候用 C++,在你可以選擇的時候,選擇你喜歡的,在你不可以選擇的時候,適應它並用它寫出好代碼。

當你不能用一門語言寫好代碼的時候換什麼語言都寫不出好代碼。


需要知道每行代碼背後電腦究竟做了什麼的時候


setjmp/longjmp 的時候. 很簡單的一個 RAII 泄漏例子:

vector& a;
a.push_back(3);
longjmp(env, 0);
// 編譯器插入隱藏的析構調用被跳過了

一些語言例如 Ruby 的 raise/yield/throw 就是用長跳實現的, 用 C++ 寫擴展的話, 一不小心就造成內存泄漏了:

vector& a;
a.push_back(3);
rb_funcall(obj, rb_intern("foo"), 0); // 你並不知道 foo 里會不會拋異常長跳

C++ 的 throw 因為也會插入隱藏的析構代碼造成坑, 所以這類情況還是推薦只用 C


因為有些環境沒有靠譜的C++編譯器。當然對於電腦和手機來說,合格的程序員用C++比起C沒有任何多出來的壞處,至少很多C只能用void*亂搞的情況下,C++有好的多的方法。


你開始欣賞到純 C代碼所帶來的 「美感」 了,即簡單性和可拆分性。代碼是自底向上構造,一個模塊只做好一個模塊的事情,任意拆分組合。對於有參考的 OOP系統建模,自頂向下的構造代碼抽象方法是有效率的,是方便的,對於新領域,沒有任何參考時,刻意抽象會帶來額外負擔,並進一步增加系統耦合性,設計調整,往往需要大面積修改代碼。

有興趣你可以讀讀《Unix編程藝術》,OOP的思維模式,是大一統的;C的思維模式,是分離的。前者方便但容易造成高耦合,後者靈活但開發開發太累。用 C開發,應該刻意強調 「簡單」 和 「可拆分」。一個個象搭積木一樣的把基礎系統搭建出來,哪個模塊出問題,局部替換即可。

自底向上的開發模式,並不是從不站在大局考慮問題,而是從某個子系統具體實現開始,從局部迭代,逐步反思全局設計,刻意保持低偶合,一個模塊一個模塊的來,再逐步嘗試組合。

自底向上強調先有實踐,再總結理論,理論反過來指導實踐,又從實踐中迭代修正理論。這和人類認識世界的順序是一樣的,先捕獵築巢,反思自然是怎麼回事,又發現可以生火,又思考自然到底怎麼回事情。

它的反面,是指大一統設計,你一開始用 UML畫出整套系統的類結構,然後再開工設計。這種思維習慣,如果是參考已有系統做一個類似的設計,問題不大,全新設計的話,他總有一個前提,就是 「你能完整認識整個大自然」,就像人類一開始就要認識捕獵和築巢還有取火一樣。否則每次對世界有了新認識,OOP的自頂向下設計方法都能給你帶來巨大的負擔。

所以有些人才會說:OOP設計習慣會依賴一系列設計靈巧的 BaseObject,然而過段時間後再來看你的項目,當其中某個基礎抽象類出現問題是,往往面臨大範圍的代碼調整。這其實就是他們使用自頂向下思維方法,在逐步進入新世界時候,所帶來的困惑。

當然也有人批判這種強調簡單性和可拆分性的 Unix思維。認為世界不是總能保持簡單和可拆分的,他們之間是有各種千絲萬縷聯繫的,你一味的保持簡單性和可拆分性,你會讓別人很累。這裡給你個藥方,底層系統,基礎組建,盡量用 C的方法,很好的設計成模塊,隨著你編程的積累,這些模塊象積木一樣越來越多,而彼此都無太大關係,甚至不少 .c文件都能獨立運行,並沒有一個一統天下的 common.h讓大家去 include,介面其他語言也方便。

然後在你做到具體應用時根據不同的需求,用C++或者其他語言,將他們象膠水一樣粘合起來。這時候,再把你的 common.h,寫到你的 C++或者其他語言裡面去。當然,作為膠水的語言不一定非要是 C++了,也可以是其他語言。

-------------

PS: 這裡主要在探討 OOP存在的問題,並沒有討論嵌入式這種資源限制的情況,以及操作系統和底層等需要精確控制硬體和內存的情況,更沒有討論 C++在語言設計層面的事情。

---


內核驅動


除了寫圖形界面或者寫遊戲,或者模型上類似於界面跟遊戲類的軟體。其他優先選擇C。

比方說你寫個 dir 命令,用C語言應該是比C加加要好的,git這種無界面命令行程序,用C也是很好的。一些實時性嵌入式應用,主循環其實就是個有限狀態自動機的,用C也很好,用C加加沒有什麼優勢。

硬體驅動程序什麼的,C還是主流。用C加加的少。

實際上有些低配置系統甚至連C加加標準庫都放不下的。但是用C還是遊刃有餘。當然這樣的系統這些年越來越少了。

C加加總的來說對技術人員要求還是高些。畢竟C用靜態用全局更多一旦開闢好了空間,內存佔用長期穩定不變,而C加加動態分配多,查內存泄露之類的複雜很多,當然主要還是對嵌入式系統更明顯,桌面系統其實泄露點內存問題不大,因為其進程每次持續運行時間不長,嵌入式系統要7x24小時運行所以內存泄露問題才顯得重要。

大致就這樣了。


感測器底層協議


說C++容易造成高耦合, 因為你不認識Design Pattern.

說C簡單, 他的確很簡單. 但碰到需要高度複雜性的系統(有商業邏輯性的系統/需要分層開發的系統), C的局限性就出來了.

Linux的世界的確C用的很多, 但是請記住, 這個世界不是全部都是Linux, 有更多的工業/商業用的系統必須要用到C++.

C++只要你認識他夠深, 可讀性根本不是C可以比的.

維護性來說, C寫出來的code在一定行數內是還好, 但是當你的code超過一個行數, 你不用C++其實很痛苦.

看過一個function超過萬行的嗎..... 真想給那個同事一棍打下去啊


希望我不是那些少數愛著函數變數前置聲明的人! 一個函數僅做一件事情, 並把它做好. 用 c 寫代碼你永遠不會偏離上面這句哲理, 而前置變數聲明又讓你清晰的明白你的函數究竟幹了什麼, 如果聲明的變數過多, 那麼你會立刻警覺你的函數書寫出問題了. 其他語言因為沒有嚴格的前置變數聲明規定, 你會無約束地寫出複雜度超過控制的函數代碼.

用 c 寫代碼的理由非常多, 但是個人經歷肯定是佔了多數. 會從 cpp 轉型回 c 寫代碼的人應該是有 Zen (禪) 的頓悟, 處於不同維度的人自然難以理解. 當你不再需要用多餘的手法去歸納和理解你想要解決的問題的時候, 你便可以用你那個等級下你認為的最純粹的原語去表達. 所以不同等級下, 對事物的理解需要藉助的思維工具也不一樣, 能夠用更加純粹的原語去書寫問題的解法本身就是對問題更高階的認識.


不管怎麼樣,我就是不喜歡

吃(狗,屎)的寫法

更喜歡

狗.吃(屎)

或者

動物&<狗&> 小白;

小白.吃(屎);

= =


1、C++的運行時不可用時

2、需要生成能夠跨編譯器(比如CL生成模塊卻要用mingw的ld鏈接)進行鏈接的模塊時

3、維護C寫的老代碼時

4、老闆硬性要求時


C++ 有些問題:

  • C++ 編譯器,暗中在代碼中做了很多手腳,導致一行 C++ 代碼產生的代價不可預測。

  • C++ 的的符號鏈接每個編譯器都不一樣。

  • C++ 太複雜,導致編譯運行C++程序耗費的資源也更多。

所以,在一些場合更傾向於用C

  • 需要精確控制每行代碼的行為。比如一些硬體驅動。

  • 需要跟其它語言、模塊交互。比如用C導出介面,給java調用。

  • 一些資源緊缺,跑不起C++環境的場合。比如一些嵌入式系統。

至於性能,C++ 跟 C 的性能本身差不了太遠。但用C語言設計,往往會更加直接。C++設計,會分更多的無用層。就導致實現同樣的功能,C會比C++快一些。這個是設計的問題,並非語言本身的問題。


1.沒有C++等其他編譯器可用的時候。有些dsp,單片機只有C編譯器。

2.沒有GUI,且對效率要求特別高的地方。如一些音視頻編碼器,大多使用純C開發。

3.一些可移植的協議棧。這些不光對效率有要求,而且需要可移植性非常好,需要能夠適配各種系統。純C的可移植性顯然比C++要好。

btw:你同事的觀點不對哦,即使只用到了封裝這一個特性,也可以說是OO了。並不是要把C++的所有OO特性都用全了才能叫OO的。當然,如果連封裝這個特性都不用,那確實不像OO了。


目前我還沒遇到過因為用了C艹而造成性能瓶頸,必須改為C的情況, 於是在我看來性能方面應該不成問題.

問題在於某些嵌入式環境是沒地方讓你裝這個庫那個庫的, 而C就是小巧玲瓏~ 在這裡吊打各種高級語言.

然而真正碼代碼的時候又誰能單獨用C艹而不用C呢? (假如如題主所說,C艹和C重疊部分算C的話)有時候定義一個全局變數就是比單例方便.


接觸過更喜歡C的coder,不知道個人品位算不算一個理由,C++的體量和高級feature不是所有項目都必要的。


這個問題提煉後是"什麼時候 用C 而不用C++中的面向對象OOP",那實際上後者是一種思想,一種模式,跟語言不是一回事.

回答問題:

1 OOP(這裡指動態的多態繼承那套)只是C++一部分而已,按現在C++新標準的解讀(11,14) 委員會更大的心思主要放在元編程,函數式編程支持上對OOP更多的是之前挖坑的修補.

2 關於C++與C,如果這個特徵你覺得不適合不喜歡,那就不用.C++的抽象是內聯的抽象,你可以先用C的想法實現一遍(這種實現熟悉後更多是在腦子裡構想的),然後再看看有沒好的C++特徵可以更好的套用

3 用與不用,關鍵是你的想法是什麼構思是什麼

提主參考下 編程的宗派


推薦閱讀:

C 語言中,a+=1 和 a=a+1、a++ 有區別嗎?
郝斌的 C 語言教程怎麼樣?
為什麼學c語言我只會寫計算程序?
c語言中x*x和pow(x,2)哪個計算更快一點?
2==c會導致的這樣的異常嗎?

TAG:C編程語言 | 面向對象編程 | C |