深入 char * ,char ** ,char a[ ] ,char *a[] 核心
C語言中由於指標的靈活性,導致指標能代替陣列使用,或者混合使用,這些導致了許多指標和陣列的迷惑,因此,刻意再次深入探究了指標和陣列這玩意兒,其他型別的陣列比較簡單,容易混淆的是字元陣列和字元指標這兩個。。。下面就開始剖析一下這兩位的恩怨情仇。。。
1 陣列的本質
陣列是多個元素的集合,在記憶體中分佈在地址相連的單元中,所以可以通過其下標訪問不同單元的元素。。
2 指標。
指標也是一種變數,只不過它的記憶體單元中儲存的是一個標識其他位置的地址。。由於地址也是整數,在32位平臺下,指標預設為32位。。
3 指標的指向?
指向的直接意思就是指標變數所儲存的其他的地址單元中所存放的資料型別。
int * p ;//p 變數儲存的地址所在記憶體單元中的資料型別為整型
float *q;// ........................................浮點型
不論指向的資料型別為那種,指標變數其本身永遠為整型,因為它儲存的地址。
4 字元陣列。。。
字面意思是陣列,陣列中的元素是字元。。確實,這就是它的本質意義。
char str[10];
定義了一個有十個元素的陣列,元素型別為字元。
C語言中定義一個變數時可以初始化。
char str[10] = {"hello world"};
當編譯器遇到這句時,會把str陣列中從第一個元素把hello world\0 逐個填入。。
由於C語言中沒有真正的字串型別,可以通過字元陣列表示字串,因為它的元素地址是連續的,這就足夠了。
C語言中規定陣列代表陣列所在記憶體位置的首地址,也是 str[0]的地址,即str = &str[0];
而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;
}
可以看到 s = 0x00422020 ,這也是"China"的首地址
所以,printf("%s",0x00422020);也是等效的。。
字元陣列:
char str[10] = "hello";
前面已經說了,str = &str[0] , 也等於 "hello"的首地址。。
所以printf("%s",str); 本質也是 printf("%s", 地址");
C語言中操作字串是通過它在記憶體中的儲存單元的首地址進行的,這是字串的終極本質。。。
5 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'
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),就會崩潰
why??
咱來慢慢推敲一下。。
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);
}
二級指標的簡單用法。。。。,說白了,二級指標儲存的是一級指標的地址,它的型別是指標變數,而一級指標儲存的是指向資料所在的記憶體單元的地址,雖然都是地址,但是型別是不一樣的。。。