C語言是一門面向結構化的高級編程語言(也有人認為它是中級語言),用于通用編程需求。基本上,C語言是其基本語法和庫函數的集合,因此程序員定義自己的函數并且将其包含在C語言庫中也是很方便的。
C語言的主要用途是編寫其他編程語言的編譯器、操作系統、文本編輯器、後台服務程序、驅動程序、數據庫、腳本語言的解釋器,以及其他各種實用的程序。
C語言甚至能夠編寫自己的編譯器。
如果讀者對C語言感興趣,并且希望得到一份C語言程序員的工作,那麼下面這 7 道面試題将會非常有趣。
問題1,C語言的顯著特點是什麼?可移植。C語言是一種與平台無關的編程語言,不使用平台依賴庫的C語言程序可以輕易移植到各種平台。模塊化。我們能夠輕易的将一個非常大的C語言項目拆分成若幹個小的模塊,并逐個實現,最終組合解決該大項目。靈活。C語言給與程序員最大的自由,因此隻要某種代碼C語言的語法沒有禁止,程序員就可使用。也即所謂的“法無禁止即可行”。
問題2,什麼是C語言中的“懸空指針”?
C語言中的指針可以指向一塊内存,如果這塊内存稍後被操作系統回收(被釋放),但是指針仍然指向這塊内存,那麼,此時該指針就是“懸空指針”。下面這段C語言代碼是一個例子,請看:
void *p = malloc(size);
assert(p);
free(p); // 現在 p 是“懸空指針”
C語言中的“懸空指針”會引發不可預知的錯誤,而且這種錯誤一旦發生,很難定位。這是因為在 free(p) 之後,p 指針仍然指向之前分配的内存,如果這塊内存暫時可以被程序訪問并且不會造成沖突,那麼之後使用 p 并不會引發錯誤。
最難調試的 bug 總是不能輕易複現的 bug,對不?
所以在實際的C語言程序開發中,為了避免出現“懸空指針”引發不可預知的錯誤,在釋放内存之後,常常會将指針 p 賦值為 NULL:
void *p = malloc(size);
assert(p);
free(p);
// 避免“懸空指針”
p = NULL;
這麼做的好處是一旦再次使用被釋放的指針 p,就會立刻引發“段錯誤”,程序員也就能立刻知道應該修改C語言代碼了。
問題3,C語言中的“野指針”是什麼?
“懸空指針”是指向被釋放内存的指針,“野指針”則是不确定其具體指向的指針。“野指針”最常來自于未初始化的指針,例如下面這段C語言代碼:
void *p;// 此時 p 是“野指針”
因為“野指針”可能指向任意内存段,因此它可能會損壞正常的數據,也有可能引發其他未知錯誤,所以C語言中的“野指針”危害性甚至比“懸空指針”還要嚴重。在實際的C語言程序開發中,定義指針時,一般都要盡量避免“野指針”的出現(賦初值):
void *p = NULL;
void *data = malloc(size);
問題4,C語言中的 static 函數有什麼用?相信讀者在不少的C語言項目中看到類似于下面這樣的 static 函數,為什麼使用 static 關鍵字修飾函數呢?這麼做有什麼用呢?
static void foo(){ ...}
稍大的C語言項目中一般都會出現這樣的 static 函數(靜态函數),C語言中的靜态函數最主要的特點就在于其作用域——僅限所述文件。例如在 fun.c 文件中定義的 static 函數,不能在如 main.c 等其他文件中使用。
讀者可以嘗試使用 extern 關鍵字引入其他文件中定義的 static 函數。
C語言中 static 函數的這個特性使得它常常被定義在 .h 文件中,一般和 inline 關鍵字一起使用,以獲得 define 函數式宏定義類似的高效率。
問題5,C語言中的“循環”數據類型是指什麼?
所謂的“循環”數據類型,其實就是某種類型的數據溢出後,又從頭開始存儲。一個典型的例子是 unsigned char 變量若已經等于 255,仍然對其加 1,那麼該變量就會溢出從頭開始,也即等于零:
unsigned char a = 255;
a = a 1;// a 等于 0
unsigned char 型變量 a 是無符号的 8 位整數,它能表示的最大值是 8 個位全為 1,也即 0xff=255,若此時再對其加一,将得到 0x100。a 隻索引 8 位,也即 0x100 中的 0x00=0。
C語言中的 int,long,short 等類型也有類似的“循環”特性,該特性不會引發語法編譯錯誤,因此較難判斷這些類型的變量是否溢出。而C語言中的 float,double 類型則沒有“循環”特性,因此實際C語言程序開發中一個常用的檢查整型數據是否溢出的技巧,就是借助于 float 和 double 類型的,這一點在我之前的文章中說過,感興趣的讀者可以看看。
問題6,C語言中的頭文件有什麼用?一般C語言程序項目中的頭文件後綴名都為 .h,h 是 header 的縮寫。頭文件的使用一般和 #include 結合使用,例如在 main.c 文件中寫下:
#include "header.h"
意味着在該處将 header.h 中的内容展開到此。所以C語言中的頭文件中一般包含程序需要使用的函數定義和原型,也可以包含相關的數據結構類型定義。
這裡再啰嗦下“在該處将 header.h 中的内容展開到此”的含義——假如 header.h 頭文件中的内容是:
// header.h 頭文件
printf("hello world\n");
那麼,在其他文件中寫下
#include "header.h"就等價于
// header.h 頭文件
printf("hello world\n");
問題7,C語言中的指針可以做加法運算嗎?
C語言中的指針包含地址詳細信息,一般是不可以直接做加法運算的,例如下面這段C語言代碼:
void *p1 = (void *)1;
void *p2 = (void *)2;
// 下面是非法的
void *p = p1 p2;
讀者可自行嘗試,指針 p1 和指針 p2 是無法直接相加的,否則編譯器就會報錯。但是如果想對指針 p1 和 p2 的地址值相加,可以将其強制轉換為整數類型,例如:
void *p1 = (void *)1;
void *p2 = (void *)2;
long p = (long)p1 (long)p2;
應該确保強制轉換的整數類型寬度大于指針類型寬度,否則可能會因為數值截斷導緻得到錯誤的結果。
雖然C語言中的指針不能直接與指針相加,但是卻可以與其他整數相加,例如下面這段C語言代碼:
char *p1 = (char *)1;
char *p = p1 1;
指針p1 指向地址 1,因此指針 p 指向地址 2,這沒什麼好說的。但是,讀者應該注意下面這樣的“陷阱”:
int *p1 = (int *)1;
int *p = p1 1;
與上面的C語言代碼例子相比,這裡僅僅将 char 換成 int。那麼,指針 p 指向哪個地址呢?編寫打印代碼:
int *p1 = (int *)1;
int *p = p1 1;
printf("p1=%p, p=%p\n", p1, p);
編譯并執行上面這段C語言代碼,會發現輸出如下:
p1=0x1, p=0x5
可見,“1 1”并不等于 2,而是等于 5 了。這其實是因為C語言中的指針是有其自己的含義的,不同的指針類型索引内存的大小也往往不同,我的機器上 int 類型占用 4 個字節内存空間,因此指針 p1 1 實際上是往後移動了 4 個字節。
讀者可自行将 int 換成其他類型試試。
小結本節列舉的 7 個C語言問題其實屬于C語言的基本語法和特點,如果能夠熟練掌握,相信對找到一份相關的工作是有幫助的。
另外,對于編程方面,學習C/C 編程或者工作想提升的夥伴,如果你想更好的提升你的編程能力幫助你提升水平!筆者這裡或許可以幫到你~
編程學習書籍分享:
編程學習視頻分享:
分享(源碼、項目實戰視頻、項目筆記,基礎入門教程)
歡迎轉行和學習編程的夥伴,利用更多的資料學習成長比自己琢磨更快哦!
對于C/C 感興趣可以關注小編在後台私信我:【編程交流】一起來學習哦!可以領取一些C/C 的項目學習視頻資料哦!已經設置好了關鍵詞自動回複,自動領取就好了!
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!