C-風格字串
技術標籤:c++
C-風格字串
字串是儲存在記憶體的連續位元組中的一系列字元。C++處理字串的方式有兩種。第一種來自C語言,常被稱為C-風格字串(C-style string);另一種是基於string類庫的方法。
本文介紹C-風格字串。
儲存在連續位元組中的一系列字元意味著可以將字串儲存在char陣列中,其中每個字元都位於自己的陣列元素中。C-風格字串具有一種特殊的性質:以空字元(null character)結尾,空字元被寫作\0,起ASCII碼為0,用來標記字串的結尾。例如:
char dog[8] = {'b','e','a','u','x',' ','I','I'}; //不是字串 char cat[8] = {'f','a','t','e','s','s','a','\0'}; //是字串
- 這兩個都是char陣列,但只有第二個是字串。空字元對C-風格字串至關重要。C++有很多處理字串的函式,包括cout使用的那些函式,它們都逐個地處理字串中的字元,直到發現空字元為止。
如果使用cout顯示上面的dog陣列(它不是字串),cout將打印出陣列中的8個字母,並接著將記憶體中隨後的各個位元組解釋為要列印的字元,直到遇到空字元為止。
在cat陣列示例中,將陣列初始化為字串的方法要使用大量的單引號,且必須記住加上空字元,這種方法十分麻煩。有一種更好的將字元陣列初始化為字串的方法——只需使用一個用雙引號闊氣的字串即可,這種字串被稱為字串常量(string constant)或字串字面值(string literal),如下所示:
char bird[11] = "Mr. Cheeps"; //結尾自動包含\0
char fish[] = "Bubbles"; //讓編譯器自動計數
用雙引號括起的字串隱式地包含結尾的空字元,各種C++輸入工具輸入字串到char陣列中時,將自動加上結尾的空字元。
應確保陣列足夠大,能夠儲存字串中的所有字元——包括空字元。使用字串常量初始化字元陣列時,讓編譯器自動計算元素數目更為安全。
字串常量(使用雙引號)與字元常量(使用單引號)不能互換。
字元常量(如’S’)是字串編碼的簡寫表示,而”S”不是字元常量,它表示字元S和\0組成的字串。而且,”S”實際上表示的是字串所在的記憶體地址。
char shirt_size = 'S'; //合理,將83賦給shirt_size
char shirt_size = "S"; //型別不匹配,試圖將一個記憶體地址賦給shirt_size
1.拼接字串常量
有時候,字串很長,無法放到一行中。C++允許拼接字串常量,講講兩個用雙引號括起的字串合併為一個。事實上,任何兩個由空白(空格、tab和換行符)分隔的字元創常量都將自動拼接成一個。因此,西面所有的輸出語句都是等效的:
cout << "I'd give my right arm to be" " a great violinist.\n";
cout << "I'd give my right arm to be a great violinist.\n";
cout << "I'd give my right ar"
"m to be a great violinist.\n";
注意:拼接時不會在被連線的字串之間新增空格,第二個字串的第一個字元將緊跟在第一個字串的最後一個字元(不考慮\0)後面。第一個字串中的\0將被第二個字串的第一個字元取代。
2.在陣列中使用字串
程式4.2
#include<iostream>
#include<cstring> //使用strlen()函式
int main()
{
using namespace std;
const int Size = 15;
char name1[Size]; //空陣列
char name2[Size] = "C++owboy"; //初始化陣列
cout << "Howdy! I'm " << name2;
cout << "! What's your name?\n";
cin >> name1;
cout << "Well, " << name1 << ", your name has ";
cout << strlen(name1) << " letters and is stored\n";
cout << "in an array of " << sizeof(name1) << " bytes.\n";
cout << "Your initial is " << name1[0] << ".\n";
name2[3] = '\0'; //設定空字元
cout << "Here are the first 3 charaters of my name: ";
cout << name2 << endl;
cin.get();
cin.get();
return 0;
}
執行結果:
sizeof()返回整個陣列的長度:15位元組;
strlen()返回儲存在陣列中的字串的長度,而不是陣列本身的長度。strlen()只計算可見的字元,而不把空字元計算在內。
程式4.2將name2[3]設定為空字元,使得該字串在第三個字元後就結束。
3.字串輸入
程式4.2有一個缺陷,但該缺陷由於精心選擇輸入而被掩蓋掉了。
程式4.3
#include<iostream>
int main()
{
using namespace std;
const int ArSize = 20;
char name[ArSize];
char dessert[ArSize];
cout << "Enter your name:\n";
cin >> name;
cout << "Enter your favorite dessert:\n";
cin >> dessert;
cout << "I have some delicious " << dessert;
cout << " for you, " << name << ".\n";
cin.get();
cin.get();
return 0;
}
行結果:
在輸入完name(CSDN blog)後,還沒有輸入dessert,程式便將它顯示出來了,並立即顯示了最後一行。
cin如何確定字串輸入的完成呢?
由於不能用鍵盤輸入空字元,因此cin使用空白(空格、製表符和換行符)來確定字串的結束位置。
這意味著cin在獲取字元陣列輸入時只讀取一個單詞,讀取該單詞後,cin將該字串放到陣列中,並自動在結尾新增空字元。
因此,程式4.3中,cin把CSDN作為第一個字串,並將它放到name陣列中,並自動在結尾新增空字元。這把blog留在了輸入佇列中,當cin在輸入佇列中搜索dessert時,發現了blog,因此cin讀取blog並存在dessert中。
另一個問題是,輸入字串的長度可能比目標陣列長。
4. 每次讀取一行字串輸入
每次讀取一個單詞通常不是最好的選擇。要將整條短語而不是一個單詞作為字串輸入,需要採用面向行而不是面向單詞的方法。
istream中的類(如cin)提供了一些面向行的類成員函式:getline()和get()。這兩個函式都能讀取一行,直到到達換行符。不同的是,getline()將丟棄換行符,而get()將換行符保留在輸入序列中。
1. 面向行的輸入:getline()
使用cin.getline()函式有兩個引數,第一個引數是用來儲存輸入行的陣列名稱;第二個引數是要讀取的字元數,如果這個引數為20,則最多讀取19個字元,餘下的空間用於儲存在動在結尾處新增的空字元。getline()成員函式在讀取指定數目的字元或遇到換行符時停止讀取。
cin.getline(name,20); //將輸入讀入到一個包含20個元素的name陣列中
程式4.4
#include<iostream>
int main()
{
using namespace std;
const int ArSize = 20;
char name[ArSize];
char dessert[ArSize];
cout << "Enter your name:\n";
cin.getline(name, ArSize); //讀取一行
cout << "Enter your favorite dessert:\n";
cin.getline(dessert, Arsize);
cout << "I have some delicious " << dessert;
cout << " for you, " << name << ".\n";
cin.get();
cin.get();
return 0;
}
2. 面向行的輸入:get()
cin.get()與getline()類似,它們的引數相同。但到行尾時,get()不丟棄換行符,而是將換行符保留在輸入佇列中。假設我們連續兩次呼叫get():
cin.get(name, Arsize);
cin.get(dessert, Arsize); //第二次呼叫看到的第一個字元便是換行符,因此get()認為已到達行尾,
//而沒有發現任何可讀取的內容.
get()有另一種變體,使用不帶任何引數的cin.get()呼叫可讀取下一個字元(即使是換行符),因此可以用它來處理換行符,為讀取下一行做準備,如:
cin.get(name, Arsize);
cin.get();
cin.get(dessert, Arsize);
或者將兩個類成員函式合併起來:
cin,get(name, Arsize).get();
之所以可以這樣做,是因為cin.get(name,Arsize)返回一個cin物件,該物件隨後呼叫get()函式。同樣,下面的語句將連續輸入的兩行分別讀入到陣列name1和name2中,其效果與兩次呼叫cin.getline()相同:
cin.getline(name1, Arsize).getline(name2, Arsize);
程式4.5
#include<iostream>
int main()
{
using namespace std;
const int ArSize = 20;
char name[ArSize];
char dessert[ArSize];
cout << "Enter your name:\n";
cin.get(name, ArSize).get(); //讀取一行
cout << "Enter your favorite dessert:\n";
cin.get(dessert, ArSize).get();
cout << "I have some delicious " << dessert;
cout << " for you, " << name << ".\n";
cin.get();
cin.get();
return 0;
}
3. 空行和其他問題
當get()讀取空行後,將設定失效位(failbit),接下來的輸入將被阻斷,但可以用下面的命令來恢復輸入:
cin.clear();
如果輸入行包含的字元數比指定的多,則getline()和get()將把餘下的字元留在輸入佇列中。getline()還會設定失效位,並關閉後面的輸入。
5. 混合輸入字串和數字
混合輸入數字和麵向行的字串會導致問題。
程式4.6
#include<iostream>
int main()
{
using namespace std;
cout << "What year was your house built?\n";
int year;
cin >> year;
cout << "What is its street address?\n";
char address[80];
cin.getline(address, 80);
cout << "Year built: " << year << endl;
cout << "Address: " << address << endl;
cout << "Done!\n";
cin.get();
cin.get();
cin.get();
return 0;
}
執行結果:
使用者根本沒有輸入address的機會。問題在於,當cin讀取年份是,將回車鍵生成的換行符留在了輸入佇列中。後面的cin.getline()看到換行符後,將認為是一個空行,並將一個空字串賦給address陣列。
解決辦法是,在讀取地址之前先丟棄換行符。如:
cin>>year;
cin.get(); //or cin.get(ch);
(cin>>year).get(); //or (cin>>year).get(ch);
C++程式常用指標(而不是陣列)處理字串。