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