標籤:
(內容主要源於網上,只是加入了些自己的剖析)
假設有一個二重指標:
char **p;
同時有一個指標數組
char *name[4];
如何引用p呢? 首先我們有程式碼如下
#include <stdio.h> int main() { char *s = "I love you"; char *s1 = "you love me"; char *s2 = "she love you"; char *s3 = "he love her"; char *name[4]; name[0] = s; name[1] = s1; name[2] = s2; name[3] = s3; char **p; p = name + 2; printf("%d\n", *p); printf("%d\n", **p); printf("%c\n", **p); //為什麼這個列印s printf("%d\n", p); printf("%d\n", *&p); printf("%d\n", &p); printf("%s\n", *p); //而這個列印出了字串 return 0; }
輸出結果依次為:
4333636
115
s
1244976
1244976
1244964
she love you
分析:
1、 p是一個二重指標,它本身是一個變數。既然是變數,那麼它本身就應該儲存值。 這就和 int i = 65;一樣,i 儲存了數值 65。而p儲存的是地址,地址也是一種數值。 所以當我們列印p時,列印出來的就是p中儲存的地址數值,結合本題就是1244976。
2、 同理:列印*&p,我們先取p的地址(&p),p是變數,當然自身也會有存放的地址;然後我們 用*去用該地址上儲存的數值,顯然就是p這個變數所代表的數值,當然還是1244976。
3、 我們在分析用%d來列印*p, 為何得到了4333636。 理由是:p是二重指標,所以*p表示還是一個一重指標變數,它的具體表示可以是*(p + 0)。 為了便於理解,我們這樣假設有一個 char *row; row = *(p + 0);這樣我們就把問題轉化 成了一維的指標了,我們列印*p,其實就是列印row,這樣我們可以參考分析(1)。
4、 分析用%s來列印*p,為何得到了 she love you 其實我們完全可以利用分析(3)的思路,這時候,row代表的是這句話的首地址, 我們通過該句的首地址,來列印這個字串。
5、 我們再來分析用%d列印**p 為何得到的是115;其實我們可以這樣來看*(*(p + 0) + 0);這樣的好處是可以簡化,變成*(row + 0);既然row是一維 指標,那麼這個(row + 0)代表的就是我們想要列印的字串的第0個字元的地址!那麼我們用* 來用這個地址的值,剛好就是‘s‘,便忘了‘s‘對應的ACSII碼就是115!
6、 分析%c 為何能列印‘s‘。分析(5)已經全說了。
相關面試題:
錯誤的常式:
#include <stdio.h>#include <stdlib.h>#include <string.h>void Getmemery(char *p){ p=(char *)malloc(100);
//下面一行是自己加的
*p=1; //為了對比問題出在哪裡}void main(){ char *str=NULL; Getmemery(str); strcpy(str,"hello world"); printf("%s",str); free(str);}
編譯時間通過,運行時會輸出段錯誤。
其實還是C裡面經典的錯誤(值傳遞swap問題),為什麼第一眼看上去是對的,因為我們一貫地認為指標傳遞就可以這麼操作,然後忽略了後面的修改是否有效。
錯就錯在:傳入了指標,後面做的卻不是對指標的操作,而是對指標變數的操作。而對指標變數的操作又變成了原來類似值傳遞swap的問題,使其對指標的修改無效。指標變數與其他普通類型的變數沒太大區別,都是一段記憶體的別稱(只不過指標變數表示的記憶體放的是地址,其他類型的變數放的是資料),傳入子函數後,只是將以str為別稱的記憶體的值NULL地址傳給了以p為別稱的記憶體,以p為別稱的記憶體則通過malloc成了另一塊記憶體的別稱。但是子函數結束後,回到原來的主函數,str還是原來的str,並沒有發生變化,還是NULL指標。
若想要其有效只能,用指標的方式傳入str(其實指標變數也是某一塊記憶體的內容)[&str]並對指標操作(下面第2種修改方法),或者將修改後的指標變數返回(下面第1種方法)。
一種修正方法:
#include <stdio.h>#include <stdlib.h>#include <string.h>char *Getmemery(void) //通過返回指標變數{ char *p=(char *)malloc(100); return p;}void main(){ char *str=NULL; str = Getmemery(); strcpy(str,"hello world"); printf("%s",str); free(str);}
另一種修改方法
#include <stdio.h>#include <stdlib.h>#include <string.h>void Getmemery(void **p) //通過使用二重指標{ *p=(void **)malloc(100);}void main(){ char *str=NULL; Getmemery(&str); strcpy(str,"hello world"); printf("%s",str); free(str);}
分析一下之前的程式為什麼會錯了。
(1)void Getmemery(char *p)
(2)char *str=NULL;
(3)Getmemery(str);
1中子程式的形參是一個指標,然後很自然會想到2,3中的調用方式,本來的想法是用malloc分配記憶體,然後修改傳入的指標變數,那麼最後就根據通過 strcpy(str,"hello world"); 就可以向分配的記憶體裡面寫資料了。一切都是那樣流暢,對,因為這個用法平時用習慣了,所以根本不會去考慮正確性。
然而,這裡就出問題了。首先,Getmemery(str) 傳遞的是 srt指標的地址,這個沒有問題,C不同於C++,參數是通過傳遞的,而不是通過引用。也就是說,實際參數 str 先自己copy一份,然後傳遞給形式參數 *P接收,這個C語言的指標的時候已經強調多次了,但是自己還是錯了啊,哈哈。
然後,在子程式裡面,如果通過 *P 那麼訪問到的將是 *str的內容,這是等價的。但是,本程式一個致命的錯誤,非常隱蔽,那是子程式企圖修改 p 的內容,而不是 *p 的內容!!這個錯誤找了我很久終於給揪出來了。修改了 p 的值是沒有意義的,這個值是形式參數,並不會返回任何的東西,而 *p 則是通過p的地址直接存取需要的變數,這是不同的用法。所以說白了,void Getmemery(char *p) 執行之後並沒有改變任何的東西,str的值並沒有修改過,保持NULL,所以訪問 *0 地址會被作業系統禁止,得到一個錯誤。
解決辦法,是用2重指標。目的是要修改指標的地址,但是按照上面的分析,我們並不能去修改,但是我們可以用2重指標,將*str的地址值str,用2重指標來改變。
void Getmemery(void **p)
{
*p=(void **)malloc(100);
}
子程式修改為這個樣子,出入的參數也得修改
char *str=NULL;
Getmemery(&str);
那麼可以這樣理解,因為形參是2重指標,所以 p 對應 &str ,*P 對應 str,之前說了,我們的目的是要修改 str的值,所以很自然,我們用 *p = xxx 這樣的形式去修改了。
這樣得到的程式就正確了。
malloc
標準3部曲:malloc + free +指標置空
malloc申請的是堆(heap)空間
/*date:20100824description:malloc使用規範探討in參數:申請的堆記憶體位元組數,注意int,short,float需要自己乘上相應位元組數。out傳回值:void **/main(){char *str=NULL;str=(char *)malloc(10); //注意malloc傳回值是void *,申請時需要強制轉換成需要的類型memset(str,0,10); //如果不清空,申請的地區值是隨機的,養成好習慣strcpy(str,"happylife"); //使用strcpy特別注意拷貝的字串長度<=10-1,即要預留字串結束標誌‘\0‘puts(str);free(str);printf("str[address]:%s[%x]\n",str,str); //這裡的str內容為空白,但是指標地址還在str=NULL; //注意指標free之後該指標仍然存在,最好將它指向為空白。printf("str[address]:%s[%x]\n",str,str); //這裡的str內容為空白,地址也為空白}
C語言二重指標與malloc