西邮Linux兴趣小组2019-2021年纳新面试题解析
目錄
2019年
2020年
2021年
2019年
1、
?
結(jié)果:無(wú)限打印“ = ”
大家都知道在碼長(zhǎng)為16位的情況下,int 的取值范圍為-32768~32767,而unsinged int即無(wú)符號(hào)整形的范圍為0~65535。
那么問(wèn)題來(lái)了哈,unsigned int類型如果遇到的賦值為負(fù)數(shù),它會(huì)怎么處理呢?
答案是它會(huì)以補(bǔ)碼的形式轉(zhuǎn)換表示。
回歸到本題:當(dāng)for循環(huán)最后一次將unsigned int類型的i自減為-1時(shí),此時(shí)i將會(huì)變成其十進(jìn)制補(bǔ)碼:65535。而跳脫for循環(huán)的條件是i<0,但每每i想要成為負(fù)數(shù)的那一刻,啪,直接給轉(zhuǎn)為正數(shù)補(bǔ)碼了,所以無(wú)法結(jié)束這個(gè)循環(huán),也就只能一直打印“ = ”了。
2、
?
?(1)主要引用了一個(gè)中間變量c,可看作它是一個(gè)空杯子,相當(dāng)于一個(gè)交換媒介。先把a(bǔ)的值暫存于“空杯子"c中,再把b的值賦給a,最后將”空杯子“c(也就是a)的值賦給b,即完成了數(shù)值交換的操作。
(2)這就是一個(gè)數(shù)學(xué)上的簡(jiǎn)單交換障眼術(shù)。
(3)使用異或進(jìn)行解決。對(duì)于兩個(gè)值來(lái)說(shuō),異或結(jié)果為:同0異1。
例如兩個(gè)整數(shù)a=10100001,b=00000110,可通過(guò)下列語(yǔ)句實(shí)現(xiàn)交換:
a = a^b; //a=10100111
b = b^a; //b=10100001
a = a^b; //a=00000110
于是a、b就這樣交換了數(shù)值。
3、
?
?結(jié)果:輸出結(jié)果不一致,每調(diào)用一次函數(shù),a值就遞增1,而b值始終為1。
這道題主要涉及局部變量和static關(guān)鍵字的作用。我們?cè)诔绦蛑卸啻握{(diào)用的輸出如下:
?
?
首先我們知道a、b都定義在函數(shù)體內(nèi),為局部變量。而局部變量一旦離開(kāi)了定義它的代碼塊時(shí),聲明周期結(jié)束,變量就會(huì)被銷(xiāo)毀,無(wú)法再繼續(xù)使用。 (所以每調(diào)用一次函數(shù)結(jié)束時(shí),b都會(huì)被清零,b固然每次都為1)
那憑什么a每次都能遞增?那是因?yàn)榇藭r(shí)對(duì)a設(shè)定了“static”—a就變成了局部靜態(tài)變量:
局部靜態(tài)變量:
作用域同樣為局部作用域,同樣也是當(dāng)定義它的函數(shù)結(jié)束時(shí),作用域結(jié)束。但是!當(dāng)局部靜態(tài)變量離開(kāi)作用域后,其本身數(shù)據(jù)并沒(méi)有被銷(xiāo)毀,而是留在了內(nèi)存當(dāng)中。直到該函數(shù)再次被調(diào)用,此值就會(huì)在原值的基礎(chǔ)上做改變。
也就是說(shuō),每調(diào)用一次函數(shù)對(duì)a實(shí)現(xiàn)++時(shí),最后的結(jié)果都被保存在了內(nèi)存中沒(méi)被清零,因此a呈遞增狀態(tài)。 ( static就相當(dāng)于局部變量值的“保護(hù)傘”啦!)
4、
?
結(jié)果:Xiyou Linux Group2019
主要考察printf函數(shù)的返回值。
printf函數(shù)返回其成功打印字符的個(gè)數(shù)(包括空格、換行符'\n')。本題的輸出自右往左,第一個(gè)輸出沒(méi)有內(nèi)容,因此'%d'處的值為0;接著第二個(gè)函數(shù)輸出為'Xiyou Linux Group20';第三處使用第二處打印的返回值(打印字符個(gè)數(shù))為19。最后連起來(lái)即為輸出結(jié)果。
5、
?
?結(jié)果:-1 0
(a是啥!a沒(méi)聲明也沒(méi)初始化又不能當(dāng)作常量賦值,就導(dǎo)致編譯錯(cuò)誤了,小問(wèn)題這應(yīng)該是題目不小心打錯(cuò)了)修改后代碼如下:
?
?
本題和第一題類似,都是考察數(shù)據(jù)類型的范圍越界問(wèn)題。
- unsigned char(無(wú)符號(hào)型)的范圍為0~255
- signed char即 char(有符號(hào)型)的范圍為-128~127。
那么為什么這里的255輸出為-1呢?是不是char類型的問(wèn)題?那接下來(lái)我們?cè)囋嚒?55”對(duì)于int類型和char類型的輸出分別是多少:
int main(int argc,char *argv[]) {char ch = 255;int sh = 255;printf("ch = %d\nsh = %d\n",ch,sh); }結(jié)果輸出為:
?
我們看到int類正常輸出255,而char類輸出卻為-1。
首先在計(jì)算機(jī)中,存儲(chǔ)數(shù)據(jù)采用補(bǔ)碼的形式存儲(chǔ)。
所以當(dāng)255作為int類型來(lái)聲明時(shí),因?yàn)闆](méi)有超出int類型的有效范圍,并且作為正數(shù)的255的二進(jìn)制原反補(bǔ)碼都相同,即都為1111 1111(255),因此以int類型來(lái)定義時(shí)其輸出值為255;
再來(lái)看看,當(dāng)255以char類型來(lái)聲明時(shí),因?yàn)橛蟹?hào)char型范圍是-128~127:
?
完全可以將其范圍看作是一個(gè)頭接尾的圓圈,一個(gè)圈所容納的數(shù)據(jù)數(shù)量恰好為256(127+128還有0的存在再+1),也就代表著我們可以把255當(dāng)作是這個(gè)圈內(nèi)的第255個(gè)元素,也就是-1了。
用式子表達(dá)就是:在255中,當(dāng)?shù)?27+1越界后變?yōu)?128(即跳到負(fù)數(shù)表達(dá)范圍內(nèi)),-128再與((255-127)-1)相加=(-128+127)=-1。
6、
?
結(jié)果為:x=-1, y=4, t=-1 // x=0, y=5, t=1
本題對(duì)于x和y的輸出很簡(jiǎn)單,就是普通的加操作。重點(diǎn)在于t的兩個(gè)輸出,這其中涉及的兩個(gè)運(yùn)算符是:‘ | ’和‘ || ’
運(yùn)算符|:
指的是對(duì)兩數(shù)的二進(jìn)制補(bǔ)碼進(jìn)行按位或運(yùn)算,有1則1,注意最后的結(jié)果也為補(bǔ)碼形式。(‘ & ’有0則0)
邏輯運(yùn)算符||:
如果兩個(gè)數(shù)中有一個(gè)為非零,則為真(1)。(‘ && ’兩個(gè)數(shù)都為非零才為真)
同時(shí)注意進(jìn)行運(yùn)算時(shí),x先++再(運(yùn)算/判斷),y先(運(yùn)算/判斷)再++
也就是說(shuō)當(dāng)執(zhí)行代碼段第三行時(shí),x值為-1、y值為3,此時(shí)進(jìn)行按位或運(yùn)算。-1的二進(jìn)制補(bǔ)碼表示為1111 1111,誰(shuí)碰上它進(jìn)行或運(yùn)算每位都必為1,最終結(jié)果都必為-1(1111 1111 ),因此第一個(gè)t的輸出為-1。
同理,當(dāng)執(zhí)行代碼段第五行時(shí),x的值為0、y值為4,此時(shí)進(jìn)行邏輯或判斷。判斷開(kāi)始時(shí)已判斷x為正,因此邏輯判斷為真返回1,所以第二個(gè)t的輸出為1。
7、
?
瞧這個(gè)大坑,你是不是看到“輸出為4”的坑就呼呼往里頭栽?(反正我是)
看看人家輸出:
?
?人家輸出的是3。
不要以為這里面的 X*X =(1+1)*(1+1)= 4,這是人腦思維。計(jì)算機(jī)在#define宏定義的時(shí)候接受的是X = a*b,那它在后續(xù)計(jì)算的時(shí)候只會(huì)傻愣往里頭代 a*b 而不是(a*b),所以此時(shí)X*X = 1+1*1+1=3。
你的初衷如果是想要它輸出4,那你就要把宏定義改為:#define X (a+b),手動(dòng)加括號(hào)。
8、
?
?
輸出結(jié)果為:
?
9、
?輸出結(jié)果:
?
*q = p使得p和q同時(shí)指向了malloc出來(lái)的這塊空間,q里存放著p的地址。所以當(dāng)輸入‘Xiyou’時(shí),‘Xiyou’存放在這塊內(nèi)存中;但當(dāng)‘Linux’輸入后,內(nèi)容刷新,覆蓋了前面的‘Xiyou’,因此輸出結(jié)果為兩個(gè)Linux。
10、
輸出的結(jié)果為:
?
第一個(gè)打印的兩個(gè)值相同。a用%p打印表示數(shù)組的首地址,&a表示數(shù)組首元素的地址。
第二個(gè)打印的值都發(fā)生了變化。a+1 是數(shù)組下一元素的首地址,即a[1]的首地址。因?yàn)闉閕nt類型,所以增加了4個(gè)字節(jié);&a+1 是下一個(gè)數(shù)組的首地址。
11、
實(shí)現(xiàn)代碼如下:
#include <stdio.h> int feib(int n) {if (n==1||n==2)return 1;else{return feib(n-1)+feib(n-2);} } int main() {int a;scanf("%d", &a);printf("斐波那契數(shù)列第%d項(xiàng)的值為:%d\n", a, feib(a));return 0; }?12、
上述代碼為冒泡排序。
冒泡排序顧名思義就是通過(guò)元素的兩兩比較,判斷是否符合要求,如不符合就交換位置來(lái)達(dá)到排序的目的。就像它的名字,在交換過(guò)程中,各個(gè)數(shù)值類似水冒泡,小(大)的元素經(jīng)過(guò)不斷的交換由水底慢慢的浮到水的頂端。通過(guò)重復(fù)的循環(huán)訪問(wèn)數(shù)組,直到?jīng)]有可以交換的元素,那么整個(gè)排序就已經(jīng)完成了。
冒泡的三種優(yōu)化:
(1)泡泡到頂以后再也不進(jìn)行掃描:減少無(wú)效掃描
#include <stdio.h> void bubble_sort(int arr[],int sz) {int i = 0;int t;for (i = 0; i < sz-1; i++)//進(jìn)行元素個(gè)數(shù)減一次排序{int j;for (j = 0; j <sz-1-i; j++)//每次排序次數(shù)比較次數(shù)減少i個(gè),也就是不再對(duì)已經(jīng)上浮到位的元素進(jìn)行比較{if (arr[j] > arr[j + 1]){t = arr[j];arr[j] = arr[j + 1];arr[j + 1] = t;}elsecontinue;}} } int main() {int i=0;int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };//排為升序int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr,sz);//冒泡排序函數(shù)for (i = 0; i < sz; i++){printf("%d\n", arr[i]);} return 0; }(2)如果檢測(cè)到一整輪沒(méi)有發(fā)生交換,則直接跳出結(jié)束排序。
#include <stdio.h> void bubble_sort(int arr[],int sz) {int i = 0;int t;for (i = 0; i < sz-1; i++)//進(jìn)行元素個(gè)數(shù)減一次排序{int flag = 1;//定義一個(gè)整型變量一會(huì)用來(lái)判斷if語(yǔ)句int j;for (j = 0; j <sz-1-i; j++)//冒泡雙重for嵌套循環(huán){if (arr[j] > arr[j + 1]){t = arr[j];arr[j] = arr[j + 1];arr[j + 1] = t;flag = 0;//程序如果沒(méi)來(lái)這,說(shuō)明這一次掃描沒(méi)有發(fā)生過(guò)換位,所以可以直接結(jié)束排序}elsecontinue;}if (flag == 1){break;}} } int main() {int i=0;int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };//排為升序int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr,sz);//冒泡排序函數(shù)for (i = 0; i < sz; i++){printf("%d\n", arr[i]);} return 0; }(3)冒泡的同時(shí)把“水”沉下去:上浮+下沉的雙向排序(雞尾酒排序)
雞尾酒排序:冒泡排序的每一輪都是從左至右來(lái)比較元素,進(jìn)行單向的位置交換。那么雞尾酒排序做了怎樣的優(yōu)化呢?雞尾酒排序的元素和交換過(guò)程是雙向的。
下面舉一個(gè)例子:有數(shù)組[2, 3, 4, 5, 6, 7, 8, 1],希望對(duì)其進(jìn)行從小到大的排序。如果按照冒泡排序的思想去做的話,元素2, 3, 4, 5, 6, 7, 8已經(jīng)是有序的了,只有元素1的位置不對(duì),卻還要進(jìn)行7輪排序!雞尾酒排序正是要解決這個(gè)問(wèn)題的。
雞尾酒排序過(guò)程:
第1輪:和冒泡排序一樣,從左至右依次比較并交換;
第2輪:反過(guò)來(lái),從右至左依次比較并交換;
第3輪:同第1輪...
第4輪:同第2輪...
直到某一輪排序中沒(méi)有元素進(jìn)行交換,證明已經(jīng)有序,排序結(jié)束。這就是雞尾酒排序的思路。排序過(guò)程就像鐘擺一樣,從左至右,從右至左,循環(huán)往復(fù)直至有序。
其他排序方法還有選擇排序、快速排序、直接插入排序、希爾排序、歸并排序、基數(shù)排序和堆排序。
13、
- 大端模式:指高位字節(jié)放在內(nèi)存的低地址端,低位字節(jié)放在內(nèi)存的高地址端。這樣的存儲(chǔ)模式有點(diǎn)類似于把數(shù)據(jù)當(dāng)作字符串順序處理:地址由小向大增加,而數(shù)據(jù)從高位往低位放;這和我們的閱讀習(xí)慣一致。
低地址 --------------------> 高地址
0x12? |? 0x34? |? 0x56? |? 0x78
- 小端模式:指低位字節(jié)放在內(nèi)存的低地址端,高位字節(jié)放在內(nèi)存的高地址端。這種存儲(chǔ)模式將地址的高低和數(shù)據(jù)位權(quán)有效地結(jié)合起來(lái),高地址部分權(quán)值高,低地址部分權(quán)值低。
低地址 --------------------> 高地址
0x78? |? 0x56? |? 0x34? |? 0x12
采用大端方式進(jìn)行數(shù)據(jù)存放符合人類的正常思維
采用小端方式進(jìn)行數(shù)據(jù)存放利于計(jì)算機(jī)處理
下面這段代碼可以用來(lái)測(cè)試一下你的編譯器是大端模式還是小端模式:
#include <stdio.h> int main() {short int x;char x0,x1;x=0x1122;x0=*((char*)&x); //低地址單元 ,或者((char*)&x)[0];x1=*((char*)&x + 1); //高地址單元,或者((char*)&x)[1];printf("x0=%x\nx1=%x\n",x0,x1); }若x0=0x11,則是大端;若x0=0x22,則是小端。
我的是小端模式 。
?14、
詳見(jiàn)博客:
初探Linux操作系統(tǒng)與文件_Yan__Ran的博客-CSDN博客
2020年
1、
?結(jié)果:>
上面的代碼沒(méi)有對(duì)全局變量i進(jìn)行初始化,因此編譯器自動(dòng)默認(rèn)其值為0。經(jīng)過(guò)i--的操作i變?yōu)?1,同時(shí)sizeof(i)的值為int類型所占字節(jié)數(shù)為4。
但要注意一點(diǎn):sizeof返回值為無(wú)符號(hào)數(shù),而i為有符號(hào)數(shù)。當(dāng)有符號(hào)數(shù)遇到無(wú)符號(hào)數(shù)時(shí),將會(huì)被強(qiáng)制轉(zhuǎn)換為無(wú)符號(hào)數(shù),那么i將會(huì)變成一個(gè)非常大的數(shù)。
2、
結(jié)果:11
這題和上面那套2019年納新題的第7題一個(gè)道理,#define宏定義直接替換C為A*B=2+2*3+3=11,不多描述。
3、
結(jié)果:26 27
主要考察strlen函數(shù)和sizeof關(guān)鍵字的輸出。strlen函數(shù)主要用于計(jì)算字符長(zhǎng)度,而sizeof主要用于計(jì)算所占內(nèi)存數(shù)。兩值相差1是因?yàn)閟tr是一個(gè)字符串,在字符串末端會(huì)有一個(gè)‘ \0 ’,sizeof關(guān)鍵字會(huì)計(jì)算它。
4、
?多次執(zhí)行該函數(shù),結(jié)果為:
本題與2019年面試題第3題同理,涉及static設(shè)置局部靜態(tài)變量保留上一次執(zhí)行的值的知識(shí)點(diǎn),不多描述。
5、
輸出測(cè)試:
可以看得出來(lái)這串代碼的作用是將十進(jìn)制數(shù)轉(zhuǎn)化為32位的二進(jìn)制數(shù)。下面我們來(lái)看看這串代碼:
這里面涉及兩個(gè)運(yùn)算符:左移運(yùn)算符<<和右移運(yùn)算符>>
-
<< 左移運(yùn)算符
V << N,表示數(shù)值V左移N位
右邊空出的位用0填補(bǔ),高位左移溢出則舍棄。
例如:3的二進(jìn)制是0000 0011。如果我們把這個(gè)數(shù)值往左移動(dòng)2位,即3<<2,那么就得到0000 1100,此時(shí)表示的十進(jìn)制是12。
數(shù)值N往左移動(dòng)X位,得到是數(shù)值是N * (2^X),即N乘以2的X次方。
-
>> 右移運(yùn)算符
V >> N,表示數(shù)值V右移N位
左邊空出的高位用0或者1補(bǔ)(正數(shù)用0補(bǔ),負(fù)數(shù)用1補(bǔ)),低位右移溢出則舍棄。
例如:8的二進(jìn)制是 0000 1000。如果把它往右移動(dòng)2位,即8>>2,那么低字節(jié)的2位移出,高位補(bǔ)0,就得到0000 0010,就是2的數(shù)值。
數(shù)值N往右移動(dòng)X位,得到是數(shù)值是N / (2^X),即N除以2的X次方。
下面我們來(lái)看一下題中的代碼:
#include <stdio.h> int main(int argc, char *argv[]) {int number;unsigned mask;mask = 1u << 31; //1u表示將1轉(zhuǎn)化為unsigned int即無(wú)符號(hào)整型scanf("%d", &number);while(mask) //當(dāng)mask不為0時(shí)進(jìn)入循環(huán){printf("%d",(number & mask) ? 1 : 0); //number和mask按位與運(yùn)算(有0則為0)mask>>=1;}return 0; }首先第五行操作先將1轉(zhuǎn)化為無(wú)符號(hào)整型并將其32位編碼向左移31位,即最終得到1000..00(32位),并將其賦值給mask。接下來(lái)作while循環(huán),當(dāng)mask不為0時(shí)進(jìn)入循環(huán)。接下來(lái)作運(yùn)算符判斷:
(number & mask) ? 1 : 0mask>>=1;這是代碼的核心部分。number和mask作與運(yùn)算 ,并且每進(jìn)行一次運(yùn)算就對(duì)mask做一次右移操作(使得1移動(dòng))。這套操作主要是想要通過(guò)與運(yùn)算(都為1時(shí)才為1)以及對(duì)1右移實(shí)現(xiàn)對(duì)輸入數(shù)字二進(jìn)制編碼的遍歷,從而一個(gè)一個(gè)輸出對(duì)應(yīng)的0或1,輸出的值就是number所對(duì)應(yīng)的32位二進(jìn)制編碼了 。
6、
結(jié)果:Y
*str指向字符串的首字母X,而X+1則表示為X的下一個(gè)字母即Y。
7、
結(jié)果:XiyouLinuxGroup
-
判斷浮點(diǎn)數(shù)是否相同:
判斷兩個(gè)浮點(diǎn)數(shù)時(shí)需要注意兩者類型的統(tǒng)一。像"float"和"double"雖然都表示浮點(diǎn)數(shù),但兩者的精度不同。double精度高,有效16位,而float的精度為7位。(但double消耗內(nèi)存是float的兩倍,所以double的運(yùn)算速度比f(wàn)loat慢得多。)
第二行中的float b=a看似將a和b等價(jià)了起來(lái),但因?yàn)榫炔煌瑢?dǎo)致了a精度的缺失。
第一個(gè)if判斷中"(float)a"的操作對(duì)a進(jìn)行了暫時(shí)的強(qiáng)制轉(zhuǎn)換,所以a與b能相等;反觀第二個(gè)if判斷,沒(méi)有對(duì)a、b中任意一個(gè)值進(jìn)行強(qiáng)制轉(zhuǎn)換類型,所以類型不相同,成立第二個(gè)if的判斷條件打印其內(nèi)容。
8、
結(jié)果:Xiyou Linux Group 2020 ?
數(shù)組a中表示的是ASCII碼的值,打印出來(lái)為Xiyou Linux Group 20。其中最后一個(gè)元素0被讀取為NULL,使該字符串的輸出停止。最外層打印讀取有效字符數(shù)為20。
9、
結(jié)果:2 0 2 0
考察多維數(shù)組的補(bǔ)位問(wèn)題。來(lái)看看前兩個(gè)數(shù)組的結(jié)構(gòu):
?c、d數(shù)組同理,最后即得輸出2 0 2 0。
10、
?結(jié)果:1
屬實(shí)是套中套中套了。首先一個(gè)指針指向了一個(gè)char類型指針,而這個(gè)char類型指針又指向a的地址。所以返回去究其根本指向的還是a,所以最后打印a的值1。
11、
取消第三行const注釋后,a將會(huì)被定義為只讀常量,不可修改。
取消三個(gè)注釋后,程序運(yùn)行顯示:
不能對(duì)只讀常量賦值
那我們現(xiàn)在把第五行代碼注釋掉再執(zhí)行,程序顯示:
出現(xiàn)段錯(cuò)誤,顯然第六行的存在也不合理。 段錯(cuò)誤究其根本就是訪問(wèn)了非法內(nèi)存。如果你想要將字符串b中的第6個(gè)字符替換為\0的話,可以這樣修改:
#include<stdio.h> int main(int argc,char*argv[]) {const char a[]="xiyoulinux\0";char b[20]="xiyoulinux\0"; //修改指針為數(shù)組b[5]='\0'; //替換printf("%s\n",a);printf("%s\n",b);return 0; }輸出結(jié)果:
原代碼最終注釋掉兩行結(jié)果正常輸出兩遍Xiyou Linux Group。
12、
C源文件到可執(zhí)行文件共經(jīng)歷了4個(gè)過(guò)程:預(yù)處理、編譯、匯編、鏈接
-
預(yù)處理
預(yù)處理階段編譯器主要作加載頭文件、宏替換、條件編譯的作用。一般處理帶“#”的語(yǔ)句。
我們可以通過(guò)gcc的-E進(jìn)行查看:
root@gougou:/home/yanran# gcc -E main.c -o main.i編譯器將main.c預(yù)處理結(jié)果輸出 main.i 文件。
-
編譯
在編譯過(guò)程中,編譯器主要作語(yǔ)法檢查和詞法分析。我們可以通過(guò)-S來(lái)進(jìn)行查看,該選項(xiàng)預(yù)處理之后的結(jié)果翻譯成匯編代碼:
root@gougou:/home/yanran# gcc -S main.i編譯器將預(yù)處理輸出文件main.i 文件編譯成main.s 文件。
-
匯編
在匯編過(guò)程中,編譯器把匯編代碼轉(zhuǎn)化為機(jī)器代碼。我們可以通過(guò)-c來(lái)進(jìn)行查看:
root@gougou:/home/yanran# gcc –c main.s編譯器將main.s 文件轉(zhuǎn)化為main.o 文件。
-
鏈接
鏈接就是將目標(biāo)文件、啟動(dòng)代碼、庫(kù)文件鏈接成可執(zhí)行文件的過(guò)程,這個(gè)文件可被加載或拷貝到存儲(chǔ)器執(zhí)行。我們可以通過(guò)gcc main.o查看文件,如下所示:
root@gougou:/home/yanran# gcc –o main.o編譯器將輸出文件main.o 鏈接成最終可執(zhí)行文件a.out。
13、
冒泡排序的三種優(yōu)化方法,參考2019年面試題的第12題。
14、
(1) 查看工作路徑:pwd+文件名
(2) 查看文件:ls
(3)創(chuàng)建目錄
- 在當(dāng)前目錄下創(chuàng)建一個(gè)新目錄:mkdir+新目錄名? ??? ????
- 在指定目錄下創(chuàng)建一個(gè)名為aaa的目錄:mkdir+/目錄名/新目錄名
- 創(chuàng)建文件:touch+新文件名
?(4) 拷貝目錄:cp -r +目錄名稱+拷貝的目標(biāo)位置
2021年
結(jié)果:16 12
sizeof和strlen的異同
-
異
sizeof關(guān)鍵字計(jì)算所占內(nèi)存字節(jié)長(zhǎng)度
strlen函數(shù)計(jì)算字符串字符長(zhǎng)度
strlen函數(shù)遇\0停止,而sizeof計(jì)算\0。
-
同
都可以計(jì)算字符串?dāng)?shù)組中元素的個(gè)數(shù)
值得注意的是代碼中字符串的結(jié)尾還有一個(gè)隱藏的\0,所以sizeof值為16而非15。
結(jié)果:相等,都為16
主要考察結(jié)構(gòu)體內(nèi)存對(duì)齊的知識(shí)點(diǎn)。
- 第一個(gè)成員在結(jié)構(gòu)體變量偏移量為0的地址處。
- 其他成員變量要對(duì)齊到對(duì)齊數(shù)整數(shù)倍的地址處。
對(duì)齊數(shù) = 編譯器默認(rèn)的一個(gè)對(duì)齊數(shù)與該成員大小中的較小值。(vs中默認(rèn)值是8 Linux默認(rèn)值為4。)
- 結(jié)構(gòu)體總大小為最大對(duì)齊數(shù)的整數(shù)倍。
由此可見(jiàn),test1三個(gè)元素內(nèi)存分別為4、2、8,其中2補(bǔ)齊2變?yōu)?,所以結(jié)果為4+4+8=16。test2三個(gè)元素內(nèi)存分別為2、4、8,同樣2補(bǔ)齊變?yōu)?,結(jié)果同樣為16。
?
二維數(shù)組作為函數(shù)參數(shù)來(lái)傳遞的三種方法:
1、行參給出第二維的長(zhǎng)度
void func(int n,char arr[][13]) {int i;for(i=0;i<n;i++){printf("%s\n",i,arr[i]);} }//下面兩個(gè)的函數(shù)體內(nèi)部結(jié)構(gòu)和這個(gè)一樣,后面的不展開(kāi)寫(xiě)了?2、形參聲明為指向數(shù)組的指針
void func(int n,char(*arr)[13])?3、形參聲明為指針的指針
void func(int n,char **arr)?
-
傳值和傳址的區(qū)別
傳值:傳值相當(dāng)于單純把值傳給別人,相當(dāng)于復(fù)制粘貼的操作,傳值不會(huì)改變傳值方的值。
傳址:傳地址相當(dāng)于將地址賦給了對(duì)方,也就是說(shuō)兩個(gè)參數(shù)此時(shí)就是綁在一起的螞蚱,它們指向同一片地址。傳址后兩個(gè)參數(shù)只要有一個(gè)發(fā)生改變,另一個(gè)參數(shù)就會(huì)隨之改變。
-
變量的生命周期
變量類型主要有全局變量、局部變量和靜態(tài)變量。
局部變量:定義在函數(shù)體內(nèi)部的變量,作用域僅限于函數(shù)體內(nèi)部。離開(kāi)函數(shù)體就會(huì)無(wú)效,再調(diào)用就是出錯(cuò)。
全局變量:所有的函數(shù)外部定義的變量,它的作用域是整個(gè)程序,也就是所有的源文件,包括.c和.h文件。
靜態(tài)變量(static):變量在運(yùn)行區(qū)間結(jié)束后內(nèi)存不釋放,地址不變。
對(duì)上面代碼中的各個(gè)函數(shù)區(qū)域變量的解析
#include <stdio.h> int ver=123; //全局變量 void func1(int ver) //只在func1函數(shù)中有效 {ver++;printf("ver=%d\n",ver);//1026 出了這個(gè)函數(shù)數(shù)據(jù)就被滅掉 }void func2(int*pr)//傳局部變量ver的地址給指針pr {*pr=1234;//在此函數(shù)內(nèi)修改pr指向地址中的值(ver)printf("*pr=%d\n",*pr);//1234 修改成功 并且會(huì)影響主函數(shù)中ver的值(因?yàn)槭莻鞯牡刂?#xff09;pr=5678;//直接把int型常量賦給地址會(huì)出現(xiàn)警告printf("ver=%d\n",ver);//123 此時(shí)ver的值為全局變量ver的值。因?yàn)樵趂unc2函數(shù)中的參數(shù)是指針,沒(méi)有int類型常量的定義所以從全局中調(diào)變量值。 }int main() {int a=0;//局部變量,只在主函數(shù)體內(nèi)有效int ver=1025;//同上for(int a=3;a<4;a++) //for循環(huán)內(nèi)a的初定義{static int a=5;//靜態(tài)局部變量a,一次循環(huán)結(jié)束后的值會(huì)保留至下一循環(huán)迭代使用。僅在此循環(huán)中有效printf("a=%d\n",a);//5a=ver;printf("a=%d\n",a);//1025func1(ver); //傳入的是主函數(shù)內(nèi)定義的局部變量verint ver=7;//僅在此循環(huán)中有效的局部變量verprintf("ver=%d\n",ver);//7func2(&ver);//傳局部變量ver的地址給指針pr//如果在這里插入printf("%d",ver);輸出為:ver = 1234,因?yàn)樵趂unc2函數(shù)中通過(guò)修改*pr指針修改了ver的值}//出了這個(gè)for循環(huán),循環(huán)內(nèi)定義的a和ver空間被釋放,其值1025和1234被清空。printf("a=%d\tver=%d\n",a,ver);//0 1025 就是在主函數(shù)內(nèi)剛開(kāi)始定義和初始化的值return 0; }?輸出如下:
結(jié)果:5050
表達(dá)式?結(jié)果一:結(jié)果二
表達(dá)式若為真(1),調(diào)用結(jié)果一;若為假(0),則調(diào)用結(jié)果二。
遞歸中的“歸”是如何實(shí)現(xiàn)的
遞歸中必須存在一個(gè)臨界點(diǎn)。比如最內(nèi)層被調(diào)用的函數(shù)被賦給了一個(gè)確切的返回值,這個(gè)確切的返回值就是遞歸的臨界點(diǎn)。接受到確切的值后最內(nèi)層函數(shù)的值確定,然后將結(jié)果返回給上一層函數(shù),然后一層層結(jié)束,一層層返回,這就是“歸”。
引用一篇博客中的例子:
我們?cè)倩貧w到這道題中,來(lái)看看函數(shù)體:
unsigned sum(unsigned n) {return n ? sum(n-1) + n : 0; }函數(shù)自己調(diào)用自己,一看就是老遞歸了。?
-
遞
從n = 100開(kāi)始遞減,當(dāng)n不為0時(shí),函數(shù)不停返回sum(n-1)調(diào)用自己;
-
歸
當(dāng)n減到1時(shí),返回sum(0)+1,而sum(0)通過(guò)運(yùn)算符判斷返回0(臨界點(diǎn)),sum(0)=0再返回給sum(1)...就這樣逐層返回最后0+1+2+...+100=5050。
代碼分析:
#include<stdio.h> void func(void); int main() {func();return 0; }void func(void) {short a=-2; //a十進(jìn)制-2,二進(jìn)制1111 1111 1111 1110,十六進(jìn)制fffeunsigned int b=1;b+=a; //b十進(jìn)制-1,二進(jìn)制1111 1111 1111 1111 1111 1111 1111 1111,十六進(jìn)制ffffffffint c=-1;unsigned short d=c*256;c<<=4; //c的補(bǔ)碼右移4位變?yōu)?111 1111 1111 1111 1111 1111 1111 0000,十進(jìn)制-16,十六進(jìn)制fffffff0int e=2;e=~e|6; //~取反后進(jìn)行或運(yùn)算(兩個(gè)只要有一個(gè)為1就為1)得e二進(jìn)制為1111 1111 1111 1111 1111 1111 1111 1111,十六進(jìn)制ffffffffd=(d&0xff)+0x2022; //與運(yùn)算(都為1才是1)結(jié)果為0+0x2022 =0x2022printf("a=0x%hx\t b=0x%x\t d=0x%hx\t e=0x%x\n",a,b,d,e);//十六進(jìn)制輸出(%hx表示輸出short類型值)printf("c=0x%hhx\t\n",(signed char) c);//(%hhx表示輸出short short類型值) }程序輸出:
?結(jié)果:10 4 9
首先a是一個(gè)三維數(shù)組,b是一個(gè)指向一維數(shù)組的數(shù)組指針。
++b操作如下(框內(nèi)為數(shù)組a[3][3]):
此時(shí)對(duì)b[1][1]賦值10實(shí)際上是將a[2][1]賦為了10,即7替換成了10
int*ptr=(int*)(&a+1):指向整個(gè)a數(shù)組的下一個(gè)數(shù)組
這里插播一個(gè):
對(duì)于數(shù)組:a和&a的區(qū)別
- a代表數(shù)組首元素的地址
- &a代表整個(gè)數(shù)組的地址
這倆的數(shù)據(jù)類型也不同:a是首元素的類型,&a是數(shù)組的類型
在最后打印中:
**(a+1):對(duì)a數(shù)組一維+1得到第二維,然后再對(duì)第二維處首元素取值得到4
*(ptr-1):整個(gè)數(shù)組的下一個(gè)數(shù)組-1,得到上一個(gè)數(shù)組a中的最后一個(gè)元素9
先回答后兩個(gè)問(wèn)題:
下面是錯(cuò)誤代碼分析(func2、func3、func4有錯(cuò)):
主要涉及‘對(duì)于指針來(lái)說(shuō),const作用位置所對(duì)應(yīng)的不可改變位置’
void func2(const int *n)//const作用于*n:不可修改指針n指向的值 {*n+=1;//向只讀形參賦值,錯(cuò)誤n=&a; } void func3(int*const n)//const作用于n:不可修改指針n指向的地址 {*n+=1;n=&a;//向只讀位置賦值,錯(cuò)誤 } void func4(const int *const n)//const作用于*n和n:地址和值都不能修改 {*n+=1;//向只讀形參賦值,錯(cuò)誤n=&a;//向只讀位置賦值,錯(cuò)誤 }如果沒(méi)有限制,則可以使用大小寫(xiě)字母的ASCII相差32的性質(zhì)來(lái)解決大小寫(xiě)反轉(zhuǎn)問(wèn)題:
#include<stdio.h> int main() {int I=2; //隨便賦個(gè)值使I不為零while (I) //當(dāng)括號(hào)內(nèi)表達(dá)式不為零時(shí)實(shí)現(xiàn)后面的循環(huán){char b;scanf("%c", &b);if (b >= 'A' && b <= 'Z'){b = b + 32;printf("%c", b);}else if (b >= 'a' && b <= 'z'){b = b - 32;printf("%c", b);}}return 0; }不過(guò)該題目要求使用以指針為形參的函數(shù)來(lái)解決,并且是特定字符串,最后還要返回反轉(zhuǎn)后的字符串。
代碼實(shí)現(xiàn)如下:
char *convert(const char *s) {char *k=(char*)malloc(N);//在函數(shù)中分配一段空間并且讓指針k指向這段字符串的位置char *t=s;//指針t用來(lái)保存原字符串s的位置char *q=k;//再用一個(gè)指針保留新分配的內(nèi)存地址memset(k,0,N);//將新分配的內(nèi)存空間中的數(shù)全部置為0char str[N];//用一個(gè)數(shù)組來(lái)存放轉(zhuǎn)換后的字符串for(;*s!='\0';*s++,k++)//通過(guò)指針的移動(dòng)將數(shù)據(jù)儲(chǔ)存在新地址上{if(*s>='a'&&*s<='z')*k=toupper(*s); //小寫(xiě)轉(zhuǎn)大寫(xiě)else if(*s>='A'&&*s<='Z')*k=tolower(*s); //大寫(xiě)轉(zhuǎn)小寫(xiě)else*k=*s;}t=q;//讓原指向s的指針指向新地址return t; }- 前兩種方法正確,最后一種錯(cuò)誤。
Swap1:直接從原函數(shù)中傳遞參數(shù),比較方便
Swap2:在參數(shù)中創(chuàng)建交換變量t,作用時(shí)間短且占用內(nèi)存空間小
Swap3:錯(cuò)誤!只傳遞了a,b變量的值,并沒(méi)有傳地址,所以僅在函數(shù)內(nèi)部改變了a,b的值。而函數(shù)作用完后就釋放掉了函數(shù)中變量的內(nèi)存,a,b的值依舊沒(méi)有改變
-
do{...}while(0)的作用:宏定義中實(shí)現(xiàn)局部作用域
我們知道宏定義只是做一個(gè)標(biāo)識(shí)符和字符串的替換,盡管宏定義的形式可以類似于函數(shù),但它實(shí)際并不具備與函數(shù)類似的局部作用域。當(dāng)然,我們可以通過(guò)在宏定義中使用大括號(hào)的形式,如:#define swap(x){...} 來(lái)實(shí)現(xiàn)局部作用域,但也會(huì)帶來(lái)新的麻煩:
#define swap(a, b){a = a+b; b = a-b; a = a-b;}int main() {int a = 1, b = 2;if(1)swap(a,b);elsea = b = 0;return 0; }上面的代碼乍看并沒(méi)有問(wèn)題,但要想到一點(diǎn):宏定義會(huì)在預(yù)編譯后被替換
下面是替換后的代碼:
int main() {int a = 1, b = 2;if(1){a = a+b; b = a-b; a = a-b;};elsea = b = 0;return 0; }這下問(wèn)題就明顯了:在if后的代碼塊后面多出了一個(gè) ;,這會(huì)引發(fā)編譯錯(cuò)誤。
使用該宏定義時(shí)不在后面加;可以解決這個(gè)問(wèn)題,但這顯然不符合我們的編碼習(xí)慣。
在宏定義中使用do…while(0)可以解決這個(gè)問(wèn)題:
#define swap(a, b) do{\a = a+b;\ b = a-b;\a = a-b;\}while(0)int main() {int a = 1, b = 2;if(1)swap(a,b);elsea = b = 0;return 0; }像上面的代碼那樣,我們可以放心地在宏定義后面使用分號(hào),就不會(huì)造成問(wèn)題啦。
- 實(shí)現(xiàn)swap功能的其他方法,可參考2019年面試題的第2題。
?
argc為參數(shù)個(gè)數(shù),argv為指針數(shù)組的首元素(指針)。
要想在不使用argc的前提下完成對(duì)argv的遍歷,只能從argv自身出發(fā)。當(dāng)argv指針不為空時(shí)即可完成遍歷:
#include<stdio.h> int main(int argc, char*argv[]) {int i=0;while(argv[i]!=NULL)printf("%s\n",argv[i++]);return 0; }int *func1(void) {static int n=0;//靜態(tài)變量的生命周期是從調(diào)用它開(kāi)始到本函數(shù)結(jié)束n=1;return &n;//只能返回指針值也就是n的地址(此處可返回地址是因?yàn)殪o態(tài)變量的內(nèi)存不會(huì)被釋放,值會(huì)一直保存且地址一直存在) }int *func2(void) {int *p=(int*)malloc(sizeof(int)); //手動(dòng)給指針p分配內(nèi)存*p=3;return p;//返回首地址 }/*func3錯(cuò)誤*/ int *func3(void) {int n;//沒(méi)初始化return &n;//不可返回局部變量的地址(&n),因?yàn)樗饔猛昃捅会尫帕?#xff0c;相當(dāng)于返回一個(gè)野指針 }
結(jié)果:Welcome to xiyou linux group 2021
涉及大小端問(wèn)題,詳情可參考2019年第13題和2020年第8題。
詳情參考2020年面試題第12題 。
后面會(huì)單開(kāi)一篇博客討論這個(gè)。
建立頭文件(.h)是為了聲明c文件(.c)中的函數(shù),以及包括宏定義。
首先要建一個(gè).h文件:
打開(kāi).h文件進(jìn)行建立編輯。建立頭文件是有一定步驟的,要用到#ifndef ...、#define ...、#endif,這是為了避免重復(fù)定義。
#ifndef后面要寫(xiě)的是頭文件名稱的大寫(xiě)。例如:ceshi.h要寫(xiě)成__CESHI_H__。前面與后面是兩個(gè)下劃線。然后在define與endif中間聲明函數(shù)名,記得寫(xiě)冒號(hào)。
然后在.c文件中寫(xiě)上include這個(gè)頭文件,就可以調(diào)用了。
?最后編譯運(yùn)行就完成了。
詳情參考2020年面試題第14題,以及我的另一篇博客:初探Linux操作系統(tǒng)與文件_Yan__Ran的博客-CSDN博客
此處應(yīng)有表情包(自行腦補(bǔ)
總結(jié)
以上是生活随笔為你收集整理的西邮Linux兴趣小组2019-2021年纳新面试题解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: HTTP网络连接相关知识整理(三):网络
- 下一篇: Note For Linux By Je