引言
- 除了第2章介紹的內置類型之外,C++語言還定義了 -個內容豐富的抽象數據類型庫。其中,string和 vector是兩種最重耍的標準庫類型,前者支持可變長字符串,后者則 表示可變長的集合。還有…種標準庫類型是迭代器,它是string和vector的配套類型,常被用于訪問string中的字符或vector中的元素。
- 內置數組是一種更基礎的類型,string和vector都是對它的某種抽象。本章將分別介紹數組以及標準摩類型string和 vector.
- 第2 章介紹的內置類型是由C++語言直接定義的。這些類型,比如數字和字符,體現了大多數計算機硬件本身具備的能力。標準庫定義了另外一組具有更高級性質的類型,它們尚未直接實現到計算機硬件中。
- 本章將介紹兩種最重要的標準庫類型:string和 vector。string表示可變長的 字符序列,vector存放的是某種給定類型對象的可變長序列。本章還將介紹內置數組類型,和其他內置類型一樣,數組的實現與硬件密切相關。因此相較于標準庫類型string和vector,數組在靈活性上稍顯不足。 在開始介紹標準庫類型之前,先來學習一種訪問庫中名字的簡單方法
3.1命名空間的using聲明
- 目前為止,我們用到的庫函數基本上都屬于命名空間std,而程序也顯式地將這一點標示了出來。例如,std::cin表示從標準輸入中讀取內容。此處使用作用域操作符(::)(參見1.2節,第7頁)的含義是:編譯器應從操作符左側名字所示的作用域中尋找右側那個名字。因此,std::cin的意思就是要使用命名空間std中的名字cin。上面的方法顯得比較煩瑣,然而幸運的是,通過更簡單的途徑也能使用到命名空間中的成員。本節將學習其中一種最安全的方法,也就是使用using聲明(usingdeclaration).18.2.2節(第702頁)會介紹另一種方法。
- 有了using聲明就無須專門的前綴(形如命名空間::)也能使用所需的名字了。using聲明具有如下的形式:
- using namespace::name;? ?一旦聲明了上述語句,就可以直接訪問命名空間中的名字
每個名字都需要獨立的using聲明
- 按照規定,每個using聲明引入命名空間中的一個成員。例如,可以把要用到的標準庫中的名字都以using聲明的形式表示出來,重寫1.2節 (第 5 頁)的程序如下:
- 在上述程序中,一開始就有對cin、cout和 endl的 using聲明,這意味著我們不用再 添加std::形式的前綴就能直接使用它們。C++語言的形式比較自由,因此既可以一行只放一條using聲明語句,也可以一行放上多條。不過要注意,用到的每個名字都必須有自己的聲明語句,而且每句話都得以分號結束。
頭文件不應包含using聲明
- 位于頭文件的代碼(參見2.6.3節,第67頁)一般來說不應該使用using聲明。這是因為頭文件的內容會拷貝到所有引用它的文件中去,如果頭文件里有某個using聲明,那么每個使用了該頭文件的文件就都會有這個聲明。對于某些程序來說,由于不經意間包含了一些名字,反而可能產生始料未及的名字沖突。
一點注意事項
- 經本節所述,后面的所有例子將假設,但凡用到的標準庫中的名字都已經使用using語句聲明過了。例如,我們將在代碼中直接使用cin,而不再使用std::cin?為了讓書中的代碼盡量簡潔,今后將不會再把所有using聲明和#include指令一一標出。附錄A中的表A.1(第766頁)列出了本書涉及的所有標準庫中的名字及對應的頭文件。
3.2標準庫類型string
- 標準庫類型string表示可變長的字符序列,使用string類型必須首先包含<~84~]string頭文件。作為標準庫的一部分,string定義在命名空間std中。接下來的示例都假定已包含了下述代碼:
- #include <string>? ? using std::string;
- 本節描述最常用的string操作,9.5節 (第320頁)還將介紹另外一些。?
3 .2 .1 定義和初始化string對象
- 如何初始化類的對象是由類本身決定的。一個類可以定義很多種初始化對象的方式,只不過這些方式之間必須有所區別:或者是初始值的數量不同,或者是初始值的類型不同。
表 3.1列出了初始化string對象最常用的一些方式,下面是幾個例子:
- 可以通過默認的方式(參見2.2.1節,第 40頁)初始化一個string對象,這樣就會得到 —個空的string,也就是說,該string對象中沒有任何字符。如果提供了一個字符串字面值(參見2.1.3節,第 36頁),則該字面值中除了最后那個空字符外其他所有的字符都被拷貝到新創建的string對象中去。如果提供的是一個數字和一個字符,則string對象的內容是給定字符連續重復若干次后得到的序列
直接初始化和拷貝初始化
- 由2.2.1節(第39頁)的學習可知,C++語言有幾種不同的初始化方式,通過string我們可以清楚地看到在這些初始化方式之間到底有什么區別和聯系。如果使用等號(=)初始化-個變量,實際上執行的是拷貝初始化(copyinitialization),編譯器把等號右側的初始值拷貝到新創建的對象中去。與之相反,如果不使用等號,則執行的是直接初始化(directinitialization)。
- 等號使用拷貝初始化;無等號使用直接初始化
- 當初始值只有一個時,使用直接初始化或拷貝初始化都行。如果像上面的S4那樣初始化要用到的值有多個,一般來說只能使用直接初始化的方式:
3.2.2string對象上的操作
- 一個類除了要規定初始化其對象的方式外,還要定義對象上所能執行的操作。其中,類既能定義通過函數名調用的操作,就像Sales_item類的isbn函數那樣(參見1.5.2節,第20頁),也能定義<<、+等各種運算符在該類知象上的新含義。表3.2中列舉了string的大多數操作。
讀寫string對象
- 第 1章曾經介紹過,使用標準庫中的iostream來讀寫int, double等內置類型的 值。同樣,也可以使用IO操作符讀寫string對象:
- cin<<s;//將string對象讀入s,遇到空白停止
- cout<<s<<endl;//輸出s
使用getline讀取一整行
- 有時我們希望能在最終得到的字符串中保留輸入時的空白符,這時應該用getline函數代替原來的>>運算符。getline函數的參數是一個輸入流和一個string對象,函數從給定的輸入流中讀入內容,直到遇到換行符為止(注意換行符也被讀進來了),然后把所讀的內容存入到那個string對象中去(注意不存換行符)。getline只要一遇到換行符就結束讀取操作并返回結果,哪怕輸入的一開始就是換行符也是如此。如果輸入真的一開始就是換行符,那么所得的結果是個空string。和輸入運算符一樣,getline也會返回它的流參數。因此既然輸入運算符能作為判斷的條件(參見1.4.3節,第13頁),我們也能用getline的結果作為條件。例如,可以通過改寫之前的程序讓它一次輸出一整行,而不再是每行輸出一個詞了:
- 因為line中不包含換行符,所以我們手動地加上換行操作符。和往常一樣,使用endl 結束當前行并刷新顯示緩沖區。
- 觸發getline函數返回的那個換行符實際上被丟棄掉了,得到的string對象中并不包含該換行符
string 的 empty 和 size 操作
- 顧名思義,empty函數根據string對象是否為空返回一個對應的布爾值(參見第2.1節,30頁)。和Sales_item類(參見1.5.2節,第20頁)的isbn成員一樣,empty也是string的一個成員函數。調用該函數的方法很簡單,只要使用點操作符指明是哪個對象執行了empty函數就可以了。
- 在上面的程序中,if語句的條件部分使用了邏輯非運算符(!),它返回與其運算對象相反的結果。此例中,如果str不為空則返回真。size函數返回string對象的長度(即string對象中字符的個數),可以使用size函數只輸出長度超過80個字符的行:
string::size_type類型
- 對于size函數來說,返回一個int或者如前面2.1.1節(第31頁)所述的那樣返回-個unsigned似乎都是合情合理的。但其實size函數返回的是一個string::size_type類型的值,下面就對這種新的類型稍作解釋。string類及其他大多數標準庫類型都定義了幾種配套的類型。這些配套類型體現了標準庫類型與機器無關的特性,類型size_type即是其中的一種。在具體使用的時候,通過作用域操作符來表明名字size_type是在類string中定義的。盡管我們不太清楚string::size_type類型的細節,但有一點是肯定的:它是一個無符號類型的值(參見2.1.1節,第30員)而且能足夠存放下任何string對象的大小。
- 所有用于存放string類的size函數返回值的變量,都應該是string::size_type類型的。
- 過去,string::size_type這種類型有點兒神秘,不太容易理解和使用。在C++11新標準中,允許編譯器通過auto或者decltype(參見2.5.2節,第61頁)來推斷變量的類型:
- auto len=line.size();//len的類型是string::size_type
- 由于size函數返回的是一個無符號整型數,因此切記,如果在表達式中混用了帶符號數和無符號數將可能產生意想不到的結果(參見2.1.2節,第33頁)。例如,假設n是一個具有負值的int,則表達式s.size()<n的判斷結果幾乎肯定是true。這是因為負值n會自動地轉換成一個比較大的無符號值。
- 如果一條表達式中已經有了size()函數就不要再使用int了,這樣可以避免丁中混用int和unsigned可能帶來的問題
比較string對象
- string類定義了幾種用于比較字符串的運算符。這些比較運算符逐一比較string對象中的字符,并且對大小寫敏感,也就是說,在比較時同一個字母的大寫形式和小寫形式是不同的。
- 相等性運算符(==和!=)分別檢驗兩個string對象相等或不相等,string對象相等意味著它們的長度相同而且所包含的字符也全都相同。關系運算符<、<=、>、>=分別檢驗一個string對象是否小于、小于等于、大于、大于等于另外一個string對象。上述這些運算符都依照(大小寫敏感的)字典順序:
- 1.如果兩個string對象的長度不同,而且較短string對象的每個字符都與較長string對象對應位置上的字符相同,就說較短string對象小于較長string對象。
- 2,如果兩個string對象在某些對應的位置上不一致,則string對象比較的結果其實是string對象中第一對相異字符比較的結果
兩個string對象相加
- 兩個string對象相加得到一個新的string對象,其內容是把左側的運算對象與右側的運算對象串接而成。也就是說,對string對象使用加法運算符(+)的結果是一個新的string對象,它所包含的字符由兩部分組成:前半部分是加號左側string對象所含的字符、后半部分是加號右側string對象所含的字符。另外,復合賦值運算符(+=)(參見1.4.1節,第10頁)負責把右側string對象的內容追加到左側string對象的后面:
字面值和string對象相加
- 如2.1.2節(第33頁)所講的,即使一種類型并非所需,我們也可以使用它,不過前提是該種類型可以自動轉換成所需的類型。因為標準庫允許把字符字面值和字符串字面值(參見2.1.3節,第36頁)轉換成string對象,所以在需要string對象的地方就可以使用這兩種字面值來替代。利用這一點將之前的程序改寫為如下形式:
- string si = nhelloM , s2 = "world"; // 在 si 和 s2 中都沒有標點符號
- string s3 = si + ", " + s2 + f \nf ;
- 當把string對象和字符字面值及字符串字面值混在一條語句中使用時,必須確保每個加 法運算符( + ) 的兩側的運算對象至少有一個是string:
- 用雙引號包含起來的叫做字符串字面值 / 字面值,等號兩邊至少一個需要 是string?
- s4和 s5初始化時只用到了一個加法運算符,因此很容易判斷是否合法。s6的初始化形式之前沒有出現過,但其實它的工作機理和連續輸入連續輸出(參見1.2節,第 6 頁)是一樣的,可以用如下的形式分組:
- string s6 = (si + ", ") + "world";
- 其中子表達式sl + "/的結果是一個string對象,它同時作為第二個加法運算符的左側運算對象,因此上述語句和下面的兩個語句是等價的:
- 很容易看到,括號內的子表達式試圖把兩個字符串字面值加在一起,而編譯器根本沒法做到這一點,所以這條語句是錯誤的。
- 因為某些歷史原因,也為了與C兼容,所以C++語言中的字符串字面值并不是標準庫類型string的對象切記,字符串字面值與string是不同的類型.
3.2.3處理string對象中的字符
- 我們經常需要單獨處理string對象中的字符,比如檢查一個string對象是否包含空白,或者把string對象中的字母改成小寫,再或者查看某個特定的字符是否出現等。這類處理的一個關鍵問題是如何獲取字符本身。有時需要處理string對象中的每一個字符,另外一些時候則只需處理某個特定的字符,還有些時候遇到某個條件處理就要停下來。以往的經驗告訴我們,處理這些情況常常要涉及語言和庫的很多方面。
- 另一個關鍵問題是要知道能改變某個字符的特性。在 cctype頭文件中定義了一組標準庫函數處理這部分工作,表 3.3列出了主要的函數名及其含義。
建議:使用C++版本的C標準庫頭文件
- C++標準庫中除了定義C++語言特有的功能外,也兼容了C語言的標準庫。C語言的頭文件形如name.h,C++則將這些文件命名為cname。也就是去掉了.h后綴,而在文件名mme之前添加了字母c,這里的c表示這是一個屬于C語言標準庫的頭文件。因此,cctype頭文件和ctype.h頭文件的內容是一樣的,只不過從命名規范上來講更符合C++語言的要求。特別的,在名為cname頭文件中定義的名字從屬于命名空間std,而定義在名為.h的頭文件中的則不然。一般來說,C++程序應該使用名cname的頭文件而不使用name.h的形式,標準庫中的名字總能在命名空間std中找到。如果使用.h形式的頭文件,程序員就不得不時刻牢記哪些是從C語言那兒繼承過來的,哪些又是C++語言所獨有的。
使用范圍for語句改變字符串中的字符
- 如果想要改變string對象中字符的值,必須把循環變量定義成引用類型(參見2.3.1節,第45頁)。記住,所謂引用只是給定對象的一個別名,因此當使用引用作為循環控制變量時,這個變量實際上被依次綁定到了序列的每個元素上。使用這個引用,我們就能改變它綁定的字符。
- 新的例子不再是統計標點符號的個數了,假設我們想要把字符串改寫為大寫字母的形式。為了做到這一點可以使用標準庫函數toupper,該函數接收一個字符,然后輸出其對應的大寫形式。這樣,為了把整個string對象轉換成大寫,只要對其中的每個字符調用toupper函數并將結果再賦給原字符就可以了:
- 每次迭代時,變量c 引用string對象s 的下一個字符,賦值給c 也就是在改變s 中對應字符的值。因此當執行下面的語句時,
- c = toupper (c) ; // c 是一個引用,因此賦值語句將改變s 中字符的值
- 實際上改變了 c 綁定的字符的值。整個循環結束后,str中的所有字符都變成了大寫形式。