int main() { char car[]="Tata"; printf("car p格式 = %p, &car[0] = %p \n",car,&car[0]); printf("*car = %c, car[0] = %c \n",*car,car[0]); printf("car s格式 = %s \n",car); printf("(car+1) = %c, car[1] = %c \n",*(car+1),car[1]); return 0; }
觀察上面代碼,容易得出如下結論:數組
car=&car[0]=字符串首地址(car須要%p或者%u格式,固然應該是%p格式最恰當,%u格式彷佛不是特別嚴謹)函數
*car=car[0]=字符串首個字符(‘T’)spa
car=整個字符串內容(car須要%s格式)指針
*(car+1)=car[1]=字符串的第二個元素的值code
三、數組和指針(字符串的指針和數組形式)blog
const char *pt1="Something is pointing at me."; const char at1[ ]="Something is pointing at me.";
數組形式和指針形式有何不一樣?以上面的2個聲明爲例:內存
數組形式(ar1[])在計算機的內存中分配爲一個內含29個元素的數組(每一個元素對應一個字符,還加上一個末尾的空字符'\0'),每一個元素被初始化爲字符串字面量(順序)對應的字符。一般,字符串都做爲可執行文件的一部分存儲在數據段中。當把程序載入內存時,也載入了程序中的字符串。字符串存儲在靜態存儲區(static memory)中。可是,程序在開始時纔會爲該數組分配內存。此時,纔將字符串拷貝到數組中。注意:此時字符串有兩個副本。一個是在靜態內存中的字符串字面量(字符串常量),另外一個是存儲在ar1數組中的字符串。此後,編譯器便把數組名ar1識別爲該數組首元素地址( &ar1[0] )的別名。這裏關鍵要理解,在數組形式中,ar1是地址常量。不能更改ar1(的值,就是ar1不能作左值),若是改變了ar1,則意味着改變了數組的存儲位置(即地址)。能夠進行ar1+1這樣的操做,來識別數組的下一個元素。可是不容許++ar1這樣的操做。遞增運算符只能用於變量名前(或歸納地說,只能用於可修改的左值),不能用於常量。ci
指針形式(*pt1)也使得編譯器爲字符串在靜態存儲區預留29個元素的空間。另外,一旦開始執行程序,它會爲指針變量pt1留出一個存儲位置,並把字符串的地址存儲在指針變量中。該變量最初指向該字符串的首字符,可是它的值能夠改變。所以,可使用遞增運算符.例如,++pt1將指向第2個字符‘o’。字符串字面量(字符串常量)被視爲const數據。因爲pt1指向這個const數據,因此應該把pt1聲明爲指向const數據的指針。這意味着不能用pt1改變它所指向的數據,可是仍然能夠改變pt1的值(即,pt1指向的位置)。若是把一個字符串字面量(字符串常量)拷貝給一個數組,就能夠隨意改變數據(應該是數組的元素),除非把數組聲明爲const。rem
總之,初始化數組把靜態存儲區的字符串拷貝到數組中,而初始化指針只能把字符串的地址拷貝給指針。字符串
以上是書上的內容(括號內的除外),下面我來總結一下:
一、字符串字面量(或者叫字符串常量)不管是以數組形式仍是以指針形式聲明,都會放在數據段(靜態存儲區)。
二、區別是,若是以數組形式聲明,那麼,當程序運行起來之後,編譯器會給數組分配內存(數組所佔內存應該是在棧上),並將保存在靜態存儲區的字符串拷貝至數組。所以,以數組形式聲明的字符串有2個副本(有正本嗎?)。指針形式聲明的字符串則沒有2個副本。閱讀下面代碼:仔細體會該程序所驗證的第1條,第2條總結內容:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MSG "I am special" //定義一個宏字符串 /* 本程序演示了數組字符串、指針字符串、宏字符串及字符串自己的地址,選自CPremer第六版 ch11-3,並稍微改動*/ int main(void) { char ar[] = MSG; const char* pt = MSG; printf("address of \"I am special\": %p \n", "I am special");//輸出字符串地址 printf(" address ar: %p \n", ar); //輸出數組字符串地址 printf(" address pt: %p \n", pt); //輸出指針字符串地址 printf(" address of MSG: %p \n", MSG); //數組宏字符串地址 printf("address of \"I am special\": %p \n\n", "I am special"); //輸出字符串地址 int num = 200; printf("address of num = %p \n", num); //對於整數num,輸出的是其值的16進制數 printf("address of num = %p \n\n", &num); //對於整數num,&num格式才能正確輸出地址 char msgA[]="I am special,too."; printf("address of \"I am special,too.\": %p \n", "I am special,too."); printf(" address msgA: %p \n", msgA); char *msgP2=msgA; printf(" address msgP2: %p msgp2=%s \n", msgP2,msgP2); //因爲msgP2=msgA,因此msgP2的地址與數組msgA相同。 char *msgP="I am special,too."; printf(" address msgP: %p \n", msgP); msgA[0]='i'; printf("數組msgA=%s \n",msgA); //查看數組msgA的值是否發生改變。 printf("msgP=%s\n\n",msgP); //查看數組的值發生改變後,是否影響到放在數據區的字符串 /* 上面這一小段程序驗證了我對書上內容的理解:字符串字面量(或者叫字符串常量)不管是以數組 形式仍是以指針形式聲明,都會放在數據段(靜態存儲區)。區別是,若是以數組形式聲明,那麼, 當程序運行起來之後,編譯器會給數組分配內存(數組所佔內存應該是在棧上),並將保存在靜態 存儲區的字符串拷貝至數組。 且因爲針對字符串數組的操做是在棧上它的副本進行的,因此,能夠更改字符串數組元素的值。但 是這種更改不會影響到在數據區存放的字符串正本。同時因爲字符串數組存放在棧上,當其所在的 函數運行完以後,字符串數組所在空間自動釋放。所以,返回字符串數組地址將是無效的。可是存 放在數據區的字符串正本不會被釋放,它的地址能夠返回到調用它的函數。 */ system("pause"); return 0; } /* 從程序運行結果能夠看出:除數組字符串之外,宏字符串地址=指針字符串地址=字符串地址 另:當數組名、指針名、宏名、字符串名配上'%p'時,printf輸出的是地址 */
三、因爲數組保存在棧上,因此,數組元素(字符串)能夠被更改(數組元素被更改之後,存放於靜態存儲區的副本是否也隨之變化那?不會變,上面的程序已經驗證了不會變。)。且具備保存在棧上的變量的特徵:當其所在的函數運行完以後,字符串數組所在空間自動釋放。所以,返回字符串數組地址將是無效的。可是存 放在數據區的字符串正本不會被釋放,它的地址能夠返回到調用它的函數。而由於指針形式聲明的字符串只存放在靜態存儲區,沒有存放在其餘地方的副本,因此,指針形式聲明的字符串不能夠被更改,它的地址也能夠返回到調用它的函數。
四、同時,數組名所表明的地址不能夠被更改,也就是數組名不能夠作左值。指針名則能夠。閱讀下面代碼,仔細體會該程序所驗證的第3條,第4條總結內容:
#include <stdio.h> #include <stdlib.h> #include <string.h> char* arrayString() { char astr[] = "I am an astr !"; printf("調用arrayString函數時,astr=%s \n", astr); /* 不管是以數組形式仍是以指針形式聲明的字符串,字符串的名字既能夠按照 指針的方式來操做,也能夠按照數組的方式來操做。詳見下面2行程序語句 */ astr[0] = 'i'; //若字符串以數組形式聲明,則能夠更改字符串的元素值 *(astr + 2) = 'A';//若字符串以數組形式聲明,則能夠更改字符串的元素值 printf("給字符串個別元素從新賦值後,astr=%s \n", astr); printf("將數組的地址+1,以輸出第2個元素,++astr=%c \n", ++astr); //編譯上面的語句時,編譯器報錯並提示:「表達式必須是可修改的左值」,這 //顯然與CPrimer書說法一致:以數組形式聲明的字符串,其名字不能夠當左值。 //即:以數組形式聲明的字符串名,其表示的地址不能夠被更改。 return astr; } void test01() { char* a = NULL; a = arrayString(); printf("arrayString函數退出後,astr=%s \n\n", a); } char* pointString() { char* pstr = "I am a *pstr !"; //pstr[0] = 'i'; //執行這2條語句時,系統報錯,顯然:若字符串 //*pstr='i'; //以指針形式聲明,則不能夠更改字符串的元素值 printf("調用pointString函數時,pstr=%s \n", pstr); printf("將數組的地址+2,以輸出第3個元素,*(++pstr+1)=%c \n", *(++pstr+1)); //看上行語句:若字符串以指針形式聲明,則字符串名能夠當左值,即: //字符串名錶示的地址能夠被更改。 --pstr; //將pstr指向的地址恢復。 return pstr; } void test02() { char* p = NULL; p = pointString(); printf("pointString函數退出後,pstr=%s, *p=%c, p[0]=%c \n", p,*p,p[0]); //當指針指向字符串常量時,若是以'%s'格式printf,則輸出字符串常量。 } //若是以'%p'格式printf,則輸出字符串地址 int main() { test01(); test02(); system("pause"); return 0; }