深刻理解char * ,char ** ,char a[ ] ,char *a[]

1.數組的本質

數組是多個元素的集合,在內存中分佈在地址相連的單元中,因此能夠經過其下標訪問不一樣單元的元素。ubuntu

2.指針

指針也是一種變量,只不過它的內存單元中保存的是一個標識其餘位置的地址。因爲地址也是整數,在32位平臺下,指針默認爲32位。數組

3.指針的指向

指向的直接意思就是指針變量所保存的其餘的地址單元中所存放的數據類型。操作系統

int *p; //p變量保存的地址所在內存單元中的數據類型爲整型

float *q; // .............浮點型

不論指向的數據類型爲哪一種,指針變量其自己永遠爲整型,由於它保存的地址。.net

4.字符數組

字面意思是數組,數組中的元素是字符。確實,這就是它的本質意義。指針

char  str[10];   //定義了一個有十個元素的數組,元素類型爲字符。

C語言中定義一個變量時能夠初始化。code

char  str[10] = {"hello"};

當編譯器遇到這句時,會把str數組中從第一個元素把hello\0 逐個填入。blog

因爲C語言中沒有真正的字符串類型,能夠經過字符數組表示字符串,由於它的元素地址是連續的,這就足夠了。內存

C語言中規定數組表明數組所在內存位置的首地址,也是 str[0]的地址,即str = &str[0];字符串

另外get

printf("%s",str);

爲何用首地址就能夠輸出字符串。

由於還有一個關鍵,在C語言中字符串常量的本質表示實際上是一個地址,這是許多初學者比較難理解的問題。

舉例:

char  *s ;

s = "China";

爲何能夠把一個字符串賦給一個指針變量。

這不是類型不一致嗎

這就是上面提到的關鍵 。

C語言中編譯器會給字符串常量分配地址,若是 "China", 存儲在內存中的 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 .

s = "China" ,意識是什麼,對了,地址。

其實真正的意義是 s ="China" = 0x3000;

看清楚了吧 ,你把China 看做是字符串,可是編譯器把它看做是地址 0x3000,即字符串常量的本質表現是表明它的第一個字符的地址。

s = 0x3000

這樣寫彷佛更符合直觀的意思。

搞清楚這個問題。

那麼 %s ,它的原理其實也是經過字符串首地址輸出字符串,printf("%s ", s); 傳給它的實際上是s所保存的字符串的地址。

好比

#include <stdio.h>  

int main()  
{  
    char *s;  
    s = "hello";  
    printf("%p\n",s);  
    return 0;  
}

結果:

00422020

能夠看到 s = 0x00422020 ,這也是"hello"的首地址

因此,printf("%s",0x00422020);也是等效的。

字符數組:

char  str[10] = "hello";

前面已經說了,str = &str[0] ,也等於 "hello"的首地址。

因此printf("%s",str); 本質也是 printf("%s", 地址);

C語言中操做字符串是經過它在內存中的存儲單元的首地址進行的,這是字符串的終極本質。

4.char * 與 char a[]

char  *s;

char  a[] ;

前面說到 a表明字符串的首地址,而s 這個指針也保存字符串的地址(其實首地址),即第一個字符的地址,這個地址單元中的數據是一個字符,

這也與 s 所指向的 char 一致。

所以能夠 s = a;

可是不能 a = s;

C語言中數組名能夠複製給指針表示地址, 可是卻不能賦給給數組名,它是一個常量類型,因此不能修改。

固然也能夠這樣:

char  a [ ] = "hello";  
char *s =a;  
for(int i= 0; i < strlen(a) ; i++){
        printf("%c", s[i]);  //或  printf("%c",*s++);
}

字符指針能夠用 間接操做符 *取其內容,也能夠用數組的下標形式 [ ],數組名也能夠用 *操做,由於它自己表示一個地址 。

好比

printf("%c",*a);

將會打印出 'h'

5.char * 與 char a[] 的本質區別:

當定義 char a[10] 時,編譯器會給數組分配十個單元,每一個單元的數據類型爲字符。

定義 char *s 時, 這是個指針變量,只佔四個字節,32位,用來保存一個地址。

sizeof(a) = 10 ;
 
sizeof(s)  = ?

固然是4了,編譯器分配4個字節32位的空間,這個空間中將要保存地址。

printf("%p",s);

這個表示 s 的單元中所保存的地址。

printf("%p",&s);

這個表示變量自己所在內存單元地址,不要搞混了。

用一句話來歸納,就是 char *s 只是一個保存字符串首地址的指針變量, char a[] 是許多連續的內存單元,單元中的元素爲char 。

之因此用 char *能達到char a[]的效果,仍是字符串的本質,地址。即給你一個字符串地址,即可以爲所欲爲的操做他,可是,char *和char a[]的本質屬性是不同的。

6.char ** 與char *a[]

先看
char *a[] ;
因爲[] 的優先級高於 * 因此a先和 []結合,他仍是一個數組,數組中的元素纔是char * ,前面講到char * 是一個變量,保存的地址。

char *a[] = {"China","French","America","German"};

經過這句能夠看到, 數組中的元素是字符串,那麼sizeof(a) 是多少呢,有人會想到是五個單詞的佔內存中的所有字節數 6+7+8+7 = 28;

可是其實sizeof(a) = 16;

爲何,前面已經說到, 字符串常量的本質是地址,a 數組中的元素爲char * 指針,指針變量佔四個字節,那麼四個元素就是16個字節了

看一下實例:

#include <stdio.h>  
  int main()  
  {  
    char *a [] = {"China","French","America","German"};  
    printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]);  
    return 0;  
  }

能夠看到數組中的四個元素保存了四個內存地址,這四個地址中就表明了四個字符串的首地址,而不是字符串自己。

所以sizeof(a)固然是16了。。

注意這四個地址是不連續的,它是編譯器爲"China","French","America","German" 分配的內存空間的地址, 因此,四個地址沒有關聯。

#include <stdio.h>  
  int main()  
  {  
    char *a [ ] = {"China","French","America","German"};  
    printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]); //數組元素中保存的地址  
    printf("%p %p %p %p\n",&a[0],&a[1],&a[2],&a[3]);//數組元素單元自己的地址  
    return 0;  
  }

能夠看到 0012FF38 0012FF3C 0012FF40 0012FF44,這四個是元素單元所在的地址,每一個地址相差四個字節,這是因爲每一個元素是一個指針變量佔四個字節。

char **s;

char **爲二級指針, s保存一級指針 char *的地址,關於二級指針就在這裏不詳細討論了 ,簡單的說一下二級指針的易錯點。

舉例:

char *a[] = {"China","French","America","German"};  
 char **s = a;

爲何能把 a賦給s,由於數組名a表明數組元素內存單元的首地址,即 a = &a[0] = 0012FF38;

而 0x12FF38即 a[0]中保存的又是 00422FB8 ,這個地址, 00422FB8爲字符串"China"的首地址。

*s = 00422FB8 = "China";

這樣即可以經過s 操做 a 中的數據

printf("%s",*s);  
  printf("%s",a[0]);  
  printf("%s",*a);

都是同樣的。

但仍是要注意,不能a = s,前面已經說到,a 是一個常量。

再看一個易錯的點:

char **s = "hello world";

這樣是錯誤的,

由於 s 的類型是 char ** 而 "hello world "的類型是 char *

雖然都是地址, 可是指向的類型不同,所以,不能這樣用。

從其本質來分析,"hello world",表明一個地址,好比0x003001,這個地址中的內容是 'h',爲 char 型,而 s 也保存一個地址 ,這個地址中的內容(*s) 是char * ,是一個指針類型,因此二者類型是不同的。

若是是這樣呢:

char  **s;  
 *s = "hello world";

貌似是合理的,編譯也沒有問題,可是 printf("%s",*s),就會崩潰,
咱來慢慢推敲一下。

printf("%s",*s); 時,首先得有s 保存的地址,再在這個地址中找到 char * 的地址,即 *s;

** 舉例:**

s = 0x1000;

在0x1000所在的內存單元中保存了"hello world"的地址 0x003001 , *s = 0x003001;

這樣printf("%s",*s);

這樣會先找到 0x1000,而後找到0x003001;

若是直接

char  **s;

*s = "hello world";

s 變量中保存的是一個無效隨機不可用的地址, 誰也不知道它指向哪裏,*s 操做會崩潰。

因此用 char **s 時,要給它分配一個內存地址。

char  **s ;  
s = (char **) malloc(sizeof(char**));  
*s =  "hello world";

這樣 s 給分配了了一個可用的地址,好比 s = 0x412f;
而後在 0x412f所在的內存中的位置,保存 "hello world"的值。

再如:

#include  <stdio.h>  
void  buf( char **s)  
 {  
        *s = "message";  
 }  
 int main()  
 {  
     char *s ;  
     buf(&s);  
     printf("%s\n",s);  
 }

二級指針的簡單用法。,說白了,二級指針保存的是一級指針的地址,它的類型是指針變量,而一級指針保存的是指向數據所在的內存單元的地址,雖然都是地址,可是類型是不同的。

最後說明,sizoof(指針)的大小根據電腦操做系統而定,通常32位操做系統所佔用的內存大小是4,64位操做系統指針的大小是8。

char  **s;  
 *s = "hello world";

在個人ubuntu上打印 printf("%s",*s),沒有崩潰。

更多參考

相關文章
相關標籤/搜索