開始讀《C專家編程》之前,有一個很擔心的問題:94年出的講語言的書,在現在(2012)還有多少是適用的。因此,一邊讀,一邊用VS2010做實驗。最后發現大部分內容都還在用。讀完后,覺得最精彩的部分有二:一是講解如何理解聲明,二是深入地講解數組名與指針。下文是將看書過程中所做的筆記進行的整理。
p.s: 以下代碼均在VS2010測試過
1. 使用無符號數時要特別注意(不推薦使用無符號數)
當無符號數與有符號數在同一條表達式中出現時,有符號數會被轉換為無符號數。e.g:
int feng = -1;
unsigned int yan = 5;
bool result = (feng < yan) ? true : false; //最后的結果會是false
原因是C語言在計算含有不同類型的表達式時,會將類型向上提升。在本例中,int被提升了unsigned int,從而使-1的補碼被解析為很大的整數
2. 相鄰的字符串常量會被自動合並成一個字符串
e.g:
char *str[] = {"feng" "yan", "zero"}; //"feng"和"yan"被合並成一個了:"fengyan"
3. 易出錯的優先級
.高於* e.g: *p.f 正確的理解:*(p.f)
[]高於* e.g: int *ap[] 正確的理解:int *(ap[]) ap是個數組,其元素為int*
函數()高於* e.g: int *fp() 正確的理解:int* fp() fp是返回int*的函數
==和!=高於位操作符 e.g: val & mask != 0 正確的理解:val & (mask != 0)
==和!=高於賦值符 e.g: c = getchar() != EOF 正確的理解:c = (getchar() != EOF)
算術運算高於移位運算 e.g: msb<<4 + lsb 正確的理解:msb << (4 + lsb)
逗號運算符優先級最低 e.g: i = 1, 2 正確的理解:(i = 1), 2
4. 理解聲明,定義,typedef語句的步驟
a. 找標識符
b. 找被括號括起來的部分
c. 找后綴操作符,如果是(),則表示是函數;如果是[],則表示是數組
d. 找前綴操作符,如果是*,則表示“指向XX的指針”
e. 找const和volatile,如果const,volatile后面緊跟類型(如int,long),那么它作用於類型,其它情況下,作用於它左邊緊鄰的項
e.g:
int const * zero; //zero是一個指針,指向一個常量整形
char (*zero)[20]; //zero是一個指針,指向一個有20個char元素的數組
typedef void (*ptr_to_func)(int); //ptr_to_func是新類型名,這種類型是一個函數指針,指向接收一個int參數,並返回void的函數
char* const * (*zero)(); //zero是一個函數指針,該函數無參數,並返回一個指針,返回的指針指向一個常量指針
char* (*zero[10])(int **p); //zero是一個數組,元素是函數指針,其指向的函數授受一個二維指針,並返回一個指向char的指針
void (*signal
(int sig, void (*func)(int)))(int); //signal是一個函數,該函數接收一個int,一個函數指針,並返回一個函數指針
5. 左值與右值
左值通常表示存儲結果的地方(地址),其值在編譯時可知
右值通常表示
地址的內容,其值通常要到運行時才知道
6. 指針與數組名不等同的情況(定義為數組,卻聲明為指針,或者反過來)
前提知識(假設有定義:int array[10], *ptr;):
a. 使用數組名下標訪問(如:array[1]),會直接將數組名的
地址加上偏移值作為變量的地址(即array[1]的地址)
b. 使用指針下標訪問(如:ptr[1]),會先取指針指向的內容,然后將這個
內容加上偏移值作為變量的地址(即ptr[1]的地址)
不等同的原因:
當定義為數組,卻聲明為指針時,相當於用指針下標訪問的方法來解析一個數組名下標,即先取數組第0號元素的內容,然后將這個內容加上偏移值作為變量的地址,從而訪問了不該訪問的東西。反之亦然。
7. 指針與數組等同的情況
a. 編譯器將
表達式中的數組名當作指向該數組第0號元素的指針,下標當作指針的偏移量,即array[i]會被當作*(array + i)
b. 編譯器將 函數參數聲明中的數組名當作指向該數組第0號元素的指針,即在函數內部得到的是指針,而不是數組名
b. 編譯器將 函數參數聲明中的數組名當作指向該數組第0號元素的指針,即在函數內部得到的是指針,而不是數組名
基於a情況,可知這條謠言是假的(至少在一維數組中一定不成立):
用指針迭代數組比用下標迭代數組更快
基於b情況,可解釋為什么在傳遞數組后,不能用以下方法計算數組長度
int ArrayLength(int arr[]) {
return sizeof(arr) / sizeof(arr[0]); //返回值必定是1,因為此時的arr是一個指針,而不是數組名
}
注意b情況的將數組改寫為指針並不是遞歸定義的,e.g:
實參 char zero[10][10] 被改寫為 char (*zero)[10],這里將數組的數組改寫為數組的指針
實參 char *zero[10] 被改寫為 char **zero,這里將指針數組改寫為指針的指針
實參 cahr (*zero)[10] 不改變,因為此時的zero是指針,而不是數組
8. interposition
interposition指用戶定義的函數取代庫中聲明完全相同的函數,注意這不是指重載,而是指下面這種:
void zero(); //user defined function
void zero(); //library function
出現interposition時,要特別注意以下情況:
void zero(); //user defined function
int main() {
zero(); //調用用戶定義的函數zero,而不是庫函數zero
FengYan(); //
假設這是另一個庫函數,並且函數內調用庫函數zero,此時由於interposition,變成調用用戶定義的zero
return 0;
}
備注:
出現interposition時,在VS2010會出現warning: inconsistent dll linkage
9. 堆棧段作用
a. 存儲局部變量
b. 函數調用時,存儲有關的維護信息
c. 用作暫時存儲區。e.g: 計算一個很長的表達式時,會把部分結果先壓到堆棧中