C語言中的指針和字符串

前言

務必理解指針與內存模型,不要死記硬背。數組

內存裏的字符串

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的指針。這種狀況你能夠獲得正確的答案,可是不推薦,調用這個函數的人頗有可能

  • 不知道函數裏面分配過內存
  • 不知道應該何時free這部份內存
  • 忘了free這部份內存

一旦沒有注意,屢次調用這個函數,結果就是內存溢出,這樣的錯誤還很是很差排查,因此不推薦

正確的作法

正確的作法是把分配內存這種事情放在函數外面作,正如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,這裏只是爲了方便而已。

相關文章
相關標籤/搜索