字串 String

字串在程式語言中是其中一個非常重要的資料型態。如果沒有字串,那我們如果想要表達一句英文的話就會變得非常麻煩。

在這一章,我們就是要來介紹字串這個觀念。包括應該如何使用、有分什麼種類等等!

什麼是字串

所以到底什麼是字串?還記得我們在 什麼是IDE 那章的第一個程式碼嗎?

int main() {
    std::cout << "Hello World!" << std::endl;
    return 0;
}

這裡的 "Hello World!" 就是一個字串,英文叫做 string

字串顧名思義就是將一個一個字符串起來,我們常常用字串來表示名字或各種單字。

在 C++ 中,我們有兩種方式可以用來表示字串:

  1. C-style string
  2. std::string

C-style string

C-style string,中文翻譯叫做 C 風格字串。從名字上就可以知道這種表達方式源自於 C 語言,而 C++ 則繼續採用了這種表達方式。

這種表達方式最重要的一個特色就是每一個字串最後都是由 \0 結尾,這個符號叫做 null terminator。

因此 C++ 在做讀取的時候,只要讀到 \0 他就知道這個是一個字串的結尾,也就會停止讀取。

那麼要如何宣告一個 C-style string 呢?其實就跟前一章 陣列 在講如何宣告陣列基本上是一樣的方法,只是這個陣列的資料型態為 char

所以我們可以看到,其實 C-style string 本身是一個陣列,而這個鎮列中的每一個格子則儲存一個字符。

我們可以這樣宣告:

char str[]{"abc"};

在宣告時,我們並沒有加上 \0,那是因為 編譯器 在編譯時會自動在最尾端幫我們加上去。

因此,實際上這個字串 str 的長度其實是 4。我們可以執行以下程式進行驗證。

int main() {
    char str[]{"abc"};
    int length{std::size(str)};
    std::cout << length << std::endl;

    return 0;
}

因為 C-style string 本身其實是一個陣列,因此當我們想要更改 str  時,我們必須以更改陣列的方式進行修改。也就是說:

str = "def";  // 這是不可以的
str[0] = "d"; // 這是可以的,str會變成"dbc"

而有一點是初學者常常犯的錯,也就是如果我們想要給定這個陣列的大小,那如果我們的字串剛好等於這個陣列的大小,則程式就會出錯。

這是因為在 C-style string,我們永遠要留一個空位給 null terminator \0

char str[4]{"abc"};  // 正確
char str[3]{"abc"};  // 程式會報錯

std::string

C++ 提供了另一種字串類型:std::string

要使用這個類別我們需要引進相關的資料庫,因此需要使用 <string> 這個程式庫。

在宣告方面,std::string 也是比 C-style string 好用非常多。

std::string name{"Arthur"};

如果我們想要將這個 name 換成別的名字,也是非常容易。

name = "Simon"

更方便的是,如果我們只想更改其中一個字元,我們可以像更改陣列那樣更改特定字元。

int main() {
    std::string str{"Arthur"};
    str[0] = 'B';
    std::cout << str << std::endl; // str 的結果為 Brthur
    return 0;
}

要使用 C-style string 還是 std::string

講了這麼多,當我們在寫程式時到底要用哪一個呢?

如果可以的話,盡量使用 std::string!std::string 最大的優點就是相比於 C-style string,std::string 更容易使用,並且也有更多樣的應用方式。

在很少數的應用中你可能會需要去嚴格控制你的記憶體空間,在這種情況下你就可以用 C-style string,因為你可以自由控制需要給每一個字串多少儲存空間 (buffer size)。

然而絕大多數情況下,我們仍然偏好使用 std::string。但是,要真正的了解 C++,知道 C-style string 是如何運作也是非常必要的!因為在許多底層應用中,C-style string 仍是非常常見的。

字串的基本操作

現在我們了解了字串的作用,以及不同種類的字串。那麼我們可以用字串來做什麼呢?

這邊我們來介紹五種常見的功能:

  • 字串連接
  • 長度檢視
  • 查找
  • 替代
  • 提取

字串連接

我們可以利用 + 來完成數個字串的連接。舉例來說,"abc""def" 連接後就會是 "abcdef"

我們可以這樣用

int main() {
    std::string s1 = "abc";
    std::string s2 = "def";
    std::cout << "連接後:" << s1 + s2 << std::endl;

    std::string n1 = "123";
    std::string n2 = "456";
    std::cout << "連接後:" << n1 + n2 << std::endl;

    std::string message = "Hello" + ", " + "world!";
    std::cout << "連接後:" << message << std::endl;

    return 0;
}

這樣輸出結果就會是

連接後: abcdef
連接後: 123456
連接後: Hello, world!

值得注意的是,這裡的 "123" 依舊是一個字串而非數字。

因此 "123" + "456" 的結果並不是 "579"

長度檢視

有時候我們會想知道當前字串的長度,這時候我們就可以利用 .length() 或是 .size()

這兩個函數做的是一樣的事,所以可以隨意選擇。

之所以會有兩個函數主要是因為歷史和命名的原因。在其他的 C++ 容器比方說陣列,它也會有 .size() 這樣一個函數,以供我們得知存在容器內的元素數量。

另外,我們也可以使用 .empty() 來看看字串是否為空。

int main() {
    std::string s = "abc";
    std::cout << "字串長度: " << s.length() << std::endl;

    if (s.empty() == true) {
        std::cout << "字串為空" << std::endl;
    }
    else {
        std::cout << "字串不為空字串" << std::endl;
    }

    return 0;
}

這樣的輸出結果就會是

字串長度: 3
字串不為空字串

查找

有時候我們會想要查找字串中的子字串。什麼是子字串?舉個例子,"abc" 就是 "abcdef" 的子字串,"bcd" 也是。

這時候,.find() 就會非常好用。.find() 會回傳找到的第一個子字串的位置。如果沒有找到,那麼就會回傳 -1

比方說:

int main() {
    std::string sentence = "The brown fox jumps over the lazy dog";

    int found = sentence.find("lazy"); // 查找 "lazy" 的位置

    if (found != -1) {
        std::cout << "\"lazy\" found at position: " << found << std::endl;
    }
    else {
        std::cout << "\"lazy\" not found in the sentence." << std::endl;
    }

    return 0;
}

因為 "lazy" 出現在字串中,所以輸出結果會是

"lazy" found at position: 29

替代

有時候,我們會想要替換掉一個句子裡面的某的特定詞語,這時候,.replace() 就會非常好用!

.replace() 這個函數需要三個參數:

  • 開始替代的位置
  • 被替代字串的長度
  • 替代的字串
int main() {
    std::string sentence = "The string is amazing!";

    sentence.replace(sentence.find("amazing"), 7, "cool");
    std::cout << "新的句子: " << sentence << std::endl;

    return 0;
}

注意我們可以搭配前面提到的 .find() 來查看想要替換的字串的位置,這樣我們就不用一個一個數啦!

輸出結果就會是

新的句子: The string is cool!

提取

我們有時候會想要提取一個字串中特定的子字串,這時候我們就可以用 .substr()

.substr() 則需要兩個參數:

  • 開始位置
  • 提取長度
int main() {
    std::string sentence = "The string is amazing!";
    std::string sub_string = sentence.substr(sentence.find("amazing"), 7);

    std::cout << "提取的字串是: " << sub_string << std::endl;

    return 0;
}

一樣我們可以搭配 .find() 使用,讓我們更方便一些。

這樣的輸出就是

提取的字串是: amazing

總結

在這一章中我們了解到字串的功能,以及兩種不同的字串種類,包括 C-style string 和 std::string。同時我們也了解了一些常用的函數。

在不同的情況下,這兩種分別有各自的優點。在大多數情況下,std::string 還是比較常見,因此剛學程式語言的新手們可以學習 std::string 就好了。

但是對於想要更近一步了解底層運作邏輯的人,以及那些真正想把程式語言學好的人,我會說 C-style string 也是一個一定要學習的知識點!

那就這樣啦我們下一章見~