0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

學(xué)習(xí)C++(以及編程)的原因及方法詳讀

C語(yǔ)言專家集中營(yíng) ? 2017-11-24 16:58 ? 次閱讀

我的blog以前很長(zhǎng)一段時(shí)間關(guān)注的都是C++中的技術(shù)&細(xì)節(jié),乃至于讀者和應(yīng)者都寥寥。然而5月份的時(shí)候?qū)懙囊黄澳銘?yīng)當(dāng)如何學(xué)習(xí)C++”,閱讀量卻達(dá)到了3萬(wàn)多,在blog上所有文章中卻是最高的(且遠(yuǎn)遠(yuǎn)超過(guò)了第二位);評(píng)論數(shù)目也有一百多。為什么獨(dú)獨(dú)這篇能夠激起這么多的回應(yīng),想必是國(guó)內(nèi)的C++社群被C++壓抑太久,或者,嚴(yán)格來(lái)說(shuō),是被C++的教育方式壓抑太久。實(shí)際上,不管是在各大國(guó)內(nèi)論壇上,還是在comp.lang.c++.moderated這樣的國(guó)際C++論壇上,乃至于在douban上的小組內(nèi),有心者都會(huì)發(fā)現(xiàn),對(duì)C++語(yǔ)言的細(xì)節(jié)的關(guān)注一直都沒(méi)有停止過(guò),同樣,對(duì)C++語(yǔ)言的細(xì)節(jié)的抱怨也從來(lái)都沒(méi)有停止過(guò)。一個(gè)例子就是comp.lang.c++.moderated上的一個(gè)技術(shù)牛人James Kanze說(shuō)的,他說(shuō)接觸C++十年了,到現(xiàn)在還需要不時(shí)去翻C++標(biāo)準(zhǔn)。這就難怪Eric Raymond老大在《The Art of Unix Programming》中說(shuō)“C++是反緊湊”的了。C++中的細(xì)節(jié)太多,就算都看過(guò)了,也不可能都記住。更關(guān)鍵的是,就算都記住了,也不能讓你成為一個(gè)真正的好程序員。

絕大多數(shù)人都把細(xì)節(jié)太多(或者用貶義詞來(lái)說(shuō)就是“陰暗角落太多”)歸結(jié)為C++的本質(zhì)問(wèn)題,認(rèn)為一切邪惡由此而生。也正因此,大約9月份的時(shí)候,Linus在郵件列表上說(shuō)“C++是一門(mén)有思想包袱的語(yǔ)言;僅僅是為了讓程序員遠(yuǎn)離C++,我也要用C”。這句短短的話在國(guó)內(nèi)引起了很大的反應(yīng),最初是劉江轉(zhuǎn)了Linus的話,然后云風(fēng)和孟巖都發(fā)表了自己的看法;我也寫(xiě)了一篇“Why C++”(后來(lái)發(fā)給Bjarne,Bjarne對(duì)這篇文章做了一個(gè)友情評(píng)注)。

然而,這一通渾水?dāng)囘^(guò)之后,我相信引起的變化未必很大。大多數(shù)原先的反對(duì)者能從中找出反對(duì)的理由,于是更加反對(duì);大多數(shù)原先的贊同者也能從中找到贊同的理由,于是更加贊同;而剩下來(lái)的原先沒(méi)有明確意見(jiàn)的,看雙方各有各的道理,可能還是沒(méi)有頭緒。

擺脫自我服務(wù)偏見(jiàn)——理性思考的前提

《決策與判斷》上提到過(guò)一個(gè)有趣的真實(shí)故事:1980年的某一天,美國(guó)空戰(zhàn)司令部的計(jì)算機(jī)突然發(fā)出警報(bào)——蘇聯(lián)的一枚核彈正在向美國(guó)本土飛來(lái)。司令部立即調(diào)兵遣將,迅速為一場(chǎng)核戰(zhàn)做好了準(zhǔn)備,然而3分鐘之后,工程人員發(fā)現(xiàn)是計(jì)算機(jī)的一個(gè)小零部件故障造成的。然而,這場(chǎng)虛驚之后,大眾的反應(yīng)才是真正有意思的:原先支持核武裝的,認(rèn)為現(xiàn)在感覺(jué)更加安全了(因?yàn)椤笆聦?shí)證明這類的故障是完全可克服的”);而原先反對(duì)核武裝的則認(rèn)為更不安全了(因?yàn)椤斑@類錯(cuò)誤信號(hào)可能導(dǎo)致蘇聯(lián)過(guò)度反應(yīng),引發(fā)真正的核戰(zhàn)”)。類似的情況也發(fā)生在三里島核泄露事件之后,同樣的,反對(duì)者認(rèn)為(“這表明管理部門(mén)沒(méi)有辦法安全管理核能”),支持者認(rèn)為(“這正表明這樣的危險(xiǎn)沒(méi)有想像得那么嚴(yán)重,是可克服的”)。社會(huì)心理學(xué)把諸如此類的現(xiàn)象總結(jié)為“自我服務(wù)偏見(jiàn)”。不幸的是,“真理越辯越明”其實(shí)只適用于理性思考者。

為什么啰嗦這么一大通呢?就是因?yàn)?,一直以?lái)泛濫于程序員社群的“語(yǔ)言之爭(zhēng)”,背后真正的原因其實(shí)并不在于語(yǔ)言實(shí)質(zhì)上的優(yōu)劣,而在于觀察者的眼睛。在觀察者的眼睛里面,語(yǔ)言并非一門(mén)工具,而是自己花了N多時(shí)間(其中尤數(shù)C++為最)來(lái)“修煉”的技能,對(duì)于這樣的技能,被否定無(wú)疑等同于自己被否定。所以,從心理學(xué)上講,語(yǔ)言并不是工具(盡管一直有這么一種呼吁),而是信仰。這樣的信仰在越是花得時(shí)間久的語(yǔ)言上越是激烈。有趣的是,幾乎所有的“熱鬧”的社群都有這樣的現(xiàn)象,Java、Python、Ruby…莫不如是;因?yàn)榫退阏Z(yǔ)言本身不復(fù)雜,程序員仍然還是要投入大量的精力去學(xué)習(xí)各種各樣的框架類庫(kù)(想想Java的那些框架?)。因此這些語(yǔ)言社區(qū)的信仰未必不比C++社群的強(qiáng)烈。

然而,一旦弄清我們?yōu)槭裁磿?huì)把語(yǔ)言當(dāng)成信仰,就非常有助于擺脫在看待語(yǔ)言時(shí)的“自我服務(wù)偏見(jiàn)”,從客觀的角度去看待問(wèn)題。——“當(dāng)你看到的是支持某個(gè)意見(jiàn)的證據(jù)時(shí),試著去想一想有哪些證據(jù)是不支持它的”。

那么為什么要擺脫自我服務(wù)偏見(jiàn)?說(shuō)小了,是為了成為一個(gè)更優(yōu)秀的程序員(誰(shuí)也不希望因?yàn)槠?jiàn)而去使用一門(mén)低效的語(yǔ)言乃至不妥當(dāng)?shù)恼Z(yǔ)言)。說(shuō)大了是節(jié)省生命(因?yàn)槠?jiàn)可能導(dǎo)致越陷越深,浪費(fèi)時(shí)間)。

所以,如果你能夠理性的思考我們將要討論的問(wèn)題,避免自我服務(wù)偏見(jiàn)(就當(dāng)你從來(lái)沒(méi)有花時(shí)間在C++上一樣)。那么我們便可以開(kāi)始討論真正的問(wèn)題了。

現(xiàn)在,幾乎每個(gè)學(xué)習(xí)C++的都知道C++的核心問(wèn)題是其復(fù)雜性;甚至本身不在C++社群的,也知道這是事實(shí)。群眾的眼睛是雪亮的,何況這還是個(gè)太顯而易見(jiàn)的事實(shí)。

但看了無(wú)數(shù)篇闡述C++復(fù)雜性的文章,和爭(zhēng)論C++復(fù)雜性的吐沫星子(包括我前段時(shí)間寫(xiě)的兩篇關(guān)于C++的總結(jié))。我始終都有一個(gè)感覺(jué)——沒(méi)分析透,就跟盲人摸象一樣。正如“Why C++”的一位讀者批評(píng)的,我在文章里面沒(méi)有寫(xiě)明到底哪些是C++的“非本質(zhì)復(fù)雜性”。當(dāng)然,我自己憑感覺(jué)就能知道,而接觸C++一段時(shí)間的人大致也能知道,但新手乃至非新手則對(duì)我所謂的“非本質(zhì)復(fù)雜性”根本沒(méi)有一個(gè)具體的認(rèn)識(shí),這就使得那篇“Why C++”脫離了原本的意圖——面向所有C++使用者和學(xué)習(xí)者。

同樣的原因,在寫(xiě)了“你應(yīng)當(dāng)如何學(xué)習(xí)C++”一文之后,當(dāng)孟巖先生邀請(qǐng)我給《程序員》寫(xiě)一個(gè)系列的文章,介紹一下我在接觸C++的過(guò)程中的態(tài)度和認(rèn)識(shí)轉(zhuǎn)變時(shí),我雖然非常高興的答應(yīng)了,但直到現(xiàn)在3個(gè)月過(guò)去了還是顆粒無(wú)收。為什么?因?yàn)槲矣X(jué)得真正本質(zhì)的問(wèn)題沒(méi)有被清晰的觸摸到;所以直到現(xiàn)在我都沒(méi)有動(dòng)筆,免得廢話說(shuō)了一大堆,除了能被當(dāng)成小說(shuō)讀讀之外,對(duì)真正考慮是否要學(xué)習(xí)乃至使用C++的人未必有什么實(shí)際用處。

然而,這么個(gè)念頭一直都放在潛意識(shí)里面。前一陣子和Bjarne通信,談到了關(guān)于C++復(fù)雜性的一些想法,在郵件里面總結(jié)了一下C++的復(fù)雜性來(lái)源,感覺(jué)思路清晰了許多。而這篇文章要達(dá)到的目的,正是傳達(dá)對(duì)C++的復(fù)雜性的一個(gè)具體而明確的認(rèn)識(shí),有了這個(gè)認(rèn)識(shí)作為支持,我們便可以推導(dǎo)出學(xué)習(xí)C++的最佳(實(shí)踐者)的方法。

為什么要學(xué)習(xí)(并使用)C++

顯然,如果找不出要學(xué)習(xí)C++的理由,那么談什么“正確的學(xué)習(xí)方法”等于是廢話。

首先重復(fù)一句Bjarne的話:“我們的系統(tǒng)已經(jīng)是極度復(fù)雜的了,為了避開(kāi)C++的復(fù)雜性而干脆不用C++(Linus的做法),無(wú)異于因噎廢食?!痹谒锌捎肅和C++的領(lǐng)域,C++都是比C更好的語(yǔ)言。當(dāng)我說(shuō)“更好的”時(shí)候,我說(shuō)的是C++擁有比C更安全的類型檢查、更好的抽象機(jī)制、更優(yōu)秀的庫(kù)。當(dāng)然,凡事都有例外,如果你做的項(xiàng)目1)不大。2)編碼中用不到什么抽象機(jī)制,甚至ADT(抽象數(shù)據(jù)類型,例如std::complex這種不含多態(tài)和繼承的)也用不到,RAII也用不到,異常也用不到。3)你連基礎(chǔ)庫(kù)(如,簡(jiǎn)化資源管理的智能指針、智能容器)都用不著。那么也許你用C的確沒(méi)問(wèn)題;所以如果你的情況如此,不用和我爭(zhēng)論,因?yàn)槲覠o(wú)法反駁你。我們這里說(shuō)的領(lǐng)域大致是Bjarne在“C++應(yīng)用列表”里面列出來(lái)的那些地方。

底線是:如果把C++中的諸多不必要的復(fù)雜性去掉,留下那些本質(zhì)的,重要的語(yǔ)言特性,簡(jiǎn)化語(yǔ)言模型,消除歷史包袱。即便是C++的反對(duì)者也許也很難找到理由說(shuō)“我還是不用C++”。在我看來(lái),一個(gè)真正從實(shí)踐意義上理性反對(duì)使用C++的人只有一個(gè)理由:C++的復(fù)雜性帶來(lái)的混亂抵消乃至超過(guò)了C++的抽象機(jī)制和庫(kù)(在他的特定項(xiàng)目中)帶來(lái)的好處。

值得注意的是,這里需要避免一個(gè)陷阱,就是一旦人們認(rèn)定了“C++不好”,那么這個(gè)理由就會(huì)“長(zhǎng)出自己的腳來(lái)”,即,就算我們拿掉C++的復(fù)雜性,他們可能也會(huì)堅(jiān)持還是不用C++,并為之找一堆理由。我假定你不是這樣的人。不過(guò),也許最可能的是他會(huì)說(shuō):“問(wèn)題是我們今天用的C++并非如此(簡(jiǎn)潔),你的假設(shè)不成立。”是的,我的假設(shè)不成立。但雖然我們無(wú)法消除復(fù)雜性,我們實(shí)際上是可以容易地避開(kāi)復(fù)雜性,避短揚(yáng)長(zhǎng)的。這也是本文的要點(diǎn),容我后面再詳述。

當(dāng)然,到現(xiàn)在你可能還是會(huì)說(shuō)。我還是不用C++,因?yàn)槲铱梢杂肈;或者如果你本來(lái)做的項(xiàng)目就不需要C++,你則可能會(huì)說(shuō),我用Python。首先,如果你的項(xiàng)目能用Java/Python乃至Ruby做,那么用C++是自討苦吃。因?yàn)槟苡媚切┱Z(yǔ)言代表你的項(xiàng)目在效率上本身要求就不高,那么用一門(mén)效率上討不到太大好處,復(fù)雜性上卻綽綽有余的語(yǔ)言,有什么價(jià)值呢?其次,如果你的項(xiàng)目效率是很重要的,你可能會(huì)說(shuō)可以用D。然而現(xiàn)實(shí)是D在工業(yè)界尤其是國(guó)內(nèi)被運(yùn)用得非常少,幾乎沒(méi)有。而C++卻有大量的既有代碼,已經(jīng)使用C++去做他們的產(chǎn)品公司,在很長(zhǎng)一段時(shí)間之內(nèi)幾乎是不可能用別的語(yǔ)言重寫(xiě)代碼的,正如Joel所說(shuō),決定重寫(xiě)一個(gè)非平凡的代碼基==自殺。所以,我們至少要注意以下兩個(gè)明顯的事實(shí):

事實(shí)1:C++在工業(yè)界仍有穩(wěn)定的核心市場(chǎng)。

這個(gè)事實(shí)大概不需要多加闡述,很多大公司的核心技術(shù)還是要靠C++來(lái)支撐的(見(jiàn)Bjarne主頁(yè)上的C++應(yīng)用列表)。所謂事實(shí),就是未必是大家最愿意承認(rèn)的情況,但又不得不承認(rèn)。C++積累了龐大的代碼基,這個(gè)代碼基不是一朝一夕能夠推翻的。D從語(yǔ)言角度來(lái)說(shuō)的確優(yōu)于C++,但最關(guān)鍵的就是還沒(méi)有深入工業(yè)界(也許根本原因是沒(méi)有錢(qián)支持,但這不是我們討論的重點(diǎn))。而C呢,根據(jù)Bjarne本人的說(shuō)法,他的觀察是主流工業(yè)界的趨勢(shì)一直是“從C到C++”的,而不是反過(guò)來(lái),至少在歐美是如此。在國(guó)內(nèi)我們則可以通過(guò)CSDN上的招聘情況得到一個(gè)大致類似的信息。

事實(shí)2:C++程序員往往能享受到有競(jìng)爭(zhēng)力的薪酬。

是的,這不是一篇不食人間煙火的技術(shù)文章。這個(gè)事實(shí)基于的邏輯很簡(jiǎn)單:物以稀為貴。Andrei Alexandrescu這次來(lái)中國(guó)SD2.0大會(huì)的時(shí)候,在接受采訪時(shí)也說(shuō)過(guò):“最賺錢(qián)的軟件(如MS Office)是C++寫(xiě)的”。孟巖也在blog上提到這么個(gè)事實(shí),我想他作為CSDN的技術(shù)總編,業(yè)界觀察肯定比我清晰深刻。所以我這里就不多廢話了。

當(dāng)然,以上邏輯并不就意味著在慫恿你去學(xué)C++,一切還要看你的興趣。所以如果你志不在C++身處的那些應(yīng)用領(lǐng)域,那這篇文章并非為你而寫(xiě)。

“C++的復(fù)雜性是根本原因”——一個(gè)有漏洞的推理

一旦我們認(rèn)識(shí)了C++在一些領(lǐng)域是有需求的(值得學(xué)習(xí)和掌握的)這個(gè)問(wèn)題之后,就可以接下來(lái)討論“怎樣正確學(xué)習(xí)和掌握C++”這個(gè)核心問(wèn)題了。

其實(shí),對(duì)于這個(gè)問(wèn)題,Bjarne已經(jīng)宣傳了十年。早在99年的時(shí)候Bjarne就寫(xiě)了“Learning C++ as A New Language”,并在好幾篇技術(shù)訪談(這里,這里,這里,還有這里)里面提到如何正確對(duì)待和使用C++中支持的多種抽象機(jī)制的問(wèn)題。Andrew Koenig也寫(xiě)了一本現(xiàn)代C++教程Accelerated C++》(這本書(shū)后面還會(huì)提到)。然而這么多年來(lái),C++社群的狀況改善了嗎?就我所知,就算有改善,也是很小的。學(xué)習(xí)者還是盲目鉆語(yǔ)言細(xì)節(jié),只見(jiàn)樹(shù)木不見(jiàn)森林;網(wǎng)上還是彌漫著各種各樣的“技術(shù)”文章和不靠譜的“學(xué)習(xí)C++的XX個(gè)建議”;一些業(yè)界的有身份的專家還是在一本接一本的出語(yǔ)言孔乙己的書(shū)(寫(xiě)一些普通程序員八輩子用不著的技巧和碰不著的角落);而業(yè)界真正使用C++的公司在面試的時(shí)候還總是問(wèn)一些邊邊角角的細(xì)節(jié)問(wèn)題,而不是考察編程的基本素養(yǎng)(不,掌握所有的語(yǔ)言細(xì)節(jié)也不能讓你成為一個(gè)合格的程序員)。這個(gè)面試?yán)砟钍清e(cuò)誤的,估計(jì)其背后的推理應(yīng)該是“如果這個(gè)家伙不知道這個(gè)細(xì)節(jié),那么估計(jì)他對(duì)語(yǔ)言也熟悉不到哪兒去;而如果他知道,那么雖然他可能并不是好的程序員,但我們還是能夠就后一個(gè)問(wèn)題進(jìn)一步測(cè)試的”,這個(gè)理念的問(wèn)題在于,對(duì)語(yǔ)言熟悉到一定程度(什么程度后面會(huì)具體建議)就已經(jīng)可以很好的編程了(剩下的只需查查文檔);而很多公司在測(cè)試“對(duì)語(yǔ)言熟悉程度”的時(shí)候走得明顯太遠(yuǎn)了(比如,問(wèn)臨時(shí)對(duì)象生命期和析構(gòu)順序當(dāng)然是無(wú)可厚非的,但問(wèn)如何避免一個(gè)類被拷貝或者如何避免其構(gòu)建在堆上?);當(dāng)然,有些語(yǔ)言知識(shí)是必須要提前掌握的,具體有哪些后面會(huì)提到,面試的時(shí)候并非不能問(wèn)語(yǔ)言細(xì)節(jié),關(guān)鍵是“問(wèn)哪些”。

所以說(shuō):

事實(shí)3:C++的整個(gè)生態(tài)圈這么些年來(lái)在學(xué)習(xí)C++的哲學(xué)上,實(shí)在沒(méi)有多少改善。

為什么?是因?yàn)锽jarne介紹的學(xué)習(xí)方法在技術(shù)上沒(méi)有說(shuō)到點(diǎn)子上?是Andrew Koenig的書(shū)寫(xiě)得不夠好?說(shuō)了誰(shuí)也不會(huì)相信。因?yàn)閷?shí)際上,這里的原因根本不是技術(shù)上的,而是非技術(shù)的。

眾所周知的一個(gè)事實(shí)是,從最表層講,C++的最嚴(yán)重問(wèn)題是在語(yǔ)言學(xué)習(xí)階段占用了學(xué)習(xí)者的太多時(shí)間。翻一翻你的C++書(shū)架或者電子書(shū)目錄,絕大多數(shù)的C++“經(jīng)典”都是在講語(yǔ)言。在我們通常的意義上,要“入門(mén)”C++,在語(yǔ)言上需要耗的時(shí)間一般要兩三年。而要“精通”C++,則搞不好需要耗上十年八年的。(這跟Peter Norvig說(shuō)的“十年學(xué)習(xí)編程”其實(shí)不是一回事,人家那是說(shuō)一般意義上的編程技能,不是叫你當(dāng)語(yǔ)言律師。)

那為什么我說(shuō)“C++的復(fù)雜性是根本原因”是個(gè)有漏洞的推理呢?因?yàn)?,要讓人們?cè)谑褂靡婚T(mén)語(yǔ)言去做事情之前耗上大量時(shí)間去學(xué)習(xí)語(yǔ)言中各種復(fù)雜性,除了語(yǔ)言本身的復(fù)雜性的事實(shí)之外,還有一個(gè)重要的事實(shí),那就是學(xué)習(xí)者的態(tài)度和(更重要的)方法。而目前大多數(shù)C++學(xué)習(xí)者的態(tài)度和方法是什么呢?——在真正用C++之前看上一摞語(yǔ)言書(shū)(日常編程八輩子都未必用得到)。而為什么會(huì)存在這樣的學(xué)習(xí)態(tài)度呢?這就是真正需要解釋的問(wèn)題。實(shí)際上,有兩方面的原因:

事實(shí)4:市面上的絕大多數(shù)C++書(shū)籍(包括很多被人們廣泛稱為“必讀經(jīng)典”的)實(shí)際上都是反面教材。

也就是說(shuō),隨便你拿起哪本C++書(shū)籍(包括很多被人們廣泛稱為“必讀經(jīng)典”的),那么有很大的可能這本書(shū)中的內(nèi)容不是你應(yīng)該學(xué)的,而是你不應(yīng)該學(xué)的。我之所以這么說(shuō)有兩個(gè)原因,因?yàn)橐?,我曾?jīng)是受害者。二,也是更實(shí)質(zhì)性的原因,這些所謂的必讀經(jīng)典,充斥的是介紹C++中的陷阱和對(duì)于C++的缺陷的各種workarounds(好聽(tīng)一點(diǎn)叫Idioms(慣用法)或techniques(技術(shù)));又因?yàn)镃++中的這類陷阱和缺陷實(shí)在數(shù)不勝數(shù),所以就拉出了一個(gè)“長(zhǎng)尾”;這類書(shū)籍在所有語(yǔ)言中都存在(“C缺陷和陷阱”、“Effective Java”、“Effective C#”等等),然而在C++里面這個(gè)尾巴特別長(zhǎng),導(dǎo)致這類書(shū)數(shù)不勝數(shù)。三,這些書(shū)中列出來(lái)的缺陷和陷阱根本不區(qū)分常見(jiàn)程度,對(duì)于一個(gè)用本程序員來(lái)說(shuō),應(yīng)該希望看到“從最常見(jiàn)的問(wèn)題到最不常見(jiàn)的問(wèn)題”這樣的順序來(lái)羅列內(nèi)容,然而這些書(shū)里面要么全部混在一起,要么按照“資源管理、類設(shè)計(jì)、泛型”這樣的技術(shù)分類來(lái)介紹內(nèi)容,這根本毫無(wú)幫助(如果我看到一個(gè)章節(jié)的內(nèi)容,我當(dāng)然知道它講的是類設(shè)計(jì)還是資源管理,還用廢話么?),使得一個(gè)學(xué)習(xí)者無(wú)法辨別并將最重要的時(shí)間花在最常見(jiàn)的問(wèn)題之上。

最最關(guān)鍵的是:這些書(shū)當(dāng)中介紹的內(nèi)容與成為一個(gè)好程序員根本毫無(wú)關(guān)系,它們頂多只能告訴你——嗨,小心跌入這個(gè)陷阱。或者告訴你——嗨,你知道當(dāng)你(八輩子都不一定遇到)遇到這個(gè)需求的時(shí)候,可以通過(guò)這個(gè)技巧來(lái)得以解決嗎?結(jié)果讀了一本又一本之后,你腦袋里除了塞滿了“禁止”、“警戒”、“燈泡”符號(hào)之外,真正的編程素質(zhì)卻是一無(wú)長(zhǎng)進(jìn)。又或者有這樣一類書(shū),熱衷于解釋語(yǔ)言實(shí)現(xiàn)背后的機(jī)制,然而語(yǔ)言特性本質(zhì)上是干嘛用的?是用來(lái)在實(shí)際編碼中進(jìn)行抽象的(說(shuō)得好聽(tīng)一點(diǎn)就是“設(shè)計(jì)”),不是用來(lái)告訴你這個(gè)特性是怎么支持的。比如我就見(jiàn)過(guò)以下的情景:面試官問(wèn):“你知道虛函數(shù)嗎?”得到的回答是一堆關(guān)于虛函數(shù)表機(jī)制的解釋。面試官又問(wèn):“那虛函數(shù)的好處是什么呢?”到底為什么要虛函數(shù)呢?得到的回答是:“恩…啊…就是…多態(tài)吧”(這時(shí)已經(jīng)覺(jué)得回答不夠深刻了)。再問(wèn):“那多態(tài)是干嘛的呢?”啞口無(wú)言。

事實(shí)5:就算記住一門(mén)語(yǔ)言的所有細(xì)節(jié)也不能讓你成為一個(gè)合格的程序員。

事實(shí)6:了解語(yǔ)言實(shí)現(xiàn)固然有其實(shí)踐意義(在極端場(chǎng)合的hack手法,以及出現(xiàn)底層bug的時(shí)候迅速定位問(wèn)題),然而如果為了了解語(yǔ)言機(jī)制而去了解語(yǔ)言機(jī)制便脫離了學(xué)習(xí)語(yǔ)言的本意了。

在C++里面這樣的情況很多見(jiàn):知道了語(yǔ)言實(shí)現(xiàn)的底層機(jī)制,卻不知道語(yǔ)言特性本身的意義在什么地方。本末倒置。為什么?書(shū)害的。二,這類書(shū)當(dāng)中介紹的所有情景加起來(lái)其實(shí)只屬于那20%(二八法則),甚至20%都不到的場(chǎng)景(究竟是哪些書(shū),后面會(huì)介紹,我不便直接列出書(shū)名,打擊面太大,但我會(huì)把我認(rèn)為essential的書(shū)列出來(lái))。這就是為什么我說(shuō)“八輩子都用不著”的原因。

事實(shí)7:80%的C++書(shū)籍(包括一些“經(jīng)典”)只涉及到20%(或者更少)的場(chǎng)景。

你可能會(huì)說(shuō),那難道這些書(shū)就根本不值得看了嗎?

我的回答是,對(duì)。根本不值得看。——但是值得放在旁邊作為必要的時(shí)候的參考(記住從索引或目錄翻起,只看嚴(yán)格必要的部分),如果你是個(gè)嚴(yán)肅的程序員的話。因?yàn)椴还艹姓J(rèn)與否,墨菲法則的強(qiáng)大力量是不可忽視的——如果有一個(gè)可能遇到的陷阱,那么總會(huì)遇到的。而同樣,C++的那些奇技淫巧也并非空穴來(lái)風(fēng),總有時(shí)候會(huì)需要用到的。但是你不需要預(yù)先把C++的所有細(xì)節(jié)和技巧存在腦子里才能夠去編程,即:

建議1:有辨別力地閱讀(包括那些被廣泛稱為“經(jīng)典”的)C++書(shū)籍。

如果書(shū)中介紹的某塊內(nèi)容你認(rèn)為在日常編程中基本不會(huì)用到(屬于20%場(chǎng)景),那么也許最好的做法是非常大概的瀏覽一下,留個(gè)印象,而不是順著這條線深究下去。關(guān)于在初學(xué)的時(shí)候應(yīng)該讀哪些書(shū),后面還會(huì)提到。

實(shí)際上,除了語(yǔ)言無(wú)關(guān)的編程修養(yǎng)之外(需要閱讀什么書(shū)后面會(huì)提到),對(duì)于C++這門(mén)特定的語(yǔ)言,要開(kāi)始用它來(lái)編程,你只需知道一些基礎(chǔ)但重要的語(yǔ)言知識(shí)(需要閱讀哪些書(shū)后面會(huì)提到)以及“C++里面有許多缺陷和陷阱”的事實(shí),并且——

建議2:養(yǎng)成隨時(shí)查閱資料和文檔的習(xí)慣。

“查文檔”幾乎可以說(shuō)是作為一個(gè)程序員最重要的能力(是的,能力)了;它是如此重要,以至于在英文里面有一個(gè)專門(mén)的縮寫(xiě)——RTFM。為什么這個(gè)能力如此重要,原因很簡(jiǎn)單:編程領(lǐng)域的知識(shí)太雞零狗碎了。不僅知識(shí)量巨大,而且知識(shí)的細(xì)節(jié)性簡(jiǎn)直是任何學(xué)科都無(wú)與倫比的(隨便找一個(gè)框架類庫(kù)看看它的API文檔吧)。所以,把如此巨量的信息預(yù)先放在腦子里不僅不實(shí)際,而且簡(jiǎn)直是自作孽。你需要的是“元能力”,也就是查文檔的能力——從你手頭遇到的問(wèn)題開(kāi)始,進(jìn)行正確合理的分析,預(yù)測(cè)問(wèn)題的解決方案可能在什么地方,找到關(guān)于后者的資料,閱讀理解,運(yùn)用。

同樣,在C++中也是如此,如果你從學(xué)習(xí)C++一開(kāi)始就抱著這種態(tài)度的話,那么即便等到面試的時(shí)候被問(wèn)到某個(gè)語(yǔ)言細(xì)節(jié),你也可以胸有成竹的說(shuō)你雖然并不知道這個(gè)細(xì)節(jié),但在實(shí)際編碼中遇到相應(yīng)問(wèn)題的時(shí)候肯定會(huì)找到合適的參考資料并很快解決問(wèn)題(解決問(wèn)題,才是最終目的)。當(dāng)然,更大的可能性是,你在平常編碼中已經(jīng)接觸過(guò)了最常見(jiàn)的那80%的陷阱和技巧了,由于你用的是實(shí)踐指導(dǎo)性的學(xué)習(xí)方式,所以你遇到的需要去學(xué)習(xí)的陷阱和技巧幾乎肯定都是常見(jiàn)場(chǎng)景下的,比沒(méi)頭蒼蠅似的逮住一本C++“經(jīng)典”就“細(xì)細(xì)研讀”的辦法要高效N倍,因?yàn)樵跊](méi)有實(shí)踐經(jīng)驗(yàn)的情況下,你很可能會(huì)認(rèn)為其中的每個(gè)技巧,每個(gè)陷阱,都是同樣概率發(fā)作的。

為什么市面上的C++書(shū)熱衷于那些細(xì)節(jié)和技巧呢?

你用一個(gè)天生用來(lái)開(kāi)啤酒瓶的工具開(kāi)了啤酒瓶,不但啥成就感也沒(méi)有,而且誰(shuí)也不會(huì)覺(jué)得你牛13。然而,如果你發(fā)明了一種用兩根筷子也能打開(kāi)啤酒瓶的辦法,或者你干脆生就一口好牙可以把瓶蓋啃開(kāi),那也許就大不一樣了。人家就會(huì)覺(jué)得你很好很強(qiáng)大。

事實(shí)8:每個(gè)人都喜歡戴著腳鐐跳舞。

也就是說(shuō),如果你用一個(gè)天生為某個(gè)目的的工具來(lái)做他該做的事情,沒(méi)有人會(huì)喝彩,你也不會(huì)覺(jué)得了不起。但如果你用兩個(gè)本身不是為某個(gè)目的的工具組合出新功能的話,你就是“創(chuàng)新”者(盡管也許本來(lái)就有某個(gè)現(xiàn)成的工具可用)。

而C++則是這些“創(chuàng)新”的土壤,是的,我說(shuō)的就是無(wú)窮無(wú)盡的workarounds和慣用法。但問(wèn)題是,這些“創(chuàng)新”其實(shí)根本不是創(chuàng)新,你必須認(rèn)識(shí)到的是,他們都只不過(guò)是在沒(méi)有first-class解決方案的前提下不得已折騰出來(lái)的替補(bǔ)方案。是的,它們某種程度上的確可以叫創(chuàng)新,甚至研究可行的解決方案本身也是一件非常有意思的事情,但——

事實(shí)9:我知道它們很有趣,但實(shí)際上它們只是補(bǔ)丁方案。

是的,不要因?yàn)檫@些“創(chuàng)新”方案有趣就忍不住一頭鉆進(jìn)去。你之所以覺(jué)得有趣是因?yàn)楫?dāng)你一定程度上熟悉了C++之后,C++的所有一切,包括缺陷,對(duì)你來(lái)說(shuō)就成了一個(gè)“既定事實(shí)”,一個(gè)背景,一個(gè)習(xí)以為常的東西(人是有很強(qiáng)的適應(yīng)性的)。因此,當(dāng)你發(fā)現(xiàn)在這個(gè)習(xí)以為常的環(huán)境下居然出現(xiàn)了新的可能性時(shí),你當(dāng)然是會(huì)歡呼雀躍的(比如我當(dāng)年讀《Modern C++ Design》的時(shí)候就有一次從早讀到晚,午飯都沒(méi)吃),然而實(shí)際上呢?其它語(yǔ)言中也許早就有first-class的支持了,其它語(yǔ)言也許根本不需要這個(gè)慣用法,因?yàn)樗鼈兙蜎](méi)有這些缺陷。此外,從實(shí)踐的角度來(lái)說(shuō),更重要的是,這些“解決方案”也許你平時(shí)編程根本就用不到。

不,我當(dāng)然不是說(shuō)這些補(bǔ)丁方案不重要。正如前面所說(shuō),C++中繁雜的技巧并非空穴來(lái)風(fēng),總有實(shí)際問(wèn)題在背后驅(qū)動(dòng)的。但問(wèn)題是,對(duì)于我們?nèi)粘>幊虂?lái)說(shuō),這些“實(shí)際問(wèn)題”簡(jiǎn)直是八桿子打不著的。犯不著先費(fèi)上80%的勁兒把20%時(shí)候才用到的東西揣在腦子里,用的時(shí)候查文檔或書(shū)就行了。

看到這里,塑造C++中特定的心態(tài)哲學(xué)的另一個(gè)原因想必你也已經(jīng)知道了。實(shí)際上,這個(gè)原因才是真正根本的。前面說(shuō)的一個(gè)原因是C++書(shū)籍市場(chǎng)(教育)造就的,然而為什么人們喜歡寫(xiě)這些書(shū)呢?進(jìn)一步說(shuō),為什么人們喜歡讀這些書(shū)呢?(我承認(rèn),我也曾經(jīng)讀得津津有味。)答案很簡(jiǎn)單:心理。每個(gè)人都喜歡戴著腳鐐跳舞(事實(shí)8)。認(rèn)識(shí)到這一點(diǎn)不是為了提倡它,而是只有當(dāng)我們認(rèn)識(shí)到自己為什么會(huì)津津有味地去鉆研一堆補(bǔ)丁解決方案的時(shí)候,我們才真正能夠擺脫它們的吸引。

總而言之,C++的復(fù)雜性只是一個(gè)必要條件,并非問(wèn)題的根本癥結(jié)。根本癥結(jié)在于人的心理,每個(gè)人都喜歡戴著腳鐐跳舞,并且以為是“創(chuàng)新”。意識(shí)到這一點(diǎn)之后可以幫我們避免被各種各樣名目繁多的語(yǔ)言細(xì)節(jié)和技巧占去不必要的時(shí)間。

然而,C++的復(fù)雜性始終是一個(gè)不可回避的現(xiàn)實(shí)。C++中有大量的陷阱和缺陷,后者導(dǎo)致了數(shù)目驚人的慣用法和workarounds。不加選擇的全盤(pán)預(yù)先學(xué)習(xí),是非常糟糕的做法,不僅低效,而且根本沒(méi)有必要,實(shí)在是浪費(fèi)生命。愛(ài)因斯坦曾經(jīng)說(shuō)過(guò),“我只想知道‘他’(宇宙)的設(shè)計(jì)理念,其它的都是細(xì)節(jié)”。然而,正如另一些讀者指出的,如果對(duì)C++中的這些細(xì)節(jié)事先一點(diǎn)都沒(méi)有概念的話,那么實(shí)際編碼中一旦遇到恐怕就變成沒(méi)頭蒼蠅了,也許到哪里去RTFM都不知道。這也是為什么那么多C++面試都會(huì)不厭其煩地問(wèn)一些有代表性的語(yǔ)言細(xì)節(jié)的原因。

把細(xì)節(jié)全盤(pán)裝在腦子里固然不好,但對(duì)細(xì)節(jié)一無(wú)所知同樣也不是個(gè)辦法。那么對(duì)于C++程序員來(lái)說(shuō),在學(xué)習(xí)中究竟應(yīng)該以怎樣的態(tài)度和學(xué)習(xí)方法來(lái)對(duì)付C++的復(fù)雜性呢?其實(shí)答案也非常簡(jiǎn)單,首先有一些很重要&必須的語(yǔ)言細(xì)節(jié)&特性是需要掌握的,然后我們只需知道在C++中大抵有哪些地方有復(fù)雜性(陷阱、缺陷),那么遇到問(wèn)題的時(shí)候自然能夠知道到哪兒去尋找答案了。具體的建議在后文。

C++的復(fù)雜性分類

本來(lái)這一節(jié)是打算做成一個(gè)C++復(fù)雜性索引的,然而一來(lái)C++的復(fù)雜性太多,二來(lái)網(wǎng)上其實(shí)已經(jīng)有許多資料(比如Bjarne Stroustrup本人的C++ Technical FAQ就是一個(gè)很好的文檔),加上市面上的大多數(shù)C++書(shū)里面也不停的講語(yǔ)言細(xì)節(jié);因此實(shí)際上我們不是缺乏資料,而是缺乏一種索引這些資料的辦法,以及一種掌控這些復(fù)雜性的模塊化思維方法。

由于以上原因,這里并不詳細(xì)羅列C++的復(fù)雜性,而是提供一個(gè)分類標(biāo)準(zhǔn)。

C++的復(fù)雜性有兩種分類辦法,一是分為非本質(zhì)復(fù)雜性和本質(zhì)復(fù)雜性;其中非本質(zhì)復(fù)雜性分為缺陷和陷阱兩類。另一種分類辦法是按照?qǐng)鼍胺诸悾簬?kù)開(kāi)發(fā)場(chǎng)景下的復(fù)雜性和日常編碼的復(fù)雜性。從從事日常編碼的實(shí)踐者的角度來(lái)說(shuō),采用后一種分類可以讓我們迅速掌握80%場(chǎng)景下的復(fù)雜性。

二八法則

以下通過(guò)列舉一些常見(jiàn)的例子來(lái)解釋這種分類標(biāo)準(zhǔn):

80%場(chǎng)景下的復(fù)雜性:

1. 資源管理(C++日常復(fù)雜性的最主要來(lái)源):深拷貝&淺拷貝;類的四個(gè)特殊成員函數(shù);使用STL;RAII慣用法;智能指針等等。

2. 對(duì)象生命期:局部&全局對(duì)象生存期;臨時(shí)對(duì)象銷毀;對(duì)象構(gòu)造&析構(gòu)順序等等。

3. 多態(tài)

4. 重載決議

5. 異常(除非你不用異常):棧開(kāi)解(stack-unwinding)的過(guò)程;什么時(shí)候拋出異常;在什么抽象層面上拋出異常等等。

6. undefined&unspecified&implementation defined三種行為的區(qū)別:i++ + ++i是undefined behavior(未定義行為——即“有問(wèn)題的,壞的行為,理論上什么事情都可能發(fā)生”);參數(shù)的求值順序是unspecified(未指定的——即“你不能依賴某個(gè)特定順序,但其行為是良好定義的”);當(dāng)一個(gè)double轉(zhuǎn)換至一個(gè)float時(shí),如果double變量的值不能精確表達(dá)在一個(gè)float中,那么選取下一個(gè)接近的離散值還是上一個(gè)接近的離散值是implementation defined(實(shí)現(xiàn)定義的——即“你可以在實(shí)現(xiàn)商的編譯器文檔中找到說(shuō)明”)。這些問(wèn)題會(huì)影響到你編寫(xiě)可移植的代碼。

(注:以上只是一個(gè)不完全列表,用于演示該分類標(biāo)準(zhǔn)的意義——實(shí)際上,如果我們只考慮“80%場(chǎng)景下的復(fù)雜性”,記憶和學(xué)習(xí)的負(fù)擔(dān)便會(huì)大大減小。)

20%場(chǎng)景下的復(fù)雜性:

1. 對(duì)象內(nèi)存布局

2. 模板:偏特化;非類型模板參數(shù);模板參數(shù)推導(dǎo)規(guī)則;實(shí)例化;二段式名字查找;元編程等等。

3. 名字查找&綁定規(guī)則

4. 各種缺陷以及缺陷衍生的workarounds(C++書(shū)中把這些叫做“技術(shù)”):不支持concepts(boost.concept_check庫(kù));類型透明的typedef(true-typedef慣用法);弱類型的枚舉(強(qiáng)枚舉慣用法);隱式bool轉(zhuǎn)換(safe-bool慣用法);自定義類型不支持初始化列表(boost.assign庫(kù));孱弱的元編程支持(type-traits慣用法;tag-dispatch慣用法;boost.enable_if庫(kù);boost.static_assert庫(kù));右值缺陷(loki.mojo庫(kù));不支持可變數(shù)目的模板參數(shù)列表(type-list慣用法);不支持native的alignment指定。

(注:以上只是一個(gè)不完全列表。你會(huì)發(fā)現(xiàn),這些細(xì)節(jié)或技術(shù)在日常編程中極少用到,尤其是各種語(yǔ)言缺陷衍生出來(lái)的workarounds,構(gòu)成了一個(gè)巨大的長(zhǎng)尾,在無(wú)論是C++的書(shū)還是文獻(xiàn)中都占有了很大的比重,作者們稱它們?yōu)榧夹g(shù),然而實(shí)際上這些“技術(shù)”絕大多數(shù)只在庫(kù)開(kāi)發(fā)當(dāng)中需要用到。)

非本質(zhì)復(fù)雜性&本質(zhì)復(fù)雜性

此外,考慮另一種分類辦法也是有幫助的,即分為非本質(zhì)復(fù)雜性和本質(zhì)復(fù)雜性。

非本質(zhì)復(fù)雜性(不完全列表)

1. 缺陷(指能夠克服的問(wèn)題,但解決方案很笨拙;C++的書(shū)里面把克服缺陷的workarounds稱作技術(shù),我覺(jué)得非常誤導(dǎo)):例子在前面已經(jīng)列了一堆了。

2. 陷阱(指無(wú)法克服的問(wèn)題,只能小心繞過(guò);如果跌進(jìn)去,那就意味著你不知道這個(gè)陷阱,那么很大可能性你也不知道從哪去解決這個(gè)問(wèn)題):一般來(lái)說(shuō),作為一個(gè)合格的程序員(不管是不是C++程序員),80%場(chǎng)景下的語(yǔ)言陷阱是需要記住才行的。比如深拷貝&淺拷貝;基類的析構(gòu)函數(shù)應(yīng)當(dāng)為虛;缺省生成的類成員函數(shù);求值順序&序列點(diǎn);類成員初始化順序&聲明順序;導(dǎo)致不可移植代碼的實(shí)現(xiàn)相關(guān)問(wèn)題等。

本質(zhì)復(fù)雜性(不完全列表)

1. 內(nèi)存管理

2. 對(duì)象生命期

3. 重載決議

4. 名字查找

5. 模板參數(shù)推導(dǎo)規(guī)則

6. 異常

7. OO(動(dòng)態(tài))和GP(靜態(tài))兩種范式的應(yīng)用場(chǎng)景和交互

總而言之,這一節(jié)的目的是要告訴你從一個(gè)較高的層次去把握C++中的復(fù)雜性。其中最重要的一個(gè)指導(dǎo)思想就是在學(xué)習(xí)的過(guò)程中注意你正學(xué)習(xí)的技術(shù)或細(xì)節(jié)到底是80%場(chǎng)景下的還是20%場(chǎng)景下的(一般來(lái)說(shuō),讀完兩本書(shū)——后面會(huì)提到——之后你就能夠很容易的對(duì)此進(jìn)行判斷了),如果是20%場(chǎng)景下的(有大量這類復(fù)雜性,其中尤數(shù)各種各樣的workarounds為巨),那么也許最好的做法是只記住一個(gè)大概,不去作任何深究。此外,一般來(lái)說(shuō),不管使用哪門(mén)語(yǔ)言,認(rèn)識(shí)語(yǔ)言陷阱對(duì)于編程來(lái)說(shuō)都是一個(gè)必要的條件,語(yǔ)言陷阱的特點(diǎn)是如果你掉進(jìn)去了,那么很大可能意味著你本來(lái)就不知道這有個(gè)陷阱,后者很大可能意味著你不知道如何解決。

學(xué)習(xí)C++:實(shí)踐者的方法

在上面寫(xiě)了那么多之后,如何學(xué)習(xí)C++這個(gè)問(wèn)題的答案其實(shí)已經(jīng)很明顯了。我們所欠缺的是一個(gè)書(shū)單。

第一本

如果你是一個(gè)C++程序員,那么很大的可能性你會(huì)需要用到底層知識(shí)(硬件平臺(tái)架構(gòu)、緩存、指令流水線、硬件優(yōu)化、內(nèi)存、整數(shù)&浮點(diǎn)數(shù)運(yùn)算等);這是因?yàn)閮蓚€(gè)主要原因:一,了解底層知識(shí)有助于寫(xiě)出高效的代碼。二,C++這樣的接近硬件的語(yǔ)言為了降低語(yǔ)言抽象的效率懲罰,在語(yǔ)言設(shè)計(jì)上作了很多折衷,比如內(nèi)建的有限精度整型和浮點(diǎn)型,比如指針。這就意味著,用這類語(yǔ)言編程容易掉進(jìn)Joel所謂的“抽象漏洞”,需要你在語(yǔ)言提供的抽象層面之下去思考并解決遇到的問(wèn)題,此時(shí)的底層知識(shí)便能幫上大忙。因此,一本從程序員(而不是電子工程師)的角度去介紹底層知識(shí)的書(shū)會(huì)非常有幫助——這就是推薦《Computer Systems:A Programmers Perspective》(以下簡(jiǎn)稱CSAPP)(中譯本《深入理解計(jì)算機(jī)系統(tǒng)》)的原因。

第三本(是的,第三本)

另一方面,C++不同于C的一個(gè)關(guān)鍵地方就在于,C++在完全保留有C的高效的基礎(chǔ)上,增添了抽象機(jī)制。而所謂的“現(xiàn)代C++風(fēng)格”便是倡導(dǎo)正確利用C++的抽象機(jī)制和這些機(jī)制構(gòu)建出來(lái)的現(xiàn)代C++庫(kù)(以STL為代表)的,Bjarne也很早就倡導(dǎo)將C++當(dāng)作一門(mén)不同于C的新語(yǔ)言來(lái)學(xué)習(xí)(就拿內(nèi)存管理來(lái)說(shuō),使用現(xiàn)代C++的內(nèi)存管理技術(shù),幾乎可以完全避免new和delete),因此,一本從這個(gè)思路來(lái)介紹C++的入門(mén)書(shū)籍是非常必要的——這就是推薦《Accelerated C++》的原因(以下簡(jiǎn)稱AC++)?!禔ccelerated C++》的作者Andrew Koenig是C++標(biāo)準(zhǔn)化過(guò)程中的核心人物之一。

第二本

C++是在C語(yǔ)言大行其道的歷史背景下發(fā)展起來(lái)的,在一開(kāi)始以及后來(lái)的相當(dāng)長(zhǎng)一段時(shí)間內(nèi),C++是C的超集,所有C的特性在C++里面都有,因此導(dǎo)致了大量后來(lái)的C++入門(mén)書(shū)籍都從C講起,實(shí)際上,這是一個(gè)誤導(dǎo),因?yàn)镃++雖然是C的超集,然而用抽象機(jī)制擴(kuò)展C語(yǔ)言的重大意義就在于用抽象去覆蓋C當(dāng)中裸露的種種語(yǔ)言特性,讓程序員能夠在一個(gè)更自然的抽象層面上編程,比如你不是用int*加一個(gè)數(shù)組大小n來(lái)表示一個(gè)數(shù)組,而是用可自動(dòng)增長(zhǎng)的vector;比如你不是用malloc/free,而是用智能指針和RAII技術(shù)來(lái)管理資源;比如你不是用一個(gè)只包含數(shù)據(jù)的結(jié)構(gòu)體加上一組函數(shù)來(lái)做一個(gè)暴露的類,而是使用真正的ADT。比如你不是使用second-class的返回值來(lái)表達(dá)錯(cuò)誤,而是利用first-class的語(yǔ)言級(jí)異常機(jī)制等等。然而,C畢竟是C++的源頭,剝開(kāi)C++的抽象外衣,底層仍然還是C;而且,更關(guān)鍵的是,在實(shí)際編碼當(dāng)中,有時(shí)候還的確要“C”一把,比如在模塊級(jí)的二進(jìn)制接口封裝上。Bjarne也說(shuō)過(guò),OO/GP這些抽象機(jī)制只有用在合適的地方才是合適的。當(dāng)人們手頭有的是錘子的時(shí)候,很容易把所有的目標(biāo)都當(dāng)成釘子,有時(shí)候C的確能夠提供簡(jiǎn)潔高效的解決方案,比如C標(biāo)準(zhǔn)庫(kù)里面的printf和fopen(此例受云風(fēng)的啟發(fā))的使用界面就是典型的例子。簡(jiǎn)而言之,理解C語(yǔ)言的精神不僅有助于更好地理解C++,更理性地使用C++,而且也有其實(shí)踐意義——這就是推薦《The C Programming Language》(以下簡(jiǎn)稱TCPL)的原因。此外,建議在閱讀《Accelerated C++》之前先閱讀《The C Programming Language》。因?yàn)?,一,《The C Programming Language》非常薄。二,如果你帶著比較的眼光去看問(wèn)題,看完《The C Programming Language》再看《Accelerated C++》,你便會(huì)更深刻的理解C++語(yǔ)言引入抽象機(jī)制的意義和實(shí)際作用。

第四本

《Accelerated C++》固然寫(xiě)得非常漂亮,但正如所有漂亮的入門(mén)書(shū)一樣,它的優(yōu)點(diǎn)和弱點(diǎn)都在于它的輕薄短小。短短3百頁(yè),對(duì)現(xiàn)代C++的運(yùn)用精神作了極好的概述。然而要熟練運(yùn)用C++,我們還需要更多的講解,這個(gè)時(shí)候一本全面但又不鉆語(yǔ)言牛角尖,從“語(yǔ)言是如何支持抽象設(shè)計(jì)”的角度而不是“為了講語(yǔ)言特性而講語(yǔ)言特性”的角度來(lái)介紹一門(mén)語(yǔ)言的書(shū)便至關(guān)重要,在C++里面,我還沒(méi)有見(jiàn)到比C++之父本人的《The C++ Programming Language》(以下簡(jiǎn)稱TC++PL)做得更好的,C++之父本人既有大規(guī)模C++運(yùn)用的經(jīng)驗(yàn)又有語(yǔ)言設(shè)計(jì)思想的最本質(zhì)把握,因此TC++PL才能做到高屋建瓴,不為細(xì)節(jié)所累;同時(shí)又能做到實(shí)踐導(dǎo)向,不落于為介紹語(yǔ)言而介紹語(yǔ)言的巢臼。最后有一個(gè)需要提醒的地方,TC++PL其實(shí)沒(méi)有它看起來(lái)那么厚,因?yàn)檎嬲榻B語(yǔ)言的內(nèi)容只有區(qū)區(qū)500頁(yè)(第一部分:基礎(chǔ);第二部分:抽象機(jī)制;以及第四部分:用C++設(shè)計(jì)),剩下的是介紹標(biāo)準(zhǔn)庫(kù)的,可以當(dāng)作Manual(參考手冊(cè))。

建議3:CSAPP &TCPL& AC++&TC++PL。

是的,在C++方面登堂入室并不需要閱讀多得恐怖的所謂“經(jīng)典”,至于為什么這些“經(jīng)典”無(wú)需閱讀,前面已經(jīng)講的很詳細(xì)了。其實(shí)你只需要這四本書(shū),就可以奠定一個(gè)深厚的基礎(chǔ),以及對(duì)C++的成熟理性的現(xiàn)代運(yùn)用理念。其余的書(shū)都可以當(dāng)成參考資料,用到的時(shí)候再去翻閱,即:

建議4:實(shí)踐驅(qū)動(dòng)地學(xué)習(xí)。

實(shí)踐驅(qū)動(dòng)當(dāng)然不代表什么基礎(chǔ)都不打,直接捋起袖管就上。不管運(yùn)用哪種工具,首先都需要知道關(guān)于它的一定程度的基本知識(shí)(包括應(yīng)該怎么用,和不應(yīng)該怎么用)。知道應(yīng)該怎么用可以幫你發(fā)揮出它的正確和最大效用,知道不應(yīng)該怎么用則可以幫你避免用的過(guò)程中傷及自身的危險(xiǎn)。這就是為什么我建議你看四本書(shū),以及建議你要了解C++中的陷阱(大部分來(lái)自C,因此你可以閱讀《C缺陷和陷阱》)的原因。

實(shí)踐驅(qū)動(dòng)代表著一旦一個(gè)扎實(shí)的基礎(chǔ)具備了之后獲得延伸知識(shí)的方式。出于環(huán)境和心理的原因,C++學(xué)習(xí)者們?cè)谶@條路上走錯(cuò)的幾率非常大,許多人乃至以上來(lái)就拿Effective C++&More Effective C++、Inside C++ Object Model這類書(shū)去讀(是的,我也是,所以我才會(huì)在這里寫(xiě)下這篇文章),結(jié)果讀了一本又一本,出現(xiàn)知道虛函數(shù)實(shí)現(xiàn)機(jī)制的每個(gè)細(xì)節(jié)卻不知道虛函數(shù)作用的情況。

實(shí)踐驅(qū)動(dòng)其實(shí)很簡(jiǎn)單:實(shí)踐+查文檔。知識(shí)便在這樣一個(gè)簡(jiǎn)單的循環(huán)中積累起來(lái)。實(shí)踐驅(qū)動(dòng)的最大好處就是你學(xué)到的都是實(shí)踐當(dāng)中真正需要的,屬于那“80%”最有用的。而查文檔的重要性前面已經(jīng)說(shuō)過(guò)了,但對(duì)于C++實(shí)踐者來(lái)說(shuō),哪些“文檔”是非常重要的呢?

第二本

《C++ Coding Standard》。無(wú)需多作介紹,這是一本濃縮了C++社群多年來(lái)寶貴的經(jīng)驗(yàn)結(jié)晶的書(shū),貼近實(shí)踐,處處以80%場(chǎng)景為主導(dǎo),不鉆語(yǔ)言旮旯,用本為主…總之,非常值得放在手邊時(shí)時(shí)參閱。因?yàn)闀?shū)很薄,所以也不妨先往腦袋里面裝一遍。書(shū)中的101條建議的介紹都很簡(jiǎn)略,并且指出了詳細(xì)介紹的延伸閱讀,在延伸閱讀的時(shí)候還是要注意不要陷入無(wú)關(guān)的細(xì)節(jié)和不必要的技巧中,時(shí)時(shí)抬頭看一看你需要解決的問(wèn)題。在C++編碼標(biāo)準(zhǔn)方面,Bjarne也有一些建議。

第一本

《The Pragmatic Programmer》,用本程序員的杰作;雖然不是一本C++的書(shū),但其介紹的實(shí)踐理念卻是所有程序員都需要的。

第三本

《Code Complete, 2nd Edition》,這是一本非常卓越的參考資料,涉及開(kāi)發(fā)過(guò)程的全景,有大量寶貴的經(jīng)驗(yàn)。你未必要一口氣讀完,但你至少應(yīng)該知道它里面都寫(xiě)了哪些內(nèi)容,以便可以回頭參閱。

其它

所有優(yōu)秀的技術(shù)書(shū)籍都是資料來(lái)源。一旦養(yǎng)成了查文檔的習(xí)慣,所有的電子書(shū)、紙書(shū)、網(wǎng)絡(luò)上的資源實(shí)際上都是你的財(cái)富。不過(guò),查文檔的前提是你要從手邊的問(wèn)題分析出應(yīng)該到什么地方去查資料,這里,分析問(wèn)題的能力很重要,因此:

建議5:思考。

這個(gè)建議就把我們帶到了第四本書(shū):

第四本:

《你的燈亮著嗎?》。不作介紹,自己閱讀,這本書(shū)只有一百多頁(yè),但精彩非常,妙趣橫生。

最后,要想理性地運(yùn)用一門(mén)語(yǔ)言,不僅需要看到這門(mén)語(yǔ)言的特點(diǎn),還要能夠從另一個(gè)角度去看這門(mén)語(yǔ)言——即看到它的缺點(diǎn),因?yàn)閺男睦砩稀?/p>

事實(shí)10:一旦我們熟悉了一門(mén)語(yǔ)言之后,就容易不知不覺(jué)地在其框架下思考,受到語(yǔ)言特性的細(xì)節(jié)的影響,作出second-class的設(shè)計(jì)。

對(duì)于像C++這樣的在抽象機(jī)制上作了折衷的語(yǔ)言,尤其如此,思考容易受到語(yǔ)言機(jī)制本身細(xì)節(jié)的影響,往往在心里頭還沒(méi)想好怎么抽象,就已經(jīng)確定了使用什么語(yǔ)言機(jī)制乃至技巧;更有甚者是為了使用某個(gè)特性而去使用某個(gè)特性。然而,實(shí)際上,我們應(yīng)該——

建議6:脫離語(yǔ)言思考,使用語(yǔ)言實(shí)現(xiàn)。

關(guān)于設(shè)計(jì)的一般理念,Eric Raymond在《The Art of Unix Programming》的第二部分有非常精彩的闡述。

此外,除了脫離語(yǔ)言的具體抽象機(jī)制來(lái)思考設(shè)計(jì)之外,學(xué)習(xí)其它語(yǔ)言對(duì)同類抽象機(jī)制的支持也是非常有益的,正如老話所說(shuō),“兼聽(tīng)則明”。前一陣子reddit上也常出現(xiàn)“How Learning XXX help me become a Better YYY programmer”(其中XXX和YYY指代編程語(yǔ)言)的帖子,正是這個(gè)道理,這就把我們帶到了最后一個(gè)建議:學(xué)習(xí)其它語(yǔ)言。

建議7:學(xué)習(xí)其它語(yǔ)言。

如果你是一個(gè)系統(tǒng)程序員,你可能會(huì)覺(jué)得沒(méi)有必要學(xué)習(xí)其它語(yǔ)言,然而未必如此,你未必需要精通其它語(yǔ)言,而是可以去試著了解其它語(yǔ)言的設(shè)計(jì)理念,是如何支持日常編程中的設(shè)計(jì)的。這一招非常有利于在使用你自己的語(yǔ)言編程時(shí)心理上脫離語(yǔ)言機(jī)制細(xì)節(jié)的影響,作出更好的抽象設(shè)計(jì)。

尾聲

建議8(可選):重讀本文。

注:這篇文章的目的是給國(guó)內(nèi)的C++學(xué)習(xí)者(尤其是初學(xué)者)一個(gè)可操作的建議。我打算不斷修訂并完善它;因?yàn)檫@是根據(jù)我個(gè)人的經(jīng)驗(yàn)來(lái)寫(xiě)的,而基于我對(duì)C++的熟悉程度,可能會(huì)有地方并不能完完全全站到初學(xué)者的視角來(lái)看問(wèn)題。


聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • C語(yǔ)言
    +關(guān)注

    關(guān)注

    180

    文章

    7575

    瀏覽量

    134052

原文標(biāo)題:你應(yīng)當(dāng)如何學(xué)習(xí)C++(以及編程)

文章出處:【微信號(hào):C_Expert,微信公眾號(hào):C語(yǔ)言專家集中營(yíng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    匯道科技淺談:學(xué)Java編程之前需要學(xué)習(xí)C++嗎?

    也不同,所以學(xué)Java編程之前不用學(xué)習(xí)c++。另一個(gè)角度,我們知道c++是比較復(fù)雜的。和c++在工業(yè)領(lǐng)域中所處的位置有關(guān)系。
    發(fā)表于 02-28 15:21

    初學(xué)者該如何學(xué)習(xí)C++

    的錯(cuò)誤學(xué)習(xí)方法,很多人認(rèn)為學(xué)習(xí)方法真的是一抓一大把,關(guān)于這些編程語(yǔ)言的學(xué)習(xí)方法,程序員總是非常苦惱,想要找到一條符合自己學(xué)習(xí)的道路其實(shí)并沒(méi)有
    發(fā)表于 05-22 16:41

    學(xué)習(xí)c++的經(jīng)驗(yàn)分享!

    ;b)只學(xué)而不堅(jiān)持的人;16.把時(shí)髦的技術(shù)掛在嘴邊,還不如把過(guò)時(shí)的技術(shù)記在心里;17.C++不僅僅是支持面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言;18.學(xué)習(xí)編程最好的方法之一就是閱讀源代碼;19.在任何
    發(fā)表于 10-08 03:46

    如何學(xué)習(xí)C++,如何學(xué)好C++

    大家分享下我自己的學(xué)習(xí)心得與體會(huì)!Linus曾說(shuō)過(guò):“C++是一門(mén)很恐怖的語(yǔ)言,而比它更恐怖的是很多不合格的程序員在使用著它”,這個(gè)世界上最難的編程語(yǔ)言可能非C++莫屬了,呵呵,雖然有
    發(fā)表于 08-20 06:27

    如何學(xué)習(xí)編程c++語(yǔ)言?

      如何學(xué)習(xí)編程c++語(yǔ)言?粵嵌來(lái)講解嵌入式C語(yǔ)言在各種項(xiàng)目中要用到的知識(shí)點(diǎn),尤其是嵌入式C語(yǔ)言之變量與常量的內(nèi)容:   1、變量類型和表示
    發(fā)表于 12-15 08:28

    學(xué)習(xí)C++方法以及C++的就業(yè)方向

    學(xué)習(xí)方向:嵌入式+人工智能嵌入式是一門(mén)技術(shù)學(xué)習(xí)目標(biāo)1.嵌入式開(kāi)發(fā)概述;(面向?qū)ο笤谇度胧介_(kāi)發(fā)中角色)2.嵌入式Linux C++編程;(C++
    發(fā)表于 12-24 07:32

    C++編程思想

    C++編程思想,很好的資料,大家下載看看吧!夠20字了吧,哈哈哈!
    發(fā)表于 11-17 11:38 ?0次下載

    C++“指針”學(xué)習(xí)建議

    C++“指針”學(xué)習(xí)建議
    發(fā)表于 03-31 15:53 ?3次下載

    如何提高cc++的安全編程能力?《CC++安全編碼》帶你詳細(xì)學(xué)習(xí)

    CC++安全編碼是C/C++安全編碼領(lǐng)域的權(quán)威著作,被視為“標(biāo)準(zhǔn)”參考書(shū),由國(guó)際資深軟件安全專家撰寫(xiě),美國(guó)CERT主管親自作序推薦。本書(shū)結(jié)合國(guó)際標(biāo)準(zhǔn)
    發(fā)表于 08-28 08:00 ?0次下載

    C++編程調(diào)試秘笈

    C++編程調(diào)試秘笈資料下載。
    發(fā)表于 06-01 15:35 ?14次下載

    Linux C/C++ 學(xué)習(xí)路線

    一、秋招 Linux C/C++ offer 情況二、Linux C/C++ 方向的一些思考三、計(jì)算機(jī)基礎(chǔ)知識(shí)的梳理四、C++ 方向的深入
    發(fā)表于 11-06 19:36 ?14次下載
    Linux <b class='flag-5'>C</b>/<b class='flag-5'>C++</b> <b class='flag-5'>學(xué)習(xí)</b>路線

    CC++經(jīng)典著作-C專家編程.PDF

    CC++經(jīng)典著作-C專家編程.PDF
    發(fā)表于 12-13 17:11 ?0次下載

    CC++實(shí)物精選《C專家編程

    CC++實(shí)物精選《C專家編程
    發(fā)表于 01-17 09:55 ?0次下載

    C++學(xué)習(xí)筆記之c++的基本認(rèn)識(shí)

    自這篇文章我們即將開(kāi)始C++的奇幻之旅,其內(nèi)容主要是讀C++ Primer的總結(jié)和筆記,有興趣可以找原版書(shū)看看,對(duì)于學(xué)習(xí)C++還是有很大幫助的。這篇文章將從一個(gè)經(jīng)典的程序開(kāi)始介紹
    的頭像 發(fā)表于 03-17 13:57 ?626次閱讀

    c語(yǔ)言,c++,java,python區(qū)別

    操作系統(tǒng)、嵌入式系統(tǒng)等對(duì)性能要求較高的場(chǎng)景。C語(yǔ)言的語(yǔ)法相對(duì)簡(jiǎn)單,學(xué)習(xí)曲線較平緩,也是學(xué)習(xí)其他高級(jí)語(yǔ)言的入門(mén)語(yǔ)言。 C++C++是在
    的頭像 發(fā)表于 02-05 14:11 ?1364次閱讀