相信很多人懂這個問題,也很多人沒想過,包括我,今天看書想到了就寫下來。先看程序(抱歉在linux下沒有找到舒服的可以復制terminal的工具,只好截圖了,將就着看看)
注釋的就先不看了,看那幾行沒有注釋的enum coordinate_type 表示一個枚舉(Enumeration)類型。枚舉類型的成員是常量,它們的值由編譯器自動分配,例如定義了上面的枚舉類型之后,RECTANGULAR就表示常量0,POLAR 表示常量1。如果不希望從0開始分配,可以這樣定義:enum coordinate_type { RECTANGULAR = 1, POLAR };
這樣,RECTANGULAR就表示常量1,而POLAR 表示常量2。枚舉常量也是一種整型,其值在編譯時確定,因此也可以出現在常量表達式中,可以用於初始化全局變量或者作為case 分支的判斷條件。注意:枚舉常量是不占用內存的,它們在編譯時被全部求值,只有定義了enum 變量才會占用內存。
有一點需要注意,雖然結構體的成員名和變量名不在同一命名空間中,但枚舉的成員名卻和變量名在同一命名空間中,所以會出現命名沖突。那什么是命名空間呢?我的理解是在運行程序時會為每一個函數開辟一個函數幀棧,局部變量之類的可以在這里賦值運算等,如果在這個函數幀棧里同個等級里(指的是不再加{}構成語句塊)同樣的命名會造成沖突的那就屬於同個命名空間,如上所述,結構體的成員名跟某個變量名命令重復是不會沖突的,而枚舉類型成員名跟某個變量名重復是會造成沖突的,如編譯時會提示錯誤如下:
那如果加了{}呢,如:
再次編譯,提示就不一樣了:
這時就不會提示發生沖突,大家都知道如果函數內的局部變量跟全局變量重名,則在函數內全局變量被屏蔽了,這里也是同樣的道理,就是在函數內{}語句塊也屏蔽了外圍的,里所應當的是函數的局部變量等函數調用完后存儲空間就會釋放,而{}里面更快釋放,可以看到打印完之后里面的rectanger變量就會被釋放,但polar變量得等整個函數調用完畢才會釋放,因為這里使用的是枚舉類型中的成員。
那這里提示警告,是否能運行呢?當然了,因為只要不出現錯誤只出現警告是可以生成可執行文件的,只是有警告就意味着程序有bug,是很危險的。這里的意思是因為局部變量rectanger沒有初始化,所以運行打印時會是不確定的值,即每次運行都可能是不一樣的結果,要記住:局部變量是函數調用時才賦值的!局部變量存儲空間地址也許會隨着每次函數調用時而不同,如果你設定了初值,那空間怎么變里面的值都是你賦予的那個,但如果沒有初始化,那每次運行都是不確定的值。如下圖:
下面看把枚舉類型寫在函數外面的情況:
編譯一下,看看出現什么提示:
可以看到沒有發生命名沖突,只是還是提示沒有初始化的問題,因為在這里的枚舉常量是全局的,不會跟局部變量命名沖突,但是會被覆蓋掉。
如果前面加前綴const如 const int A; 表明是只讀的,注意,像A這種const 變量在定義時必須初始化如const int A = 100;。因為只有初始化時才有機會給它一個值,對於全局來說一旦定義之后就不能再改寫了,也就是不能再賦值了,編譯通過但運行時會出現段錯誤。而如果A 是局部變量則可以通過 int *p = &A; *p = 200;來改寫,編譯通過且可以運行。其實加了關鍵字const只是提示編譯器這個變量是常量,如果我們在接下來的操作中試圖更改它,編譯器會報錯,而並不是真正的常量,上面的例子也說明通過指針也是可以更改的,什么情況下完全不能修改呢,當A是加const限定且初始化的全局變量,此時A位於.rodata段
還有個特例就是:函數中的static變量不同於以前我們講的局部變量,它並不是在調用函數時分配,在函數返回時釋放,而是像全局變量一樣靜態分配,所以用“static”(靜態)這個詞。另一方面,函數中的static變量的作用域和以前講的局部變量一樣,只在函數中起作用。
全局變量的作用域從開始定義的地方到文件的末尾,在任何函數中都可以訪問全局變量,整個程序運行完畢會釋放全局變量的存儲空間,當然同時還有代碼的存儲空間也會被釋放,而局部變量的存儲空間早在他們之前釋放; 如果全局變量沒有賦予初值,則初始化為0,位於.bss段。
如果全局變量前面加個前綴static則表示此變量是local的而不是global的,意思是不能被其他文件所調用。
下面看預處理:
看看編譯會提示什么:
很明顯就是因為宏定義了rectanger,如果有重名的話,宏定義覆蓋所有其它標識符,因為它在預處理階段而不是
編譯階段處理,所以在函數里面重新定義rectanger會提示表明這個變量名已經是個常量了。
我們可以使用 gcc -E來查看預處理后而編譯前的東西,一看這么多頁屏幕都看不完整加個less查看,居然有好幾屏幕,只截取最后面的一部分來看:
是不是發現了啊,預處理的時候已經把rectanger 都替換成宏定義中的 1了,所以接下去進行編譯時當然會報錯了,因為你在 int 1啊,能不錯嗎?那前面輸出的是什么東西呢,其實就是將一些頭函數進行展開。
反正處理的步驟就是 預處理 --》 編譯 --》 運行,但步驟的不同是涉及到很多東西的,比如全局變量和局部變量的賦值,為什么全局變量只能用常量來初始化而局部變量可以用帶數學函數的表達式來初始化呢?如double pi = acos(-1.0); 因為程序開始運行時要用適當的值來初始化全局變量,所以初始值必須保存在編譯生成的可執行文件中,因此初始值在編譯時就要計算出來,然而上面那種Initializer的值必須在程序運行時調用 acos函數才能得到,所以不能用來初始化全局變量。