C++ 指针的指针与动态多维数组详解

指针的指针

“指针的指针”即一个指针,其保存的内容是“另一个指针的地址”。

  • 指向 int 的一级指针使用一个星号:
    int* ptr;   // 指向 int 的指针,一个星号
    
  • 指向“指向 int 的指针”的指针使用两个星号:
    int** ptrptr; // 指向 int* 的指针,两个星号
    

其使用方式与普通指针一致:先解引用获得一级指针值,再解引用一级指针取得最终数据;可连续解引用:

```cpp
int value{ 5 };

int* ptr{ &value };
std::cout << *ptr << '\n';      // 第一次解引用,输出 5

int** ptrptr{ &ptr };
std::cout << **ptrptr << '\n';  // 两次解引用,仍输出 5

注意:不能直接把“值”的地址再取地址:

```cpp
int value{ 5 };
int** ptrptr{ &&value }; // ❌ 非法,&value 为右值,operator& 需左值

但可将指针的指针初始化为空指针:

```cpp
int** ptrptr{ nullptr };

指针数组

指针的指针常用于动态分配“指针数组”:

```cpp
int** array{ new int*[10] }; // 分配含 10 个 int* 的动态数组

与常规动态数组的唯一区别是元素类型为 int* 而非 int

二维动态数组

指针的指针另一常见用途是实现动态二维数组(回顾《C 风格多维数组》)。
固定二维数组可简单声明:

```cpp
int array[10][5];

动态分配二维数组则稍显复杂。若最右维度为 constexpr,可写:

```cpp
int x{ 7 };                    // 运行期值
int (*array)[5]{ new int[x][5] }; // 括号必不可少

括号确保 array 为“指向含 5 个 int 的数组的指针”。若无括号,编译器会将其解释为“含 5 个 int* 的数组”。

可用 auto 简化:

```cpp
int x{ 7 };
auto array{ new int[x][5] }; // 简洁得多

但若最右维度非常量,则必须分步构造:

  1. 先分配指针数组(行);
  2. 再为每行独立分配列数组。
```cpp
int** array{ new int*[10] }; // 10 行
for (int count{ 0 }; count < 10; ++count)
    array[count] = new int[5]; // 每行 5 列

访问方式与静态二维数组相同:

```cpp
array[9][4] = 3; // 等价于 (array[9])[4] = 3;

利用此方法,各行列数可不同,例如生成三角矩阵:

```cpp
int** array{ new int*[10] };
for (int count{ 0 }; count < 10; ++count)
    array[count] = new int[count + 1]; // 第 i 行有 i+1 列

释放须按相反顺序:先逐行 delete[],再 delete[] 行指针数组:

```cpp
for (int count{ 0 }; count < 10; ++count)
    delete[] array[count];
delete[] array; // 最后释放行指针数组

若先释放 array 再释放各行,会访问已回收内存,导致未定义行为。

扁平化二维数组

由于二维动态数组分配/释放复杂且易出错,常将二维数组(x×y)扁平成一维数组(x*y):

```cpp
// 不再写:
int** array{ new int*[10] };
for (int count{ 0 }; count < 10; ++count)
    array[count] = new int[5];

// 而是写:
int* array{ new int[50] }; // 10×5 扁平成一维

用简单公式把二维坐标映射到一维索引:

```cpp
int getSingleIndex(int row, int col, int numberOfColumnsInArray)
{
    return row * numberOfColumnsInArray + col;
}

// 将扁平数组的 [9][4] 设为 3
array[getSingleIndex(9, 4, 5)] = 3;

按地址传递指针

与通过指针形参修改实参值类似,可把“指针的指针”传给函数,以改变指针本身指向。不过,若函数需修改指针实参指向何处,更推荐使用“指针的引用”(参见《按地址传递(下)》)。

多级指针

可继续声明三级指针:

```cpp
int*** ptrx3;

用于动态三维数组,但需嵌套循环,极易出错。
甚至可声明四级指针:

```cpp
int**** ptrx4;

或更多层,但实践中极少需要如此深度的间接寻址。

结论

除非别无选择,否则应避免使用指针的指针:

  • 使用复杂;
  • 极易因双重解引用而误碰空指针或悬空指针。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"