访问数组元素与指针和数组的关系(四十五)

1. 访问数组元素

1.1 使用下标运算符

数组的基本声明形式为:

type arrayName[dimension];

其中,dimension 表示数组中元素的个数,下标从 0 开始,因此对于一个含有 10 个元素的数组,其合法下标范围是 0 到 9。

例如:

#include <iostream>
using std::cout;
using std::endl;

int main() {
    
    
    int arr[10] = {
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    // 访问第三个元素(下标为2)
    cout << "arr[2] = " << arr[2] << endl;
    return 0;
}

提示:
访问数组元素时,建议使用 size_t 类型作为下标变量,因为 size_t 是无符号类型且足够大来表示内存中任意对象的大小,定义在 <cstddef> 中。

1.2 使用范围 for 语句遍历数组

范围 for 语句可以简化数组元素的遍历。编译器会自动推导数组的大小,因此无需手动控制循环变量。

示例:

#include <iostream>
using std::cout;
using std::endl;

int main() {
    
    
    int scores[11] = {
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // 遍历数组中的所有元素并输出
    for (auto count : scores)
        cout << count << " ";
    cout << endl;
    return 0;
}

在上述代码中,范围 for 语句会遍历数组 scores 中的每个元素,并依次输出。

2. 指针与数组

2.1 数组名自动转换为指针

在 C++ 中,数组名在许多表达式中会自动转换为指向数组首元素的指针。例如:

#include <iostream>
#include <string>
using std::string;
using std::cout;
using std::endl;

int main() {
    
    
    string nums[] = {
    
    "one", "two", "three"};
    // 显式地取数组首元素的地址
    string *p = &nums[0];
    // 数组名在表达式中自动转换为指向首元素的指针
    string *p2 = nums;  // 等价于 p2 = &nums[0]
    
    cout << "First element: " << *p2 << endl; // 输出 "one"
    return 0;
}

2.2 使用 auto 推导数组指针类型

当使用数组作为 auto 变量的初始值时,数组会自动转换为指向首元素的指针。例如:

#include <iostream>
using std::cout;
using std::endl;

int main() {
    
    
    int ia[] = {
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    auto ia2 = ia;  // ia2 的类型是 int*, 指向数组 ia 的首元素
    //ia2 = 42;   // 错误:不能将整型值赋给一个指针
    cout << "ia2[2] = " << ia2[2] << endl;  // 输出 2
    return 0;
}

注意:
如果使用 decltype(ia) 来获取类型,则返回的是整个数组类型,而不是指针。例如:

decltype(ia) ia3 = {
     
     0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // ia3 的类型为 int[10]
// ia3 = ia2; // 错误:类型不匹配

2.3 指针运算与数组下标

指针可以执行与迭代器类似的随机访问运算。当指针指向数组中的某个元素时,可以利用加法运算符移动指针到其它位置,并使用解引用运算符获取对应元素。例如:

#include <iostream>
using std::cout;
using std::endl;

int main() {
    
    
    int arr[] = {
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *p = arr;         // p 指向 arr[0]
    int *e = arr + 10;    // e 指向 arr 尾元素的下一位置
    
    // 使用递增运算遍历数组
    for (int *it = p; it != e; ++it)
        cout << *it << " ";
    cout << endl;
    
    // 指针运算示例:获取 arr[4] 的值
    int last = *(arr + 4); // 等价于 arr[4]
    cout << "arr[4] = " << last << endl;
    return 0;
}

提示:
对于数组下标运算 arr[n],编译器实际上将其转换为 *(arr + n)。因此,只要确保 arr + n 在合法范围内,就可以通过解引用获取对应元素。

2.4 使用标准库函数 begin 和 end

C++11 引入了 begin()end() 函数,用于获取数组首元素和尾后位置的指针,使得遍历数组更加安全和方便。示例:

#include <iostream>
#include <iterator> // 定义了 begin 和 end
using std::cout;
using std::endl;

int main() {
    
    
    int ia[] = {
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *beg = std::begin(ia);  // 指向 ia 的首元素
    int *last = std::end(ia);   // 指向 ia 尾元素的下一位置
    
    // 遍历数组
    for (int *p = beg; p != last; ++p)
        cout << *p << " ";
    cout << endl;
    
    return 0;
}

这样,无论数组大小如何,使用 std::begin()std::end() 都可以确保遍历在合法范围内进行。

3. 总结

  • 数组名自动转换:
    在大多数表达式中,数组名自动转换为指向其首元素的指针,使得很多数组操作实际上是指针操作。

  • 指针与下标:
    通过下标运算符访问数组元素时,编译器将数组名转换为指针,然后执行指针加法和解引用。

  • 指针运算:
    指针支持递增、加减运算以及相减得到距离,这与迭代器运算非常相似。确保运算结果指向同一数组内的有效位置(或尾后位置)。

  • 标准库 begin/end:
    C++11 提供的 std::begin()std::end() 函数使得获取数组首元素和尾后位置的指针更安全、易用。

  • 类型推导与数组:
    使用 auto 定义变量时,如果初始值是数组,则会转换为指向首元素的指针,而使用 decltype 则可以获取整个数组类型。

通过深入理解数组与指针的关系,以及如何利用标准库函数安全地访问数组元素,你可以编写出既高效又安全的 C++ 代码,避免因数组越界而产生的安全问题。

参考资料

  • cppreference.com 关于数组、指针以及 std::begin()/std::end() 的详细说明
  • 各大 C++ 编码规范(如 Google C++ Style Guide)中对数组访问的建议

希望这篇详细讲解能帮助你全面理解数组和指针的关系,从而更安全有效地访问和操作内置数组中的元素。