務必理解指針與內存模型,不要死記硬背。數組
C語言中的字符串通常是char *
類型的,這是怎樣存在內存中的呢?數據結構
cchar *s = "NIHAO"; | s:400 | |---|---|---|---| |'N'|'I'|'H'|'A'|'O'| 0 | |---|---|---|---|---|---| |400|401|402|403|404|405|
如是上圖,假設字母A處於內存的第400號格子,那麼後面幾個字母也是緊跟着的。
變量s自己並無儲存字符串,而存的是字符串的首地址400。也即,s指向這個字符串。less
爲何沒有專門一個字符串的類型而是要靠一個指針指向它呢?由於字符串的長度是不固定的,因此一個字符串還包含着長度信息,基本類型是沒法處理數據結構的。函數
咱們都知道字符串是以0結尾的,並且這個更像是一種約定,C編譯器自己並無對此作任何保證。好比這樣指針
cchar s[3] = "asd"; puts(s); /* prints "asd" or something longer */
這樣作是危險的,由於s只有3個格子,字符串結尾的0並無放進去。若是在它後面的內存格子並非0,那打印這個字符串時就跟咱們預期的不同了。code
c"abc"[0] = 'z'; /* wrong */ char *s = "abc"; s[0] = 'z'; /* wrong */ char s[5] = "abc"; s[0] = 'z' /* right */
當指針s指向的是字符串常量(即直接寫在程序裏面的字符串時),要注意它是不可寫的
爲啥用數組就沒問題呢,由於數組的初始化和指針有點區別內存
cchar s[5] = "abc"; /* 至關於 */ char s[5]; strcpy(s, "abc");
若是擔憂本身會不當心寫錯,能夠加上const
關鍵字,這樣編譯的時候就會報錯
這是一個好習慣,接下來的示例程序中都會這麼寫。字符串
cconst char *s = "abc"; s[0] = 'z'; /* causes a compiling error instead of runtime error */
c/* wrong */ char *s; s[0];
上面的程序編譯是能過的(可能有warning),但運行是必定會出錯的,由於編譯器並不知道s指向哪些格子。編譯器
c/* right */ const char *s = "NIHAO"; s[0];
這樣,實際上是隱式的分配了6個格子(包括字符串結尾的0),並讓s指向它們string
c/* right */ char s[6]; s[0];
c/* right */ char s[6] = "NIHAO"; s[0];
數組其實跟指針沒什麼區別,主要的區別是它在聲明的時候就分配好了格子(方括號裏的6就是告訴編譯器給我6個格子),並且數組不能改變它的指向(也不能再要更多的格子)。
cconst char *s = "abcd"; const char *t = "abcd"; /* wrong */ if (s == t) { ... } /* right */ if (!strcmp(s, t)) { ... }
由於s和t都沒有存字符串的內容,它們存的是字符串的地址,若是用==
比較,比較的是兩個字符串的地址是否相同。咱們但願比較的是內容是否相同。
請使用C語言庫函數中的strcmp
比較字符串是否相等
c/* tries to copy a string */ char s[5] = "abcd"; char *t = s; t[3] = 'z'; puts(s); /* puts "abcz" */
上面這種作法讓t和s指向同一字符串,修改t指向的內容,會發現s指向的內容也被修改了。這種作法沒有錯,常常會用到,但不必定是你想要的。
c/* wrong */ char *s = "abcd"; char *t; /* not initialized */ strcpy(t, s);
c/* right */ char *s = "abcd"; char t[10] = {0}; /* or char *t = (char *) malloc(5*sizeof(char)); */ strcpy(t, s);
使用strcpy
複製字符串的內容而不是指針,但也要注意初始化t這個指針
int,float之類的很簡單直接return就好
但如今我想寫一個函數,它可以獲得一個字符串
c/* no problem, but meaningless */ const char *f() { const char *s = "abcd"; return s; } /* wrong */ char *f() { char s[100]; /* do something with s */ return s; } /* result correct but not good */ char *f() { int n = 10; char *s = (char *) malloc(n*sizeof(char)); /* do something with s */ return s; }
第一種狀況就不說了,返回一個字符串常量並無問題由於它不可修改,可是不可修改也就沒什麼意義了。
第二種狀況是徹底錯誤的,返回一個局部的數組。這個數組的內存會在函數調用完後被收回,所以返回的指針指向的時候沒有意義的地方。現代編譯器通常都會對這個有warning。
第三種狀況是返回malloc的指針。這種狀況你能夠獲得正確的答案,可是不推薦,調用這個函數的人頗有可能
一旦沒有注意,屢次調用這個函數,結果就是內存溢出,這樣的錯誤還很是很差排查,因此不推薦
正確的作法是把分配內存這種事情放在函數外面作,正如strcpy
同樣
cchar *strcpy(char *dest, const char *src) { int i; for (i = 0; i < strlen(src); i++) { dest[i] = src[i]; } return dest; }
dest
是咱們想要返回的字符串,它是從外面傳進來的緣由是咱們不想在函數內部爲它分配內存,而是在外面分配好了,裏面只對這個字符串進行修改。
注意這裏返回了char *
但其實返回的正是本來傳進來的dest
,這裏只是爲了方便而已。