<p>声明指向指针的指针需逐级使用 ,如 int* pp = &p;初始化必须逐层取地址,不可跳级(如 &a 或 &&a 非法);解引用前须检查每层非空,否则易段错误;多用于动态修改指针值或不规则二维数据。</p>

怎么声明和初始化一个指向指针的指针
C++里没有“多级指针”这个语法概念,只有“指针类型可以指向另一个指针变量”。比如 int*<em></em> 是一个指向 int 类型变量的指针,不是什么特殊结构。
声明时要对齐层级:
-
int* p指向int -
int*<em> pp</em>指向int(即p的地址) -
int*<strong> ppp</strong>指向int(即pp的地址)
初始化必须逐层取地址:
int a = 42; int* p = &a; // p 存 a 的地址 int** pp = &p; // pp 存 p 的地址,不能写成 &a 或 &&a int*** ppp = &pp; // 同理,只能是 &pp
常见错误是跳级赋值:int** pp = &&a 非法,C++ 不允许连续取地址。
立即学习“C++免费学习笔记(深入)”;
解引用时为什么容易段错误
**pp 看似简单,实际要经过两次内存访问:先读 pp 的值(得到 p 的地址),再用那个地址去读 p 的值(得到 a 的地址),最后再读一次才拿到 a。任意一层为空或野指针都会崩。
典型崩溃场景:
-
pp未初始化(值为随机地址) -
pp指向了一个已释放的int*变量 -
*pp是空指针,但直接写**pp = 100
安全做法是逐层检查:
if (pp != nullptr && *pp != nullptr) {
**pp = 100; // 这样才相对稳妥
}
注意:编译器不会帮你做这些检查,运行时崩溃前毫无提示。
什么时候真需要 int** 而不是传引用或二级数组
int** 主要用于两类场景,其他情况多半是设计过重:
- 函数需要修改调用方的指针值本身(比如动态分配内存并让外层指针指向新地址)
- 表示不规则二维数据,比如稀疏矩阵、字符串数组(
char** argv就是典型)
对比更简单的替代方案:
- 想修改指针指向?用
int*&引用参数更清晰、不易出错 - 想传二维数组?固定大小用
int arr[10][20],动态大小优先考虑std::vector<std::vector<int>></int> -
int**无法表达“每行长度一致”的语义,编译器也做不了越界防护
性能上,int** 多一次间接寻址,缓存不友好;而 std::vector 内存连续,局部性更好。
delete 和 free 怎么配对才不泄漏不重复释放
int** 常见于手动管理二维数组,释放必须严格逆序、逐层操作:
int** mat = new int*[rows];
for (int i = 0; i < rows; ++i) {
mat[i] = new int[cols];
}
// ... 使用
// 释放:
for (int i = 0; i < rows; ++i) {
delete[] mat[i]; // 先删每行
}
delete[] mat; // 再删指针数组本身
关键点:
- 每个
new[]必须配对delete[],混用delete会 UB - 不能只写
delete[] mat就完事——那只会释放指针数组,里面的int*全变野指针 - 如果某行分配失败,要手动回滚前面成功的行,否则泄漏
- C 风格的
malloc/free不能和new/delete混用,哪怕类型看起来一样
现代 C++ 中,这类逻辑几乎全被 std::vector 或 std::unique_ptr 替代,因为手动管理太容易漏掉一层。
指针层级越多,空值检查、生命周期管理和释放顺序就越容易出岔子。三层及以上(int***)基本意味着该重构了。











