**************const有關的變量聲明*****************
(1)const == read-only
const修飾的變量被許多人錯誤的認爲是常量,但是const所修飾的變量應該是隻讀變量
檢驗這個結論可以用下面這個方法:
const int two = 2;
switch(i)
{
case 1: printf("case 1.\n");
case two:printf("case 2.\n");
case 3: printf("case 3.\n");
default:
break;
}
這段代碼會產生一個編譯時的錯誤,switch的i必須用常量或常量表達式,這裏在用two
代替的時候出現error,很明顯,two並不是常量,它是一個只讀變量,實質還是變量。
(有人提到這段代碼在微軟的vc6.0和vs2010下能編譯通過並且正確運行,一開始我是這麼認爲的:
這個其實和編譯器對代碼的處理有關,因爲在gcc下這段代碼會提示如下問題:
error:case label does not reduce to an integer constant
warning:variable [two] set but not used
後來在衆多牛人的解釋之下,我才發現,原來這不是編譯器的問題,而只是const在C和C++中意義不一樣
C++中將const定義爲了常量。
)
(2)const修飾指針
看這四個聲明:
int const *i;
const int *i;
int * const i;
const int * const i;
到底const的修飾對指針i起只讀作用,還是對i所指向的int值起只讀作用呢?
這裏有一種直觀的做法來認清它的真面目:
int const *i; const int *i;int * const i;constint * const i;
結果是不是一目瞭然?將數據類型直接劃掉即可
最後一個聲明保證了指針i和i指向的對象都爲只讀變量。
const最有用之處就是用它來限定函數的形參,比如strcpy函數的原型。
**************typedef與define的區別*****************
(1)整個結構體的定義提倡這樣定義:
struct student
{
int id;
char name[10];
};
struct student LiSi,ZhangSan;
這種定義方式才易於閱讀,雖然多寫了一點代碼
(2)糾結的結構標籤和結構類型
typedef struct foo{int i;}foo;
struct foo{int i;}foo;
我們如果用sizeof(foo)的話,編譯器是不是肯定會報錯的?這當時肯定是的。第一個定義中,聲明瞭結構標籤foo(第一個)和有typedef聲明的結構類型foo(第二個)。
使用結構標籤的foo效果:struct foo value
使用結構類型的foo效果:foo value
第二個定義中,聲明瞭結構標籤foo和變量foo
只有結構標籤能夠在以後的聲明中使用:struct foo value
這就是利用typedef來定義類型的新名字帶來的頭疼之事,所以建議
不要用typedef來做這種複雜的事,下面來看看它與define的區別
(3)typedef與define
typedef等價於一種徹底的“封裝”:聲明之後不能再次增加其他內容
區別一:
#define elem int
unsigned elem i;/* OK */
typedef int elem;
unsigned elem i;/* ERROR */
對宏類型名我們可以用其它類型說明符對其進行擴展,但對typedef所定義的類型名卻不能。
所以第一個定義是對的,但第二個定義會報錯
區別二:
#define int_ptr int *
int_ptr a,b;
typedef int * int_ptr;
int_ptr a,b;
第一個利用宏定義定義a,b變量之後,經過宏擴展a的類型是int *,b的類型卻還是int
第二個typedef之後的a,b變量的類型都是int *,這就是兩者的第二個區別
最後提醒一點,這兩個東西不可以相互嵌套。。。。
(今天剛寫個二叉樹的程序,想用非遞歸實現,需要用到棧,結果把棧的宏定義內容改爲
二叉樹的typedef定義的結構類型就發生ERROR,弄了好久才解決)
(4)typedef的適用
1、數組、結構、指針已經函數的組合類型
2、可移植類型(將數據類型改一下就可以輕鬆適應不同的平臺了)
3、也可以用在強制類型轉換時提供一個簡單的名字
**************各種括號組成的複雜聲明*****************
先來頭腦風暴一下:
int *(*abc)[6];
int *(*abc())();
int *(*(*abc)())[6];
int *(*(*(*abc()))[6])();
大概看到的人都有一點昏迷了。。。。
下面給出讀懂複雜的函數聲明的要訣:
最重要的是弄懂各種操作符的優先級,其次先通過最後一對操作符來判斷是函數還是數組
在函數的高級聲明中,主要用到()、[]和*操作符,只需記住()[]->.四個操作符的優先級最高即可
以第三個爲例,來分析一下高級聲明該怎麼理解:
1、有三對圓括號,最後一對是方括號,說明是一個指向數組的指針吧,數組長度爲6;
2、(這裏有重大修改,先前寫的是從最裏層開始分析)先從最外層開始分析,int *(A)[6],我們
可以確定是一個A是一個指向數據爲int *型數組的指針,數組長度爲6,再分析A
3、A爲(*(*abc)()),從裏層的兩個圓括號看,其中一個圓括號爲空,說明肯定是一個函數,也就是
說整個的最外層int *()[6]肯定是一個函數的返回值了,在看(*abc)這是一個abc指向的函數指針,返
回值是什麼?通過兩個並排的原括號前面的*決定,這裏(*abc)之所以沒有先於*一起結合看而先於()
一起結合看作一個函數,是因爲*的優先級沒()高
4、*決定了裏層的函數指針的返回值,它的內容當然就是外層的那些個東東,即是:指向int型數組
的一個指針
5、先在加上對裏層的分析,結論就是:
返回值爲“指向int型數組的指針”的函數指針
整個的順序是簡化出來是這樣:
int *( )[6]---->確定這是一個指向int型數組的指針
int *( (*abc) )[6]---->abc肯定是一個指針了,具體還無法判斷
int *( (*abc)())[6]---->這下可以確定另一個東西了,abc是一個指針函數,函數總得有返回值吧,繼續
int *(*(*abc)())[6]---->返回值出來了,通過第二個*確定的,它表明返回的是外面那個*所指向的內容
整個過程總結爲:
A、先取最外層的,抽出裏層內容,對裏層的從它的名字開始讀取,按照優先級順序依次讀取
B、優先級順序:
1、聲明中被圓括號括起來的部分
2、後綴操作符:()函數、[]數組
3、前綴操作符:*指向什麼的指針
C、如果還有const、volatile關鍵字在類型說明符前面說明它作用於類型說明符,如前面講的const的
修飾作用,其他情況他倆一般作用於它左邊緊挨的*操作符
很明顯,如果用typedef來爲這些個圓括號重新命名的話,再複雜的函數聲明你剖析的時候都會遊刃有餘的
(由於昨天剛開始寫這個聲明的分析時頭腦還沒怎麼理順這個思路,造成解說的很混亂,今天重新對
此進行整理,如果有錯,請大家友情指出。)