《隨筆十八》—— C++中的 “ 標準庫型別string ”
目錄
string 的 size ()操作 和 string::size_type 型別
使用string物件之前,要在程式碼頭部加上
#include <string> 和using namespace std::string;
(使用名稱空間中的名字之前應該用using 宣告引入該名字,但是注意using 宣告不要放在標頭檔案中,以避免使用了該標頭檔案的檔案中會產生名字衝突)。
初始化 string 物件的方式
string s1; // 預設建構函式,s1為空串 string s2(s1); //將s2初始化為s1的一個副本, 叫做直接初始化 string s2 = s1; //等價於string s2(s1),拷貝初始化 string s3("value"); //s3是字面值"value"的副本,除了字面值最後的那個空字元外 string s4(n,'c'); //把s4初始化為由連續n個字元c組成的串 string s5 = string(n,'c'); //拷貝初始化方式, 不建議這樣初始化
string 物件的操作
getline( is ,s7,'a'); 一個直到‘a’結束,其中任何字元包括'\n'都能夠讀入.
cin 和 getline 讀寫 string物件
int main()
{
string s;
cin >> s;
cout << "輸出為:" << s << endl;
system("pause");
return 0;
}
輸出結果為:
hello world
輸出為:hello
在執行讀取操作時, string物件會自動忽略開頭的空白(即空格符、換行符、製表符等)並從第一個真正的字元開始讀起,直到遇見下一處空白為止。
和內建型別的輸入輸出操作一樣, string物件的此類操作也是返回運算子左側的運算物件作為其結果。因此,多個輸入或者多個輸出可以連寫在一起。
有時我們希望能在最終得到的字串中保留輸入時的空白符,這時應該用getline函式代替原來的 >>運算子。getline函式的引數是一個輸入流和一個string物件, 函式從給定的輸入流中讀入內容, 直到遇到換行符為止(注意換行符也被讀進來了), 然後把所讀的內容存入到那個string物件中去 (注意不存換行符), getline只要一遇到換行符就結束讀取操作並返回結果, 哪怕輸入的一開始就是換行符也是如此。如果輸入真的一開始就是換行符,那麼所得的結果是個空string.
int main()
{
string str1;
string str2("the size of ");
string str3 = " hello world ";//空格不會忽略
str3 += str2;
str3.append("haha secessful");
cout << "輸出str3 的結果:"<< str3 << endl;
cout << "str2 的大小是 " << str2.size() << endl;
/*注意這裡取長度的str2.size(),和str2.length(),但是注意str2.size()返回的值並不是int型別,
事實表明size_type儲存的string長度是int所能儲存的兩倍*/
cout << "輸入一個字串:";
getline(cin, str1);
while (!str1.empty()) //返回一個bool值,空的話返回true,否則返回false。
{
for (string::size_type i = 0; i != str1.size(); ++i) //注意size_type型別
{
cout << str1[i];
}
cout << endl; break;
}
system("pause");
return 0;
}
輸出結果為:
輸出str3 的結果: hello world the size of haha secessful
str2 的大小是 12
輸入一個字串:huang cheng tao
huang cheng tao
注意: 觸發getline函式返回的那個換行符實際上被丟棄掉了, 得到的string物件中並不包含該換行符。
string 的 size ()操作 和 string::size_type 型別
該函式返回string物件中字元的個數,計算的時候每個字元都會包含在內,包括空格,string物件還包含 一個成員函式length(),同size()一樣,該函式返回string物件中的字元個數,因此在含此型別的表示式中不要使用int.(此時int會轉換為unsigned int,可能會帶來非預期想要的運算結果)。
● size() 函式返回的是一個 string::size_type 型別的值, 在具體使用的時候, 通過作用域操作符來表明 size_type 是在類 string 中定義的。
● string::size_type 型別 是一個無符號型別的值, 而且能足夠存放任何string 物件的大小, 所有用於存放string類 的 size 函式返回值的變數, 都應該是 string::size_type 型別 的。
● 允許編譯器通過 auto 或者 decltype 來推斷變數的型別:
auto len = line.size() // len的型別是string::size_type 型別
● 由於 size函式返回的是一個無符號整型數, 注意 : 如果在表示式中混用了帶符號數和無符號數將可能產生意想不到的結果,
假設n是一個具有負值的int, 則表示式 s. zize() < n的判斷結果幾乎肯定是 true, 這是因為負值n 會自動地轉換成一個比較大的無符號值。
建議 : 如果一條表示式中已經有了 size () 函式就不要再使用 int了, 這樣可以避免混用 int 和 unsigned 可能帶來的問題。
比較string物件
● 這些比較運算子逐一比較string物件中的字元,並且對大小寫敏感,也就是說,在比較時同一個字母的大寫形式和小寫形式是不同的。
比較的規則是:
- 如果兩個string物件的長度不同,而且較短string物件的每個字元都與較長string物件對應位置上的字元相同,就說較短string物件小於較長string物件。
- 如果兩個string物件在某些對應的位置上不一致,則string物件比較的結果其實是string物件中第一對相異字元比較的結果。
注意: 按照英文字母的順序排列, 字母靠前越小,靠後越大。 同一個字母 小寫字母比大寫字母大。
兩個string物件相加
● 兩個string物件相加得到一個新的string物件,其內容是把左側的運算物件與右則的運算物件串接而成。也就是說對string物件使用加法運算子(+)的結果是一個新的string物件,它所包含的字元由兩部分組成:
前半部分是加號左側string物件所含的字元、後半部分是加號右側string物件所含的字元。另外,複合賦值運算子(+=)負責把右側string物件的內容追加到左側string物件的後面。
int main()
{
string str = "huang";
string str1 = "cheng";
cout << "輸出結果為:" << (str + str1) << endl;
system("pause");
return 0;
}
輸出結果為:
輸出結果為:huangcheng
字面值和string物件相加
● 因為標準庫允許把字元字面值和字串字面值 轉換成string物件,所以在需要string物件的地方就可以用這兩種字面值來代替。
● 當把string物件和字元字面值及字串字面值混在一條語句中使用時,必須確保每個加法運算子 的兩側運算物件至少有一個string物件:
int main()
{
string str = "huang";
string str1 = "cheng";
string str2 = str + "," + str1 + '\n';
string str3 = str1 + ",";
string str4 = "hello" + ","; //錯誤
string str5 = str1 + "," + "hello";
string str6 = "huang" + "," + str1; //錯誤,不能把字面值直接相加
system("pause");
return 0;
}
● 注意:c++語言中的字串字面值並不是標準庫型別 string 的物件, 切記, 字串字面值與string是不同的型別 。
處理string物件中的字元
● 我們經常需要單獨處理string物件中的字元,比如檢查一個string物件是否包含空白, 或者把string物件中的字母改成小寫,再或者檢視某個特定的字元是否出現等。
這類處理的一個關鍵問題是如何獲取字元本身。有時需要處理string物件中的每一個字元,另外一些時候則只需處理某個特定的字元,還有些時候遇到某個條件處理就要停下來。
● 在標頭檔案 cctype 中定義了一組標準庫函式處理上述的工作:
處理每個字元? 使用基於範圍的for語句
int main()
{
string s("huang cheng tao!!!!");
decltype(s.size()) punct_cnt = 0; //punct_cnt 的型別就是s.size()的返回型別,也就是 string::size_type
// 統計s中標點符號的個數
for (auto c : s)
{
if (ispunct(c)) //對於s中的每個字元,如果該字元是標點符號,計數值加1
++punct_cnt;
}
cout << "s中的標點符號的個數為:" << punct_cnt << endl;
system("pause");
return 0;
}
輸出結果為:
s中的標點符號的個數為:3
使用範圍for語句改變字串中的字元
● 如果想要改變 string 物件中字元的值, 必須把迴圈變數定義成引用型別。當使用引用作為迴圈控制變數時,這個變數實際上被依次繫結到了序列的每個元素上。 使用這個引用,我們就能改變它繫結的字元。
int main()
{
string s("huang cheng tao!!!");
// 把每一個字元轉換成大寫形式
for (auto &c : s)
{
c = toupper(c);
}
cout << "s中的字元轉換成大寫形式為" << s << endl;
system("pause");
return 0;
}
輸出結果為:
s中的字元轉換成大寫形式為HUANG CHENG TAO!!!
每次迭代時, 變數c引用string物件s的下一個字元,賦值給c也就是在改變s中對應字元的值。因此當執行下面的語句時,
c = toupper(c); //c是一個引用,因此賦值語句將改變s中字元的值
實際上改變了c繫結的字元的值。整個迴圈結束後, str中的所有字元都變成了大寫形式。
使用下標和迭代器訪問string物件
● 要想訪問string物件中的單個字元有兩種方式: 一種是 使用下標, 另一種是使用迭代器。
● 下標運算子 [ ] 接收的輸入引數是 string::sizee_type 型別的值,這個引數表示要訪問的字元的位置; 返回值是該位置上字元的引用。
● 注意 : s[s.size()-1] 是最後一個字元。
● 注意 : string 物件的下標值必須 大於等於0而小於 s.size(), 使用超過此範圍的下標將引發不可預知的結果, 以此推斷, 使用下標訪問空string 也會引發不可預知的結果。
● 注意:下標的值稱作“索引”或者 “ 下標”, 任何表示式只要它的值是一個整型值就能作為索引, 不過, 如果某個索引是帶符號型別的值將被自動轉換成由 string::size_type 表達的無符號型別的值。
● 注意: 不管什麼時候只要對string物件使用了下標, 都要確認在那個位置上確實有值。
if(!s.empty()) // 確保確實有字元需要輸出 cout<<s[0]<<endl;
● 只要字串不是常量, 就能為下標運算子返回的字元賦新值。
int main()
{
string s("huang cheng tao!!!");
if (!s.empty())
{
s[0] = toupper(s[0]); // 為s 的第一個字元賦一個新值
}
cout << "輸出s的值:" << s << endl;
system("pause");
return 0;
}
輸出結果為:
輸出s的值:Huang cheng tao!!!
● 另一個列子是把s中的第一個單詞該成大寫形式:
int main()
{
string s("huang cheng tao!!!");
for (decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index)
{
s[index] = toupper(s[index]); //每次迴圈把當前的字元改成大寫形式
}
cout << "輸出s的值:" << s << endl;
system("pause");
return 0;
}
輸出結果為:
輸出s的值:HUANG cheng tao!!!
● 其實,也能通過計算得到某個下標值,然後直接獲取對應位置的字元,並不是每次都得從前往後依次訪問。