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.7 的 int arr[] 參數是同一回事,8.8 的 strlen、strcmp 收的也正是這種參數。看懂它們就夠了——回到自己的程式,字串仍然用 10.7 的 string 最省事。