寫在前面
工作三年多,常聽各位前輩講:
語言是其次的,重要的是思想。
深以爲然,於是繼續貫徹陶淵明的:
好讀書,不求甚解。
結果,工作了三年多,稀裏糊塗的代碼寫了不少,犯的錯誤也很多,吃一塹長一智的過程中,也會經常有很多不解:“這樣寫,難道不對麼?”
最近終於有時間,終於還是決定花點時間,把c語言掌握的清晰一點,選中了這本已經帶領無數大神走向巔峯的神書《c專家編程》,打算通讀一遍,空口無憑,記個筆記,所以,到這裏您可以返回了,這,只不過是一個簡陋的讀書筆記。。。
01 #define中的空格
記得最開始學習編程的時候,有聽過一些說法,宏定義中的空格實際上沒有作用,例如:
//01-1.1
#include <stdio.h>
#define sum(a, b) ((a) + (b))
#define sum_1(a, b) ((a)+(b))
#define sum_2(a,b) ((a)+(b))
int main(int argc, char* argv[])
{
printf("sum = %d\n", sum(1, 1));
printf("sum_1 = %d\n", sum_1(1, 1));
printf("sum_2 = %d\n", sum_2(1, 1));
return 0;
}
預編譯後的產物,其實是這樣的:
//01-1.2
//...不相關,省略
int main(int argc, char* argv[])
{
printf("sum = %d\n", ((1) + (1)));
printf("sum_1 = %d\n", ((1)+(1)));
printf("sum_2 = %d\n", ((1)+(1)));
return 0;
}
上面的空格的確不會影響結果輸出,而且,行尾多加的幾個空格也沒有什麼用處,然而另外一些情況,顯然不是如此:
//01-2.1.1
#define a(y) a_expanded(y)
int a_expanded(int y)
{
return y + 10;
}
int main(int argc, char* argv[])
{
int x = 100;
a(x);
return 0;
}
預編譯後如下:
//01-2.1.2
//...不相關,省略
int main(int argc, char* argv[])
{
int x = 100;
a_expanded(x);
return 0;
}
而下面這段代碼,加了兩個空格之後,含義卻完全不同:
//01-2.2.1
#define a (y) a_expanded (y)
int a_expanded(int y)
{
return y + 10;
}
int main(int argc, char* argv[])
{
int x = 100;
a(x);
return 0;
}
預編譯後的產物卻是這樣:
//01-2.2.2
//...不相關,省略
int main(int argc, char* argv[])
{
int x = 100;
(y) a_expanded (y)(x);
return 0;
}
那麼顯然,空格影響了宏定義的含義,當然,代碼01-2.2.1實際上無法執行,因此,宏定義中,顯然還是要小心的注意某些位置空格的問題。
02 const修飾的是誰?
int foo(const char** p)
{
}
int main(int argc, char** argv)
{
foo(argv);
}
上面這段例子裏,編譯會報warning:
const.c: In function ‘main’:
const.c:8:9: warning: passing argument 1 of ‘foo’ from incompatible pointer type [-Wincompatible-pointer-types]
foo(argv);
^~~~
const.c:1:5: note: expected ‘const char **’ but argument is of type ‘char **’
int foo(const char** p)
^~~
那麼顯然,編譯器認爲,char**
並不能直接複製給const char **
,會發生隱式類型轉換,可是,下面的例子卻很常見:
int foo(const char* p)
{
}
int main(int argc, char* argv)
{
foo(argv);
}
那麼,一個簡單的問題就是,c語言的參數傳遞實際上是一個賦值過程,那麼char*
可以賦值給const char*
,爲什麼char **
不能賦值給const char**
?
兩個操作數都是指向有限定符或無限定符的相容類型的指針,左邊指針所指向的類型必須包含右邊指針所指向類型的全部限定符。
那麼,上面主要有兩處需要注意:
- 賦值的左右兩邊必須類型相容。
- 左邊必須包含右邊的全部限定符。
根據這兩個條件,解釋以下這個問題:
char*
是一個指向沒有限定符的char類型的指針。
const char*
是一個有const限定符的char類型的指針。
char
類型相容,左邊包含右邊的限定符,因此可以賦值。
char**
是一個指向char類型的指針的指針。
const char**
是一個指向有const限定符限制的char類型的指針的指針。
那麼顯然,二者均沒有限定符,且是指針,前者指向char*,後者指向const char*,二者不相容,也因此,char**
實際上與const char**
不相容,因此,上面的編譯會報warning。