本節書摘來自異步社群出版社《visual c++ 開發從入門到精通》一書中的第2章,第2.9節,作者: 王東華 , 李櫻,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
在幾十年前,ansi c标準規定名字不準超過6個字元,現在的c++/c規則不再有此限制。一般來說,長名字能更好地表達含義,是以函數名、變量名、類名長達十幾個字元不足為怪。那麼名字是否越長越好?不見得! 例如,變量名maxval maxvalueuntiloverflow好用,單字元的名字也有用,常見的有i、j、k、m、n、x、y、z等,它們通常可用作函數内的局部變量。
字元和字元串的差異很小,因為字元串也是由一個個字元組合而成的,兩者的主要差別如下。
字元使用單引号标注,而字元串使用雙引号标注。
字元串需要使用轉義字元'0'來說明結束位置,而字元則不存在這個問題。
字元是一個元素,隻能存放單個字元。而字元串則是字元的集合,可以存放多個字元。
相同内容的字元數組和字元串都是字元的集合,但是字元數組比字元串數組少了一個轉義字元'0'。
c ++提供的由c++字元串得到對應c_string的方法使用的是data()、c_str()和copy()。其中,data()以字元數組的形式傳回字元串内容,但并不添加'0',c_str()傳回一個以'0'結尾的字元數組,而copy()則把字元串的内容複制或寫到既有的c_string或字元數組。c++字元串并不以'0'結尾。筆者建議在程式中盡量使用c++字元串,一般情況下不選用c_string。
為了測定字元串的實際長度,c++規定了一個“字元串結束标志”,以字元′0′代表。在上面的數組中,第11個字元為′0′,表明字元串的有效字元為其前面的10個字元。也就是說,遇到字元′0′就表示字元串到此結束,由它前面的字元組成字元串。
對一個字元串常量,系統會自動在所有字元的後面加一個'0'作為結束符。例如,字元串“i am happy”共有10個字元,但是在記憶體中共占11個位元組,最後一個位元組'0'是由系統自動加上的。
在程式中往往依靠檢測'0'的位置來判定字元串是否結束,而不是根據數組的長度來決定字元串的長度。當然,在定義字元數組時應估計實際字元串的長度,應保證數組長度始終大于字元串的實際長度。如果在一個字元數組中先後存放多個不同長度的字元串,則應使數組長度大于最長的字元串的長度。
面向對象指的是把屬性和方法封裝成類,執行個體化對象後,要完成某個操作時,直接調用類裡面相應的方法。面向過程則不進行封裝,要完成什麼功能需要詳細地把算法寫出來。舉個例子來說,要完成買東西這個任務,面向對象的實作方法就是,先對手下的人辦個教育訓練,教他們怎麼去買(相當于定義類的屬性和方法),以後要讓他們買東西,隻要喊“張三(或者李四,相當于執行個體化對象),你用上次我教你的方法去買個東西。”這樣就可以了;而面向過程的方法則不進行教育訓練,每次要去買東西,都找張三過來,再教他怎麼去買,但是下次再叫他去買,又要重新教一次。
c語言是一門面向過程的語言,c++是一門面向對象的語言。究竟面向對象和面向過程有什麼差別呢?面向過程就是分析出解決問題所需要的步驟,然後用函數把這些步驟一步一步實作,使用的時候一個一個依次調用就可以了。面向對象是把構成問題的事務分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描述某個事物在整個解決問題步驟中的行為。
例如,要開發一個五子棋遊戲,使用面向過程的設計思路的步驟如下。
(1)開始遊戲,(2)黑子先走,(3)繪制畫面,(4)判斷輸赢,(5)輪到白子,(6)繪制畫面,(7)判斷輸赢,(8)傳回步驟(2),(9)輸出最後的結果。
把上面每個步驟分别用函數來實作,問題就解決了。而面向對象的設計則是從另外的思路來解決問題的,開發整個五子棋遊戲的基本過程如下。
(1)設計黑白雙方,這兩方的行為是一模一樣的。
(2)設計棋盤系統,負責繪制畫面。
(3)開發規則系統,負責判定,如犯規、輸赢等。
上述3個過程分别代表3個對象,其中第一類對象(玩家對象)負責接收使用者輸入,并告知第二類對象(棋盤對象)棋子布局的變化,棋盤對象接收到棋子的變化就要負責在螢幕上顯示出這種變化,同時利用第三類對象(規則系統)來對棋局進行判定。
由此可以明顯地看出,面向對象是以功能來劃分問題的,而不是步驟。同樣是繪制棋局,這樣的行為在面向過程的設計中分散在多個步驟中,很可能出現不同的繪制版本,因為通常設計人員會考慮到實際情況進行各種各樣的簡化。而面向對象的設計中,繪圖隻可能在棋盤對象中出現,進而保證了繪圖的統一。
功能上的統一保證了面向對象設計的可擴充性,比如要加入“悔棋”這一功能,如果要改動面向過程的設計,那麼從輸入到判斷到顯示這一連串的步驟都要改動,甚至步驟之間的順序都要進行大規模調整。如果是面向對象的話,隻用改動棋盤對象就行了,棋盤系統儲存了黑白雙方的棋譜,簡單回溯就可以了,而顯示和規則判斷則不用顧及,同時整個對象功能的調用順序都沒有變化,改動隻是局部的。
再如,要把這個五子棋遊戲改為圍棋遊戲,如果使用的是面向過程設計,那麼五子棋的規則就分布在程式的每一個角落,要改動還不如重寫。但是如果一開始就使用了面向對象的設計,那麼隻用改動規則對象就可以了,五子棋和圍棋的差別主要就是規則,而下棋的大緻步驟從面向對象的角度來看沒有任何變化。
當然,要達到改動隻是局部的效果需要設計人員有足夠的經驗,使用對象不能保證程式就是面向對象的,初學者或者蹩腳的程式員很可能以面向對象之虛而行面向過程之實,這樣設計出來的所謂面向對象的程式很難有良好的可移植性和可擴充性。
因為常量屬于辨別符,是以也需要遵循c++辨別符的命名規範,也和變量的命名規範類似。另外,c++常量還要遵循如下3點規範。
用#define定義的常量最好大寫且以下劃線開始,如_pi和_max。
如果用#define定義的常量用于代替一個常數,則常量名和其常數符号要對應,如圓周率就用pi表示。
用const聲明的常量完全遵循c++變量的命名規範。
在c++程式中,既可以用const定義常量,也可以用#define定義常量,前者比後者有如下4個優勢。
(1)const常量有資料類型,而宏常量沒有資料類型。編譯器可以對const進行類型安全檢查,而對後者隻進行字元替換,沒有類型安全檢查,并且在字元替換中可能會産生意料不到的錯誤(邊際效應)。
(2)有些內建化的調試工具可以對const常量進行調試,但是不能對宏常量進行調試。在c++程式中隻使用const常量而不适用宏常量,即const常量完全取代宏常量。
(3)編譯器處理方式不同。define宏是在預處理階段展開;const常量在編譯運作階段使用。
(4)存儲方式不同。define宏僅僅是展開,有多少地方使用,就展開多少次,不會配置設定記憶體;const常量會在記憶體中配置設定(可以是堆中也可以是棧中)。
在c++程式中,const是一個很重要的關鍵字,能夠對常量施加一種限制。有限制其實不是件壞事情,無窮的權利意味着無窮的災難。應用了const之後,就不可以改變變量的數值了,要是一不小心改變了編譯器就會報錯,你就容易找到錯誤的地方。不要害怕編譯器報錯,正如不要害怕朋友指出你的缺點一樣,編譯器是程式員的朋友,編譯時期找到的錯誤越多,隐藏着的錯誤就會越少。是以,隻要你覺得有不變的地方,就用const修飾,用得越多越好。比如想求圓的周長,需要用到pi,pi不會變的,就加const,const double pi=3.1415926;再如,需要在函數中傳引用,隻讀,不會變的,前面加const;比如函數有個傳回值,傳回值是個引用,隻讀,不會變的,前面加const;比如類中有個private資料,外界要以函數方式讀取,不會變的,加const;這個時候就是加在函數定義末尾,加在末尾隻不過是個文法問題。其實文法問題不用太過注重,文法隻不過是末節,記不住了,翻翻書就可以了,接觸多了,自然記得,主要是一些概念難以了解。想一下,const加在前面修飾函數傳回值,這時候const不放在末尾就沒有什麼地方放了。
在c語言中,隻能用常數對全局變量進行初始化,否則編譯器會報錯。在c++中,如果在一個檔案中定義了:
<code>int a = 5;</code>
要在另一個檔案中定義下面的b:
int b = a;
前面必須對a進行聲明:
`
extern int a;`
否則編譯不通過。即使是這樣,int b = a;這句話也是分兩步進行的:在編譯階段,編譯器把b當成未初始化資料而将它初始化為0;在執行階段,在main被執行前有一個全局對象的構造過程,int b = a;被當成int型對象b的副本初始化構造來執行。
其實在c++中,全局對象、變量的初始化是獨立的,如果不是像:
int a = 5;`
這樣的已初始化資料,那麼就是像b這樣的未初始化資料。而c++中全局對象、變量的構造函數調用順序是跟聲明有一定關系的,即在同一個檔案中先聲明的先調用。對于不同檔案中的全局對象、變量,它們的構造函數調用順序是未定義的,取決于具體的編譯器。
變量在記憶體位址的分布格式為:
堆-棧-代碼區-全局靜态-常量資料`
同一區域的各變量按聲明的順序在記憶體中依次由低到高配置設定空間,隻有未指派的全局變量是個例外。全局變量和靜态變量如果不指派,預設為0。棧中的變量如果不指派,則是一個随機的資料。編譯器會認為全局變量和靜态變量是等同的,已初始化的全局變量和靜态變量配置設定在一起,未初始化的全局變量和靜态變量配置設定在另一起。
靜态變量進行初始化順序是基類的靜态變量先初始化,然後是它的派生類。直到所有的靜态變量都被初始化。這裡需要注意全局變量和靜态變量的初始化是不分次序的。這也不難了解,其實靜态變量和全局變量都被放在公共記憶體區。可以把靜态變量了解為帶有“作用域”的全局變量。在一切初始化工作結束後,main函數會被調用,如果某個類的構造函數被執行,那麼會先初始化基類的成員變量。要注意的是,成員變量的初始化次序隻與定義成員變量的順序有關,與構造函數中初始化清單的順序無關。因為成員變量的初始化次序是根據變量在記憶體中次序有關,而記憶體中的排列順序早在編譯期就根據變量的定義次序決定了。
本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。