##C++ Primer 學習筆記(第三章:字符串、向量和數組)git
[TOC]express
###3.1 命名空間的using
聲明數組
using
聲明語句能夠一行放多條。安全
位於頭文件的代碼,通常來講不該該使用using
聲明。由於其內容會拷貝到每一個使用該頭文件的文件中,可能會產生名字衝突。函數
###3.2 標準庫類型string
性能
使用string
類型必須首先包含string
頭文件,做爲標準庫的一部分,string
定義在命名空間std
中。學習
定義和初始化string
對象spa
string s1;//默認初始化,空串 string s2(s1);//s2是s1副本 string s2 = s1;//和上一條等價 string s3("value");//s3是字面值副本,除了字面值最後那個空字符外,直接初始化 string s3 = "value";//與上一條相同,拷貝初始化 string s4(n,'c');//把s4初始化爲由連續n個字符c組成的串,直接初始化
string
上的操做os<<s;//輸出流 is>>s;//讀取字符賦給s,字符串以空白分隔,返回is(自動忽略空白) getline(is,s);//從is中讀取一行賦給s,返回is s.empty();//爲空返回true s.size();//字符個數 s[n];//返回第n個字符的引用 s1 + s2;//鏈接 s1 = s2;//用s2的副本代替s1 s1 == s2;s1 != s2;//相等性判斷,對字母大小寫敏感
<, <=, >, >+;字典序比較,對大小寫敏感指針
getline(is,s)
參數是一個輸入流和一個string
對象,從給定的輸入流讀入,直到遇到換行符爲止,並把所讀內容(不含換行符)存入到string
對象中。code
string::size_type
類型 size
函數返回的就是一個string::size_type
類型的值,是一個無符號類型 可使用auto
或者decltype
來推斷變量類型:
auto len = line.size();
因爲無符號數和帶符號數混用會產生其餘問題,若是一個表達式中有size
就不要再用int
了。
比較string
對象 依照字典順序: (1)若是公共部份內容相同,則較短的小於較長的。 (2)若是存在不一致,則以第一對相異字符比較的結果爲準。
在使用相加運算符時,必須確保每一個加法運算符(+
)的兩側運算對象至少一個是string
:
string s6 = s1 + ", " + "world";//正確 string s7 = "hello" + ", " + s2;//錯誤:不能把字面值直接相加
切記:字符字面值('\n'
)和字符串字面值("word"
)string
是不一樣類型,能夠用來初始化或相加的緣由是標準庫容許把字符字面值和字符串字面值轉換成string
對象。
string
對象中的字符,在cctype
頭文件中定義了一組標準庫函數。isalnum(c);//c是字母或數字 isalpha(c);//c是字母 iscntrl(c);//c是控制字符 isdigit(c);//c是數字 isgraph(c);//c不是空格但可打印 islower(c);//c爲小寫字母 isprint(c);//c是可打印字符(即c是空格或c具備可視形式) ispunct(c);//c是標點符號(即c不是控制字符、數字、字母、可打印空白中的一種) isspace(c);//c是空白(即c是空格、橫向製表符、縱向製表符、回車符、換行符、進紙符中的一種) isupper(c);//c是大寫 isxdigit(c);//c是十六進制數字 tolower(c);//把c變小寫 toupper(c);//把c變大寫
C++
標準庫兼容了C
的標準庫,將name.h
命名爲cname
,並去掉了後綴。即cctype
與ctype.h
的內容是同樣的。 在名爲cname
的頭文件中定義的名字從屬於命名空間std
,而定義在.h
頭文件中則否則。 通常來講C++
程序應該使用cname
的頭文件而不使用.h形式,標準庫中的名字總能在std
命名空間中找到。
遍歷string
中的每一個字符使用範圍for
語句(C++11
):
for (declaration : expression)//這種遍歷只能處理元素,不能改變內容 statement
expression:一個對象,用於表示一個序列 declaration:定義一個變量,用於訪問序列中的基礎元素,每次迭代會被初始化爲expression部分的下一個元素值 example1:
string str("some string"); for(auto c : str) cout << c << endl;
example2:
string s("Hello World!!!"); decltype(s.size()) punct_cnt = 0; for(auto c : s) if(ispunct(c)) ++punct_cnt; cout << punct_cnt << endl;
for
必須把循環變量定義成引用類型。string s("Hello World!!!"); for (auto &c : s) c = toupper(c); cout << s << endl;
string
對象中的單個字符有兩種方式:下標和迭代器。 下標運算符[]
接受的輸入參數是string::size_type
類型值,返回該位置上字符的引用。 s[s.size() - 1]
是最後一個字符 在訪問指定字符時必定要首先檢查字符串對象是否爲空。(s.empty()
) 只要字符串不是常量,就能爲下標運算符返回的字符賦新值。 例子:(第一個單詞大寫)for(decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index) s[index] = toupper(s[index]);
###3.3 標準庫類型vector
(vector
表示對象的集合,也被稱爲容器)
模板自己不是類或函數,相反能夠講模板當作編譯器生成類或函數編寫的一份說明。編譯器根據模板建立類或函數的過程稱爲實例化。
vector
能容納絕大多數類型的對象做爲其元素,可是由於引用不是對象,因此不存在包含引用的vector
。
定義和初始化:
vector<T> v1;//默認初始化,即空 vector<T> v2(v1);//v2包含v1全部副本,這必須保證T一致 vector<T> v2 = v1;//與(2)等價 vector<T> v3(n, val);//n個重複元素val vector<T> v4(n);//n個重複執行初始化的對象,初始值由元素類型決定(類型T必須支持默認初始化) vector<T> v5{a, b, c...};//就是這些元素構成的向量,列表初始化(必須是大括號{}) vector<T> v5 = {a, b, c...};//等價於上面 vector<int> v1(10);vector<int> v2{10}; vector<int> v3(10, 1);vector<int> v4{10, 1};
圓括號是構造對象,花括號是列表初始化。 直接初始化只適用於:初始值已知且數量不多、初始值是另外一個vector
的副本、全部元素初始值都同樣。
若是循環體內包含有向vector
對象添加元素的語句,則不能使用範圍for
循環(第五章)。
vector
的操做:
v.empty();//爲空 v.size();//返回v中元素的個數,類型爲vector<T>::size_type v.push_back(t);//添加元素 v[n];//返回位置上的引用,下標類型是vector<T>::size_type v1 = v2;//拷貝替換 v1 = {a, b, c...};//列表拷貝替換 v1 ==(!=) v2;//判斷相等 <, <=, >, >=;//字典序比較。若是容量不一樣,可是相同部分一致,則元素少的較小;其他以第一對相異元素比較結果爲準。(元素的類型必須支持比較,不然不能比較)
vector
對象(以及string
對象)的下標運算符可用於訪問已存在的元素,而不能用於添加元素。試圖用下標方式訪問一個不存在的元素會引起錯誤,不過這種錯誤不會被編譯器發現。 而確保下標合法的一種有效手段就是儘量使用範圍for
語句。
能夠巧妙地使用退格鍵:
if(!v.empty()) cout<<"\b";
來覆蓋多餘的輸出。(好比cout << a[i] << ",";
,到了最後一個元素能夠覆蓋掉)
###3.4 迭代器介紹
全部標準庫容器(vector
)均可以使用迭代器,可是其中只有少數幾種才同時支持下標運算符。嚴格來講string
對象不屬於容器類型,可是string
支持不少與容器相似的操做,例如和vector
同樣都支持下標和迭代器。
迭代器相似指針類型,都提供了對象的間接訪問,也有有效和無效之分。有效地迭代器或者指向某個元素,或者指向容器中尾元素的下一位置,其餘全部狀況都屬於無效。
有迭代器的類型都有返回迭代器的成員:begin
和end
。 begin
返回指向第一個元素(或字符)的迭代器;end
返回指向容器(或string
對象)尾元素的下一位置(也被稱做尾後迭代器)。 若是容器爲空,則它倆返回同一個迭代器,都是尾後迭代器。(因此s.begin() != s.end()
能夠用來判斷容器或字符串是否非空) 一般用auto
來定義迭代器類型:auto b = v.begin();
迭代器運算符:
*iter;//返回所指元素引用(解引用),注意尾後迭代器沒法解引用 iter->mem;//解引用並獲取mem成員((*iter).mem) ++iter;//下一元素 --iter;//上一元素 iter1 == iter2;iter1 != iter2//判斷兩個迭代器相不相等,只有指示的是同一元素或同一容器的尾後迭代器才相等
end
返回的迭代器並不實際指示某個元素,因此不能對其進行遞增或解引用操做。
循環中:迭代器用!=
,下標用<
。 標準庫容器所有定義了==
和!=
,可是大多數都沒有定義<
或者不支持下標運算符。所以要養成使用迭代器和!=
的習慣。
和下標類型(::size_type
)相對應,標準庫的迭代器類型爲iterator
和const_iterator
。 iterator
可讀可寫,const_iterator
相似常量指針,只讀。 若是string
或者vector
對象是一個常量,只能使用const_iterator
。
若是對象是常量,那麼此時的.begin()
和.end()
也是const_iterator
類型;若是不是常量返回iterator
。 若是對象只需讀操做無須寫操做最好使用常量類型(const_iterator
),這時可以使用cbegin
和cend
。(C++11
)不管對象是不是常量都將返回const_iterator
。
C++
的箭頭運算符把解引用和成員訪問兩個操做結合一塊兒。 即it->mem
和(*it).mem
相同
一些限制: 不能在範圍for
循環向vector
對象添加元素。 任何一種可能改變vector
對象容量的操做(push_back
),都會使該vector
對象的迭代器失效。即但凡是使用了迭代器的循環體都不要向迭代器所屬的容器添加元素。
vector
和string
迭代器支持的運算:
iter + n;//仍獲得一個迭代器,位置向前移動n個元素 iter - n;//仍獲得一個迭代器,位置向後移動n個元素 iter += n; iter -= n; iter1 - iter2;//兩個迭代器之間的距離。參與運算的兩個迭代器必須指向同一容器中的元素或者下一元素。
這個獲得的類型爲difference_type
的帶符號整型,由於它可正可負。(注意迭代器之間的加法是沒有意義的)
>, >=, <, <=;//前面小,後面大。參與運算的兩個迭代器必須指向同一容器中的元素或者下一元素。
// text必須有序,查找的內容爲sought auto beg = text.begin(), end = text.end(); auto mid = text.begin() + (end - beg) / 2; while(mid != end && *mid != sought){ if(sought < *mid) end = mid; else beg = mid + 1; mid = beg + (end - beg) / 2;//這裏不用(beg + end) / 2的緣由是沒有定義迭代器加法 } ###3.5 數組 1. 數組與`vector`: 類似:存放類型相同對象的容器,對象自己沒有名字,須要經過位置訪問。 不一樣:數組大小肯定不變,不能隨意添加元素。運行時性能較好,但犧牲了靈活性。 2. 數組是複合類型,其維度也屬於數組類型的一部分,編譯的時候必須已知。 也就是說維度必須是一個常量表達式。(`const`、`constexpr`) ```cpp unsigned cnt = 42; string bad[cnt];//錯誤 string strs[get_size()];//當get_size()是constexpr時正確,不然錯誤
數組定義時必須指定數組類型,不容許用auto
由初始值列表推斷。另外,和vector
同樣,數組的元素應爲對象,不存在引用的數組。
對數組的元素進行列表初始化。 容許忽略數組維度,編譯器會根據初始值數量計算並推測出維度; 若是指明瞭維度,而初始值總數量少於維度,則剩下元素將被默認初始化。
int a1[] = {1, 2, 3};維度是3 int a2[5] = {1, 2, 3};//1,2,3,0,0
char a1[] = {'C', '+', '+'}; char a2[] = {'C', '+', '+', '\0'}; const char a3[4] = "C++";
int a1[] = {0, 1, 2}; int a2[] = a1;//錯誤 a2 = a1;//錯誤
int *ptrs[10];//ptrs是含有10個整型指針的數組(從右向左依次綁定,首先是一個大小爲10的數組,名字是ptrs,數組存放的類型爲int*) int &refs[10] = /*?*/;//錯誤,不存在引用的數組 int (*ptrs)[10] = &arr;//ptrs指向一個含有10個整型的數組(從內向外綁定,ptrs是一個指針,它指向一個大小爲10的數組,數組存放的類型是整型) int (&refs)[10] = arr;//refs引用一個含有10個整型的數組(也是從內向外) int *(&arry)[10] = ptrs;//arry是一個數組的引用,該數組有10個指針
使用數組下標時,一般將其定義爲size_t
類型,在cstddef
頭文件中定義。
和string
、vector
同樣,當須要遍歷數組全部元素時,最好的辦法也是使用範圍for
:
for(auto i : scores) cout << i << " "; cout << endl;
範圍for
最大的好處是能夠減輕人爲控制遍歷過程的負擔。
undefined
):a[10] = {};
int ia[] = {1, 2, 3, 4, 5}; auto ia2(ia);//ia2是一個整型指針,至關於auto ia2(&ia[0])
decltype
時就不會發生上面的狀況:decltype(ia) ia3 = {0, 1, 2}; ia[2] = 3; decltype(ia)返回的類型是由10個整數構成的數組
指針和迭代器同樣,指針支持迭代器的所有操做。 首元素:ia
; 尾元素下一位置指針:int *e = &ia[5];
尾後指針不指向具體元素,不能對尾後指針執行解引用或遞增等操做。
begin()
和end()
函數,將數組做爲參數獲取首元素指針和尾後指針。(C++11
)
int *beg = begin(ia); int *last = end(ia);//定義在<iterator>頭文件中
ptrdiff_t
,也是定義在cstddef
頭文件中,它是一個帶符號類型。小結: size_t
,數組下標類型。 different_type
,迭代器之間距離。 ptrdiff_t
,指針距離。
int *p = &ia[2]; int j = p[1];//至關於*(p+1)即ia[3] int k = p[-2];//至關於*(p-2)即ia[0]
C
風格字符串(cstring
):char[]
,字符串存放在數組中以空字符('\0'
)結束。 (1)函數strlen(p);//返回p的長度,空字符不算在內 strcmp(p1, p2);//比較p1和p2,p1>p2返回正值 strcat(p1, p2);//p2附加到p1後,返回p1 strcpy(p1, p2);//將p2拷貝給p1,返回p1
傳入此類函數的指針必須指向空字符做爲結束的數組。 (2)比較字符串 不能按照標準庫string
類型對象直接比較大小的方法,由於使用數組時其實使用的是首元素指針,它們並不是指向同一對象,比較的結果將是未定義的。 應該使用strcmp
函數。 (3)字符串拼接 標準庫string
只須要相加就能夠。 cstring
須要strcat()
strcpy(largeStr, ca1); strcat(largeStr, " "); strcat(largeStr, ca2);
可是這裏在largeStr
所需空間上不容易估算準確,容易致使安全泄露。
string
和C
風格字符串 (1)容許使用以空字符結束的字符數組來初始化string
對象或爲string
對象賦值。char *ch = "I am"; string s = ch;
(2)在string
對象的加法運算中容許使用以空字符結束的字符數組做爲其中一個運算對象(但不能兩個都是)。 (3)可是若是須要一個C
風格字符串,沒法直接用string
對象代替它。解決的方法是使用string
的c_str
函數。
string s("Hello World"); char *str = s;//Wrong! const char *str = s.c_str();//注意必須是const,c_str()返回一個常量。
若是執行完c_str()
函數後程序想一直保留返回的數組,須要將數組拷貝一份,由於當s
改變後,c_str()
返回的數組也將改變。(這裏c_str()
內部的機制?)
C
風格)和標準庫Vector
不容許數組之間的直接賦值和初始化,vector
也不能給數組初始化。 可是容許數組爲vector
初始化,方法就是前面的begin()
函數和end()
函數。 此外,用於初始化vector
對象的值也能夠僅是數組的一部分:vector<int> subVec(int_arr + 1, int_arr + 4);//它包括int_arr[1-3]三個元素
總結:現代C++
程序應當儘可能使用vector
和迭代器,避免使用內置數組和指針;應該儘可能使用string
,避免使用C
風格的基於數組的字符串。
C++
語言中沒有多維數組,一般說的多維數組就是數組的數組。
多維數組的初始化: 容許用花括號括起來的一組值初始化多維數組:
int ia[3][4] = {//從內向外閱讀,ia是一個含有3個元素的數組,ia的元素是一個含有4個元素的數組 {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} }; int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};//內層嵌套的花括號並不是必需 int ia[3][4] = {{0}, {4}, {8}};//容許並不是全部值都包含在初始化列表中。(顯式初始化了每行首元素,但注意必須有大括號,不然就是順序元素了)
int (&row)[4] = ia[1];//把row綁定到ia的第二個4元素數組上
for
在多維數組中的用法: (1)size_t cnt = 0; for (auto &row : ia) for(auto &col : row){ col = cnt; ++cnt; }
(2)
for (const auto &row : ia) for (auto col : row) cout << col <<endl;
(1)中使用引用是由於要改變元素的值 (2)中沒有改變值卻在外層還要使用引用,由於若是不用引用編譯器初始化row
時會將數組形式的元素轉換成一個指針,使row
的類型爲int*
,這樣內層的循環就不合法了。 注意:要使用範圍for
語句處理多維數組,除了最內層的循環外,其餘全部循環的控制變量都應該是引用類型。
int *ip[4];//表示整型指針的數組 int (*ip)[4];//表示指向含有4個整數的數組 //遍歷二維數組ia: for (auto p = begin(ia); p != end(ia); ++p){ for(auto q = begin(*p); q != end(*p); ++q) cout << *q << " "; cout << endl; }
using int_array = int[4]; typedef int[4] int_array;