語法書 / AA 競程語法書 下冊 / 附錄 / H. char * 當字串

H. char * 當字串

8.6 說過,C 字串就是「一串字元+結尾記號 '\0'」,宣告寫成 char s[] = "abc";8.7)。考卷上還有另一種長相,星號跑到了名字前面:

char s1[] = "abc";          // 陣列:複製一份給你,內容可以改
const char *s2 = "abc";     // 指標:只記住這串字放在哪

考卷是 C 語言,那個 const 在 C 裡不必寫,直接 char *s2 = "abc"; 完全合法。同一行在 C++ 已經不合法,只是 g++ 網開一面放行、給一個警告 ISO C++ forbids converting a string constant to ‘char*’——自己寫要補上 const

兩者用起來幾乎一樣——s[0] 取字元、cout << s 印整串、丟給函式,行為都相同。原因 11.2 講過:運算式裡的陣列名稱會轉成「指向第一個元素的指標」。一個本來就是指標,另一個用的時候變成指標,殊途同歸。真正的差別是誰擁有那串字

比較項目 char s[] = "abc" char *s = "abc"
s 是什麼 一個 4 格的字元陣列 一個指標,裡面存的是位置
字串住在哪 s 自己的格子裡 程式的唯讀區
能改內容嗎 s[0] = 'A' 沒問題 ✗ 未定義行為(上冊 6.8

取字元與走訪

指標一樣能用中括號:s[2] 的意思是「從 s 往後跳 2 格再取值」,也就是 *(s + 2)——兩種寫法完全等價,考卷兩種都會出現。

C 字串沒有附長度,一切以結尾記號為準,於是有了這個到處都是的慣用寫法:

while (*s) {         // 等同於 while (*s != '\0')
    s++;             // 移動的是指標本身
}

'\0' 的編號是 0,而 0 為假、非 0 為真,所以 *s 直接當條件就是「還沒走到結尾」。注意這個迴圈動的是 s 自己:跑完它已經停在結尾記號上,開頭的位置找不回來了。

完整範例

#include <iostream>
using namespace std;

int length(const char *s) {           // 收到的只是「開頭在哪」
    int n = 0;
    while (*s) {                      // 取出來的字元不是 '\0' 就繼續
        n++;
        s++;                          // 指標往後移一格
    }
    return n;
}

int palindrome(const char *s) {
    int n = length(s);
    for (int i = 0; i < n / 2; i++) {
        if (s[i] != s[n - 1 - i]) return 0;
    }
    return 1;
}

int main() {
    char word[105] = "level";         // 陣列:自己的一份,可以改
    const char *lit = "abcba";        // 指標:指向唯讀的字面常數

    word[0] = 'L';                    // 改陣列的內容沒問題
    cout << word << ' ' << length(word) << '\n';
    cout << lit[2] << ' ' << *(lit + 2) << '\n';
    cout << palindrome("abcba") << ' ' << palindrome("abcd") << '\n';
    return 0;
}

執行結果:

Level 5
c c
1 0

兩個函式的參數都是 const char *s,所以字元陣列 word、指標 lit、直接丟一個 "abcba" 進去,三種都收得下——傳過去的都只是開頭位置(11.3)。上冊 7.7int arr[] 參數是同一回事,8.8strlenstrcmp 收的也正是這種參數。看懂它們就夠了——回到自己的程式,字串仍然用 10.7string 最省事。