C中常量字串和字元陣列的區別
1、 常量字串
在程式碼裡直接出現的”abcdef”這種字串,在程式執行的時候,系統會將它們放在常量區,所謂常量區就是一直存在的,只讀的,不可更改的資料區域,並且一個字串只會有一份。假設你在程式裡有兩行程式碼
char* p1 = “agcd”;
char* p2 = “agcd”;
無論你這兩個行程式碼隔了多遠,如果你想知道p1和p2所指向的字串在記憶體中是不是同一個,那答案是肯定的,p1和p2的值完全一樣。”agcd”這是一個存在於記憶體中的常量字串,它從程式一開始就在那裡,一直到程式結束都不會改變。在記憶體中,”agcd”是以如下方式儲存的
‘a’ |
’g’ |
‘c’ |
‘d’ |
‘\0’ |
它的最後肯定有一個字串結束標誌’\0’。這種字串的名字叫“以空字元為結束標誌的字串”。
char* p1 = “agcd”;
如果你這時候想改變第一個字元的值,用p[0] =’b’,系統會報一個錯,常量字元不能更改。(這裡為什麼指標可以當陣列用,下面再解釋)。
2、 字元陣列
如果你定義一個char a[10],那麼系統會“只分配”10個char這麼長的記憶體區域,一個char是一個位元組,那麼系統會分配十個位元組的記憶體空間,並且將這一片連續的記憶體空間的首地址賦值給a。也就是說“陣列名的值是陣列所在記憶體區域的首地址”換句話說“陣列名是一個指標,指向陣列第一個值的地址”。
如果你定義一個char a[] = “abcdefg”;這句程式碼就複雜點了。定義一個數組,陣列長度未知,那麼系統會根據等號後面的值來“初始化”這個陣列,等號後面是什麼?前面說過,它是一個常量字串。在記憶體中佔8個位元組,7個字元加上一個結束標誌。這時候在記憶體中就有兩個”abcdefg”的字串了,一個是常量區域的,另一個是根據前者複製了一份的。這句程式碼的意思就是複製一個常量區域的字串,將複製後的字串的首字母的地址賦值給a。
也就是說,最後a所指向的記憶體區域,已經不是常量裡的”abcdefg”了,這裡為什麼要複製一份呢?原因是因為常量是不允許更改的,而陣列一般都意味著需要修改,所以就複製了一份資料,放在非常量區域,就可以更改了。下面的測試程式可證明上述結論:
char* p1 = "abcdef";
char* p2 = "abcdef" ;
char a[]= "abcdef" ;
unsigned long dwP1 = (unsignedlong)p1 ;
//32位系統裡的指標就是4個位元組的整數,這樣可以具體檢視指標的值。
unsigned
long
unsigned long dwA = (unsignedlong)a ;
printf("p1的值(32位地址)= 0x%X\n",dwP1);
//%X是列印十六進位制,X是大寫,x是小寫
printf("p2的值(32位地址)= 0x%X\n",dwP2);
printf("a的值(32位地址) = 0x%X\n",dwA);
執行後的截圖:
從上面的結果可看出,常量字串在記憶體中只有一份。而賦值給陣列的時候,系統會拷貝一份。如果我們列印a[6]會是什麼結果呢,請注意,字串只有6個,最高索引是5。
printf("a[6]的值=%d",a[6]);//以整數列印
雖然陣列的可用長度是6,但是它第7個位置還是存在的,那就是空字元結束標誌。空字元結束標誌是必須的,因為很多時候系統並不知道你的字串有多長。
3、 應用
當明白了這些本質後,我們怎麼來靈活使用字元陣列呢。在實際的編碼中,比如從檔案裡讀一段文字出來,假設當前檔案中的字串有20個字母。
第一步讀取檔案大小。
int fileSize = file.getSize();
第二步,分配快取。這是動態分配陣列。這種用法和java相似。
char* p = new char[fileSize+1] ;//加1是為了後面放’\0’.
第三步,讀取
file.read(p,fileSize);//意思是從檔案裡讀取filesize個位元組,並且放在p所指向的快取中
第四步,標誌結尾
P[fileSize] = ‘\0’; //由於指標指向的是字串首字母地址,而陣列名也是一樣的,所以C/C++裡指標和陣列幾乎用法一樣。其他語言裡不一樣。這也是C/C++的魅力所在,夠靈活。
這時候的p所指向的就是一片連續的記憶體控制元件,它的內容就是檔案裡的字串,並且它的最後有一個結束標誌(這樣就可以在系統裡靈活使用了)。