指针与数组


在定义数组之后,使用到数组变量时,会获取首元素的地址,例如在下面的程序中将指出,数组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 语法,也会导致编译失败。


展开阅读全文