陣列 (C++)

C++ Arrays 

陣列 (array) 是用來儲存相同資料型態元素的集合,陣列中所有元素會使用記憶體中連續的空間來儲存,且陣列必須明確定義其大小,執行期間不能更改。 


一維陣列 (One Dimensional Arrays)



一維陣列可視為一組線性系列元素。假設有一組整數資料:10 、20、30 、40 以及 50 存入一名為 A 的陣列,其對應的索引值、位址以及元素名稱如圖一所示。在此,每一整數佔記憶體空間為 4 個位元組 (Bytes),且 C/C++ 語言中陣列的起始索引值為 0。此ㄧ維陣列元素 A[0] 的位址,被稱為基底位址 (base address)。

儲存有10 、20、30 以及 40 四個整數的一維陣列
圖一    儲存有10 、20、30 以及 40 四個整數的一維陣列


陣列 A 與其元素的位址,一整數資料型態元素佔記憶體空間 4 位組
圖二    陣列 A 與其元素的位址,一個整數資料型態的元素佔記憶體空間 4 位組


如圖二所示,元素 A[0] 的位址若是 23565450,因為儲存ㄧ個整數會佔記憶體空間 4 個位元組 (Bytes),因此元素 A[1] 的位址就會是 23565454,其餘元素的位址依此類推。亦即元素 

A[i] 的位址 = A[0] 的位址 + i $\times $ sizeof(int)

其中的 i 是陣列的索引值須為正整數,其值起始於 0 至 sizeof (A) / sizeof(int) - 1。利用 sizeof ( ) 運算子可找出陣列或各種資料型態,佔記憶體多少個位元組。例如 sizeof (A) 即可用來找出整個陣列 A 佔記憶體多少個位元組,sizeof(int) 用來找出一個整數資料型態數值佔記憶體多少個位元組。因此,sizeof (A) / sizeof(int) 或 sizeof (A) / sizeof(A[0]) 就可找出陣列 A 的元素個數為何。事實上,陣列名稱 A,就是一個指標 (pointer),也就是常數指標 A 本身儲存著陣列在記憶體中的起始位址 ( 等同於 &A[0] ),而索引值 i $\times $ sizeof(int) 的值就是元素位置在記憶體中的偏移量 (offset) 。因此,對陣列名稱進行操作,其實就是在操作這一個指標。

陣列第 i 個元素,可直接以 A[i] 對其進行存取;若採用指標方式,則以 *(A+i) 對其進行存取,系統會自動將 i 乘上 sizeof(int) 再加上 A 存放陣列起始位址,找到要存取元素的位址。

    
ㄧ維陣列的宣告 (Declaration) 與初始化 (Initialization)

陣列的宣告必須包含有資料型態陣列名稱元素的個數。而一維陣列的宣告方式如下:

資料型態  陣列名稱[元素個數];

例如:

int  number[5];

宣告了名為 number 的陣列,可存放 5 個資料型態為 int 的整數,接下來就可以輸入或指定各個元素值了。

例如:

const int maxSize = 5;
int  number[maxSize];
for (int i = 0; i < maxSize; i++)
{
        cout << "輸入第" << i << "個元素值:";
        cin >> number[i];
}

 cout << "以 number[2] 方式取得陣列第 2 個元素為 " << number[2] << " << "\n";
 cout << "以 *(number + 2) 方式取得陣列第 2 個元素為 " << *(number + 2) << " << "\n";


在上例中我們以宣告資料型態為 const  int 的常數整數來存放指定的陣列大小。須注意的是:由於陣列的大小在宣告時就必須固定下來,因此用來存放陣列的大小的資料型態必須是 const  int ,而不能是 int,否則編譯時會發生錯誤。此外,上例嘗試以 number[2] 方式以及 *(number + 2) 方式來取得陣列第 2 個元素值,下面範例程式一將實際驗證二者的確都可達到相同目的。


範例程式一


#include <iostream>
using namespace std;
const int maxSize = 5;
int  number[maxSize];

int main()
{      
        int i;
        for (i = 0; i < maxSize; i++)
        {
                cout << "輸入第" << i << "個元素值:";
                cin >> number[i];
        }
        cout << "---------------------\n";

        int j;
        for (j = 0; j < maxSize; j++)
                cout << "number[" << j << "] = "<< number[j] << "\n";
        cout << "---------------------\n";
        for (j = 0; j < maxSize; j++)
                cout << "*(number+" << j << ") = " << *(number + j) << "\n";

        return 0;

}


範例程式一輸出


範例程式一輸出


此外,我們也可以在宣告陣列時,一併完成該陣列的初始化。且若陣列宣告時一併初始化,則元素的個數可以直接指明,也可以省略。

例如:

double  price[5] = {50.4, 86.2, 14.0, 36.7, 66.9};

宣告了名為 price 的陣列,可存放 5 個資料型態為 double 的雙精度浮點數。元素 price[0] 為 50.4, price[1] 為 86.2,price[2] 為 14.0,price[3] 為 36.7 且 price[4] 為 66.9。在這個例子中,元素的個數在陣列宣告時就指明為 5。

例如:

int score[ ] = {96, 63, 81, 52};

宣告了名為 score 的 int 整數資料型態陣列,score[0] = 96,score[1] = 63,score[2] = 81 且 score[3] = 52。在這個例子中,元素的個數在陣列宣告時並未指明。注意:元素的個數在此省略不指明,編譯器仍可根據初始化的元素個數來得知陣列的大小。



ㄧ維陣列作為函數的形式參數 (Formal Parameters)

當函數的形式參數是傳值參數 (call by value) 時,傳給函數的參數是實際參數 (actual parameter) 的複製值,函數內對參數的修改並不會影響函數外實際參數的值;而當函數的形式參數是參照參數 (reference parameter) 時,函數則是直接操作實際參數 (actual parameter)。然而為了避免大型陣列在呼叫時的大量複製,陣列若要作為函數的形式參數,在 C++ 中只能以參照參數作為函數的形式參數。 

例如:

如下的函數

int  sum(int x[ ], int arraySize)
{
        int sum = 0;
        int i;
        for (i = 0; i < arraySize; i++)
                sum += x[i];
        return sum;
}

且假設陣列 number 已宣告如下:

const int maxSize = 5;
int number[maxSize] = {10, 20, 30, 40, 50};
int sumOfArray;

則主程式中要呼叫函數 sum() 可寫成如下的敘述

sumOfArray = sum(number, maxSize);

如前所述,陣列名稱 number 是常數指標,其本身的內容值即為陣列的起始位址。因此,只須將陣列的起始位址以及陣列的大小作為函數 sum 的形式參數,函數 sum 就可以直接對陣列 number[ ] 進行直接的操作。


範例程式二


#include <iostream>
using namespace std;

const int maxSize = 5;
int  number[maxSize];
int sum(int x[ ], int arraySize);

int main()
{
    
    for (int i = 0; i < maxSize; i++)
    {
        cout << "輸入第" << i << "個元素值:";
        cin >> number[i];
    }
    cout << "---------------------\n";
    cout << "陣列元素總和為:" << sum(number, maxSize) << "\n";
    
    return 0;
}

int sum(int x[ ], int arraySize)
{
    int sum = 0;
    int i;
    for (i = 0; i < arraySize; i++)
        sum += x[i];
    return sum;
}


範例程式二輸出


範例程式二輸出



如上所述,陣列在 C++ 中只能以參照參數作為函數的形式參數,當形式參數改變時,實際上就是在直接改變真實參數。倘若在某些情況下,你不希望陣列內的元素值會因參照參數的傳入而受到改變,你可在形式參數宣告時,在其前面加上保留字 const。如果函數內有嘗試修改的敘述,將會導致編譯錯誤。

例如:

宣告如下的函數

int  sum(const int x[ ], int arraySize)

就代表陣列 x[ ] 不能在函數 sum( ) 內被修改。 



二維陣列 (Two Dimensional Arrays)



如果我們想要用表格或矩陣的方式來存取資料,我們就可以考慮使用二維陣列。二維陣列,可以看作是一維陣列的陣列。不過,即使是二維陣列,實際上其在記憶體中一樣是線性系列配置陣列元素。

圖三    儲存有 10 、20、30 、40、50 以及 60 六個整數的二維陣列 my_2D_array


二維陣列 my_2D_array 的記憶體配置
圖四    二維陣列 my_2D_array 的記憶體配置


二維陣列的宣告與初始化


二維陣列的宣告方式如下:

資料型態  陣列名稱[總列數][行數];

例如:

int B[4][3];

就是宣告一資料型態為 int ,名稱為 B,4 列 (rows) 3 行 (columns) 的陣列 (注意:在此列是橫向,行是縱向)。


例如:

以下這些敘述可宣告並初始化同樣的二維陣列:

int my_2D_array[2][3] = {{10, 20, 30},        // 第 0 列
                                           {40, 50, 60}};     //  第 1 列


int my_2D_array[2][3] = {10, 20, 30, 40, 50, 60}; 


int my_2D_array[ ][3] = {10, 20, 30, 40, 50, 60}; 

其中可注意到,總列數在宣告一併初始化時是可以省略,但總行數不可省略。因為二維陣列元素在記憶體內是以列的排列方式儲存,依序由第一列儲存,接著第二列,再第三列,一直到最後一列。編譯器若知道總行數,就知道每一列在哪裡結束,下一列在哪裡開始。且初始化後元素總數既可得知,總列數就也可得知。因此,綜合上述,在宣告一併初始化時總列數是可以省略的 ( 但宣告時未一併初始化,總列數則不可省略 )。

上面的宣告除了會產生了一個二維陣列外,還會產生一組常數指標 my_2D_array 常數指標陣列 my_2D_array[2] (內含兩個常數指標元素 my_2D_array[0] 與 my_2D_array[1]),如圖五所示。

其中的 my_2D_array 指向 my_2D_array[0] 的位址,my_2D_array[0] 指向 my_2D_array[0][0] 的位址,my_2D_array[1] 指向 my_2D_array[1][0] 的位址。

因此,若要存取 my_2D_array 第 i 列第 j 行的元素 (i 與 j 為正整數,且 $0 \le i \lt $ 總列數,以及 $0 \le j \lt $ 總行數),除了可以 my_2D_array[ i ][ j ] 進行存取外,也可透過指標 *my_2D_array[ i ] *(*(my_2D_array + i) + j) 來進行存取。

其實二維陣列若透過指標存取的運作概念,其實可視為延伸自一維陣列。還記得若存在有一個一維陣列

A[5]

則陣列名稱 A 其實是一個常數指標,其內容值為該陣列的起始位址。陣列第 i 個元素的位址為 A + i,陣列第 i 個元素的內容值則為 *(A+i)。接下來我們來看二維陣列,若存在有一個二維陣列

B[2][4]

延伸自一維陣列的概念,B[2] 是指向 B[2][4] 的常數指標陣列,而 B 是 B[2] 這個常數指標陣列的常數指標,其內容值為常數指標陣列 B[2] 的起始位址;而常數指標陣列 B[2] 內的 B[0] 元素也是常數指標,其內容值為 B[2][4] 陣列第 0 列的起始位址,而 B[2] 內的常數指標 B[1] 元素,其內容值為 B[2][4] 陣列第 1 列的起始位址。

因此,若要存取該陣列第 i 列 (row) 第 j 行 (column) 的元素值,透過指標方式可以以 

*(B[i] + j) 

或 

*(*(B + i) + j) 

方式存取。換言之,B[i] 或 *(B + i ) 先找到元素所屬列的位址,接著 B[i] + j 或 *(B + i ) + j 再找到元素所屬列中的所屬行位址。最後 *(B[i] + j ) 或 *(*(B + i ) + j) 就可存取第 i 列 (row) 第 j 行 (column) 元素值。


二維陣列 my_2D_array 的常數指標指向位址關係圖

圖五    二維陣列 my_2D_array 的常數指標指向位址關係圖


範例程式三


#include <iostream>
using namespace std;

int main()
{      
        int my_2D_array[2][3] = {{10, 20, 30}, {40, 50, 60}};
        int i, j;
        for (i = 0; i < 2; i++)
            for (j = 0; j < 3; j++)
                cout << "my_2D_array[" << i<< "][" << j <<" ]= " << my_2D_array[i][j] << "\n";
        cout << "-----------------------------\n";
        for (i = 0; i < 2; i++)
             for (j = 0; j < 3; j++)
                 cout << "*(*(my_2D_array + " << i << ") +" << j << ") = " << *(*(my_2D_array + i) + j) << "\n";
       cout << "-----------------------------\n";
       for (i = 0; i < 2; i++)
             for (j = 0; j < 3; j++)
                 cout << "*(my_2D_array[" << i << "] + " << j << ") = " << *(my_2D_array[i] + j) << "\n";
       
        return 0;
}


範例程式三輸出



範例程式三輸出



二維陣列作為函數的形式參數 


二維陣列同一維陣列,須以參照參數的方式作為函數的形式參數。

例如:

宣告一無回傳值,名為 sum 的函數,資料型態為 double 的二維陣列 A 以參照參數的方式作為函數的形式參數如下

void  sum( double A[ ][numCol] );


void  sum( double [ ][numCol]);



範例程式四


#include <iostream>
using namespace std;
const int NUMBER_OF_ROWS = 2;
const int NUMBER_OF_COLUMNS = 3;
void displayArray(int A[ ][NUMBER_OF_COLUMNS]);

int main()
{
    int A[NUMBER_OF_ROWS][NUMBER_OF_COLUMNS] = { 10, 20, 30, 40, 50, 60 };
    displayArray(A);
    return 0;
}

void displayArray(int A[ ][NUMBER_OF_COLUMNS])
{
    for (int i = 0; i < NUMBER_OF_ROWS; i++)
        for (int j = 0; j < NUMBER_OF_COLUMNS; j++)
            cout << A[i][j] << "\n";
    return;
}


範例程式四輸出


範例程式四輸出




三維陣列 (Three Dimensional Arrays)


三維陣列可視為儲存表格式資料的二維陣列的陣列,或可看成二個以上的二維陣列水平疊置,而水平疊置的數目在此我們姑且稱之為層數如圖六所示,位置 (0, 0, 0) 是第一層二維陣列的起頭,位置 (1, 0, 0) 是第二層二維陣列的起頭,位置 (2, 0, 0) 則是第三層二維陣列的起頭。



三維陣列示意圖
圖六    三維陣列示意圖

三維陣列的宣告與初始化


三維陣列的宣告方式如下:

資料型態  陣列名稱[總層數][總列數][行數];


例如:

以下這些敘述可宣告並初始化同樣的二維陣列:

int my_3D_array [2][3][3] = { { { 10, 12, 17 }, { 24, 13, 64 }, { 44, 54, 22 } }, // 第一層的 3 $\times $ 3 表格
                                                { { 8, 33, 14 }, { 28, 95,88 }, { 17, 61, 33} } };   // 第二層的 3 $\times $ 3 表格


int my_3D_array [ ][3][3] = { { { 10, 12, 17 }, { 24, 13, 64 }, { 44, 54, 22 } },
                                                { { 8, 33, 14 }, { 28, 95,88 }, { 17, 61, 33} } };

其中可注意到,總層數在宣告一併初始化時是可以省略,但總列數總行數不可省略。

其記憶體配置如圖七所示。

三維陣列記憶體配置
圖七    三維陣列記憶體配置

如圖八所示,上面的宣告除了會產生了一個三維陣列外,還會產生一個常數指標 my_3D_array 以及二個常數指標陣列 my_3D_array[2] ( 內含常數指標元素 my_3D_array[0] 與 my_3D_array[1] ) 與 my_3D_array[2][3]  ( 內含常數指標元素 my_3D_array[0][0] 、my_3D_array[0][1]、my_3D_array[0][2]、my_3D_array[1][0]、my_3D_array[1][1] 以及  my_3D_array[1][2] )。
                               
三維陣列 my_3D_array 的常數指標指向位址關係圖

圖八    三維陣列 my_3D_array 的常數指標指向位址關係圖

其中的 my_3D_array 指向 my_3D_array[0] 的位址,my_3D_array[0] 指向 my_3D_array[0][0]的位址;my_my_3D_array[1] 指向 my_3D_array[1][0]的位址;my_3D_array[0][0] 指向 my_3D_array[0][0][0] 的位址;my_3D_array[0][1] 指向 my_3D_array[0][1][0] 的位址...my_3D_array[1][2] 指向 my_3D_array[1][2][0] 的位址。

因此,若要存取 my_2D_array 第 i 列第 j 行的元素 (i 與 j 為正整數,且 $0 \le i \lt $ 總列數,以及 $0 \le j \lt $ 總行數),除了可以 my_2D_array[ i ][ j ] 進行存取外,也可透過指標 *my_2D_array[ i ] 或 *(*(my_2D_array + i) + j) 進行存取。

若宣告一個已知資料型態的三維陣列  A[NumberOfDepths][NumberOfRows][NumberOfColumns],則可以 A[k][i][j] ( k、i 與 j 為正整數,且 0 $\leq$ k $\lt $ NumberOfDepths, 0 $\leq$ i $\lt $ NumberOfRows, 0 $\leq$ j $\lt $ NumberOfColumns) 對位置 (k, i, j) 的元素進行存取。但是若想透過指標存取,其運作類似一、二維陣列,概念說明如下:

設三維陣列 C[2][3][3] 已宣告,記憶體除了會產生 C[2][3][3] 陣列外,還會產生一個常數指標與兩個常數指標陣列,分別是 C、C[2] 以及 C[2][3]。

陣列名稱 C 是一個常數指標,其內容值為常數指標陣列 C[2] 的起始位址,C[2] 陣列中的 C[0] 的內容值為常數指標陣列 C[2][3] 中元素 C[0][0] 的起始位址;C[2] 陣列中的 C[1] 的內容值為陣列 C[2][3] 中 C[1][0] 的起始位址;C[2][3] 中 C[0][0] 的內容值為 C[0][0][0] 的起始位址,C[0][1] 的內容值為 C[0][1][0] 的起始位址,C[2][3] 中 C[0][1] 的內容值為 C[0][1][0] 的起始位址,C[0][1] 的內容值為 C[0][1][0] 的起始位址,...,C[1][2] 的內容值為 C[1][2][0] 的起始位址。

因此,若要存取該陣列第 k 層 (depth) 第 i 列 (row) 第 j 行 (column) 元素值 ( k、i 與 j 皆為正整數,且$0 \le k \lt $ 總層數, $0 \le i \lt $ 總列數,以及 $0 \le j \lt $ 總行數 ),透過指標方式可以以

    *(*(C[k] + i) + j) 

 *(C[k][i] + j) 

或 

*(*(*(C + k) + i) + j) 

這三種方式存取。換言之,C[k] 或 *(C + k ) 先找到元素所屬層的起始位址,C[k] + i 或 C[k][i] 或 *(C + k ) + i 再找到元素所屬層中的列位址,接著 (C[k] + i ) + j 或 C[k][i] + j 或 *(*(C + k ) + i) + j 就可找到第 k 層中的第 i 列第 j 行的元素位址。最後 *((C[k] + i ) + j) 或 *(C[k][i] + j)  或 *(*(*(C + k ) + i) + j) 就可對該位置的元素進行存取。


範例程式五


#include <iostream>
using namespace std;
int main()
{
    int my_3D_array[ ][3][3] = { { { 10, 12, 17 }, { 24, 13, 64 }, { 44, 54, 22 } },
                                                { { 8, 33, 14 }, { 28, 95,88 }, { 17, 61, 33} } };
    cout << " my_3D_array[1][1][2] = " << my_3D_array[1][1][2] << "\n";
    cout << " *(*(my_3D_array[1] + 1) + 2) = " << *(*(my_3D_array[1] + 1) + 2) << "\n";
    cout << " *(my_3D_array[1][1] + 2) = " << *(my_3D_array[1][1] + 2) << "\n";
    cout << " *(*(*(my_3D_array + 1) + 1) + 2) = " << *(*(*(my_3D_array + 1)+1)+2) << "\n";
}



範例程式五輸出


範例程式五輸出



三維陣列作為函數的形式參數 



三維陣列同一維、二維陣列,須以參照參數的方式作為函數的形式參數。

例如:

宣告一無回傳值,名為 sum 的函數,資料型態為 double 的三維陣列 A 以參照參數的方式作為函數的形式參數如下

void  sum( double A[ ][numRow][numCol]);


void  sum( double [ ][numRow][numCol]);



範例程式六


#include <iostream>
using namespace std;
const int NUMBER_OF_DEPTHS = 2;
const int NUMBER_OF_ROWS = 3;
const int NUMBER_OF_COLUMNS = 3;
void displayArray(int A[ ][NUMBER_OF_ROWS][NUMBER_OF_COLUMNS]);

int main()
{
    int A[NUMBER_OF_DEPTHS][NUMBER_OF_ROWS][NUMBER_OF_COLUMNS] = { { { 10, 12, 17 }, 
                                         { 24, 13, 64 }, { 44, 54, 22 } },{ { 8, 33, 14 }, { 28, 95,88 }, { 17, 61, 33} } };
    displayArray(A);
    return 0;
}

void displayArray(int A[ ][NUMBER_OF_ROWS][NUMBER_OF_COLUMNS])
{
    for (int k = 0; k < NUMBER_OF_DEPTHS; k++)
        for (int i = 0; i < NUMBER_OF_ROWS; i++)
            for (int j = 0; j < NUMBER_OF_COLUMNS; j++)
                cout << A[k][i][j] << "\n";
    return;
}


範例程式六輸出


範例程式六輸出



三維陣列以上的多維陣列 (Multidimensional Arrays)



N 維陣列的宣告方式如下:

資料型態  陣列名稱[正整數常量表示式_1][整數常量表示式_2]﹒[整數常量表示式_N];

N 維陣列內元素的存取方式如下:

陣列名稱[整數常量表示式_1][整數常量表示式_2]﹒[整數常量表示式_N];








留言

這個網誌中的熱門文章

三段式電子開關電路

首數、尾數與位數

分壓偏壓 BJT 放大電路的直流分析及其近似解的條件

MOSFET 共汲極放大電路 (源極隨耦器) 小訊號分析

為什麼理想的 OPA 電壓放大器有虛短路與虛斷路現象

具有倒數計時自動回復功能的行人穿越道號誌控制電路