[C++ Primer Note2] string,vector和陣列
前文介紹的內建型別是C++語言直接定義的。這些型別,比如數字和字元,體現了大多數計算機硬體本身具備的能力。標準庫定義了另外一組具有更高階性質的型別,它們尚未直接實現到計算機硬體中。
- 通過 using namespace::name; 可以直接訪問名稱空間中的名字。位於標頭檔案的程式碼一般來說不應該使用using宣告,因為引用該標頭檔案的檔案很可能因此產生名字衝突 。
- 標準庫型別string 表示可變長的字元序列,使用string必須包含string標頭檔案,作為標準庫的一部分,string定義在名稱空間std中。
- string物件上的操作 :
- os<<s 將s寫到輸出流os中,返回os
- is>>s 從is中讀取字串賦給s,字串以空白符分隔,返回is,每次讀到空白符停止 (不讀空白符)
- getline(is,s) 從is中讀取一行賦給s(不存換行符但讀換行符 ),返回is,
- s.empty() 為空返回true
- s.size() 返回字元個數
- s[n] 返回第n個字元的引用
- s1+s2拼接
- s1=s2用s2的副本代替s1原來的字元
- <,<=,>,>=根據字典序比較
- 對於size函式來說,返回一個int或者unsigned都是合情合理的,但其實size函式返回的是一個string::size_type 型別的值。string類及其他大多數標準庫型別都定義了幾種配套的型別,這些配套型別體現了標準庫與機器無關 的特性,型別size_type即是其中的一種。在具體使用的時候,通過作用域操作符 來表示size_type是類string中定義的。可以肯定的是,該型別是一個無符號型別 。所以儘量避免將其與int混用。
- 標準庫允許將字元字面值和字串字面值轉換成string物件,所以可以互相拼接,但必須保證加法運算子 + 的兩側的運算物件至少有一個是string ,因為字串字面值與string是不同的型別 。
- 我們經常需要單獨處理string物件中的字元,在cctype 標頭檔案中定義了一組標準庫函式做這件事,比如判斷某個字元是否為字母,是否為大寫等等。
- C++標準庫中除了定義C++語言特有的功能外,也相容了C語言的標準庫。C語言的標頭檔案形如name.h,C++則將這些檔案命名為cname,這裡的c表示這是一個屬於C語言標準庫的標頭檔案。特別的,在名為cname的標頭檔案中定義的名字從屬於名稱空間std ,而定義在.h標頭檔案則不然。一般來說,C++程式應該使用 名為cname的標頭檔案而不是name.h形式的標頭檔案。
- C++11新標準提供了範圍for(range for) 語句,可以方便地遍歷給定序列中的每個元素
for (auto c : str) cout<< c <<endl;
- 如果想要改變string物件中字元的值,必須把迴圈變數定義成引用型別
- 除了範圍語句之外,還可以利用下標運算子[] 和迭代器 訪問string物件中的單個字元,下標運算子[ ]接收的輸入引數也是string:size_type 型別的值。
- 標準庫型別vector 表示物件的集合,其中所有物件的型別都相同。集合中的每個物件都有一個與之對應的索引,索引用於訪問物件。因為vector“容納著”其他物件,所以它也被稱作容器 。要使用vector,必須包含標頭檔案vector 。
- vector是一個類模板 ,由vector生成的型別必須指明vector中元素的型別,例如vector<int> 。
- vector能容納絕大多數型別的物件作為元素,但是因為引用不是物件,所以不能包含引用。
- 定義vector物件的常用方法:
- vector<T> v1 最常見的定義方式
- vector<T> v2(v1)
- vector<T> v2 = v1
- vector<T> v3(n,val)包含n個val
- vector<T> v4(n)包含n個執行了值初始化 的物件
- vector<T> v5{a,b,c...}v5包含了列表裡的元素
- vector<T> v5={a,b,c...}
- 列表初始化 是由C++11標準提供的
- 可以只提供vector容納的物件數量,此時元素初值由元素型別決定,且元素型別必須支援預設初始化。
- 除了所有元素的值都一樣的情況之外,定義一個空的vector物件更為高效 。
- 如果迴圈體內部包含有向vector物件新增元素的語言,則不能使用範圍for 迴圈。
- 常用vector操作:
- v.empty()
- v.size()
- v.push_back(t)
- v[n]
- v1==v2 當且僅當元素數量相同且對應位置元素值相同
- <,<=,>,>= 以字典順序進行比較
- 只有當元素的值可比較時,vector物件之間才可比較
-
vector的size()返回值是由vector定義的size_type型別。
是vector<int>::size_type而不是vector::size_type,因為前者才是型別 。 - vector物件(以及string物件)的下標運算子可用於訪問已存在 的元素,而不能 用於新增元素。
- 除了使用下標運算子[ ]來訪問string和vector的元素,還有一種更通用 的機制也可以實現相同的目的,這就是迭代器(iterator) 。類似於指標型別,迭代器也提供了對物件的間接訪問。
- 和指標不一樣的是,獲取迭代器不是使用取地址符,有迭代器的型別同時擁有返回迭代器的成員。比如,這些型別都擁有名為begin 和end 的成員,其中begin負責返回指向第一個元素 的迭代器,end返回尾元素的下一位置 (尾後迭代器 ,off-the-end iterator)。特殊情況下如果容器為空,則begin和end返回同一個迭代器 。
auto b=v.begin(), e = v.end()
- 標準容器迭代器的運算子:
- *iter返回迭代器iter所指元素的引用
- iter->mem解引用iter並獲取元素名為mem的成員,等價於(*iter).mem
- ++iter/--iter令iter指示容器的下一個/上一個元素
-
iter1==iter2/iter1!=iter2如果兩個迭代器指示同一個元素或同為尾後迭代器,則相等;否則不相等
26.就像不知道string和vector的size_type成員到底是什麼型別,一般來說我們也不必知道迭代器的精確型別。而實際上,那些擁有迭代器的標準庫型別使用iterator和const_iterator來表示迭代器型別:
vector<int>::iterator it;//能讀寫元素 vector<int>::const_iterator it2;//只讀
const_iterator和指向常量的指標 差不多,如果vector物件或string物件是一個常量,那麼只能用const_iterator。
- begin和end返回的具體型別由物件是否是常量決定,如果物件是常量,begin和end返回const_iterator;如果不是常量,返回iterator。但有時候如果只需要進行讀操作,我們可以通過C++11標準引入的兩個新函式cbegin 和cend 來獲取const_iterator。
- 凡是使用了迭代器的迴圈體,都不要 向迭代器所屬的容器新增元素。
- 陣列是一種複合型別,陣列的宣告形如a[d] ,a是陣列的名字,d是元素的個數。編譯的時候元素個數應該是已知的,必須是一個常量表達式 。
- 和內建型別的變數一樣,如果在函式內部定義了某種內建型別的陣列,那麼預設初始化會令陣列含有未定義 的值。
- 定義陣列的時候必須指定 陣列的型別,不允許用auto關鍵字。另外和vector一樣,陣列的元素應為物件,不存在引用 的陣列。
- 可以對陣列元素進行列表初始化 ,此時允許忽略陣列的元素個數。如果指明的元素個數大於列表元素,則剩下的元素被預設初始化 。
- 字元陣列有一種額外的初始化方式,可以用字串字面值 進行初始化。此時一定要注意到字串字面值的結尾處還有一個空字元 \0 ,這個空字元也會被拷貝到字元陣列中。
- 不能將陣列的內容拷貝給其他陣列作為初始值,也不能用陣列給其他陣列賦值。
- 要想理解陣列宣告的定義,最好的辦法是從陣列的名字開始按照由內而外,由右向左 的順序閱讀。
int *ptrs[10];//ptrs是含有10個整數指標的陣列 int (*Parray)[10]=&arr;//Parray指向一個含有10個整數的陣列 int (&arrRef)[10]=arr;//arrRef引用一個含有10個整數的陣列
- 與標準庫型別vector和string一樣,陣列也能使用範圍for 或運算子 來訪問。使用陣列下標時,通常將其定義為size_t 型別。(這是一種機器相關的無符號型別,被設計得足夠大以便能表示記憶體中任意物件的大小)在cstddef 標頭檔案中定義。
- 陣列有一個特性,很多用到陣列名字的地方,編譯器都會自動將其替換為一個指向陣列首元素 的指標。
string nums[]={"one","two"}; string *p=&nums[0];//p指向nums第一個元素 string *p2=nums;//等價於p2=&nums[0]
必須指出的是,當使用decltype關鍵字時轉換不會發生,返回的型別是一個10個整數構成的陣列。
- C++11新標準引入了兩個名為begin和end的函式,與容器中的兩個同名函式功能類似。
int ia[]={1,2,3,4,5,6}; int *beg=begin(ia); int *end=end(ia); while(beg!=end){ cout<<*beg<<endl ++beg; }
- 當對陣列使用下標運算子時,編譯器會自動執行轉換工作,實際上是對指向元素的指標 執行下標運算。只要指標指的是有效範圍(元素位置或者尾後位置),都可以 執行下標運算。
int *p=&ia[2]; //p指向索引為2的元素 int j=p[1];//p[1]等價於*(p+1),就是ia[3]表示的元素 itn k=p[-2];//p[-2]是ia[0]表示的元素
內建的下標運算子索引值不是 無符號型別,這點與string和vector不一樣。
- 字串字面值 是一種通用結構的例項,這種結構即是C++由C繼承而來的C風格字串 。C風格字串不是一種型別 ,而是為了表達和使用字串而形成的一種寫法。按此習慣書寫的字串存放在字元陣列並以空字元結束('\0') 。
- C標準庫String函式,它們定義在cstring 標頭檔案中:
- strlen(p)返回p的長度,不包括空字元
- strcmp(p1,p2)
- strcat(p1,p2)把p2附加到p1之後,返回p1
-
strcpy(p1,p2)把p2拷貝給p1,返回p1
傳入此類函式的指標必須指向以空字元作為結束 的陣列。
- 比較兩個C風格字串的方法和比較標準庫string物件的方法大相徑庭。比較string時,用的是普通的關係運算符。如果把這些運算子用在C風格字串上,實際比較的是指標而非字串本身 。
- 使用C標準庫函式需要估算陣列的大小避免空間不足,但這一點其實很難照顧周全。對大多數應用來說,使用標準庫string要比使用C風格字串更安全,更高效。
- 很多C++程式在標準庫之前就已經寫好了,它們肯定沒用string和vector。另外還存在著很多C++程式是與C語言的介面程式,也無法使用C++標準庫。現代C++程式不得不與那些充滿了陣列和C風格字串的程式碼銜接,為了使這一工作簡單易行,C++專門提供了一組功能。
- 前文介紹過允許使用字串字面值來初始化string物件,更一般的情況是,任何出現字串字面值的地方都可以用以空字元結束的字元陣列來替代 。但上述性質反過來就不成立,如果某處需要一個C風格字串,無法直接用string物件代替,為了完成該功能,string專門提供了一個名為c_str 的成員函式。
s="hello!"; char *str = s; //錯誤,不能用string物件初始化char* const char *str=s.c_str();//正確
函式的返回結果是一個指標,該指標指向一個以空字元結束的字元陣列,這個陣列的資料恰好與那個string物件的一樣。結果指標的型別是const char* 。
我們無法保證c_str函式返回的陣列一直有效。事實上,如果後續操作改變了s的值就可能讓之前返回的陣列失去效用。所以,最好將陣列拷貝一份
- 前文說過不允許使用一個數組為另一個內建型別的陣列賦初值,也不允許使用vector物件初始化陣列。但是,允許使用陣列來初始化vector物件,只需指明要拷貝區域的首元素地址 和尾後地址 就可以了。
int int_arr[]={0,1,2,3,4,5,6}; vector<int> ivec(begin(int_arr),end(int_arr));
- 儘量使用標準庫型別而非陣列
- 要使用範圍for語句處理多維陣列,除了最內層的迴圈外,其他所有迴圈的控制變數都應該是引用型別 。