指標與陣列


在宣告陣列之後,使用到陣列變數時,會取得首元素的位址,例如在下面的程式中將指出,陣列 arr&arr[0] 的值是相同的:

#include <iostream> 
using namespace std; 

int main() { 
    int arr[10] = {0}; 
    cout << "arr:\t\t" << arr << endl
         << "&arr[0]:\t" << &arr[0] << endl; 

    return 0; 
}

執行結果:

arr:           0x61fe98
&arr[0]:       0x61fe98

之前也曾經談過,陣列索引其實是相對於首元素位址的位移量,下面這個程式以指標運算與陣列索引操作,顯示出相同的對應位址值:

#include <iostream> 
using namespace std; 

int main() {
    constexpr int LENGTH = 10;
    int arr[LENGTH] = {0}; 
    int *p = arr; 

    for(int i = 0; i < LENGTH; i++) { 
        cout << "&arr[" << i << "]: " << &arr[i] 
             << "\tp+" << i << ": " << p + i << endl; 
    } 

    return 0; 
}

每個元素的位址型態是 int*,執行結果如下:

&arr[0]: 0x61fe8c       p+0: 0x61fe8c
&arr[1]: 0x61fe90       p+1: 0x61fe90
&arr[2]: 0x61fe94       p+2: 0x61fe94
&arr[3]: 0x61fe98       p+3: 0x61fe98
&arr[4]: 0x61fe9c       p+4: 0x61fe9c
&arr[5]: 0x61fea0       p+5: 0x61fea0
&arr[6]: 0x61fea4       p+6: 0x61fea4
&arr[7]: 0x61fea8       p+7: 0x61fea8
&arr[8]: 0x61feac       p+8: 0x61feac
&arr[9]: 0x61feb0       p+9: 0x61feb0

在這個程式中,將陣列的首元素位址指定給 p,然後對 p 遞增運算,每遞增一個單位,陣列相對應索引的元素之位址都相同。

也可以利用指標運算來取出陣列的元素值,如以下的程式所示:

#include <iostream> 
using namespace std; 

int main() {
    constexpr int LENGTH = 5;
    int arr[LENGTH] = {10, 20, 30, 40, 50}; 
    int *p = arr; 

    // 以指標方式存取
    for(int i = 0; i < LENGTH; i++) {
        cout << "*(p + " << i << "): " << *(p + i) << endl;
    }
    cout << endl;

    // 以指標方式存取資料 
    for(int i = 0; i < LENGTH; i++) {
        cout << "*(arr + " << i << "): " << *(arr+i) << endl;
    }
    cout << endl;

    return 0; 
}

執行結果:

*(p + 0): 10
*(p + 1): 20
*(p + 2): 30
*(p + 3): 40
*(p + 4): 50

*(arr + 0): 10
*(arr + 1): 20
*(arr + 2): 30
*(arr + 3): 40
*(arr + 4): 50

在〈陣列〉中談過,可以使用 sizeof 來計算陣列長度,在認識指標及其運算後,可以知道透過 C++ 11 提供的 beginend 函式,也可以計算陣列長度:

#include <iostream> 
using namespace std; 

int main() {
    constexpr int LENGTH = 5;
    int arr[LENGTH] = {10, 20, 30, 40, 50}; 

    cout << sizeof(arr)/sizeof(*arr) << endl;  // 顯示 5
    cout << end(arr) - begin(arr) << endl;     // 顯示 5

    return 0; 
}

實際上,透過以下也可以計算出陣列長度:

constexpr int LENGTH = 5;
int arr[LENGTH] = {10, 20, 30, 40, 50}; 
int len = *(&arr + 1) - arr;

來解釋一下為什麼這行得通,如果使用 &arr 會取得 arr 變數的位址值,也就是陣列資料儲存的位址,與首元素位址是相同的值:

#include <iostream> 
using namespace std; 

int main() {
    constexpr int LENGTH = 5;
    int arr[LENGTH] = {10, 20, 30, 40, 50}; 

    cout << arr << endl;  // 顯示 0x61fea8
    cout << &arr << endl; // 顯示 0x61fea8

    return 0; 
}

每個陣列元素的位址型態是 int*,這表示對它進行運算時,是以 int 長度為單位,而 arr 變數的位址處就是陣列資料的開端,&arr 型態會是…呃…int (*)[LENGTH],如果想宣告相對應的變數,可以如下:

int (*p)[LENGTH] = &arr;

int (*)[LENGTH] 表示,對它進行運算時,是以 LENGTHint 長度為單位,因此 &arr + 1 的結果,會是陣列使用的空間後之位址,而 *(&arr + 1) 的值型態會回到 int*,也就是最後一個元素後之位址,這時就可以與 int*arr 進行相減,也就是與第一個元素之位址相減,就可以得到陣列長度了。

舉這個例子的重點之一是,對於同一個位址,指標的型態決定了該怎麼看得相對應相加、相減計算;另一個重點是,透過陣列變數會取得首元素的位址,將陣列變數指定給指標 p,就只是取得首元素位址並儲存在 p,如果將 p 傳給 sizeof,那使用的會是指標 p 的型態,而不是原陣列的型態,這會令 sizeof、以及方才那神奇計算長度的方式失效,例如:

#include <iostream> 
using namespace std; 

int main() {
    constexpr int LENGTH = 5;
    int arr[LENGTH] = {10, 20, 30, 40, 50}; 
    int *p = arr;

    cout << p << endl;  // 顯示 0x61fea8
    cout << &p << endl; // 顯示 0x61fea4

    cout << sizeof(p)/sizeof(*p) << endl;  // 顯示 1
    cout << *(&p + 1) - p << endl;         // 顯示 -1605544

    return 0; 
}

以上的程式若試圖使用 begin(p)end(p),會編譯失敗,因此試著對 p 進行 for range 語法,也會導致編譯失敗。