一、using[P74]
1. 概念
使用using聲明,可以使得調用函數時候無需聲明命名空間(如:std::cout 可直接寫cout)
2.使用方法
using namespace::name
//;
using std::cin;
int main()
{ int i;
cin<<i;
}
若想調用一整個庫則使用:
using namespace xxx
T I P 1 \color{#FF0000}{TIP1} TIP1:頭檔案不應該包含using聲明
頭檔案的内容會拷貝到所有引用它的檔案中去,如果這些頭檔案裡有某個using聲明,那麼每個使用了該頭檔案的檔案就會有這個聲明。
二、string[P75]
1.準備工作
需要引入string:
#include<string.h>
using std::string;
2.初始化
string s1;//預設初始化,s1是一個空串
string s2(s1);//s2是s1的副本
string s3("value");//注意\0結尾
string s3 = "value";//與上面等價
string s4(n,'c');//初始化為連續的n個字元c
3.string對象上的操作
操作彙總:
os<<s
is>>s
getline(is,s);//從is中讀取字元串賦給s
s.empty();
s.size()
s[n]
s1+s2
s1=s2//指派操作
s1==s2;//判等
s1<s2;//字典序排列
e x a m p l e 1 \color{#0000FF}{example1} example1:每次讀入一整行,遇到空格跳過
while(getline(cin,line))
if(!line.empty())//可以寫任何邏輯,如if(line.size()>80)
cout<<line<<endl;
T I P 1 \color{#FF0000}{TIP1} TIP1: 因為cout遇到空白停止讀入string
string s1,s2;
cin>>s1;//輸入hello word
cout<<s1<<endl;//輸出hello
若想争取讀入,可改為:
string s1,s2;
cin>>s1>>s2;
cout<<s1<<s2<<endl;//和c語言讀入讀出異曲同工
T I P 2 \color{#FF0000}{TIP2} TIP2: s.size()的傳回類型
s.size()函數傳回的類型是string::size_type,并不是int或者unsigned。這展現了标準庫類型與機器無關的特性。但我們可以推測他一定是一個無符号整數,是以 一 定 不 要 在 表 達 式 s . s i z e ( ) 中 混 用 整 形 變 量 \color{#FF7D00}{一定不要在表達式s.size()中混用整形變量} 一定不要在表達式s.size()中混用整形變量,如:s.size()<n。因為整數會自動轉為無符号數,若不甚将n置為負數,則判斷的結果幾乎肯定是true。
4.比較string對象
比較string采用字典序便不必多述,要注意的是字面值和string對象相加
string s1 = "hello",s2 = "world";
string s2 = s1+s2;//正确
string s3 = "hello" + ",";//錯誤,不能把字面值直接相加
string s4 = "hello"+s2;//正确,一個string對象和一個字面值可以相加
string s5 = "hello"+","+s2;//錯誤等價于("hello"+",")+s2,同s3
string s6 = s1+","+"world";//正确(s1+",")+"world"
5.處理string對象中的字元
這一節主要解決改變串中某一個字元的特性,在cctype頭檔案中定義了一組标準庫函數:
函數名 | 解析 |
---|---|
isalnum( c ) | 當c是字母或數字時為真 |
isalpha( c ) | 當c是字母時為真 |
iscntrl( c) | 當c是控制字元時為真 |
isdigit( c) | 當c是數字時為真 |
isspace( c ) | 當c是空白時為真 |
islower( c) | 當c是小寫字母時為真 |
isuper( c) | 當c是大寫字母時為真 |
tolower( c) | 若是大寫字母轉小寫 |
touper( c) | 若是小寫字母轉大寫 |
T I P 1 \color{#FF0000}{TIP1} TIP1:使用基于範圍的for語句處理每個字元
string str("hello world");
for(auto c:str)//和python的 for _ in str:一樣
cout<<c<<endl;
如 果 我 們 要 改 變 s t r i n g 對 象 中 的 字 符 的 值 , 必 須 把 循 環 定 義 成 引 用 類 型 \color{#FF7D00}{如果我們要改變string對象中的字元的值,必須把循環定義成引用類型} 如果我們要改變string對象中的字元的值,必須把循環定義成引用類型所謂引用隻是給定對象的一個别名,是以當使用引用作為循環控制變量時,這個變量實際上被以此綁定到了序列的每一個元素上,使用這個引用我們就可以改變字元。
e x a m p l e 1 \color{#0000FF}{example1} example1:統計一個串中的标點符号字數
#include<stdio.h>
#include<string.h>
#include<cctype>
using namespace std;
string s("Hello World");
decltype(s.size()) punct_cnt = 0;
for(auto c:s)
if(ispunct(c))
++punct_cnt;
cout<<punct_cnt<<endl;
書上的這個代碼寫的還是非常漂亮的,decltype的寫法也非常嚴謹,如果是我可能直接就寫int了…
e x a m p l e 2 \color{#0000FF}{example2} example2:将串轉換為大寫形式
string s("Hello world!");
for(auto &c:s)//注意,使用的是引用
c = toupper(c);
cout<<s<<endl;
如 果 隻 想 處 理 一 部 分 字 符 , 使 用 下 表 運 算 符 ( [ ] ) \color{#FF7D00}{如果隻想處理一部分字元,使用下表運算符([ ])} 如果隻想處理一部分字元,使用下表運算符([])
e x a m p l e 3 \color{#0000FF}{example3} example3:第一個單詞改成大寫
string s("hello world");
for (decltype(s.size()) index = 0 ; index != s.size() && !isspace(s[index]);++index)
s[index] = toupper(s[index]);
二、vector[P86]
1.準備工作
需要引入vector:
#include<vector>
using std::vector;
v e c t o r 是 一 個 類 模 闆 。 模 闆 本 身 不 是 類 或 函 數 , 相 反 可 以 将 模 闆 看 作 為 編 譯 器 生 成 類 或 函 數 編 寫 的 一 份 說 明 \color{#FF7D00}{vector是一個類模闆。模闆本身不是類或函數,相反可以将模闆看作為編譯器生成類或函數編寫的一份說明} vector是一個類模闆。模闆本身不是類或函數,相反可以将模闆看作為編譯器生成類或函數編寫的一份說明
編 譯 器 根 據 模 闆 創 建 類 或 函 數 的 過 程 稱 為 實 例 化 ( i n s t a n t i a t i o n ) , 使 用 模 闆 時 , 需 指 出 編 譯 器 應 把 類 實 例 化 成 何 種 類 型 \color{#FF7D00}{編譯器根據模闆建立類或函數的過程稱為執行個體化(instantiation),使用模闆時,需指出編譯器應把類執行個體化成何種類型} 編譯器根據模闆建立類或函數的過程稱為執行個體化(instantiation),使用模闆時,需指出編譯器應把類執行個體化成何種類型
對 于 類 模 闆 來 說 , 我 們 需 要 通 過 一 些 額 外 信 息 制 定 是 何 種 模 闆 , 也 就 是 在 模 闆 後 加 一 對 尖 括 号 。 ( < > ) \color{#FF7D00}{對于類模闆來說,我們需要通過一些額外資訊制定是何種模闆,也就是在模闆後加一對尖括号。(<>)} 對于類模闆來說,我們需要通過一些額外資訊制定是何種模闆,也就是在模闆後加一對尖括号。(<>)
e x a m p l e 1 \color{#0000FF}{example1} example1:初始化
vector<int> ivec;
vector<Sales_item> Sales_vec;//儲存Sales_item類型的對象
vector<vector<string>> file;//改向量的元素是vector對象
2.初始化
vector<T> v1;
vector<T> v2(v1);//将v2初始化為v1的副本
vector<T> v2 = v1;//等價于上條
vector<T> v3(n,val);//v3包含n個重複元素,每個元素的值都是val
vector<T> v4={a,b,c,...};
vector<T> v5{a,b,c,...};//和上面等價
vector<string> v1{"abv","asd","qwer"};
vector<string> v1{"abv"};
vector<string> v1 ("abc");//×錯誤,不能用字元串字面值建構vector對象
vector<int > invc(10);//10個元素,每個初始化為0
vector<int > v2{10};//1個元素,元素值是10
vector<int > invc(10,1);//10個元素,每個初始化為1
vector<int > v2{10,1};//2個元素,元素值是10和1
vector
如果vector對象的元素是内置類型(如int),則元素初始值自動設為0,如果元素是某種類類型,如string,則元素由類預設初始化。
3.vector對象上的操作
e x a m p l e 1 \color{#0000FF}{example1} example1:添加int元素
vector<int > v2;
for (in i =0;i!=100;++i)
v2. pushi_back(i);//一次把i放到v2尾端
e x a m p l e 2 \color{#0000FF}{example2} example2:添加string元素
string word;
vector<string > text;
while(cin>>word)
text.push_back(word);
函數名 | 解析 |
---|---|
v.empty() | 如果v不含任何元素則傳回真,否則傳回假 |
v.size() | 傳回v中元素的個數 |
v.push_back(t) | 向v的尾端添加一個值為t的元素 |
v[n] | 傳回v中第n個位置上元素的引用 |
v1=v2 | copy |
v1==v2 | v1等于v2當且僅當他們的元素數量相同且對應位置元素值都相同 |
v1<v2 | 字典序比較 |
e x a m p l e 3 \color{#0000FF}{example3} example3:将vector中的元素依次平方
vector<int> v{1,2,3,4,5,6,7,8,9};
for (auto &i : v)
i*=i;
for(auto i:v)
cout<<i<<" ";
cout<<endl;
第一個循環把控制變量 i 定義為引用型,這樣就可以通過i給v的元素指派,其中i的類型由auto關鍵字指定,第二個循環輸出所有元素。
T I P 1 \color{#FF0000}{TIP1} TIP1:vector的size和empty與string是同名成員,功能完全一緻。同樣的size()的傳回類型是size_type要包含元素類型:
vector::size_type;√
vector::size_type;×
e x a m p l e 4 \color{#0000FF}{example4} example4:将vector中的元素依次平方
vector<int> v{1,2,3,4,5,6,7,8,9};
for (auto &i : v)
i*=i;
for(auto i:v)
cout<<i<<" ";
cout<<endl;
T I P 2 \color{#FF0000}{TIP2} TIP2:正常情況下可使用**v[3]、v[0]**等下标形式通路數組,但是不能用下标形式添加元素。
如下:
e x a m p l e 5 \color{#0000FF}{example5} example5:隻能對确知已存在的元素執行下标操作
vector<int > ivec;
for(decltype(ivec.size() ix=0;ix!=10;ix++)
ivec[ix] = ix;
這是嚴重的錯誤,因為ivec初始不包含任何元素
正确指派方法如下
vector<int > ivec;
for(decltype(ivec.size() ix=0;ix!=10;ix++)
ivec.push_back(ix);
總 之 就 是 v e c t o r 對 象 ( 以 及 s t r i n g 對 象 ) 的 下 标 雲 散 可 用 于 訪 問 已 存 在 的 元 素 , 不 能 用 于 添 加 元 素 。 \color{#FF7D00}{總之就是vector對象(以及string對象)的下标雲散可用于通路已存在的元素,不能用于添加元素。} 總之就是vector對象(以及string對象)的下标雲散可用于通路已存在的元素,不能用于添加元素。這就是所謂的Buffer overflow(緩沖區溢出),確定下标合法的一種有效手段是盡可能使用範圍for語句。
四、疊代器[P95]
除了可以使用下标運算通路string和vector對象的元素,還有另外一種更為通用的機制也可以實作同樣的目的:疊代器(iterator)。所有标準庫容器都可以使用疊代器(vector是一種,後續還會介紹),但其中隻有少數幾種才同時支援下标運算符,嚴格的說string對象不屬于容器類型,但是string支援很多與容器類型類似的操作。
1.使用疊代器
類似于指針,疊代器也提供了對對象的間接通路,和指針不一樣的是,擷取疊代器并不是使用取位址符,有疊代器的類型同時擁有傳回疊代器的成員。例如這些類型都擁有begin和end的成員。
auto b = v.begin(),e = v.end();
其 中 b e g i n 負 責 返 回 指 向 第 一 個 元 素 的 疊 代 器 。 \color{#FF7D00}{其中begin負責傳回指向第一個元素的疊代器。} 其中begin負責傳回指向第一個元素的疊代器。
其 中 e n d 負 責 返 回 容 器 尾 元 素 的 下 一 個 位 置 的 疊 代 器 。 \color{#FF7D00}{其中end負責傳回容器尾元素的下一個位置的疊代器。} 其中end負責傳回容器尾元素的下一個位置的疊代器。
也就是說該疊代器訓示的是容器的一個本不存在的尾後元素,僅僅是個标記。en常常被稱作尾後疊代器(off-the-end iterator) 或者尾疊代器。若容器為空,begin和end傳回同一個疊代器。
運算符 | 解析 |
---|---|
*it | 傳回疊代器iter所指的元素的引用 |
iter->mem | 解引用iter并擷取該元素的名為mem的成員,等價于(*iter).num |
++iter | 令iter訓示容器的下一個元素 |
–iter | 令iter訓示容器的上一個元素 |
iter1==iter2 | 判斷兩個疊代器是否相等 |
e x a m p l e 1 \color{#0000FF}{example1} example1:将字元改寫為大寫形式
for(auto it = s.begin();it!=s.end()&&isspace(*it);++it)
*it = toupper(*it);
T I P 1 \color{#FF0000}{TIP1} TIP1:泛型程式設計:對于使用C或者JAVA的程式員會對for循環中使用!=而非<有些奇怪,C++程式員習慣地使用!=,原因和更願意使用疊代器而非下标的原因一樣:因為這種程式設計風格在标準庫提供的所有容器都有效。
T I P 2 \color{#FF0000}{TIP2} TIP2:疊代器類型:就像不知道string和vector的size_type成員到底是什麼樣的類型一樣,一般來說我們也不知道(也無需知道)疊代器的精确類型。而實際上,那些擁有疊代器的标準庫類型使用iterator和const_iterator來表示疊代器的類型 其 中 c o n s t _ i t e r a t o r 為 隻 讀 疊 代 器 類 型 , i t e r a t o r 可 讀 可 寫 \color{#FF7D00}{其中const\_iterator為隻讀疊代器類型,iterator可讀可寫} 其中const_iterator為隻讀疊代器類型,iterator可讀可寫
vector<int>::iterator it;//it1能讀寫vector<int>的元素
string::iterator it2;//it2能讀寫string類型的字元
vector<int>::const_iterator it3;//it3隻能讀元素不能寫元素
string::const_iterator it4;//it4隻能讀字元不能寫字元
const_iterator有些像常量指針。如果vector對象或string對象是一個常量,隻能使用const_iterator。
e x a m p l e 2 \color{#0000FF}{example2} example2:依次輸出text的每一行直到遇到第一個空白行為止
for(auto it = text.cbegin();it!=text.cend()&&!it->empty();++it)
cout<<*it<<endl;
其中it->mem等價于(*it).mem。
T I P 3 \color{#FF0000}{TIP3} TIP3:某些對vector對象的操作會使疊代器失效:vector雖然可以動态地增長,但是也會有一些副作用。已知的一個限制是不能在範圍for循環中向vector對象添加元素,另外push_back也會使得vector對象的疊代器失效(第9章會詳細闡述)是以, 但 凡 是 使 用 了 疊 代 器 的 循 環 體 , 都 不 要 向 疊 代 器 所 屬 的 容 器 添 加 元 素 \color{#FF7D00}{但凡是使用了疊代器的循環體,都不要向疊代器所屬的容器添加元素} 但凡是使用了疊代器的循環體,都不要向疊代器所屬的容器添加元素
e x a m p l e 3 \color{#0000FF}{example3} example3:疊代器運算,二分搜尋
auto beg = text.begin(),end = text.end();
auto mid = text.begin()+(end-begin)/2;
while(mid!=end&&*mid!=sought)
if(sought<*mid)
end = mid;
else
beg = mid+1;
mid = beg+(end-beg)/2;
五、數組[P101]
數組的大小确定不變,不能随意向數組中添加元素。因為數組的大小固定,是以對某些特殊的應用來說程式的運作時性能較好,但是相應地也損失了一些靈活性。
1.定義和初始化内置數組
unsigned int cnt = 42;
string bad[cnt];//錯誤,cnt不是常亮表達式
constexpr unsigned sz = 42;//常量表達式
int arr[sz];//正确
string strs[get_size()];//當get_size是 constexpr時正确;否則錯誤
定義數組的時候必須指定數組的類型,不允許使用auto關鍵字。
數組不允許拷貝和指派,如下操作
char a1[ ] = {'C','+','+'};
char a2[ ] =a;//錯誤,不允許使用一個數組初始化另外一個數組
a2 = a;//錯誤;不能把一個數組直接指派給另外一個數組
T I P 1 \color{#FF0000}{TIP1} TIP1:一些編譯器支援數組的指派,這就是所謂的編譯器拓展(compiler extension),但一般來說最好避免使用非标準特性,因為含有非标準特性的程式很可能在其他編譯器上無法正常工作。
下方展示了一些複雜的數組聲明:
int *ptrs[10];//ptrs是含有10個整形指針的數組
int &refs[10] = /*?*/;//錯誤,不存在引用的數組
int (*Parray)[10] = &arr;//Parray指向一個含有10個整數的數組
int (&arrRef)[10] = arr;//arrRef引用一個含有是個整數的數組
int *(&arry)[10] = ptrs;//arry是數組的引用,該數組含有10個指針
默 認 情 況 下 , 類 型 修 飾 符 從 右 向 左 依 次 綁 定 。 \color{#FF7D00}{預設情況下,類型修飾符從右向左依次綁定。} 預設情況下,類型修飾符從右向左依次綁定。
對于ptrs來說,從右向左首先我們定義的是一個大小為10的數組,他的名字的ptrs,然後知道數組中存放的是指向int型的指針。但是對于Parray來說由于存在括号,因為數組的唯獨是緊跟着被聲明的名字的,我們應當由内向外閱讀。首先是圓括号括起來的部分,*Parray意味着Parray是個指針,接下來觀察右邊,可知道Parray是個指向大小為10的數組的指針,最後觀察左邊,知道數組中的元素是int。這樣就知道*Parray是一個指針,它指向一個int數組,數組包含10個元素。同理,(&arrRef)表示arrRef是一個引用,他引用的對象是大小為10的數組,數組中的元素類型是int。
2.通路數組的元素
在使用數組下标的時候,通常将其定義為size_t類型。size_t是一種機器相關的無符号類型,它被設計得足夠大以便能夠表示記憶體中任意對象的大小。在cstddef頭檔案中定義了size_t類型,這個檔案是C标準庫stddef.h頭檔案的C++語言版本。
3.指針與數組
使用數組的時候編譯器一般會将它轉換成指針。
string nums[] = {"one","two","three"};//數組的元素是string對象
string *p = &nums[0];//p指向nums的第一個元素
數組還有另一個特性:在很多用到數組名字的地方,編譯器都會自動地将其替換為一個指向數組首元素的指針
這就意味着當使用數組作為一個auto變量的初始值時,推斷得到的類型是指針而非數組:
int ia[]={0,1,2,3,4};
auto ia2(ia);//ia2是一個整形指針,指向ia的第一個元素
ia2 = 42;//錯誤:ia2是一個指針,不能用int值給指針指派
必須指出的是當使用decltype關鍵字時,上述轉換不會發生,decltype(ia)傳回的類型是由一個10個整數構成的數組:
//ia3是一個含有10個整數的數組
decltype(ia) ia3={2,3,4,5,6};
ia3[4] = 1;//正确:把i的值指派給ia3的一個元素
指針也是疊代器
就像使用疊代器周遊vector對象中的元素一樣,使用指針也能周遊數組中的元素。當然,這樣做的提前是先得擷取到指向數組第一個元素的指針和指向數組尾元素的下一位置的指針。通過數組名字或數組中的首元素的位址都能得到指向首元素的指針,不過擷取尾後指針就要用到數組的另外一個特殊的性質了我們可以設法擷取數組尾元素之和的那個并不存在的位址:
int *end = &arr[10];//指向arr尾元素的下一個位置的指針
for(int *b=arr;b!e;++b)
cout<<*b<<endl;//輸出arr元素
T I P 2 \color{#FF0000}{TIP2} TIP2:盡管能計算得到尾後指針,但是這種用法極易出錯。為了讓指針的使用更簡單、更安全。C++11新标準引入了兩個名為begin和end和函數。這兩個函數與容器中的兩個成員功能相似,但是數組畢竟不是類類型,是以這兩個函數不是成員函數。正确的使用形式是将數組作為它們的參數:
int ia[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(ia);//指向ia首元素的指針
int *last = end(ia);//指向arr尾元素的下一個位置的指針
這兩個函數的定義在iterator檔案中。
指針運算
和疊代器一樣,兩個指針相減的結果是他們之間的距離。參與運算的兩個指針必須指向同一個數組的元素:
兩個指針相減的結果的類型是一種名為ptrdiff_t的标準庫類型,和size_t一樣,ptrdiff_t也是一種定義在cstddef頭檔案中機器相關的類型。因為內插補點可能為負值,是以ptrdiff_t是一種帶符号類型。
解引用和指針運算的互動
int ia[] = {0,2,4,6,8};
int last = *(ia + 4);//正确:把last初始化成8,也就是ia[4]的值
隻要指針指向的數組中的元素,都可以執行下标運算:
int *p = &ia[2];
int j = p[1];//等價于p[3]
int k = p[-2];
内 置 的 下 标 運 算 符 所 用 的 索 引 值 不 是 無 符 号 類 型 , 可 以 是 負 數 , 這 一 點 與 v e c t o r 和 s t r i n g 不 同 \color{#FF7D00}{内置的下标運算符所用的索引值不是無符号類型,可以是負數,這一點與vector和string不同} 内置的下标運算符所用的索引值不是無符号類型,可以是負數,這一點與vector和string不同
4.C風格字元串
盡管C++支援C風格字元串,但是C++程式中最好還是不要想着它們。這是因為C風格字元串不僅使用起來不太友善,而且極易引發程式漏洞,是諸多安全問題的根本原因 C風格字元串不是一種類型,而是為了表達和使用字元串而形成的一種約定俗稱的寫法。按此習慣書寫的字元串存放在字元串數組中并以空字元結束(null terminated)。以控制符結束的意思是在字元串最後一個字元後面跟着一個空字元(‘\0’)。一般利用指針來操作這些字元串。
C标準庫String 函數
下表C語言标準庫提供了一組函數,這些函數可用于操作C風格字元串,他們定義在cstring頭檔案中。
函數 | 解析 |
---|---|
strlen( p ) | 傳回p的長度,空字元不計算在内 |
strcmp(p1,p2) | 比較p1和p2的相等性。如果p1==p2傳回0;如果p1>p2,傳回一個正值;如果p1<p2傳回一個負值 |
strcat(p1,p2) | 将p2附加到p1之後,傳回p1 |
strcpy(p1,p2) | 将p2拷貝給你p1,傳回p1 |
char ca[] = {'C','+','+'};//不以\0結束
cout<<strlen(ca)<<endl;//嚴重錯誤:ca沒有以空字元結束
比較字元串
如果是string類可以使用 s1<s2
如果是const char ca1[ ] = “A string example”
必須使用strcmp(c1,c2)
如果是兩個string對象連接配接我們可以使用
string largeStr = s1+" "+s2;
但如果是C語言需要使用strcat,與此同時還要保證largeStr擁有足夠大的空間容納的下字元串及末尾的空字元,雖然下面的代碼很常見,但是充滿了安全風險,極易引發嚴重錯誤:
strcpy(largeStr,cal);
strcat(largeStr," ");
strcat(largeStr,ca2);
T I P 3 \color{#FF0000}{TIP3} TIP3:對于大多數應用來說,使用标準庫string要比使用C風格字元串更安全、更高效。
與舊代碼的接口
由于很多C++程式在标準庫出現之前就已經寫成了,他們肯定沒有用到string和vector類型。而且有一些C++程式實際上的與C程式或者其他語言的接口程式,當然也無法使用C++标準可。是以C++不得不語那些充滿數組和C風格字元串的代碼銜接,為了使這一工作簡單易行,C++專門提供了一組功能。
混用string對象和C風格字元串
允許使用以空字元串結束的字元數組來初始化string對象或為string對象指派。
在string對象的加法運算中允許以空字元結束的字元串數組作為其中一個運算對象(不能兩個都是);在string對象的複合指派運算中允許使用以空字元結束的字元數組作為右側的運算對象。
反之則不成立,如:無法用一個string對象直接初始化指向字元的指針如:
使用數組初始化vector對象
int int_arr[]={0,1,2,3,4,5};
vector<int> ivec(begin(int_arr),end(int_arr));//拷貝全部
vector<int> subVec(int_arr+1,int_arr+4)//拷貝部分
T I P 4 \color{#FF0000}{TIP4} TIP4:建議使用标準庫類型而非數組,應當盡量避免使用vector和疊代器,避免使用内置數組和指針;應該盡量使用string,避免使用C風格的基于數組的字元串。
六、多元數組[P112]
嚴格來說,C++語言中沒有多元數組,通常所說的多元數組其實是數組的數組。謹記這一點,對今後的了解和使用多元數組大有益處。
int ia[3][4];//大小為3的數組,每個元素是含有4個整數的數組
int arr[10][20][30]={0};//大小為10的數組,它的每個元素都是大小為20的數組,這些數組的元素是含有30個整數的數組
對于二維數組,我們通常把第一個次元稱作行,第二個次元稱作列。
多元數組的初始化
允許使用花括号括起來的一組值初始化多元數組,如下:
int ia[3][4] = {
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}
};
or
int ia[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int ia[3][4]={{0},{4},{8}};//顯式的初始化每行的首元素
多元數組的下标引用
ia[2][3] = arr[0][0][0];
int (&row)[4] = ia[1];//把row綁定到ia第2個4元素數組上
使用範圍for語句處理多元數組
size_t cnt = 0;
for(auto &row : ia)
for(auto &col :row)
{
col = cnt;
cnt++;
}
T I P 5 \color{#FF0000}{TIP5} TIP5:注意多元數組最好寫引用,防止被auto轉為指針
指針和多元數組
定義指向多元數組的指針時,千萬别忘了這個多元數組實際上是數組的數組。
int ia[3][4];//大小為3的數組,每個元素是含有4個整數的數組
int (*p)[4] = ia;//p指向含有4個整數的數組,圓括号必不可少
p = &ia[2];//p指向ia的尾元素
/*****************************************/
随着C++11新标準的提出,通過使用auto或者decltype就能盡量避免在數組前面加上一個指針類型了
e x a m p l e 1 \color{#0000FF}{example1} example1:輸出ia每個元素的值,每個内層各占一行
//p指向含有4個整數的數組
for(auto p = ia;p!=ia+3;++p){
for(auto q = *p;q!=*p+4;++q)
cout<<*q<<" ";
cout<<endl;}
外層的for首先聲明p指向ia的第一個内層數組,然後依次疊代直到ia的全部三行都處理完為止。内層的for循環負責輸出内層數組所包含的值,像往常一樣,數組名被自動地轉換成指向數組首元素的位址。内層循環四層直到處理完為止。
也可以使用begin和end讓代碼更簡潔:
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 int_array[4];//等價的聲明
for(int_array *p = ia;p!=ia+3; ++p)
{
for(int *q = *p;q!= *p+4;++q)
cout<<*q<<' ';
cout<<endl;
}
這就将類型“4個整數組成的數組”命名為了int_array,用類型名int_array定義外層循環的控制變量,進而使得程式更加簡潔明了。