轉載html
說明:全部題目均摘錄於網絡以及我所見過的面試題目,歡迎補充!linux
無特殊說明狀況下,下面全部題s目都是linux下的32位C程序。面試
先來幾個簡單的熱熱身。數組
一、計算如下sizeof的值。網絡
char str1[] = {'a', 'b', 'c', 'd', 'e'}; char str2[] = "abcde";
char *ptr = "abcde";
char book[][80]={"計算機應用基礎","C語言","C++程序設計","數據結構"};
sizeof(str1)=? 數據結構
sizeof(str2)=? 函數
sizeof(ptr)=?spa
sizeof(book)=?.net
sizeof(book[0])=?設計
分析:
sizeof(str1)=5,就是5*sizeof(char)=5;
sizeof(str2)=6,字符串都是以'\0'結尾,因此所佔字節數爲6;
sizeof(ptr)=4,ptr是一個指針,在32位平臺上大小爲4字節;
sizeof(book)=320,book是一個二維數組,4*80*1
sizeof(book[0])=80,book[0]是第一維數組,由於此80*1
根據sizeof求數組元素的個數也很簡單,拿第一個來講,就是sizeof(str1)/sizeof(char)。
二、上面是求計算他們所佔字節數,下面來看看怎麼求字符串或數組的實際長度。計算下面strlen值。
char arryA[] = {'a','b','c','\0','d','e'};
char arryB[] = {'a','b','c','d','e'};
char arryC[6] = {'a','b','c','d','e'};
char *str = "abcde";
分析:
strlen(arryA) = 3,strlen遇到'\0'就會返回,不管後面有多少個字符;
strlen(arryB)長度沒法肯定,沒有人爲寫入‘\0’,strlen會繼續計算直到找到結束符,結果未知;
strlen(arryC)=5,指定了數組大小,編譯器會自動在空餘地方添加'\0',這其實跟char arryC[6] = {'a','b','c','d','e','\0'};等價。
strlen(str) = 5,不包括結尾的'\0'。
由以上兩個咱們來看看strlen和sizeof的區別:
(1)、sizeof是C語言中的一個單目運算操做符,相似++、--等;
用於數據類型,sizeof(type),好比sizeof(int)
用於變量,sizeof(var_name)
注意:sizeof不能用於函數類型、不徹底類型或位字段。不徹底類型是指具備未知存儲大小的數據類型,好比未知存儲大小的數組類型、
未知內容的結構體或聯合類型,void類型等。例如: sizeof(max),若此時變量max定義爲int max(); sizeof(char_v),此時char_v
定義爲char char_v[MAX]且MAX未知。
(2)、strlen是個函數,其原型爲unsigned int strlen(char *s);
streln的計算必須依賴字符序列中的'\0',經過該字符來判斷字符序列是否結束。
三、忽悠人的char str[]和char *str
(1)下面的操做合法麼?出錯的話,會是在那個階段?編譯時期仍是運行時期?
char str[] = "hello"; str[0] = 's'; //合法麼 char *str = "hello"; p[0] = 's'; //合法麼
分析:
這兩個均可以成功編譯,只是第二個會在運行時期出現段錯誤。下面來分析一下:
首先"hello"是一個字符串常量,存儲在靜態數據區域(data段),這是在編譯時期就肯定的。第一個是將字符串常量賦值給了一個變量(全局變量在數據段,局部變量在棧區),其實是將字符串常量拷貝到了變量內存中,所以修改的只是str[]這個變量的值。
第二個是將字符串常量的首地址賦值給p,對p操做就是對字符串常量進行修改!所以出現了段錯誤。
(2)理解了上面的知識,判斷一下下面的true or false?
char str1[] = "abc"; char str2[] = "abc"; const char str3[] = "abc"; const char str4[] = "abc"; const char *str5 = "abc"; const char *str6 = "abc"; char *str7 = "abc"; char *str8 = "abc"; cout << ( str1 == str2 ) << endl; cout << ( str3 == str4 ) << endl; cout << ( str5 == str6 ) << endl; cout << ( str7 == str8 ) << endl;
分析:
結果是: 0 0 1 1
先理解str1,str2,str3,str4,他們是什麼?他們是數組名,也就是數組首元素的地址!」str1 == str2「本質就是比較兩個數組的地址是否是相同。上面咱們說過,編譯器給他們分配了新的存儲空間來對字符串"abc"進行拷貝,這些變量在內存裏是相互獨立的,所以他們的地址確定不一樣!
再理解str5,str6,str7,str8,他們是什麼?他們是指針,他們的值就是字符串常量的地址!它們都指向「abc"所在的靜態數據區,因此他們都相等。
(3)更深一步:下面程序有問題麼?有的話問題出在哪裏?如何修改?
#include <stdio.h>
char *returnStr() { char p[]="hello world!"; return p; } int main() { char *str = NULL; str = returnStr(); printf("%s\n", str); return 0; }
分析:
p是個局部變量,只是把字符串"hello word!"進行了拷貝,該局部變量是存放在棧中的,當函數退出時,棧被清空,p會被釋放,所以返回的是一個已經被釋放的內存地址,這樣作是錯誤的。
能夠進行以下修改:
#include <stdio.h>
char *returnStr() { char *p = "hello world!"; return p; }
int main() { char *str = NULL; str = returnStr(); printf("%s\n", str); return 0; }
這麼寫就不會有問題了,由於"hello world!"存放在靜態數據區,將該區的首地址賦值給指針p並返回,即便returnStr函數退出,也不會對字符串常量所在的內存進行回收,所以能夠訪問該字符串常量。
固然了,也能夠這麼修改:
#include <stdio.h>
char *returnStr() { static char p[] = "hello world!"; return p; }
int main() { char *str = NULL; str = returnStr(); printf("%s\n", str); return 0; }
使用關鍵字static,static修飾的局部變量也會放在data段,即便returnStr函數退出,也不會收回該內存空間。
四、數組做爲函數參數傳遞
咱們每每會把數組當作函數的入參,看看下面的函數有啥問題:
int func(int a[])
{ int n = sizeof(a)/sizeof(int); for(int i=0;i<n;i++)
{ printf("%d ",a[i]); a[i]++; } }
結果卻發現n的值老是1!爲何會這樣呢?這是由於在C中,將數組傳遞給一個函數時,沒法按值傳遞,而是會自動退化爲指針。下面的三種寫法實際上是等價的:
"int func(int a[20]);" 等價於 "int func(int a[]);" 等價於 "int func(int *a);"。
五、兩數交換的那些坑
下面代碼想實現兩數交換,有什麼問題麼?
void swap(int* a, int* b) { int *p; p = a; a = b; b = p; }
分析:
程序在運行到調用函數時,會將參數壓棧,併爲之分配新的空間,此時傳遞進來的實際上是一個副本,以下圖所示:
a的值跟b的值都是地址,交換a和b的值,只是把兩個地址交換了而已,也就說只是改變了副本的地址而已,地址所指向的對象並無改變!。
正確的方法應該是這樣的:
void swap(int* a, int* b) { int tmp; tmp = *a; *a = *b; *b = tmp; }
a和b雖然也是副本,可是在函數內部經過該地址直接修改了對象的值,對應的實參就跟着發生了變化。
其實,指針傳遞和值傳遞的本質都是值傳遞,值傳遞是傳遞了要傳遞變量的一個副本。複製完後,實參的地址和形參的地址沒有任何聯繫,對形參地址的修改不會影響到實參,可是對形參地址所指向對象的修改卻能直接反映在實參中,這是由於形參所指向的對象就是實參的對象。正因如此,咱們在傳遞指針做爲參數時,要用const進行修飾,就是爲了防止形參地址被意外修改。
六、函數參數爲指針應當心
下面的代碼有什麼問題?運行結果會怎麼樣?
void GetMem(char *p) { p = (char*)malloc(100); } void main() { char *str = NULL; GetMem(str); strcpy(str, "hello word!"); printf(str); }
分析:
程序崩潰。在上面已經分析過了,傳遞給GetMem函數形參的只是一個副本,修改形參p的地址對實參str絲毫沒有影響。因此str仍是那個str,仍爲NULL,這時將字符串常量拷貝到一個空地址,必然引起程序崩潰。下面的方法能夠解決這個問題:
void GetMem(char **p) { *p = (char*)malloc(100); } void main() { char *str = NULL; GetMem(&str); strcpy(str, "hello word!"); printf(str);
free(str); //不free會引發內存泄漏 }
看似有點晦澀,其實很好理解。本質上是讓指針變量str指向新malloc內存的首地址,也就是把該首地址賦值給指針變量str。前面咱們說過,指針傳遞本質上也是值傳遞,要想在子函數修改str的值,必需要傳遞指向str的指針,所以子函數要傳遞的是str的地址,這樣經過指針方式修改str的值,將malloc的內存首地址賦值給str。
七、數組指針的疑惑
(1)說出下面表達式的含義?
int *p1[10]; int (*p2)[10];
第一個是指針數組,首先他是一個數組,數組的元素都是指針。
第二個是數組指針,首先他是一個指針,它指向一個數組。
下面這張圖能夠很清楚的說明:
(2)寫出下面程序運行的結果
int a[5] = { 1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf("%d,%d", *(a + 1), *(ptr - 1));
分析:
答案是2,5。本題的關鍵是理解指針運算,」+1「就是偏移量的問題:一個類型爲T的指針移動,是以sizeof(T)爲單位移動的。
a+1:在數組首元素地址的基礎上,偏移一個sizeof(a[0])單位。所以a+1就表明數組第1個元素,爲2;
&a+1:在數組首元素的基礎上,偏移一個sizeof(a)單位,&a其實就是一個數組指針,類型爲int(*)[5]。所以&a+1其實是偏移了5個元素的長度,也就是a+5;再看ptr是int*類型,所以"ptr-1"就是減去sizeof(int*),即爲a[4]=5;
a是數組首地址,也就是a[0]的地址,a+1是數組下一個元素的地址,即a[1]; &a是對象的首地址,&a+1是下一個對象的地址,即a[5]。
八、二級指針疑問
給定聲明 const char * const *pp;下列操做或說明正確的是?
(A)pp++ (B)(*pp)++ (C)(**pp)=\\c\\; (D)以上都不對
分析:
答案是A。
先從一級指針提及吧:
(1)const char p : 限定變量p爲只讀。這樣如p=2這樣的賦值操做就是錯誤的。
(2)const char *p : p爲一個指向char類型的指針,const只限定p指向的對象爲只讀。這樣,p=&a或 p++等操做都是合法的,但如*p=4這樣的操做就錯了, 由於企圖改寫這個已經被限定爲只讀屬性的對象。
(3)char *const p : 限定此指針爲只讀,這樣p=&a或 p++等操做都是不合法的。而*p=3這樣的操做合法,由於並無限定其最終對象爲只讀。
(4)const char *const p :二者皆限定爲只讀,不能改寫。
再來看二級指針問題:
(1)const char **p : p爲一個指向指針的指針,const限定其最終對象爲只讀,顯然這最終對象也是爲char類型的變量。故像**p=3這樣的賦值是錯誤的, 而像*p=? p++這樣的操做合法。
(2)const char * const *p :限定最終對象和 p指向的指針爲只讀。這樣 *p=?的操做也是錯的,可是p++這種是合法的。
(3)const char * const * const p :所有限定爲只讀,都不能夠改寫
九、*p++、 (*p)++、 *++p、 ++*p
int a[5]={1, 2, 3, 4, 5};
int *p = a;
*p++ 先取指針p指向的值(數組第一個元素1),再將指針p自增1;
cout << *p++; // 結果爲 1
cout <<(*p++); // 1
(*p)++ 先去指針p指向的值(數組第一個元素1),再將該值自增1(數組第一個元素變爲2
cout << (*p)++; // 1
cout <<((*p)++) // 2
*++p 先將指針p自增1(此時指向數組第二個元素),* 操做再取出該值
cout << *++p; // 2
cout <<(*++p) // 2
++*p 先取指針p指向的值(數組第一個元素1),再將該值自增1(數組第一個元素變爲2)
cout <<++*p; // 2
cout <<(++*p) // 2
參考博客:
一、《C語言中sizeof 與strlen 區別 》:http://www.cnblogs.com/kungfupanda/archive/2012/12/24/2831273.html
二、《常量字符串爲何位於靜態存儲區?》:https://blog.csdn.net/ebw123/article/details/51388340
三、《值傳遞、指針傳遞、引用傳遞的區別》:https://blog.csdn.net/koudan567/article/details/51298511
四、牛客網mlc答案:https://www.nowcoder.com/test/question/done?tid=16211084&qid=1409#summary