將相同型態的變數集合而成並以一個共用的名稱來表示,稱之為陣列(array)。
利用索引(index)來處理陣列中元素(element)的存取動作。陣列中每個元素(element)所在記憶體位址
是連續的,不管是一維(one-dimension ;unidimension)陣列或是多維(multi-dimension)陣列,實際儲存
在電腦中的記憶體位址都是一維方式。稍後會介紹。
宣告一維陣列的通式(general form):
T arrayName[size]; //T表示任何的資料型態,arrayName陣列名稱,size陣列大小
舉個例子:
int arrInt[10]; //陣列名稱為arrInt,型態為int,大小為10
int arrInt[0] = 10; //設定第一個元素為10
int arrInt[9] = 100; //設定第十個元素為100
在C/C++中,索引(index)是從 0 ~ size-1,本例即為arrInt[0]~arrInt[9]
第一個元素是arrInt[0]、第二個元素是arrInt[1]...第10個元素是arrInt[9]。
而使用arrInt[10]會不會出問題呢?其實不會,因為C/C++沒有提供邊界檢查(bound checking)的功能
,也因為沒有邊界檢查,而造成更改到其它資料的問題。
接著來看陣列中示素記憶體分佈(layout)的位置:
#include <iostream>
int main()
{
int arrInt[10];
int i;
std::cout<<"size-of arrInt:"<<sizeof(arrInt)<<'\n';
for( i = 0 ; i < 10 ; ++i )
std::cout<<"address of arrInt["<<i<<"]:"<<&arrInt[i]<<'\n';
return 0;
}
輸出:
size-of arrInt:40
address of arrInt[0]:0012FF58
address of arrInt[1]:0012FF5C
address of arrInt[2]:0012FF60
address of arrInt[3]:0012FF64
address of arrInt[4]:0012FF68
address of arrInt[5]:0012FF6C
address of arrInt[6]:0012FF70
address of arrInt[7]:0012FF74
address of arrInt[8]:0012FF78
address of arrInt[9]:0012FF7C
陣列arrInt總共佔的記憶體大小為40,即陣列中元素的型態大小乘於個數 => sizeof(int) * 10
陣列arrInt的位址從0012FF58,即為arrInt[0]的位址,每加4,即為下一個元素的位址。
在C/C++中arrInt與&arrInt[0]得到的結果都是一樣的,但一般都寫成前者居多。
ps:以上陣列位址可能會與本人不一樣
一維陣列介紹完畢之後,接著以二維陣列來說明多維陣列的概念
宣告二維陣列的方式與一維陣列一樣,如下:
T TwoDimArray[HEIGHT][WIDTH];
舉例:
#include <iostream>
int main()
{
int arrInt[4][3];
int i,j;
std::cout<<"size-of arrInt:"<<sizeof(arrInt)<<'\n';
for( i = 0 ; i < 4 ; ++i )
for( j = 0 ; j < 3 ; ++j )
std::cout<<"address of arrInt["<<i<<"]["<<j<<"]:"<<&arrInt[i][j]<<'\n';
return 0;
}
輸出:
size-of arrInt:48
address of arrInt[0][0]:0012FF50
address of arrInt[0][1]:0012FF54
address of arrInt[0][2]:0012FF58
address of arrInt[1][0]:0012FF5C
address of arrInt[1][1]:0012FF60
address of arrInt[1][2]:0012FF64
address of arrInt[2][0]:0012FF68
address of arrInt[2][1]:0012FF6C
address of arrInt[2][2]:0012FF70
address of arrInt[3][0]:0012FF74
address of arrInt[3][1]:0012FF78
address of arrInt[3][2]:0012FF7C
在一般書中常喜歡以下列的表格方式來呈現二維陣列:
arrInt[0][0] |
arrInt[0][1] |
arrInt[0][2] |
arrInt[1][0] |
arrInt[1][1] |
arrInt[1][2] |
arrInt[2][0] |
arrInt[2][1] |
arrInt[2][2] |
arrInt[3][0] |
arrInt[3][1] |
arrInt[3][2] |
二維陣列的起始位址即為&arrInt[0][0],也等於arrInt,而arrInt[i][j]的位址怎麼計算呢?如下:
address-of arrInt[i][j] = arrInt + ( i * WIDTH + j ) * sizeof(T)
以上arrInt陣列使用的WIDTH為3,T為int,所以可以自己推推看。
根據以上的計算可以知道,其實每個元素有不同的表示方式:
arrInt[1][0] 也等於 arrInt[0][3]
arrInt[1][1] 也等於 arrInt[0][4]
arrInt[1][2] 也等於 arrInt[0][5]
arrInt[2][0] 也等於 arrInt[0][6] 也等於arrInt[1][3]
arrInt[2][1] 也等於 arrInt[0][7] 也等於arrInt[1][4]
arrInt[2][2] 也等於 arrInt[0][8] 也等於arrInt[1][5]
arrInt[3][0] 也等於 arrInt[0][9] 也等於arrInt[1][6] 也等於arrInt[2][3]
arrInt[3][1] 也等於 arrInt[0][10] 也等於arrInt[1][7] 也等於arrInt[2][4]
arrInt[3][2] 也等於 arrInt[0][11] 也等於arrInt[1][8] 也等於arrInt[2][5]
但在使用時一般還是使用深藍色字的部份。
而arrInt佔記憶體空間大小為48,即為陣列中元素型態的大小乘於HEIGHT乘於WIDTH
本例中HEIGHT為4,WIDTH為3,元素型態大小為sizeof(int)=4,所以得到4*3*4=48
而多維陣列其實與二維陣列是類同的,在此就不做介紹。
接下來介紹一般初學者學碰到的問題,如何將陣列當作引數傳給函式呢?
先看看一維,現在要撰寫一個函數,來印出陣列內的值:
#include <iostream>
void ShowBySizedArray(int arr[3],int size) //Sized Array
{
int i;
for( i = 0 ; i < size ; ++i )
std::cout << arr[i] <<'\t';
std::cout<< '\n';
}
void ShowByUnsizedArray(int arr[],int size) //Unsized Array
{
int i;
for( i = 0 ; i < size ; ++i )
std::cout << arr[i] <<'\t';
std::cout<< '\n';
}
void ShowByPointer(int *pArr,int size) //Pointer
{
int i;
for( i = 0 ; i < size ; ++i )
std::cout << pArr[i] <<'\t';
std::cout<< '\n';
}
int main()
{
int arrInt[3] = { 2 , 3 , 4 };
ShowBySizedArray(arrInt,3);
ShowByUnsizedArray(arrInt,3);
ShowByPointer(arrInt,3);
return 0;
}
顯示結果三個都一樣,有幾個問題值得思考:
(1)ShowBySizedArray中第一個參數int arr[3],在被呼叫時,真正產生大小為3的陣列嗎?
(2)ShowBySizedArray中第一個參數int arr[3],陣列大小3是否真正發生作用呢?
(3)ShowBySizedArray,ShowByUnsizedArray與ShowByPointer三個函數操作情形一樣嗎?
口說無憑嘛!將以上程式的組語翻出來看看(未經優化後):
25: int arrInt[3] = { 2 , 3 , 4 };
00401198 mov dword ptr [ebp-0Ch],2
0040119F mov dword ptr [ebp-8],3
004011A6 mov dword ptr [ebp-4],4
26:
27: ShowBySizedArray(arrInt,3);
004011AD push 3
004011AF lea eax,[ebp-0Ch]
004011B2 push eax
004011B3 call ShowBySizedArray (00401000)
004011B8 add esp,8
28: ShowByUnsizedArray(arrInt,3);
004011BB push 3
004011BD lea ecx,[ebp-0Ch]
004011C0 push ecx
004011C1 call ShowByUnsizedArray (00401080)
004011C6 add esp,8
29: ShowByPointer(arrInt,3);
004011C9 push 3
004011CB lea edx,[ebp-0Ch]
004011CE push edx
004011CF call ShowByPointer (00401100)
004011D4 add esp,8
(若不清楚組語請自行參考書籍,若不清楚何謂stack frame的話,請參考作者所寫的【Stack Frame解析】)
注意紅色的部份,ebp-0ch為arrInt[0]的位址,即為陣列arrInt的位址
呼叫ShowBySizedArray,ShowByUnsizedArray與ShowByPointer時,產生的assembly code傳遞引數都是一樣的
將傳進來的引數由右到左一一堆進(push)stack frame中
push 3即是將函數第二個引數推進stack frame中
lea eax,[ebp-0ch] lea ecx,[ebp-0ch] lea edx,[ebp-0ch]
push eax or push ecx or push edx
將arrInt的位址推進(psuh)stack frame中,傳遞陣列的位址給函式
現在來回答以上問題的答案:
(1)不產生大小為3的陣列
(2)大小3並沒有產生作用,與int arr[]及int *pArr是一樣的
(3)三個函式的操作方式都是一樣的,編譯器會把陣列的參數當作指標來處理
實際上並沒有傳遞整個陣列,而是以傳遞陣列的位址搭配其它的參數來處理。
一維陣列介紹完畢之後,接著來介紹二維陣列的傳遞:
#include <iostream>
void ShowByArray(int arr[][3],int height,int width)
{
int i,j;
for ( i = 0 ; i < height ; ++i )
{
for ( j = 0 ; j < width ; ++j )
{
std::cout<<arr[i][j]<<'\t' ;
}
std::cout<<'\n';
}
}
int main()
{
int arrInt[2][3] = { { 2 , 3 , 4 } , { 5 , 6 , 7 } };
ShowByArray(arrInt,2,3);
return 0;
}
傳遞二維陣列時,第一個維度(dimension)或稱最左邊(leftmost)的維度可以忽略不寫,其餘必須明確表示出來。
為什麼呢?記得之前所說的二維陣列位址如何計算嗎?
T TwoDimArray[HEIGHT][WIDTH];
address-of TwoDimArray[i][j] = TwoDimArray + ( i * WIDTH + j ) * sizeof(T)
在main中產生一個二維陣列arrInt時,在傳給函式時,編譯器需要讓函式知道這個陣列的維度(dimension)和它的
偏移量(offset),才能在函式中正確的計算出每個元素的位址,而進一步做存取的動作。如果不曉得什麼意思的話
,看個簡單的範例:
#include <iostream>
void ShowByArray(int arr[][3],int height,int width)
{
std::cout<<arr[1][2]<<std::endl;
}
int main()
{
int arrInt[2][3] = { { 2 , 3 , 4 } , { 5 , 6 , 7 } };
ShowByArray(arrInt,2,3);
return 0;
}
assembly code:
2: void ShowByArray(int arr[][3],int height,int width)
3: {
...
4: std::cout<<arr[1][2]<<std::endl;
00401018 push offset std::endl (00401af0)
0040101D mov eax,dword ptr [ebp+8] ;ebp+8即為先前推入的arrInt位址,eax = arr
00401020 mov ecx,dword ptr [eax+14h]
00401023 push ecx
00401024 mov ecx,offset std::cout (00442308)
00401029 call std::basic_ostream<char,std::char_traits<char> >::operator<< (00401110)
...
5: }
...
...
9: int arrInt[2][3] = { { 2 , 3 , 4 } , { 5 , 6 , 7 } };
00401068 mov dword ptr [ebp-18h],2
0040106F mov dword ptr [ebp-14h],3
00401076 mov dword ptr [ebp-10h],4
0040107D mov dword ptr [ebp-0Ch],5
00401084 mov dword ptr [ebp-8],6
0040108B mov dword ptr [ebp-4],7
10:
11: ShowByArray(arrInt,2,3);
00401092 push 3
00401094 push 2
00401096 lea eax,[ebp-18h]
00401099 push eax
0040109A call ShowByArray (00401000)
0040109F add esp,0Ch
紅色字14h,即為十進制的20,如何計算出來的呢?
函式得知arr是二維陣列,且偏移量(offset)是3,算出arr[1][2] = arr + ( 1 * 3 + 2 ) * sizeof(int)
所以在傳遞二維陣列時,第一個維度可以忽略,而其它的維度必須明確的顯示。
傳遞多維陣列方式一樣,最左邊(leftmost)的維度可以忽略,其它的維度必須明確的顯示。
如果有興趣,可以參考以下的文章,加強對陣列的概念:
Arrays and pointers in C
Using Arrays
+另外有一個挺有趣的問題,請看以下文章
Writing Bug-Free C Code -> Chapter 2: Know Your Environment -> 2.1.1 The Array Operator
Written By James On 2004/02/08