C語言中的符號重載
C語言非常的簡潔, 以至於不願意用太多的符號, 這樣有很多符號在不同的地方有不同的含義
這樣會讓使用者很困惑, 這是c的語言特性, 也是設計上的一些失誤
static
在函數內部,表示該變數的值在各個調用間一直保持延續性;
對於函數,表示該函數只在本檔案中可見
extern
用於變數,表示該變數在其它地方定義;
用於函數定義, 表示全域可見(屬於冗餘的)
void
用於參數列表中,表示該函數參數為空白,如int main(void);
用於傳回值,表示該函數返回void,即不返回任何值,相當與pascal中的過程;
在指標聲明中,表示通用指標
*
乘法運算子;
用於指標前,表示對指標所指內容的間接引用;
用於聲明中表示指標,如int *pi,表示指向整形的指標,char *strcpy(...)表示函數的傳回值為指標
&
位與運算子;
取地址操作符
()
函數定義中,包圍函數形式參數表,如int main(int argc, char **argv);
調用一個函數,如srand();
改變運算式的運算順序,如 a * ( b + c );
類型轉換, 如 (int*)x;
定義帶參數的宏,如 #define MAX(a, b) ((a) > (b)) ? (a) : (b)
包圍sizeof操作符的運算元(如果是類型名),如 sizeof(int)
數組和指標的本質區別
C編程新手最常聽到的一句話是'數組和指標是相同的', 不幸的是, 這是一種危險的說法, 他並不正確.
最典型的一個錯誤是
file 1:
int mango[100];
file 2:
extern int *mango;
這樣的聲明是錯的, 必須聲明為extern int mango[];
關於聲明的意義在上篇講解extern時已經詳細說明過
我們首先要區分"地址y"和"地址y的內容"之間的區別, 這是一個相對微妙之處, 因為大多數程式設計語言中我們用同一個符號來表示兩樣東西, 並由編譯器根據上下文環境來判斷它的具體含義.
int X, Y;
X = Y;
這邊我們就來深入討論一下這個指派陳述式, 看上去X和Y是同一種東西, 都是int型變數
但對於編譯器而言, 一個指派陳述式兩邊的東西是不一樣的 (以彙編的角度思考)
左值 = 右值;
左值是地址, 右值是資料, 是有本質區別的
所以上面的X是X代表的地址, Y是Y代表的地址裡面的內容
所以對於編譯器而言,
X=2, 這個很直接, 把2放到X代表的地址上
X=Y, 需要先從Y代表的地址上讀出資料, 再放到X代表的地址上
需要注意, 左值是在編譯時間就可以明確知道的, 因為編譯器會為每個變數分配一個地址
而右值往往只有在運行時才可知的
那麼下面詳細看看為什麼大家會誤認為兩者相同
int a[10], *p, *q;
p = a ;
q = p+1 ;
q = a+1;
int i;
i = a[0];
i = (*a);
i = (*p);
如上的代碼, 你發現可以象操作指標一樣來運算元組變數名a, 貌似a和指標p時等價的
其實非也, a其實代表數組的首地址, 就是個常量, 進行q = a+1, 類似於X = 2+1
而p是指標變數, p=a後, p代表的地址裡存放了a,即數組首地址
那麼q = p+1, 需要從p代表的地址中讀出a, 並放到q代表的地址中去, 類似於X = Y+1
同樣對於*操作, 是取地址上的內容, *a直接取a地址上的內容, 而*p是要先從p代表的地址取出地址資料, 再取該地址資料上的內容
而且兩者還有一個區別是時間上的差別
對於q = a+1, 因為a代表常量, 在編譯時間就可以算出q的值
而對於q = p+1, p是變數, 必須到運行時才能知道q的值
兩者是有本質區別的, a代表常量, 有人把它稱為'常量指標', 是不能賦值或改變的指標, 這絕對的是誤導
a就不是指標, 根本就不能作為左值, 你有見把2作為左值的嗎
此處可以認為a等同於&p
之所以大家會混淆, 完全是因為c語言的設計不規範導致的, 相同的語句卻有著完全不同的操作
此處個人覺得是數組的設計欠缺妥當
int i, * p;
對於一般的變數, 無論是i還是p, 作為左值表示變數名所代表的地址, 作為右值表示變數名所代表的地址上存放的資料. 其實i和p從這個層面上來講, 完全沒有什麼分別, 只不過p中存放的資料是地址而不是其他.
但是對於int a[];
就不一樣了, 因為a只是作為一個資料集合的開始位置, a無法作為左值(設計者認為這樣的不恰當). 同時a作為右值時, 也無法取a地址上的資料, 因為它只是個開始位置, 你不知道取多少. 所以設計者打破了普通變數的規則, 把a當作一個地址常量來看待, 即當a作為右值時, 直接取a代表的地址作為右值, 而不會象通常變數一樣去取地址上的資料作為右值.
問題是, 根據c的文法, 其在使用時和指標又十分相似, 這樣的設計是不嚴謹的. 其實如果這樣設計會穩妥些,
將a[]作為一個整體, 不能單獨對a做任何操作, 如需取地址則用&(a[])
不過這樣應該不符合c語言的簡單美學...c語言是靈活, 簡單的語言, 但是很多設計太隨意, 不夠嚴謹, 所以導致很多地方不符合邏輯, 很難理解
再論數組
本書和很多講解數組和指標的文章過於複雜, 我個人覺得那是把簡單的問題複雜化了.
我在上面應該已經把數組和指標的關係講清楚了, 下面補充幾點
1. 書中指出數組和指標在作為函數參數時是一樣的(equivalent), 這個說法來自"C Programming Language, 2nd Ed, Kernighan & Ritchie"
如下,
char my_array[10];
char * my_ptr;
...
i = strlen( my_array );
j = strlen( my_ptr );
在c語言中, 作為函數的參數都是傳值的, 但是數組卻是個例外, 確實對於大數組, 傳值明顯是相當低效的
所以當你自己把數組作為函數參數的時候, 編譯器其實是產生一個指向數組首地址的指標, 並把這個指標作為參數傳入函數.
其實如果想把整個數組傳值傳入函數, 象前面說的, 只要把數組封裝在結構中, 即可, 不過不推薦這樣, 甚至在把結構體對象當作參數的時候, 要check結構體內是否有數組, 如果有數組, 最好把地址作為參數.
所以在作為函數參數時, 數組首地址會被轉化為指標作為參數, 這個只是編譯器出於方便統一這樣處理.
在這點上說兩者equivalent, 我個人覺得是不妥當的
2. 書中說"運算式中的數組名就是指標", 這個說法是相當危險的, 會把剛有些明白的程式員再次繞暈
int a[100];
int *p;
p = a+1;
書中的意思是, 這個操作中, 編譯器出於處理的方便, 會先把a封裝成一個指標, 然後賦值給p, 所以可以把a看作是指標, 這個說法很不負責
編譯器可以出於處理方便的需要把a封裝成指標, 但是數組名a絕對不是指標, 如果a是指標, 就應該可以作為左值進行賦值, a = p
實際上是不行的, a就是地址常量
3. 數組中的[]符號就代表地址位移
在編譯的時候, a[i] 會被改寫成 *(a+i)
對於+是沒有前後順序的
所以你這樣寫, i[a], 也是可以的, 因為在編譯時間, 也是被改寫成 *(i+a)
所以雖然看著很bt, 但是其實效果是一樣的, 這就是[]的真正意義.