1、第一部分第九課:數組威武,動靜合一
2、第一部分第十課預告:文件讀寫,海闊憑魚躍
上一課《【C++探索之旅】第一部分第八課:傳值引用,文件源頭》中,我們學習了函數參數的不同傳遞形式:值傳遞和引用傳遞,也學習了如何用頭文件和源文件來更好地組織項目。
在不少程序中,我們都需要使用多個相同類型的變量。例如:一個網站的用戶名列表(一般是string類型);或者一場比賽的前10個最佳得分(一般是int類型)。
類似地,C++和大多數編程語言一樣,也有將多個相同類型的數據組合在一起的方法。就像這一課的標題所述,我們將這種數據形式稱為 數組。
我們將學習兩種類型的數組。一種數組是預先知道其中所包含的元素數目的,例如一場比賽的前10個最佳得分;另一種數組的元素數目會變化,例如一個網站的用戶名列表。
聰明如你應該料想到了:預先知道元素數目的數組使用起來相對簡單,因此我們就由這種類型入手。
靜態數組
在這一課的介紹中,我們說了數組的大致用途:
俗語說:好記性不如爛筆頭。我覺得:好解釋不如爛例子。因此,我們就來舉個“栗子”:
實例
假如你要顯示一項比賽的前五個最高得分,那麼一般需要兩個列表:
因此,我們要聲明10個變量
例如:
string bestPlayerName1("Albert Einstein"); string bestPlayerName2("Isaac Newton"); string bestPlayerName3("Marie Curie"); string bestPlayerName4("Thomas Edison"); string bestPlayerName5("Alfred Nobel"); int bestScore1(118218); int bestScore2(100432); int bestScore3(87347); int bestScore4(64523); int bestScore5(31415);
要顯示這5個分數信息也不容易:
cout << "1) " << bestPlayerName1 << " " << bestScore1 << endl; cout << "2) " << bestPlayerName2 << " " << bestScore2 << endl; cout << "3) " << bestPlayerName3 << " " << bestScore3 << endl; cout << "4) " << bestPlayerName4 << " " << bestScore4 << endl; cout << "5) " << bestPlayerName5 << " " << bestScore5 << endl;
可以看到,光是顯示5個最高分數就已如此勞師動眾。假如要顯示的是100個最高分數,那就要聲明200個變量(100個參賽者名字和100個最高分數),顯示這些信息又要再寫100行cout… 寶寶心裡苦,但寶寶不說。
因此我們迫切需要數組的幫忙:借著數組,我們可以一次性聲明100個最高分數和100個參賽者的名字。在內存中,我們可以一次性申請儲存100個int型變量和100個string類型變量的空間。很棒,不是嗎?
當然了,同一個數組裡的元素之間要有相關性,這樣才能正確規劃我們的程序。把你家的狗狗的年齡和上網人數放到同一個數組,應該不太合適吧,雖然它們都是int類型的變量。
在上例中,我們說了,需要各申請100個int變量和100個string變量。因此,我們需要兩個數組來分別存放。100就是這兩個數組的大小。這種大小在整個程序中不會改變的數組,我們稱之為靜態數組。暫時我們只需要用靜態數組就夠了。
聲明靜態數組
在C++中,一個變量基本由名字和類型來做標識。既然數組就是相同變量的集合,這個規則仍然成立。只不過我們還需要加上另一個標識:數組的大小。
聲明一個數組就和聲明一個變量類似。如下:
類型 名字[大小];
由左到右依次寫:類型,名字,中括號,
看以下實例:
#includeusing namespace std; int main() { ?int bestScores[5]; //聲明包含5個int型變量的數組,名字是bestScores double angles[3]; //聲明包含3個double型變量的數組,名字是angles return 0; }
我們再用內存圖解來更好地理解吧:
在上圖中,我們看到,內存裡分配了兩個大的空間,各自的標簽是bestScores和angles,一個有5個"抽屜",一個有3個"抽屜"。暫時,我們還沒對這些抽屜裡的變量賦值,因此它們的值是任意的,正如上圖中用問號所示。
我們也可以將一個const變量作為數組的大小,只要這個const變量的類型是int或unsigned int。例如:
int const arraySize(20); //數組的大小是20 double angles[arraySize];
如果要作為靜態數組的大小,必須使用const變量,如果用一般的變量,是會出錯的。
建議大家盡量不要用數字來作為數組的大小,用const變量是比較好的習慣。
好了,既然我們在內存中申請好了空間,那麼就只剩使用啦。
訪問數組的元素
數組中的每一個元素既然就是一個變量,那麼其使用方式就和一般的變量沒有區別。不過要訪問數組中的這些元素,要使用比較特殊的方法:指明數組的名字和元素的號碼。之前的例子中,我們聲明了 int bestScores[5];
因此bestScores這個數組有5個元素,從第一個到第五個依次編號。
為了訪問這些元素,我們使用這樣的格式:數組名[元素號碼]
這裡的元素號碼,術語稱為 下標。
注意:數組中的第一個元素的下標是0,而不是從1開始的。
因此第一個元素的下標就是0,第二個元素的號碼是1,依次遞增。
假如我要訪問數組bestScores中的第3個元素,我就要這麼用:
bestScores[2] = 5;
因此,我們如果要像之前的示例程序一樣為bestScores的每一個元素賦值,需要這樣做:
int const bestScoreNumber(5); // 數組的大小 int bestScores[bestScoreNumber]; // 聲明數組 bestScores[0] = 118218; // 填充數組的第1個元素 bestScores[1] = 100432; // 填充數組的第2個元素 bestScores[2] = 87347; // 填充數組的第3個元素 bestScores[3] = 64523; // 填充數組的第4個元素 bestScores[4] = 31415; // 填充數組的第5個元素
遍歷數組
數組的一個強大之處就在於:我們可以用循環來很方便地遍歷數組中的元素。
既然對於靜態數組,我們預先知道數組的大小,那麼我們就可以用一個for循環來遍歷。
#include#include using namespace std; int main () { int const bestScoreNumber(5); // 數組的大小 int bestScores[bestScoreNumber]; // 聲明數組 bestScores[0] = 118218; // 填充數組的第1個元素 bestScores[1] = 100432; // 填充數組的第2個元素 bestScores[2] = 87347; // 填充數組的第3個元素 bestScores[3] = 64523; // 填充數組的第4個元素 bestScores[4] = 31415; // 填充數組的第5個元素 for (int i(0); i < bestScoreNumber; ++i) { cout << bestScores[i] << endl; } return 0; }
變量i的值依次成為0, 1, 2, 3, 4,因此被傳遞給cout的值依次是bestScores[0],bestScores[1],直到bestScores[4]。
注意:數組元素的下標一定不能超過數組元素數目減1,不然會報數組下標越界錯誤。例如上面數組的大小是5,因此數組元素的下標最大是4。
你慢慢會發現,在C++編程中,數組和for循環的組合會成為你經常使用的好工具。
數組和函數
我希望你沒忘了函數是什麼吧,不然就太傷偶的心了...
不論如何,我們都要來復習一下函數。
不過接下來,你將會看到:靜態數組和函數並不是那麼要好的朋友。
第一個限制就是:我們不能創建一個返回靜態數組的函數,做不到。
第二個限制是:靜態數組作為函數參數的時候,總是以引用的方式來傳遞的。而且並不需要加&符號:都是自動的。這就是說,當我們把一個數組傳遞給函數作參數時,函數可以修改此數組。
以下就是一個參數是數組的函數的樣式:
void function(int array[]) { //… }
如上所示,數組的中括號裡並不加數組的大小。
但是前面說過了,我們遍歷一個數組需要知道它的大小。上面的函數樣式中,我們並不能知道數組的大小,因此只能再加一個參數:函數大小。如下所示:
void function(double array[], int arraySize) { //… }
我知道,這有點令人沮喪。但是不能怪我啊,畢竟並不是我發明C++的。
為了稍作練習,希望大家寫一個函數,用來計算數組中所有元素的平均值。
以下是我的版本:
/* * 計算數組元素的平均值的函數 * - array : 要計算其平均值的數組 * - arraySize : 數組的大小 */ double average(double array[], int arraySize) { double average(0); for(int i(0); i
關於靜態數組,我們已經聊得差不多了,該說說動態數組了。
動態數組
之前說過,我們會介紹兩種數組:一種是靜態數組,數組的大小是固定不變的;另一種的大小卻可以改變,這種類型的數組就是動態數組。不過既然都是數組,有些概念還是通用的。
聲明動態數組
靜態數組和動態數組有通用的地方,但也有很多不同。
第一個不同之處就是在程序的最開始,我們需要加一行
#include
這樣我們才能使用動態數組。(當然,一般應該用new的方法來創建動態數組,我們要到第二部分:面向對象 時才會講,暫時只用vector來代表動態數組)
vector這個英語單詞本身的意思是"向量,矢量"的意思。
第二個不同點在於數組的聲明方式。
聲明動態數組的格式如下:
vector<類型> 名字(大小);
例如,聲明一個包含5個int型變量的動態數組,我們可以這樣做:
#include#include // 不要忘記 using namespace std; int main() { vector array(5); return 0; }
需要區分三點:
動態數組的情況中,類型不再是放在最前面了,這與變量和靜態數組的聲明方式不同。一開始先寫vector這個關鍵字。
我們使用了一對奇怪的尖括號<>,其中寫入類型
我們把數組的大小寫在圓括號內,而之前靜態數組時是將數組大小寫在中括號內。
這也正說明了,動態數組和靜態數組還是有差別的。不過,你將看到,遍歷數組的方式還是類似的。
在此之前,有兩個小竅門需要了解。
我們可以快捷地直接填充動態數組的所有元素,只需要在圓括號裡加上第二個參數就可以了,如下:
vector
array(5, 3); //創建一個動態數組,包含5個int型變量,值都為3 vector
nameList(12, "無名氏"); //創建一個動態數組,包含12個string型變量,值都為"無名氏" 我們可以聲明一個沒有元素的動態數組,只需要不加圓括號就行了:
vector
array; // 創建一個類型為double的動態數組,0個元素
那你要問了:創建0個元素的動態數組的意義是?
還記得動態數組的性質了嗎?數組的大小可變化。因此我們可以之後再往裡面添加元素啊。等下你就知道了。
訪問動態數組的元素
盡管動態數組的聲明方式和靜態數組很不同,但要訪問其元素,方法又類似了。我們重新使用中括號,第一個元素的下標也是從0開始。
因此,我們可以重寫我們之前的例子,用動態數組vector:
int const bestScoreNumber(5); // 數組的大小 vectorbestScores(bestScoreNumber); // 聲明動態數組 bestScores[0] = 118218; // 填充數組的第1個元素 bestScores[1] = 100432; // 填充數組的第2個元素 bestScores[2] = 87347; // 填充數組的第3個元素 bestScores[3] = 64523; // 填充數組的第4個元素 bestScores[4] = 31415; // 填充數組的第5個元素
改變動態數組的大小
來到動態數組的知識點中的關鍵之處了:改變數組大小。
首先,學習如何在數組末尾處加元素吧。
我們需要用到push_back()這個函數。用法如下:
vectorarray(3,2); //創建一個動態數組,名字是array,大小是3,每個元素都是int型變量,且值都為2 array.push_back(8); //往數組末尾添加一個新的元素(第4個),其值為8
用內存圖來解釋一下上面發生了什麼吧:
可以看到,一個新的元素被添加到了數組尾處。
當然了,我們可以用push_back函數來添加多個元素,如下:
vectorarray(3,2);//創建一個動態數組,名字是array,大小是3,每個元素都是int型變量,且值都為2 array.push_back(8);//往數組末尾添加一個新的元素(第4個),其值為8 array.push_back(7);//往數組末尾添加一個新的元素(第5個),其值為7 array.push_back(14);//往數組末尾添加一個新的元素(第6個),其值為14 //現在,數組中包含的元素是:2228714
難道vector動態數組只能插入元素而不能刪除元素嗎?
當然不可能啦。C++的作者早就想到了。
我們可以用pop_back函數將動態數組的最後一個元素刪去。用法和push_back函數類似,只不過pop_back函數的圓括號中沒有參數罷了。
vectorarray(3,2); array.pop_back();//刪去一個元素,只剩下2個了 array.pop_back();//刪去一個元素,只剩下1個了
當然也不要刪過頭了,畢竟一個動態數組不能包含少於0個元素。
既然動態數組的大小是可變的,那麼我們怎麼即時知道其大小呢?幸好,vector中有一個函數可以使用:size(),其可以返回數組的大小。
vectorarray(5,4);//包含5個值為4的int型變量的數組 intconstsize(array.size());//size這個const變量就是array數組的大小,因此值為5
動態數組和函數
相比於靜態數組,把動態數組傳遞給函數作為參數要容易得多。
因著size()函數,我們就不必添加第二個參數來指明數組的大小了。
如下所示:
//一個參數是int型動態數組的函數 voidfunction(vectorarray) { //… }
很簡便不是嗎?但我們可以做得更好。
以前的課程中,我們說過引用傳遞可以防止拷貝,就可以在一定程度上優化代碼。事實上,如果數組包含很多元素,那麼如果要拷貝就會很花時間。因此我們可以使用這個訣竅,如下:
//一個參數是int型動態數組的函數 voidfunction(vectorconst&array) { //… }
因為用了const,此動態數組在函數中就不能被改變;因為用了&這個引用符號,此動態數組就不會被拷貝了。
而要調用一個參數是動態數組的函數,也很簡單:
vectorarray(3,2); function(array);//將動態數組傳遞給上面定義的函數
之前我們學習了使用.h頭文件來儲存函數的原型,因此我們需要這樣用:
#ifndefARRAY_H_INCLUDED #defineARRAY_H_INCLUDED #include//引入vector頭文件 voidfunction(std::vector &array);//須要在vector前面加上std:: #endif//ARRAY_H_INCLUDED
當然,我們也可以創建返回值是動態數組的函數。我相信你已經知道怎麼做了:
vectorfunction(inta) { //... }
多維數組
我們可以創建int型數組,也就是說數組中的各個元素是int變量。我們也可以創建double型數組,string型數組。
我們甚至可以創建數組的數組。
我料想你也許皺起了眉頭,想著:數組的數組,這是什麼東東,干什麼用呢?
我們首先從內存圖解來慢慢理解吧:
上圖中黃色的大格子,就是一個數組變量。這個數組由5個分開的更小的格子組成,而每個更小的格子裡又各有4個小格子。
我們把數組的數組稱為多維數組。之前我們接觸的數組都是一維的。
聲明多維數組
要聲明這樣的多維數組,我們就需要用到多個中括號了,將不同維度的對應大小寫在中括號裡,一個接一個。如下:
類型 數組名[大小1][大小2];
因此,為了聲明如上面內存圖中所示的二維數組,我們可以這麼做:
intarray[5][4];
或者,更優化一些,用到const變量:
intconstsizeX(5); intconstsizeY(4); intarray[sizeX][sizeY];
訪問多維數組的元素
我相信接下來不需要過多解釋,你也猜到如何訪問多維數組的元素了吧。
例如array[0][0]就是上面的黃色數組中左下角的格子;array[0][1]對應的就是它上面的那個格子;array[1][0]對應的就是它右邊的那個格子。依次類推。
那麼如何訪問右上角那個格子呢?
也不難,就是array[4][3]
進一步探究
當然,我們可以創建三維,四維,甚至更多維的數組。例如:
doublesuperArray[5][4][6][2][7];
上面這個數組我可不想把它畫出來,因為太難了。不過,實際寫程序的時候,我們甚少會用到多於三維的數組。
上面所示的多維數組,是靜態的多維數組。我們也可以創建大小可變的多維數組,這就要用到vector了。比如要創建一個二維的動態數組,可以這樣寫:
vector>array; array.push_back(vector (5));//向二維數組中添加一行,這一行包含5個元素 array.push_back(vector (3,4));//向二維數組中添加一行,這一行包含3個元素,每個元素的值為4 多維動態數組的每一行都可以有不同的大小。我們依然可以用下標來訪問不同的元素。
array[0].push_back(8);//在第一行中添加一個值為8的元素
和多維靜態數組一樣,多維動態數組的元素也可以如下訪問,不過需要確認此元素存在:
array[2][3]=9;//改變第3行第4列的元素的值為9
可以看到,多維動態數組並不那麼好用,而且並不是有效使用內存的方式。
一般對於多維數組,我們還是使用靜態數組比較多。
字符串:字符數組
在結束這一課之前,還需要偷偷告訴你個事:我們之前一直用的字符串其實就是數組!
我們在string類型變量的聲明時,並不易窺見此。但其實字符串就很類似字符的數組,而且和vector(動態數組)很類似。
當然了,之後學到第二部分(面向對象),會知道string其實是一個類。暫不深究。
訪問字符串中的字符
了解字符串其實是字符數組可以使我們學會如何訪問其中的每一個字符,甚至改寫它們。我們還是用下標來訪問。
#include#include usingnamespacestd; intmain() { stringuserName("Julien"); cout<<"你是"<
運行,輸出:
你是 Julien.
啊,不對,你是 Lucien.
很厲害,不是嗎?但是我們可以做得更好。
字符串相關函數
我們可以用size()函數來得知string變量中字符的數目,用push_back()函數向string變量最後添加字符。就和操作vector一樣。
stringtext("AllPeopleSeemToNeedDataProcessing");//39個字符 cout<<"這個句子包含"<
我們也可以用+=符號來一次性添加多個字符到字符串中。
#include#include usingnamespacestd; intmain() { stringfirstName("Albert"); stringlastName("Einstein"); stringfullName;//空的字符串 fullName+=firstName;//將名字加入空字符串 fullName+="";//接著加入一個空格 fullName+=lastName;//最後加入姓 cout<<"你叫"<
運行,輸出:
你叫 AlbertEinstein.
總結
數組是內存中相同類型數據元素的集合,這些元素在內存地址中一個接著一個
一個靜態數組是這樣初始化的:int bestScore[5]; (包含5個元素)
數組中第一個元素的下標是0(例如 bestScore[0])
如果數組的大小(元素個數)有可能會變動,那麼就須要創建動態數組,用vector:vector
array(5); 我們可以創建多維數組,例如:int array[5][6]; 就創建了一個5行6列的二維數組.
字符串其實可以被看作字符的數組.其中每一個元素就是一個字符.
第一部分第十課預告
今天的課就到這裡,一起加油吧!
下一課我們學習:文件讀寫,海闊憑魚躍