終于煲完這本書了...這本書是一本寫給由c轉(zhuǎn)c++新手的入門書.它的亮點(diǎn)在于非常詳細(xì)清晰地闡明c++中一些基礎(chǔ)語法的來龍去脈.然后第一章對(duì)面向?qū)ο蟮暮?jiǎn)介也讓我對(duì)面向?qū)ο笥辛饲逦某醪秸J(rèn)識(shí).
本文主要以從兩個(gè)方面記錄所得.一是,c++為什么要設(shè)計(jì)成這樣.二是一些語法細(xì)節(jié).
首先,為什么c++需要面向?qū)ο?
首先c++是一門編程語言,應(yīng)先強(qiáng)調(diào)它跟其他語言一樣是一種表達(dá)工具.編程語言能夠抽象計(jì)算機(jī)的行為,人類使用編程語言向計(jì)算機(jī)表達(dá)想法,從而操縱計(jì)算機(jī)為自己服務(wù).由于編程語言是抽象機(jī)器行為的結(jié)果,所以它的結(jié)構(gòu)與計(jì)算機(jī)行為很相似.變量與存儲(chǔ)器,指令,結(jié)構(gòu)化程序設(shè)計(jì)中的函數(shù)與運(yùn)算電路.盡管從機(jī)器語言,匯編語言到結(jié)構(gòu)化程序設(shè)計(jì),編程語言的抽象層次不斷提高,表達(dá)能力不斷增增強(qiáng),但是編程語言的結(jié)構(gòu)還是離不開機(jī)器的結(jié)構(gòu):數(shù)據(jù)與對(duì)數(shù)據(jù)的操作.
這個(gè)時(shí)候問題就來了,人對(duì)實(shí)際問題的抽象結(jié)構(gòu)往往并不是"數(shù)據(jù) + 操作"的結(jié)構(gòu),為了將實(shí)際問題的概念向計(jì)算機(jī)表達(dá),人必須作概念的轉(zhuǎn)換.書中的說法是establish the association between the machine model(in the solution space) and the model of problem(in the problem space).即我們要把問題域的概念與編程語言的概念聯(lián)系起來.當(dāng)使用結(jié)構(gòu)化程序設(shè)計(jì)的時(shí)候,我們往往是將結(jié)題"過程"層層分解,直到它能細(xì)化到能使用編程語言來表達(dá).這種概念的轉(zhuǎn)換的思維成本很大.具體來說就是,當(dāng)代碼作為代碼開發(fā)者與代碼維護(hù)者之前溝通的語言時(shí),開發(fā)者將問題域的概念轉(zhuǎn)化為編程語言的概念,然后閱讀者又需要將編程語言的概念轉(zhuǎn)化為問題的概念,而后者的過程是很難進(jìn)行的,對(duì)于抽象層次整齊,變量名有較強(qiáng)表達(dá)能力的代碼來說,這會(huì)有所改善.當(dāng)面對(duì)抽象層次混亂,變量名語義模糊的代碼來說,維護(hù)工作就是災(zāi)難.
書中指出,為了減少這種思維成本,部分編程語言通過被設(shè)計(jì)成面對(duì)特定問題,來讓代碼與問題域的概念更接近.而面向?qū)ο缶幊陶Z言通過提供把數(shù)據(jù)與對(duì)數(shù)據(jù)的操作綁在一起的表達(dá)方式,提供面向問題域概念的抽象能力,代碼所表達(dá)的概念更接近問題域概念,人們期望通過使用這種抽象能力來降低概念轉(zhuǎn)化的思維成本.注意,是降低,編程語言跟人類語言不可能表達(dá)一致的概念,因?yàn)槲覀兛傄伎季幊陶Z言的語義,在面向?qū)ο蟮恼Z言中, 我們總要思考類,對(duì)象,方法.
然而,在我看來,問題域的概念有時(shí)候用函數(shù)來表達(dá)更加恰當(dāng).并不是一切問題域的概念都是對(duì)象,很多時(shí)候我們僅僅需要思考"操作".
此外,c++還解決了一些c語言的問題.
c++提供更嚴(yán)格的類型檢查.c++需要在編譯的時(shí)候通過聲明確定函數(shù)原型.函數(shù)參數(shù)在編譯時(shí)連同函數(shù)名寫入函數(shù)簽名中,這樣當(dāng)兩個(gè)函數(shù)的函數(shù)名和函數(shù)參數(shù)都一致時(shí),編譯器才認(rèn)為他們是同一函數(shù).function()不再代表任意數(shù)量的參數(shù),而是像function(void)一樣無參數(shù).這些都可以防止函數(shù)誤調(diào)用.c++中還禁止了通過void*指針亂轉(zhuǎn)數(shù)據(jù)類型的用法.const則跟進(jìn)一步劃分標(biāo)識(shí)符的類型.此外,由于const能提供宏沒有的類型檢查,const更安全.內(nèi)聯(lián)函數(shù)解決了帶參數(shù)宏各種問題,如:1.宏并不是真正的函數(shù),它的外表與函數(shù)相似而有時(shí)行為不一致會(huì)導(dǎo)致難以發(fā)現(xiàn)的bug.2.宏沒有作用域的概念.3.宏不能作為對(duì)象成員.而內(nèi)聯(lián)函數(shù)在優(yōu)化程序的同時(shí)也提供了類型檢查.
c++解決了c函數(shù)庫(kù)的問題.c不少函數(shù)庫(kù)都是struct+function的結(jié)構(gòu),因?yàn)槊鎸?duì)實(shí)際問題的時(shí)候不得不包裝數(shù)據(jù)和操作.c++的面向?qū)ο笥梅ū萻truct+function更符合語義,object.method()比向function顯式傳結(jié)構(gòu)體指針更簡(jiǎn)潔.c++還通過將函數(shù)寫入struct或class解決了名字沖突的問題.c++的access control分離了接口與實(shí)現(xiàn),使庫(kù)的用戶更清晰什么信息是他們需要的(接口).構(gòu)造函數(shù)與析構(gòu)函數(shù)讓資源分配自動(dòng)化.總之,面向?qū)ο蟮姆庋b方式能抽象出一個(gè)簡(jiǎn)單的概念,給變量封裝職責(zé),使這個(gè)變量(概念)能夠自我管理.
c++通過重載函數(shù)提供更強(qiáng)的表達(dá)能力.編譯器通過形式參數(shù)判斷函數(shù)語義符合人類通過上下文判斷詞語語義的思維模式,實(shí)現(xiàn)一次多意.這也讓我們不再需要給函數(shù)提供冗余name decoration,如swap_int(),swap_float().也讓類能夠提供多種構(gòu)造函數(shù)(構(gòu)造函數(shù)重載).默認(rèn)參數(shù)作用相似.
c++在解決c的問題的同時(shí),也引入了新的問題.由于對(duì)象這種變量的拷貝與初始化沒有內(nèi)置類型那么簡(jiǎn)單,所以在構(gòu)造函數(shù),拷貝構(gòu)造函數(shù),析構(gòu)函數(shù)都要謹(jǐn)慎考慮資源管理,c++還要引入初始化列表保證分配內(nèi)存和初始化在用戶能夠碰在數(shù)據(jù)之前完成.向函數(shù)傳對(duì)象和函數(shù)返回對(duì)象的過程也變得復(fù)雜.
此外,c++還添加了兩個(gè)新玩意:運(yùn)算符重載和模板.雖然本書說運(yùn)算符重載只不過是語法糖,但是在c++后來的發(fā)展中看到,運(yùn)算符重載為c++的擴(kuò)展提供了強(qiáng)大的能力,例如你可以通過重載 operator()來偽造出一個(gè)函數(shù)對(duì)象.一些語言如java是利用單一繼承樹來實(shí)現(xiàn)可以裝載任何對(duì)象的container,而c++不支持單一繼承數(shù),所以使用了模板來實(shí)現(xiàn)裝載任何對(duì)象的container.模板后來更衍生出強(qiáng)大的STL.
下面摘錄一些細(xì)節(jié)
c的const與c++不同.c++的const具有內(nèi)部連接性.c則是外部連接性.c++的const變量在編譯期間會(huì)被常量代替,在實(shí)際程序中不分配內(nèi)存,除非程序某處用到了該const變量的地址,除非其它目標(biāo)文件extern這個(gè)const,除非這個(gè)const變量結(jié)構(gòu)很復(fù)雜.c則沒有這種優(yōu)化.臨時(shí)對(duì)象是const對(duì)象.const成員必須會(huì)被分配內(nèi)存,需要再初始化列表中初始化.const成員變量有個(gè)特殊情況:static const,如果static const 是整型變量,它會(huì)被編譯器優(yōu)化,不會(huì)分配內(nèi)存.而且它可以在聲明的時(shí)候就初始化.當(dāng)需要用到static const 成員變量的內(nèi)存時(shí),需要在實(shí)現(xiàn)的文件中定義這個(gè)變量,并且不能帶初始化(不可以static const int Class::val = 0;).另外如果編譯器不支持static const 成員變量,則需要enum hack . 成員方法可以是const函數(shù)(不是返回值為const),它保證不修改成員變量,除了mutable的變量.另外還可以用強(qiáng)制類型轉(zhuǎn)換打破這種限制( void Y::f()const{ ((Y)this)->i++; }或者(const_cast<Y>(this))->i++ ).構(gòu)造函數(shù)和析構(gòu)函數(shù)不能是const函數(shù).const實(shí)際上分為logical const和bitwiseconst.以上說的都是logical const,畢竟還有那么多方法可以打破規(guī)則.而bitwise const 則是嚴(yán)格的const, 如果它沒有基類,且不自定義的構(gòu)造函數(shù)和析構(gòu)函數(shù),那么它可以分配到ROM中.返回const內(nèi)置類型無意義.注意
struct S { int i, j; }const S s[] = {{1, 2}}double d[s[0]]; // error s[0] 需要查內(nèi)存
內(nèi)聯(lián)函數(shù)具有內(nèi)部連接性,所以可以放在頭文件中.內(nèi)聯(lián)構(gòu)造函數(shù)和析構(gòu)函數(shù)不一定可以加快效率.inline是向編譯器提出將函數(shù)作為內(nèi)聯(lián)函數(shù)的請(qǐng)求,不一定能成功.當(dāng)函數(shù)太復(fù)雜,如有循環(huán),函數(shù)地址被調(diào)用,函數(shù)調(diào)用其它未定義函數(shù),有繼承,組合的類的構(gòu)造函數(shù)和析構(gòu)函數(shù).宏一些不像函數(shù)的地方:參數(shù)本身是復(fù)雜的表達(dá)式時(shí),新引入的操作符會(huì)改變?cè)鞠M倪\(yùn)算順序,傳入?yún)?shù)不是只傳一次,當(dāng)一個(gè)變量被傳了兩次的時(shí)候,可能在第二次傳的時(shí)候該變量已經(jīng)在第一次傳的時(shí)候被修改,如向x + x,傳 a++.仍然需要用到macro的地方有stringizing operator #, string concatenation, token pasting operator ##.在A類中定義靜態(tài)A類成員可以讓A類只實(shí)例化一次.有時(shí)候需要解決互相extern的問題.
當(dāng)使用nested friend 的時(shí)候,應(yīng)先聲明nested類,然后聲明其為友元,架在一起的時(shí)候,編譯器會(huì)將其當(dāng)成非nested看待.加了public,private,protected后內(nèi)存不是按順序布局.可以通過類指針來減少重復(fù)編譯.
static作用,將局部變量分配到靜態(tài)內(nèi)存區(qū),將全局變量設(shè)為內(nèi)部連接性,內(nèi)置變量初始化為0.因?yàn)閑xit()函數(shù)調(diào)用析構(gòu)函數(shù),所以在析構(gòu)函數(shù)內(nèi)調(diào)用exit()會(huì)死循環(huán).a(chǎn)bort()不會(huì)調(diào)用析構(gòu)函數(shù).局部靜態(tài)對(duì)象在函數(shù)未調(diào)用時(shí)不會(huì)被構(gòu)造.用無名namespace代替const.using 用法包括 scope resolution, using directive, using declaration.using 可放在函數(shù)內(nèi).static成員變量放在實(shí)現(xiàn)的文件中定義,即使為private.不管有沒有const,對(duì)象數(shù)組要再實(shí)現(xiàn)中定義.static const數(shù)組定義要放在聲明外,但聲明為內(nèi)部連接性,對(duì)象也如此.
函數(shù)調(diào)用過程為:將參數(shù)壓棧,將返回地址壓棧,將函數(shù)局部變量壓棧,返回內(nèi)置類型的時(shí)候,為了避免ISR工作完后修改了局部變量的內(nèi)存區(qū)而沒有返回正確的局部變量的值,需要通過寄存器返回變量.當(dāng)參數(shù)為對(duì)象時(shí),會(huì)有一個(gè)helper function將對(duì)象copy到棧中,當(dāng)返回值為對(duì)象時(shí),為了避免ISR干擾,而寄存器又不夠大,所以會(huì)把被賦值的對(duì)象地址和函數(shù)參數(shù)在一開始的時(shí)候壓入棧中.在返回的時(shí)候直接拷貝.當(dāng)調(diào)用返回對(duì)象的函數(shù)而不用它來賦值的時(shí)候,會(huì)自動(dòng)生成一個(gè)臨時(shí)對(duì)象來拷貝.返回的對(duì)象是直接在return 后面用構(gòu)造函數(shù)生成的時(shí)候,編譯器會(huì)作優(yōu)化,只進(jìn)行一次拷貝,而不用先構(gòu)造,拷貝,再析構(gòu).
默認(rèn)參數(shù)值放在聲明中.
重載operator = 的時(shí)候要注意檢查是否自己給自己賦值,運(yùn)算符重載應(yīng)重載回內(nèi)置的語義.Class object = Class();調(diào)用構(gòu)造函數(shù).object = Class();調(diào)用operator = .類型轉(zhuǎn)換運(yùn)算符.類型可以通過構(gòu)造函數(shù)和類型轉(zhuǎn)換運(yùn)算符.
修改了基類函數(shù)參數(shù)列表或返回值類型會(huì)把基類所有函數(shù)重載版本都隱藏.operator = 不會(huì)被繼承.私有繼承可以用來隱藏基類部分接口,可以public: using Base::func();來解除接口的私有化.
可以delete void* ,但會(huì)有bug.new運(yùn)算符可以被重載來管內(nèi)存分配,從而提高效率或者解決堆內(nèi)存碎片化問題.重載new分為成員函數(shù)重載和全局重載.作成員函數(shù)重載時(shí)會(huì)自動(dòng)變成static.可以為數(shù)組重載new,delete.new失敗后,返回0并且throw異常.還有placement new的用法.
當(dāng)子類對(duì)象用值傳給基類對(duì)象時(shí),會(huì)發(fā)生object slicing,虛函數(shù)表指針也會(huì)不一樣.子類的重載函數(shù)會(huì)覆蓋基類的所有被重載的函數(shù).當(dāng)子類override基類虛函數(shù)時(shí),不能修改返回值,基類虛函數(shù)返回值是基類指針,子類虛函數(shù)返回值是子類指針.構(gòu)造函數(shù)中會(huì)被悄悄插入初始化虛函數(shù)表指針的代碼.構(gòu)造函數(shù)內(nèi)的不會(huì)動(dòng)態(tài)綁定,只會(huì)調(diào)用本類的虛函數(shù).純虛析構(gòu)函數(shù)必須有函數(shù)體.子類的默認(rèn)析構(gòu)函數(shù)會(huì)調(diào)用基類的純虛析構(gòu)函數(shù).析構(gòu)函數(shù)中虛函數(shù)只會(huì)調(diào)用該類的,防止調(diào)用已經(jīng)被子類析構(gòu)的虛函數(shù)表.運(yùn)算符重載也可以多態(tài).downcast: dynamic_cast, static_cast
鏈接器會(huì)消除模板在不同文件中的重復(fù)定義.