C語言面試題目之指針和數組


說明:所有題目均摘錄於網絡以及我所見過的面試題目,歡迎補充!

無特殊說明情況下,下面所有題s目都是linux下的32位C程序。

 先來幾個簡單的熱熱身。

1、計算以下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)=?

sizeof(book)=?

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)。

 

2、上面是求計算他們所占字節數,下面來看看怎么求字符串或數組的實際長度。計算下面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',通過該字符來判斷字符序列是否結束。

 

3、忽悠人的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函數退出,也不會收回該內存空間。

 

 4、數組作為函數參數傳遞

 我們往往會把數組當做函數的入參,看看下面的函數有啥問題:

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);"。

 

5、兩數交換的那些坑 

下面代碼想實現兩數交換,有什么問題么?

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進行修飾,就是為了防止形參地址被意外修改。

 

6、函數參數為指針應小心

下面的代碼有什么問題?運行結果會怎么樣?

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。

 

7、數組指針的疑惑

(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]。

 

8、二級指針疑問

給定聲明 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 :全部限定為只讀,都不可以改寫

 

9、*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

 

參考博客:

1、《C語言中sizeof 與strlen 區別 》:http://www.cnblogs.com/kungfupanda/archive/2012/12/24/2831273.html

2、《常量字符串為什么位於靜態存儲區?》:https://blog.csdn.net/ebw123/article/details/51388340

3、《值傳遞、指針傳遞、引用傳遞的區別》:https://blog.csdn.net/koudan567/article/details/51298511

4、牛客網mlc答案:https://www.nowcoder.com/test/question/done?tid=16211084&qid=1409#summary


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM