C語(yǔ)言編程:最常見 7 道C語(yǔ)言面試題,還是有不少人弄不明白?
C語(yǔ)言是一門面向結(jié)構(gòu)化的高級(jí)編程語(yǔ)言(也有人認(rèn)為它是中級(jí)語(yǔ)言),用于通用編程需求。基本上,C語(yǔ)言是其基本語(yǔ)法和庫(kù)函數(shù)的集合,因此程序員定義自己的函數(shù)并且將其包含在C語(yǔ)言庫(kù)中也是很方便的。

C語(yǔ)言的主要用途是編寫其他編程語(yǔ)言的編譯器、操作系統(tǒng)、文本編輯器、后臺(tái)服務(wù)程序、驅(qū)動(dòng)程序、數(shù)據(jù)庫(kù)、腳本語(yǔ)言的解釋器,以及其他各種實(shí)用的程序。
C語(yǔ)言甚至能夠編寫自己的編譯器。
如果讀者對(duì)C語(yǔ)言感興趣,并且希望得到一份C語(yǔ)言程序員的工作,那么下面這 7 道面試題將會(huì)非常有趣。
問(wèn)題1,C語(yǔ)言的顯著特點(diǎn)是什么?
可移植。C語(yǔ)言是一種與平臺(tái)無(wú)關(guān)的編程語(yǔ)言,不使用平臺(tái)依賴庫(kù)的C語(yǔ)言程序可以輕易移植到各種平臺(tái)。模塊化。我們能夠輕易的將一個(gè)非常大的C語(yǔ)言項(xiàng)目拆分成若干個(gè)小的模塊,并逐個(gè)實(shí)現(xiàn),最終組合解決該大項(xiàng)目。靈活。C語(yǔ)言給與程序員最大的自由,因此只要某種代碼C語(yǔ)言的語(yǔ)法沒有禁止,程序員就可使用。也即所謂的“法無(wú)禁止即可行”。

問(wèn)題2,什么是C語(yǔ)言中的“懸空指針”?
C語(yǔ)言中的指針可以指向一塊內(nèi)存,如果這塊內(nèi)存稍后被操作系統(tǒng)回收(被釋放),但是指針仍然指向這塊內(nèi)存,那么,此時(shí)該指針就是“懸空指針”。下面這段C語(yǔ)言代碼是一個(gè)例子,請(qǐng)看:
void *p = malloc(size);
assert(p);
free(p); // 現(xiàn)在 p 是“懸空指針”
C語(yǔ)言中的“懸空指針”會(huì)引發(fā)不可預(yù)知的錯(cuò)誤,而且這種錯(cuò)誤一旦發(fā)生,很難定位。這是因?yàn)樵?free(p) 之后,p 指針仍然指向之前分配的內(nèi)存,如果這塊內(nèi)存暫時(shí)可以被程序訪問(wèn)并且不會(huì)造成沖突,那么之后使用 p 并不會(huì)引發(fā)錯(cuò)誤。
最難調(diào)試的 bug 總是不能輕易復(fù)現(xiàn)的 bug,對(duì)不?
所以在實(shí)際的C語(yǔ)言程序開發(fā)中,為了避免出現(xiàn)“懸空指針”引發(fā)不可預(yù)知的錯(cuò)誤,在釋放內(nèi)存之后,常常會(huì)將指針 p 賦值為 NULL:
void *p = malloc(size);
assert(p);
free(p);
// 避免“懸空指針”
p = NULL;
這么做的好處是一旦再次使用被釋放的指針 p,就會(huì)立刻引發(fā)“段錯(cuò)誤”,程序員也就能立刻知道應(yīng)該修改C語(yǔ)言代碼了。

問(wèn)題3,C語(yǔ)言中的“野指針”是什么?
“懸空指針”是指向被釋放內(nèi)存的指針,“野指針”則是不確定其具體指向的指針?!耙爸羔槨弊畛?lái)自于未初始化的指針,例如下面這段C語(yǔ)言代碼:
void *p;// 此時(shí) p 是“野指針”
因?yàn)椤耙爸羔槨笨赡苤赶蛉我鈨?nèi)存段,因此它可能會(huì)損壞正常的數(shù)據(jù),也有可能引發(fā)其他未知錯(cuò)誤,所以C語(yǔ)言中的“野指針”危害性甚至比“懸空指針”還要嚴(yán)重。在實(shí)際的C語(yǔ)言程序開發(fā)中,定義指針時(shí),一般都要盡量避免“野指針”的出現(xiàn)(賦初值):
void *p = NULL;
void *data = malloc(size);
問(wèn)題4,C語(yǔ)言中的 static 函數(shù)有什么用?
相信讀者在不少的C語(yǔ)言項(xiàng)目中看到類似于下面這樣的 static 函數(shù),為什么使用 static 關(guān)鍵字修飾函數(shù)呢?這么做有什么用呢?
static void foo(){ ...}
稍大的C語(yǔ)言項(xiàng)目中一般都會(huì)出現(xiàn)這樣的 static 函數(shù)(靜態(tài)函數(shù)),C語(yǔ)言中的靜態(tài)函數(shù)最主要的特點(diǎn)就在于其作用域——僅限所述文件。例如在 fun.c 文件中定義的 static 函數(shù),不能在如 main.c 等其他文件中使用。
讀者可以嘗試使用 extern 關(guān)鍵字引入其他文件中定義的 static 函數(shù)。
C語(yǔ)言中 static 函數(shù)的這個(gè)特性使得它常常被定義在 .h 文件中,一般和 inline 關(guān)鍵字一起使用,以獲得 define 函數(shù)式宏定義類似的高效率。

問(wèn)題5,C語(yǔ)言中的“循環(huán)”數(shù)據(jù)類型是指什么?
所謂的“循環(huán)”數(shù)據(jù)類型,其實(shí)就是某種類型的數(shù)據(jù)溢出后,又從頭開始存儲(chǔ)。一個(gè)典型的例子是 unsigned char 變量若已經(jīng)等于 255,仍然對(duì)其加 1,那么該變量就會(huì)溢出從頭開始,也即等于零:
unsigned char a = 255;
a = a+1;// a 等于 0
unsigned char 型變量 a 是無(wú)符號(hào)的 8 位整數(shù),它能表示的最大值是 8 個(gè)位全為 1,也即 0xff=255,若此時(shí)再對(duì)其加一,將得到 0x100。a 只索引 8 位,也即 0x100 中的 0x00=0。
C語(yǔ)言中的 int,long,short 等類型也有類似的“循環(huán)”特性,該特性不會(huì)引發(fā)語(yǔ)法編譯錯(cuò)誤,因此較難判斷這些類型的變量是否溢出。而C語(yǔ)言中的 float,double 類型則沒有“循環(huán)”特性,因此實(shí)際C語(yǔ)言程序開發(fā)中一個(gè)常用的檢查整型數(shù)據(jù)是否溢出的技巧,就是借助于 float 和 double 類型的,這一點(diǎn)在我之前的文章中說(shuō)過(guò),感興趣的讀者可以看看。
問(wèn)題6,C語(yǔ)言中的頭文件有什么用?
一般C語(yǔ)言程序項(xiàng)目中的頭文件后綴名都為 .h,h 是 header 的縮寫。頭文件的使用一般和 #include 結(jié)合使用,例如在 main.c 文件中寫下:
#include "header.h"
意味著在該處將 header.h 中的內(nèi)容展開到此。所以C語(yǔ)言中的頭文件中一般包含程序需要使用的函數(shù)定義和原型,也可以包含相關(guān)的數(shù)據(jù)結(jié)構(gòu)類型定義。
這里再啰嗦下“在該處將 header.h 中的內(nèi)容展開到此”的含義——假如 header.h 頭文件中的內(nèi)容是:
// header.h 頭文件
printf("hello world\n");
那么,在其他文件中寫下
#include "header.h"就等價(jià)于
// header.h 頭文件
printf("hello world\n");

問(wèn)題7,C語(yǔ)言中的指針可以做加法運(yùn)算嗎?
C語(yǔ)言中的指針包含地址詳細(xì)信息,一般是不可以直接做加法運(yùn)算的,例如下面這段C語(yǔ)言代碼:
void *p1 = (void *)1;
void *p2 = (void *)2;
// 下面是非法的
void *p = p1+p2;
讀者可自行嘗試,指針 p1 和指針 p2 是無(wú)法直接相加的,否則編譯器就會(huì)報(bào)錯(cuò)。但是如果想對(duì)指針 p1 和 p2 的地址值相加,可以將其強(qiáng)制轉(zhuǎn)換為整數(shù)類型,例如:
void *p1 = (void *)1;
void *p2 = (void *)2;
long p = (long)p1 + (long)p2;
應(yīng)該確保強(qiáng)制轉(zhuǎn)換的整數(shù)類型寬度大于指針類型寬度,否則可能會(huì)因?yàn)閿?shù)值截?cái)鄬?dǎo)致得到錯(cuò)誤的結(jié)果。
雖然C語(yǔ)言中的指針不能直接與指針相加,但是卻可以與其他整數(shù)相加,例如下面這段C語(yǔ)言代碼:
char *p1 = (char *)1;
char *p = p1+1;
指針p1 指向地址 1,因此指針 p 指向地址 2,這沒什么好說(shuō)的。但是,讀者應(yīng)該注意下面這樣的“陷阱”:
int *p1 = (int *)1;
int *p = p1+1;
與上面的C語(yǔ)言代碼例子相比,這里僅僅將 char 換成 int。那么,指針 p 指向哪個(gè)地址呢?編寫打印代碼:
int *p1 = (int *)1;
int *p = p1+1;
printf("p1=%p, p=%p\n", p1, p);
編譯并執(zhí)行上面這段C語(yǔ)言代碼,會(huì)發(fā)現(xiàn)輸出如下:
p1=0x1, p=0x5
可見,“1+1”并不等于 2,而是等于 5 了。這其實(shí)是因?yàn)镃語(yǔ)言中的指針是有其自己的含義的,不同的指針類型索引內(nèi)存的大小也往往不同,我的機(jī)器上 int 類型占用 4 個(gè)字節(jié)內(nèi)存空間,因此指針 p1+1 實(shí)際上是往后移動(dòng)了 4 個(gè)字節(jié)。
讀者可自行將 int 換成其他類型試試。
小結(jié)
本節(jié)列舉的 7 個(gè)C語(yǔ)言問(wèn)題其實(shí)屬于C語(yǔ)言的基本語(yǔ)法和特點(diǎn),如果能夠熟練掌握,相信對(duì)找到一份相關(guān)的工作是有幫助的。
另外,對(duì)于編程方面,學(xué)習(xí)C/C++編程或者工作想提升的伙伴,如果你想更好的提升你的編程能力幫助你提升水平!筆者這里或許可以幫到你~
微信公眾號(hào):C語(yǔ)言編程學(xué)習(xí)基地
歡迎轉(zhuǎn)行和學(xué)習(xí)編程的伙伴,利用更多的資料學(xué)習(xí)成長(zhǎng)比自己琢磨更快哦!
