天天看點

C++程式設計:複合資料類型—字元串

作者:尚矽谷教育

字元串我們并不陌生。之前已經介紹過,一串字元連在一起就是一個“字元串”,比如用雙引号引起來的“Hello World!”就是一個字元串字面值。

字元串其實就是所謂的“純文字”,就是各種文字、數字、符号在一起表達的一串資訊;是以字元串就是C++中用來表達和處理文本資訊的資料類型。

1. 标準庫類型string

C++的标準庫中,提供了一種用來表示字元串的資料類型string,這種類型能夠表示長度可變的字元序列。和vector類似,string類型也定義在命名空間std中,使用它必須包含string頭檔案。

#include<string>

using namespace std;

(1)定義和初始化string

我們已經接觸過C++中幾種不同的初始化方式,string也是一個标準庫類型,它的初始化與vector非常相似。

// 預設初始化,空字元串

string s1;

// 用另一個字元串變量,做拷貝初始化

string s2 = s1;

// 用一個字元串字面值,做拷貝初始化

string s3 = "Hello World!";

// 用一個字元串字面值,做直接初始化

string s4("hello world");

// 定義字元和重複的次數,做直接初始化,得到 hhhhhhhh

string s5(8, 'h');

C++程式設計:複合資料類型—字元串

初始化方式主要有:

1. 預設初始化,得到的就是一個空字元串;

2. 拷貝初始化,用指派運算符(等号“=”)表示;可以使用另一個string對象,也可以使用字元串字面值常量;

3. 直接初始化,用括号表示;可以在括号中傳入一個字元串,也可以傳入字元和重複的次數

可以發現,字元串也可以看做資料元素的集合;它裡面的元素,就是字元。

(2)處理字元串中的字元

通過初始化已經可以看出,string的行為與vector非常類似。string同樣也可以通過下标運算符通路内部的每個字元。字元的“索引”,就是在字元串中的位置。

string str = "hello world";

// 擷取第3個字元

cout << "str[2] = " << str[2] << endl;

// 将第1個字元改為'H'

str[0] = 'H';

// 将最後一個字元改為'D'

str[str.size() - 1] = 'D';

cout << "str = " << str << endl;

字元串内字元的通路,跟vector内元素的通路類似,需要注意:

  • string内字元的索引,也是從0開始;
  • string同樣有一個成員函數size,可以擷取字元串的長度;
  • 索引最大值為 (字元串長度 - 1),不能越界通路;如果直接越界通路并指派,有可能導緻非常嚴重的後果,出現安全問題;
  • 如果希望周遊字元串的元素,也可以使用普通for循環和範圍for循環,依次擷取每個字元

比如,我們可以考慮周遊所有字元,将小寫字母換成大寫:

// 周遊字元串中字元,将小寫字母變成大寫

for (int i = 0; i < str.size(); i++)

{

str[i] = toupper(str[i]);

}

這裡又調用了string的一個函數toupper,可以把傳入的字元轉換成大寫并傳回。

(3)字元串相加

string本身的長度是不定的,可以通過“相加”的方式擴充一個字元串。

// 字元串相加

string str1 = "hello", str2("world");

string str3 = str1 + str2; // str3 = "helloworld"

string str4 = str1 + ", " + str2 + "!"; // str4 = "hello, world!"

//string str5 = "hello, " + "world!"; // 錯誤,不能将兩個字元串字面值相加

需要注意:

  • 字元串相加使用加号“+”來表示,這是算術運算符“+”的運算符重載,含義是“字元串拼接”;
  • 兩個string對象,可以直接進行字元串相加;結果是将兩個字元串拼接在一起,得到一個新的string對象傳回;
  • 一個string對象和一個字元串字面值常量,可以進行字元串相加,同樣是得到一個拼接後的string對象傳回;
  • 兩個字元串字面值常量,不能相加;
  • 多個string對象和多個字元串字面值常量,可以連續相加;前提是按照左結合律,每次相加必須保證至少有一個string對象;

(4)比較字元串

string類還提供幾種用來做字元串比較的運算符,“==”和“!=”用來判斷兩個字元串是否完全一樣;而“<”“>”“<=”“>=”則用來比較兩個字元串的大小。這些都是關系型運算符的重載。

str1 = "hello";

str2 = "hello world!";

str3 = "hehehe";

str1 == str2; // false

str1 < str2; // true

str1 >= str3; // true

字元串比較的規則為:

  • 如果兩個字元串長度相同,每個位置包含的字元也都相同,那麼兩者“相等”;否則“不相等”;
  • 如果兩個字元串長度不同,而較短的字元串每個字元都跟較長字元串對應位置字元相同,那麼較短字元串“小于”較長字元串;
  • 如果兩個字元串在某一位置上開始不同,那麼就比較這兩個字元的ASCII碼,比較結果就代表兩個字元串的大小關系

2. 字元數組(C風格字元串)

通過對string的介紹可以發現,字元串就是一串字元的集合,本質上其實就是一個“字元的數組”。

在C語言中,确實是用char[]類型來表示字元串的;不過為了區分純粹的“字元數組”和“字元串”,C語言規定:字元串必須以空字元結束。空字元的ASCII碼為0,專門用來标記字元串的結尾,在程式中寫作’\0’。

// str1沒有結尾空字元,并不是一個字元串

char str1[5] = {'h','e','l','l','o'};

// str2是一個字元串

char str2[6] = { 'h','e','l','l','o','\0'};

cout << "str1 = " << str1 << endl;

cout << "str2 = " << str2 << endl;

如果每次用到字元串都要這樣定義,對程式員來說就非常不友好了。是以字元串可以用另一種更友善的形式定義出來,那就是使用雙引号:

char str3[] = "hello";

//char str3[5] = "hello"; // 錯誤,"hello"的長度為6

cout << "str3 = " << str3 << endl;

這就是我們所熟悉的字元串“字面值常量”。這裡需要注意的是,我們不需要再考慮末尾的空字元,編譯器會自動幫我們補全;但真實的字元串的長度,依然要包含空字元,是以上面的字元串“hello”長度不是5、而是6。

是以,C++中的字元串字面值常量,為了相容C依然定義為字元數組(char[])類型,這和string是兩種不同類型;兩者的差別,跟數組和vector的差別類似,char[]是更底層的類型。一般情況下,使用string會帶來更多友善,也會更加安全。

3. 讀取輸入的字元串

程式中往往需要一些互動操作,如果想擷取從鍵盤輸入的字元串,可以使用多種方法。

(1) 使用輸入操作符讀取單詞

标準庫中提供了iostream,可以使用内置的cin對象,調用重載的輸入操作符>>來讀取鍵盤輸入。

string str;

// 讀取鍵盤輸入,遇到空白符停止

cin >> str;

cout << str;

這種方式的特點是:忽略開始的空白符,遇到下一個空白符(空格、回車、制表等)就會停止。是以如果我們輸入“hello world”,那麼讀取給str的隻有“hello”:這相當于讀取了一個“單詞”。

剩下的内容“world”其實也沒有丢,而是儲存在了輸入流的“輸入隊列”裡。如果我們想讀取更多的輸入資訊,就需要使用更多的string對象來擷取:

string str1, str2;

cin >> str1 >> str2;

cout << str1 << str2 << endl;

這樣,如果輸入“hello world”,就可以輸出“helloworld”。

(2)使用getline讀取一行

如果希望直接讀取一整行輸入資訊,可以使用getline函數來替代輸入操作符。

string str3;

getline(cin, str3);

cout << "str3 = " << str3 << endl;

getline函數有兩個參數:一個是輸入流對象cin,另一個是儲存字元串的string對象;它會一直讀取輸入流中的内容,直到遇到換行符為止,然後把所有内容儲存到string對象中。是以現在可以完整讀取一整行資訊了。

(3)使用get讀取字元

還有一種方法,是調用cin的get函數讀取一個字元。

char ch;

ch = cin.get(); // 将捕獲到的字元指派給ch

cin.get(ch); // 直接将ch作為參數傳給get

有兩種方式:

  • 調用cin.get()函數,不傳參數,得到一個字元賦給char類型變量;
  • 将char類型變量作為參數傳入,将捕獲的字元指派給它,傳回的是istream對象

get函數還可以讀取一行内容。這種方式跟getline很相似,也可以讀取一整行内容,以回車結束。主要差別在于,它需要把資訊儲存在一個char[]類型的字元數組中,調用的是cin的成員函數:

// get讀取一整行

char str4[20];

cin.get(str4, 20);

cout << "str4 = " << str4 << endl;

// get讀取一個字元

cin.get(); // 先讀取之前留下的回車符

cin.get(); // 再等待下一次輸入

get函數同樣需要傳入兩個參數:一個是儲存資訊的字元數組,另一個是字元數組的長度。

這裡還要注意跟getline的另一個差別:鍵盤輸入總是以回車作為結束的;getline會把最後的回車符丢棄,而get會将回車符保留在輸入隊列中。

這樣的效果是,下次再調用get試圖讀取一行資料時,會因為直接讀到了回車符而傳回空行。這就需要再次調用get函數,捕獲下一個字元:

cin.get(); // 先讀取之前留下的回車符

cin.get(); // 再等待下一次輸入

這樣就可以将之前的回車符捕獲,進而為讀取下一行做好準備。這也就解釋了之前為什麼要寫兩個cin.get():第一個用來處理之前保留在輸入隊列的回車符;第二個用來等待下一次輸入,讓視窗保持開啟狀态。

4. 簡單讀寫檔案

實際應用中,我們往往會遇到讀寫檔案的需求,這也是一種IO操作,整體用法跟指令行的輸入輸出非常類似。

C++的IO庫中提供了專門用于檔案輸入的ifstream類和用于檔案輸出的ofstream類,要使用它們需要引入頭檔案fstream。ifstream用于讀取檔案内容,跟istream的用法類似;也可以通過輸入操作符>>來讀“單詞”(空格分隔),通過getline函數來讀取一行,通過get函數來讀取一個字元:

ifstream input("input.txt");

// 逐詞讀取

string word;

while (input >> word)

cout << word << endl;

// 逐行讀取

string line;

while (getline(input, line))

cout << line << endl;

// 逐字元讀取

char ch;

while (input.get(ch))

cout << ch << endl;

類似地,寫入檔案也可以通過使用輸出運算符 << 來實作:

ofstream output("output.txt");

output << word << endl;

繼續閱讀