正文之前
其實我的《C++ Primer》 已經看到第五章了,但是因為碼字比較費時間,所以暫時沒有迅速更新實在是對不住,但是沒辦法, 總不能一天拿出五六個小時來碼字吧。最多三個小時不能多了。不過我后期會把碼字當做是一種復習和筆記行為逐步跟上的。至少保證在我這兒可以完整的把《C++ Primer》從頭到尾擼一遍。
正文
1、 數組的定義和初始化
數組是一種類似于標準庫類型vector的數據結構,但是在性能與靈活性的權衡上又與vector不同,最大的不同是:數組的長度直接顯式的或者間接地被規定了,是不變的。不能隨意向數組中添加元素。因為這個特性,所以某些時候數組的性能較好,但是缺乏靈活性。
- 數組的長度必須是給定的常量表達式,書上是這么說的,按照書上的說法下面應該報錯,但是我的gcc給我的回復是沒有錯誤。不過大家還是盡量按照《C++ Primer》的要求來。
int len=10;
int array[len]
- 數組的初始化方式:
const unsigned sz=4;
int ial[sz]={1,2,4}; {1,2,4,0}
int a2[]={1,24,3}; // {1,24,3}
itn a3[5]={0,1,2}; //{0,1,2,0,0}
string a3[3]={"abc","def"}; // 三個字符串 “abc”,“def”,“”
int a5[2]={1,2,3}; //錯誤:初始值是三個,但是容量為2;
- 特殊的字符數組
其特殊之處在于可以用字符串字面值,來給字符數組賦值,但是一定要記住,賦值之后,會默認的多出一個\0結束符號,這個符號會被放到初始化的內容的最后一位,也就是說如果你輸入了6個字符,那么編譯器會默認在第七位加上\0符號作為arr[6]
char a1[]={'c','b','a'}; // {'c','b','a'}
char a2="c++"; //{'c','+','+','\0'}
char a3[4]="abcd"; //Error:會自動多出來一個\0 無處存放!
- 不允許拷貝和復制。不能把數組的內容直接拷貝給其他的數組作為初始值。也不能直接用數組給其他的數組賦值。PS:當然,有一些編譯器是支持這種行為的,但是這是非標準特性,是編譯器的個人行為,并非通用!
2、 很經典的復雜的數組聲明
int *ptrs[10];
從右向左依次綁定。那么ptrs先綁定[10],組成了一個數組,然后*代表這個數組的元素都是 int * 類型的。所以這個是代表著10個int 指針所組成的數組的聲明。
char a[10]="IloveYYW";
char &arr[9]='f';
char &d=a;
char &d[10]=a;
char &c=a[7];
cout<<c<<endl;
上面是關于對于數組的引用。實際運行顯示,第二行第三行第四行都是錯誤的!!!!不存在引用的數組,除非直接引用數組中的某一個元素,比如第五行的做法。這就是正確的!!
int (*parr)[10]=&ptrs;
int (&carr)[10]=ptrs;
上面兩句分別是指針和引用兩種常見的類型。為何要放在一起呢?首先容我為你解釋兩句話編譯后的含義:parr是代表著一個指向容量為10的名為ptrs的數組的指針。記住,只是一個指針,不是一個指針數組。這個時候的[10]只是告訴你,這個指針指向的是以初始化的值的地址開始的長度為0的空間內的數組,它是指向整個數組而不是數組的首地址,也就是數組名ptrs所指的地址。ok既然知道了這個[10]的含義,那么我們就知道了下面的引用的含義了。另外注意,()是必不可少,不然由于優先級的問題,會造成很嚴重的后果。下面是我的一些示例代碼,應該有助于各位理解:
char a[10]="IloveYYW";
char &c=a[7];
cout<<c<<endl;
char (*p)[10]=&a;
cout<<(*p)[3]<<endl;
cout<<(*p)<<endl;
p++;
cout<<(*p)<<endl;
char *x=a;
x++;
cout<<*x<<endl;
3、 小小實戰:成績歸檔
需求:把各個學生的成績錄入后按照十分為一個層次歸檔。最后輸出各個層次的成績;
分析:用一個含有11個元素的數組來記錄。每錄入一個成績,就把對應檔次的數組元素+1,最后遍歷即可;
實現:
int tar[11];
unsigned int grade;
while(cin>>grade)
{
if(grade<=100&&grade>=0)
++tar[grade/10];
}
for(int i=0;i<=10;i++)
{
cout<<i*10<<"~"<<(i+1)*10<<"分數段有"<<tar[i]<<"人 "<<endl;
}
PS:很重要的一點,千萬要注意數組下標的合法性。如果出現溢出,那是很麻煩的一件事,所以最好是加上一點合法性測試,比如上面代碼中的if(grade<=100&&grade>=0)
4、 指針與數組
數組有一個很神奇的也是對新人十分不友好的特性,那就是數組名是指向數組首地址的指針。
string name[3]={"zhang ","zhao","bo"};
string *p=name;
string xp=&name[0]
上面的兩個指針定義是等價的,也就是說其實數組名就等價于是第一個元素的地址,你直接用數組名給一個指針賦值,送上去的也就是第一個元素的地址,指針也只指向第一個元素,而不是整個數組所在的塊。上面說過了,要指向塊,必須早數組的后面跟上數組的長度大小。
char a[10]="IloveYYW";
char (*p)[10]=&a;
cout<<(*p)[3]<<endl;
cout<<(*p)<<endl;
p++;
cout<<(*p)<<endl;
char *x=a;
x++;
cout<<*x<<endl;
char *X=&a[0];
X++;
cout<<*X<<endl;
5、 數組的迭代器(稍微區別于vector的迭代器)
數組的迭代器。頭指針好說,尾后迭代器這個就不好說了。按照尾后迭代器的概念,那么就有如下:
int arr[10];
int *end=&arr[10];
可以看出來,arr[10]是一個不存在的元素,也是最后一個元素的下一個地址。(記住:尾后迭代器不能執行解引用以及遞增操作,意思是可以遞減!回到最后一個元素的地址)完美符合尾后迭代器的概念。但是標準函數庫里面其實已經為我們做好了準備,按照原理來說其實就是頭指針是數組名,尾指針是上面的尾后迭代器的概念:
int a[10]={1,2,3,4,5,6,7,8,9,0};
int *beg=begin(a);
int *last=end(a);
再回頭看一下vector的迭代器的操作:
vector<int > v;
auto b=v.begin();
聰明的你一定發現了~~ 對于數組是調用標準函數庫得到的,而對于vector ,人家是標準庫類型內置函數,雖然效果一樣,但是這個逼格瞬間就見高低,當然,因此數組的性能要略高于vector是肯定的。但是你耐不住現在的計算機計算能力大大提高,不在乎那么點性能和容量的損失哇!!
6、 數組的指針操作
其實數組的指針操作基本就是++ -- 那些常見的玩意 一個數組的指針加上一個長度就是到了距離這個指針所指地址多遠距離的地址,這很容易理解對不,畢竟指針本身是個對象,這個對象的內容是一個地址,那么好比說有一個游標,指向一棟大樓的樓層,現在這個指針指向3樓,你給這個指針+5 自然就指向8樓了。如果取出對象內的內容,就相當于是進入了八樓的房間咯,這些常規的就不多說了。下面看一個有意思的。
int a[10]={0,1,2,3,4,5,6,7,8,9};
int *p=&a[4];
cout<<*p<<endl;
cout<<*(p+2)<<" "<<*(p-2)<<endl;
int j=p[2];
int k=p[-2];
cout<<j<<" "<<k<<endl;
想不到還有這種騷操作吧。不過這種也只能對著指向數組某個元素的指針用。具體的思維是參照坐標系中的相對坐標。當你重新定向坐標原點(首地址,也就是代碼中的p),其實就相當于在平行的平面內重定一個坐標系。進行相對的偏移,雖然長度不變,但是范圍卻變了。好比上面的,數組的范圍從0~9 變成了 -4~5; 你難道不覺得p這個對象跟a這個對象是一個德行嗎?本身是指針,但是只要又可以加后綴,區別只是a加后綴默認取出元素,p加后綴卻是只表示了偏移量而沒有取數操作而已!! Sexy Operation 吧!!
7、使用數組初始化vector對象(書中還有些老掉牙的C字符串風格與string的操作,但是我覺得沒啥用,就不寫了)
int arr[3]={1,2,3};
vector<int > ivec(begin(arr),end(arr));
與前面介紹的vector的初始化都不一樣,這次是兩個迭代器來初始化。很是神奇,而且得益于指針。我們可以用vector截取數組的一部分,也就說用指針的某一段的頭指針和尾后指針來初始化一個vector對象。當然,現代C++應該盡量使用vector以及內置函數和迭代器來避免數組和指針的使用。避免使用基于C風格的字符串。
8 、 多維數組的初始化
嚴格來說,C++沒有多維數組,所謂的多維數組,其實是由數組的元素是數組這種形式變化而來。
⑴ 分行進行初始化
int a[2][3]={{1,2,3},{4,5,6}};
在{ }內部再用{ }把各行分開,第一對{ }中的初值1,2,3是0行的3個元素的初值。第二對{ }中的初值4,5,6是1行的3個元素的初值。相當于執行如下語句:
int a[2][3];
a[0][0]=1;
a[0][1]=2;
a[0][2]=3;
a[1][0]=4;
a[1][1]=5;
a[1][2]=6;
注意,初始化的數據個數不能超過數組元素的個數,否則出錯。
⑵ 不分行的初始化
int a[2][3]={ 1,2,3,4,5,6};
把{ }中的數據依次賦給a數組各元素(按行賦值)。即
a[0][0]=1;
a[0][1]=2;
a[0][2]=3;
a[1][0]=4;
a[1][1]=5;
a[1][2]=6;
⑶ 為部分數組元素初始化
int a[2][3]={{1,2},{4}};
第一行只有2個初值,按順序分別賦給a[0][0]和a[0][1];第二行的初值4賦給a[1][0],其它數組元素的初值為0。
static int a[2][3]={ 1,2};
只有2個初值,即a[0][0]=1,a[0][1]=2
,其余數組元素的初值均為0。
⑷ 可以省略第一維的定義,// * * 但不能省略第二維的定義 * *//。
系統根據初始化的數據個數和第2維的長度可以確定第一維的長度。
int a[ ][3]={ 1,2,3,4,5,6};
a數組的第一維的定義被省略,初始化數據共6個,第二維的長度為3,即每行3個數,所以a數組的第一維是2。例如,int a[ ][3]={ 1,2,3,4};
等價于:int a[2][3]={ 1,2,3,4};
若分行初始化,也可以省略第一維的定義。下列的數組定義中有兩對{ },已經表示a數組有兩行。 static int a[ ][3]={{1,2},{4}};
9、 多維數組的有意思的玩法
注意注意,前面說了:數組的數組名就是數組首地址的指針。那么,多維數組好比是a[2][3],那么a[0],a[1]也是指針!!! 有意思吧!!!所以對于指針來說,可以定義指向外層數組的指針,也可以定義指向內層數組的指針,
int a[2][3]={0,1,2,3,4,5};
int (*p)[3]=a;
cout<<(*p)[2]<<endl;
p++;
cout<<(*p)[2]<<endl;
可見,p所指的是第一層,而不是第一層第一個。那么p++自然就是移到第二層了。然后再在第二層解引用之后由下標取值即可!!
如果再改變一下:
int a[2][2][2]={0,1,2,3,4,5,6,7};
int (*p)[2][2]=a;
cout<<(*p)[1][1]<<endl;
p++;
cout<<(*p)[1][1]<<endl;
int (*x)[2]=a[1];
cout<<(*x)[1]<<endl;
畫圖畫錯了。。集體增加了1,大家小心理解就好,忽略我的錯誤,我下面的解釋基于這個錯誤的圖像進行講解
指針a所指的其實是包含了1234 四個元素的那個區塊,而其內不是一個二維數組,所以在p指針的ID能夠以后加兩個長度元素表示其所指向的對象是一個二維數組,a[1]則表示的是包含了56這兩個元素的那個塊,所以重新定義p的時候在后面要加一個長度來表示所指的是一個一維數組。so~~ 完了
這個時候有一個auto的出現簡直是人間福音。。看下面的代碼就知道了:
for(auto p=a;p!=ia+3;++p)
{
for auto q=*p;q!=*p+4;++q)
cout<<*q<<endl;
cout<<endl;
}
這里一不小心就會把你坑死,因為p=ia,所以其實此時p是一個指向一維數組的指針,解引用后才是指向一維數組首地址的指針,再次解開引用才是真正意義上的a[0][0]
正文之后
哎??,說好的寫簡書只花三個小時內的時間,吃完午飯在圖書館玩了一會,處理了下年級重修的工作后開始寫,寫到現在五點多了。少說去了四個小時!!!我這是干了什么?!!!!!不行!!我要好好開始看書了!!今日簡書封筆!!
明日再戰!!