《你必須知道的495個C語言問題》知識筆記及補充


1. extern在函數聲明中是什么意思?

它可以用作一種格式上的提示表明函數的定義可能在另一個源文件中,但在

extern int f(); 和 int f(); 之間並沒有實質的區別。

補充:extern可以置於變量或者函數前,以標示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。此外extern也可用來進行鏈接指定。也就是說extern有兩個作用:

第 一,當它與"C"一起連用時,如: extern "C" void fun(int a, int b);則告訴編譯器在編譯fun這個函數名時按着C的規則去翻譯相應的函數名而不是C++的,C++的規則在翻譯這個函數名時會把fun這個名字變得面目 全非,可能是fun@aBc_int_int#%$也可能是別的,這要看編譯器的"脾氣"了(不同的編譯器采用的方法不一樣),為什么這么做呢,因為 C++支持函數的重載;
第二,當extern不與"C"在一起修飾變量 或函數時,如在頭文件中: extern int g_Int; 它的作用就是聲明函數或全局變量的作用范圍的關鍵字,其聲明的函數和變量可以在本模塊活其他模塊中使用,記住它是一個聲明不是定義!也就是 說B模塊(編譯單元)要是引用模塊(編譯單元)A中定義的全局變量或函數時,它只要包含A模塊的頭文件即可,在編譯階段,模塊B雖然找不到該函數或變量, 但它不會報錯,它會在連接時從模塊A生成的目標代碼中找到此函數。


2. 怎樣建立和理解非常復雜的聲明?例如定義一個包含N個指向返回指向字符的指針的函數的指針的數組?

這個問題至少有以下3種答案:

(1)char *(*(*a[N])())();

(2)用typedef逐步完成聲明:

typedef char *pc;        /* 字符指針 */  
typedef pc fpc();        /* 返回字符指針的函數 */  
typedef fpc *pfpc;       /* 上面函數的指針 */  
typedef pfpc fpfpc();    /* 返回函數指針的函數 */  
typedef fpfpc *pfpfpc;   /* 上面函數的指針 */  
pfpfpc a[N];             /* 上面指針的數組 */ 

(3)使用cdecl程序,它可以把英文翻譯成C或者把C翻譯成英文:


通過類型轉換,cdecl也可以用於解釋復雜的聲明,指出參數應該進入哪一對括號(如同在上述的復雜函數定義中)。

補充:cdecl程序非常有用,它可以在C語言的聲明和英語之間進行轉換。它可以解釋一個現存的C語言聲明,cdecl 程序可以幫助你分析復雜的聲明。

這里有一個在線的cdecl:一個在線的cdecl.


3. 為什么struct x { ... }; x thestruct; 不對?

C不是C++。結構標簽不能自動生成類型。

補充:也就是在C++中這樣是對的。在C中不想用struct只能借助於typedef。


4. NULL可以確保是0,但空指針(null)卻不一定。


5. 數組和指針的區別是什么?

數組自動分配空間,但是不能重分配或改變大小。指針必須明確賦值以指向分配的空間(可能使用malloc),但是可以隨意重新賦值(即指向不同的對象),同時除了表示一個內存塊的基址之外,還有許多其它的用途。
由於數組和指針所謂的等價性,數組和指針經常看起來可以互換,而事實上指向malloc分配的內存塊的指針通常被看作一個真正的數組(也可以用[]引用)。但是,要小心sizeof。

6. 當我向一個接受指針的指針的函數傳入二維數組的時候,編譯器報錯了。

數組蛻化為指針的規則 不能遞歸應用。數組的數組(即二維數組)蛻化為數組的指針,而不是指針的指針。數組指針常常令人困惑,需要小心對待:

如果你向函數傳遞二維數組:
int array[NROWS][NCOLUMNS];
f(array);
那么函數的聲明必須匹配:
void f(int a[][NCOLUMNS]){ ... }
或者
void f(int (*ap)[NCOLUMNS]){ ... }  /* ap是個數組指針 */

在第一個聲明中,編譯器進行了通常的從“數組的數組”到“數組的指針”的隱式轉換;第二種形式中的指針定義顯而易見。
因為被調函數並不為數組分配地址,所以它並不需要知道總的大小,所以行數NROWS可以省略。但數組的寬度依然重要,所以列維度NCOLUMNS(對於三維或多維數組,相關的維度)必須保留。

如果一個函數已經定義為接受指針的指針,那么幾乎可以肯定直接向它傳入二維數組毫無意義。

7. 為什么這段代碼不行?

    char *str;  
    gets(str);  
    printf("%s\n", str);  
str沒有指向任何合法的位置,換言之,我們不知道指針str指向何處。因為局部變量沒有初始化,通常包含垃圾信息,所以甚至都不能保證str是一個合法的指針。
改正方法:用局部變量或用malloc()分配str緩沖區。

8. 我剛才試了這樣的代碼char *p; strcpy(p, "abc"); 而它運行正常?怎么回事?為什么它沒有崩潰?

未初始化的指針p所指向的 隨機地址恰好對你來說是可寫的,而且顯然也沒有用於什么關鍵的數據。
char *p; 編譯器只分配了足夠容納指針本身的內存; 也就是說,這種情況下,你分配了sizeo(char*)個字節的內存。但你還沒有分配任何讓指針指向的內存,因此此時p所指向的內存可能是垃圾信息也可能是可寫的區域,沒有崩潰說明是后者。

9. 下面關於itoa的實現為什么是錯誤的?如何改正?

char *itoa(int n)  
{  
     char retbuf[20];   // error  
     sprintf(retbuf, "%d", n);  
     return retbuf;     // error  
} 
這樣在編譯的時候會出現這樣的警告信息“ warning C4172: returning address of local variable or temporary”,說明retbuf的地址是臨時變量,是暫時的,函數返回時就沒有了也就是不能直接返回。

一種解決方案是把返回緩沖區聲明為靜態變量:
static char retbuf[20];

總結:若要返回字符串或其它集合,則返回指針必須是靜態分配的緩沖區,或者調用者傳入的緩沖區,或者用malloc()獲得的內存,但不能是局部(自動)數組。

10. 為什么在調用free()之后指針沒有變空?使用(賦值,比較)釋放之后的指針有多么不安全?

當你調用free()的時候,傳入指針指向的內存被釋放,但 調用函數的指針值可能保持不變,因為C的按值傳參語義意味着被調函數永遠不會改變參數的值。嚴格的說, 被釋放的指針值是無效的,對它的任何使用,即使沒有解參照,也可能帶來問題,盡管作為一種實現質量的表現,多數實現都不會對無傷大雅的無效指針使用產生例外。

補充:free()過后的指針也叫做“ 野指針”,建議free()過后立即將指針置為NULL,詳細原因請看“ 為什么free(re)過后re不為NULL呢?”。

11. calloc()和malloc()有什么區別?利用calloc的零填充功能安全嗎?free()可以釋放calloc()分配的內存嗎,還是需要一個cfree()?

calloc(m, n)本質上等價於:
    p = malloc(m * n);  
    memset(p, 0, m * n);  
 
填充的全是零,因此不能確保生成有用的空指針值或浮點零值,free()可以安全的用來釋放calloc()分配的內存。

12. 在C中,sizeof('a')=4而不是1,因為C語言中的字符常數是int型,因此sizeof('a')是sizeof(int),這是與C++不同的地方。


13. C語言中沒有提高標准的布爾類型,需要用#define或枚舉常數定義true/false。


 

14. 我試圖用ANSI“字符串化”預處理操作符#向信息中插入符號常量的值,但它字符串化的總是宏的名字而不是它的值。

你可以用下面這樣的兩步方法迫使宏既字符串化又擴展:
#define Str(x) #x  
#define Xstr(x) Str(x)  
#define OP plus  
char *opname = Xstr(OP);
這段代碼把opname置為“plus”而不是“OP”。

總結:在使用符號粘接操作符##連接兩個宏的值(而不是名字)時也要采用同樣的“迂回戰術”。

15. memcpy()和memmove()有什么區別?

如果源和目的參數有重疊,memmove()提供有保證的行為。
而memcpy()則不能提供這樣的保證,因此可以實現的更加有效率。
如果有疑問,最好使用memmvoe()。

16. 我如何在printf的格式串中輸出一個'%'?我試過"\%",但是不行。

只需要重復百分號: %%。\%不行,因為\是編譯器的轉義字符,而這里我們的問題最終是printf的轉義字符。

17. 為什么大家都說不要使用gets()?

跟fgets()不同,gets()不能被告知輸入緩沖區的大小,因此 不能避免緩沖區的溢出。標准庫的fgets()函數對gets()作了很大的改進,盡管它仍不完善。如果真的可能輸入很長的行,還是需要仔細思考,正確處理。

18. 一個粗陋但通常有效的測試NaN的方法:

 
#define isnan(x)    ((x) != (x)) 
C99提高isnan(), fpclassify()及其它一些類別的函數。

附:標准C函數庫的源代碼:
GNU工程有一個完全實現的C函數庫(http://www.gnu.org/software/libc/

19. 怎樣判斷機器的字節順序是高字節在前還是低字節在前?

有個使用指針的方法:
    int x = 1;  
    if(*(char *)&x == 1)  
        printf("little-endian\n");  
    else  
        printf("big-endian\n");  
另外一個可能是用聯合。

20. 由一個日期,怎樣知道是星期幾?

用mktime()或localtime()(注:如果tm_hour的值為0,要注意DST(夏時制)的調整);或者Zeller的congruence;或者這個由Tomohiko Sakamoto提供的優雅的代碼:
    int dayofweek(int y, int m, int d)  /* 0 = Sunday */  
    {  
        static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};  
        y -= m < 3;  
        return (y + y/4 - y/100 + y/400 + t[m - 1] + d) % 7;  
    }  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM