1. 程式人生 > 其它 >C-風格字串

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++程式常用指標(而不是陣列)處理字串。