c語言-數組、指針面試題

轉載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

相關文章
相關標籤/搜索