字串

極為常見

在 C++ 內建的 STL 函式庫中,有內建一個標頭檔叫 <string> ,這個標頭檔裡面有不少跟字串相關的函式,這些函式在字串處理相關問題上十分實用,接下來會介紹幾個字串相關基本語法及常用的函式!

宣告

最簡單的宣告如下:

string 字串名稱;

在上面這種情況下,你的字串會是空字串。那如果想給予字串一個初始值呢?有幾種方法可以達成:

// 括號裡面放字串
string s1("Hello World!");
// 宣告後直接給值
string s2 = "Hello World!";
// 也可以直接複製另一個字串
string s3 = s1;
注意

如果你有學過其他的程式語言,你可能會習慣用單引號來表示字元,但在 C++ 中,字串只能用雙引號表示。

// 不能用單引號 :(
string s1 = 'Hello World!';
// 當然,什麼都不加就更慘了
string s2 = Hello World!;

單引號在後面還有其他用途,不要跟字串用的雙引號搞混!

輸入

string s1;
cin >> s1;

如果你想要輸入一個字串的話,有一個比較容易被忽略的地方就是,當你輸入字串時,如果你的字串中有空白、換行或是 Tab,那麼之後的部分就會被當作下一個變數的輸入。以上面的程式來說,如果你輸入 Hello World,那 s1 就會是 Hello。如果想知道如何避免這個問題,請參閱前面的 整行輸入

printf 中輸出

有一些新手可能會習慣用 printf,不過當你想使用 printf 來輸出字串時,你會發現這樣寫是不行的:

string s = "APCS Guide";

printf("%s is cool", s);

這樣寫會出現錯誤,因為 printf 是 C 語言的函式,而 string 是 C++ 的類別,所以這兩者不能直接混用。如果你想要在 printf 中輸出字串,你得先把 string 透過 .c_str() 轉換成 C String(char*):

string s = "APCS Guide";

printf("%s is cool", s.c_str());

字串長度

當想取得字串長度時,只要在字串名稱後加 .size() 就可以取得:

string s1 = "Hello";
int n = s1.size();
cout << n << endl; // 5
注意

size() 的回傳值是 size_t,這是一種 unsigned 的整數型態,所以如果你要將 size() 的回傳值做運算時要非常小心。如果陣列前是空的,size() 會回傳 0,那如果你剛好將 size() 的回傳值減 1,那結果就會是一個很大的數字,這可能會造成不可預期的錯誤。

改變長度

string s = "abcde";
s.resize(3);
cout << s;

//輸出結果:abc

以上面的 code 為例,如果只想保留 s 的前三個字,那就呼叫 .resize(3),其中 () 裡裝的就是要保留的長度。最後輸出時,s 就變成 abc 了。舉例來說,如果想把字串砍半怎麼辦?只要結合剛剛的 .size() 就好了!

s.resize(s.size() / 2);

那如果想讓它變長呢?

s.resize(s.size() + 3); // abcde___
s.resize(s.size() + 3, 'a'); // abcdeaaa

如果純粹只是想讓它變長,原理就跟剛剛變短一樣,只是後面變長的空間就會變成空字元。如果想再指定多出來的長度要是什麼,只要在後面多打你指定的字元就好。

取得或修改字串中的某個字元

如果你想取得字串中的某個字元,只要在字串名稱後加上中括號,並在中括號中填入索引值(也就是你要取得的位置)就可以了。注意,索引值是從 0 開始的:

string s = "abcde";
cout << s[0] << endl; // a
cout << s[2] << endl; // c

如果你想修改字串中的某個字元,也是差不多的方法:

string s = "abcde";
s[0] = 'z';
cout << s << endl; // zbcde
注意

請注意,對於一個長度為 n 的字串,合法的索引值是 0n - 1,這點常常被很多初學者忽略!

如果你試圖取得或修改超出字串範圍的字元,程式可能會出現不可預期的結果,所以在取得或修改字元之前,最好先確定索引值是合法的。

比較

就像字元一樣,字串也可以用 ==!=<><=>= 來比較。比較過程是從字串的第一個字元開始比較(依照 ASCII),然後比較第二個字元,一直比到有一個字元不同為止,如果都一樣,就會比到長度較短的那個字串為止。這種比較方式就叫做「字典序」。

以下是一些簡單的例子:

string s1 = "abc";
string s2 = "abcd";
string s3 = "cb";
string s4 = "abd";

cout << (s1 > s3) << endl; // false 因為第一個字元就不一樣,且 c 比 a 大
cout << (s1 < s2) << endl; // true 雖然前面都相同,但 s1 長度較短
cout << (s1 < s4) << endl; // true 比到第三個字元就會發現 d 比 c 大

加法

你沒看錯!字串可以加在一起!但是要注意兩個字串相加的前後順序,例如:

string s1 = "123", s2 = "abc";
string s3 = s1 + s2; // 123abc
string s4 = s2 + s1; // abc123
s1 += s2; // 123abc
注意

如果你嘗試過這樣的寫法:

cout << "123" + "abc" << endl;

你會發現這樣寫竟然會出現錯誤?!不是說好字串可以加在一起嗎?為什麼這樣寫會出錯呢?

這是直接透過這樣語法宣告的 "123""abc" 都是「C String」(古老 C 語言中的字串表示方法,也就是 char*),而不是 C++ 的 string,所以這樣寫會被當作兩個字元陣列的記憶體位置相加,而不是字串相加(C++ 中的字串才有更多的功能!)。如果你想要這樣寫,你可以這樣寫:

cout << string("123") + "abc" << endl;

或者是這樣寫:

cout << "123"s + "abc" << endl;

這個 s 是 C++11 之後的語法,可以將 char* 轉換成 string,跟 1ll 可以將 int 轉換成 long long 一樣的概念。

子字串

子字串的定義是在一個字串 s 中,從某個位置開始連續的 i 個字元組成的字串 t,就稱 ts 的一個長度為 i 的子字串。例如 abcde 中,abc、甚至 abcde 本身都是 abcde 的子字串,但 ae 不是。

那要如何生成一個字串的子字串呢?使用 .substr 就行了!例如:

string S = "abcde";
string S1 = S.substr(2, 2); // cd
string S2 = S.substr(2); // cde

其中,第一個參數是子字串的起始位置,第二個參數是子字串的長度,如果沒有指定長度,就會直接複製到字串尾。

建立重複字元的字串

這邊要來介紹一個小技巧,如果你想要建立一個重複某個字元的字串,可以使用以下的方法,雖然這很明顯可以直接用回圈來達到一樣的效果,但這樣寫起來比較簡潔:

string s(5, 'a');

cout << s << endl; // aaaaa

或者用賦值的方式:

string s = string(5, 'a');

cout << s << endl; // aaaaa

尋找子字串

如果你想要在一個字串中尋找另一個字串,可以使用 .find 函式,這個函式要傳入一個要尋找的子字串,會回傳第一個找到的子字串的起始位置,如果找不到就會回傳 string::npos

string s = "Hello World!";

int pos = s.find("World");

if (pos != string::npos) {
    cout << pos << endl; // 6
} else {
    cout << "Not found" << endl;
}

string::npos 是一個特殊的常數,代表找不到的意思,這個常數的型態是 size_t,值是一個很大的數字,通常是 18446744073709551615,這個數字是 2^64 - 1,同時也是 unsigned long long 的最大值。

判斷是否為空字串

string 有一個函式叫做 .empty(),可以用來判斷一個字串是否為空字串:

string s1 = "Hello";
string s2;

cout << s1.empty() << endl; // 0
cout << s2.empty() << endl; // 1

不過,如果你想要自己實作的話,也可以直接用 s.size() == 0 來判斷就好。

數字轉字串

如果你想要把一個數字轉成字串,C++ 提供了一個非常方便的函式叫做 to_string,這個函式要傳入一個整數或浮點數,會回傳一個對應的字串:

int n = 123;
string s = to_string(n);
cout << s << endl; // 123

double d = 3.14;
string s2 = to_string(d);
cout << s2 << endl; // 3.14

cout << to_string(12345) << endl; // 12345

字串轉數字

如果你想要把一個字串轉成數字,C++ 也提供了一個函式叫做 stoi,這個函式要傳入一個字串,會回傳一個對應的 int 數字:

string s = "123";
int n = stoi(s);

cout << n << endl; // 123
cout << n + 1 << endl; // 124

cout << stoi("12345") * 2 << endl; // 24690

那如果是想轉換成 floatdouble 或者是其他的型態呢?C++ 對每個型態都提供了對應的函式,如下表所示:

型態函式
intstoi
longstol
long longstoll
unsigned longstoul
unsigned long longstoull
floatstof
doublestod
long doublestold
注意

這邊要注意的是,如果你的字串不是合法的數字,那這些函式會拋出一個 invalid_argument 的錯誤,所以在使用之前,最好先確定你的字串是合法的數字。

但不合法的數字不只包括非數字字元,也包括超出範圍的數字,例如 stoi("12345678901234567890") 這樣的數字就會超出 int 的範圍,所以也會拋出錯誤,因此如果數字過大的話,要記得改用 stollstoull

replace 函式

如果你有學過其他程式語言的話,你可能會習慣用 replace 來取代字串中的某個子字串,但在 C++ 中,replace 的功能是取代字串中的某個範圍的字元,而不是取代子字串。

無論如何,這個函式要傳入三個參數,分別是要取代的起始位置、要取代的長度以及要取代的字串:

string s = "Hello World!";
s.replace(6, 5, "APCS");

cout << s << endl; // Hello APCS!

小測驗

字串是根據什麼來比較大小的?