20. 【C语言】多级指针与复杂声明

📅 2026/7/5 14:56:59
20. 【C语言】多级指针与复杂声明
在第十五篇初识指针时我们说过“指针是存地址的变量”。在第十六篇我们又遇到了“存指针地址的指针”——二级指针。那时候我们浅尝辄止留了一个话头为什么要用二级指针三级指针、多级指针又是什么鬼还有那些像咒语一样的复杂声明到底该怎么读今天我们就来把这些“高阶指针”彻底拿下。读完你会觉得什么int *(*p[10])(int)不过是纸老虎。一、从一级到二级为什么要指向指针的指针一级指针int *p存的是一个int变量的地址。通过它你可以修改那个int的值。但如果你要修改的是指针本身呢比如你想在一个函数里改变传入的指针的值让它指向新分配的内存那你就需要把那个指针的地址传进去——也就是二级指针。看这个场景在函数内部分配内存并将这个内存地址“传出去”给调用者。#includestdio.h#includestdlib.hvoidallocate_array(int**ptr,intsize){*ptr(int*)malloc(size*sizeof(int));// 修改 ptr 指向的那个指针if(*ptr!NULL){for(inti0;isize;i){(*ptr)[i]i*10;}}}intmain(void){int*arrNULL;allocate_array(arr,5);// 传 arr 的地址for(inti0;i5;i){printf(%d ,arr[i]);}printf(\n);free(arr);return0;}输出0 10 20 30 40allocate_array需要修改main里的arr变量本身让它从NULL变成指向新内存的地址。C 语言只有按值传递所以我们必须把arr的地址arr传进去。这个地址的类型就是int**——指向int*的指针。一级指针可以修改值二级指针可以修改一级指针以此类推。这就是多级指针存在的根本逻辑。二、二级指针与动态二维数组第十六篇我们学过二维数组在内存中是行优先连续存储的。那种二维数组要求每一行长度相同。但有时我们需要一个“不规则的”二维数组每一行的长度可以不同比如存储变长字符串或者我们需要在运行时决定行列大小且不想被“矩形”约束。这时就可以用二级指针 多次 malloc来构造一个“指针数组”每个指针指向一个独立的一维数组。#includestdio.h#includestdlib.hintmain(void){introws3;int**matrix(int**)malloc(rows*sizeof(int*));// 1. 分配指针数组if(matrixNULL)return1;// 2. 为每一行分配不同的长度intcols[]{2,4,3};// 第一行2个元素第二行4个第三行3个for(inti0;irows;i){matrix[i](int*)malloc(cols[i]*sizeof(int));if(matrix[i]NULL)return1;}// 3. 赋值for(inti0;irows;i){for(intj0;jcols[i];j){matrix[i][j]i*10j;}}// 4. 打印for(inti0;irows;i){printf(第 %d 行: ,i);for(intj0;jcols[i];j){printf(%2d ,matrix[i][j]);}printf(\n);}// 5. 释放先释放每一行再释放指针数组for(inti0;irows;i){free(matrix[i]);}free(matrix);return0;}输出第 0 行: 0 1 第 1 行: 10 11 12 13 第 2 行: 20 21 23这种结构被称为“锯齿数组”或“Iliffe 向量”。它的优点是每行长度灵活缺点是内存不连续可能有缓存不友好的问题而且释放时比较繁琐要先释放每行再释放行指针数组。图形化理解matrix (int**) | v [0] --- [0][1] [1] --- [10][11][12][13] [2] --- [20][21][22]matrix是一个int**它指向一个由int*组成的数组每个int*又各自指向一个真正的int数组。三、三级指针多级指针的适度原则理论上可以声明三级、四级甚至更高级的指针例如int ***p;。它就是一个指向“指向int*的指针”的指针。每增加一级就是多一层的间接访问。inta5;int*p1a;// p1 - aint**p2p1;// p2 - p1 - aint***p3p2;// p3 - p2 - p1 - aprintf(%d\n,***p3);// 输出 5但说实话在常规编程中三级及以上指针极少使用。如果你发现自己需要三级指针往往意味着需要重新审视数据结构设计可能用结构体封装会更清晰。多级指针的合理用途主要有二级指针修改一级指针函数内分配内存、链表头指针操作、动态锯齿数组、命令行参数char **argv。三级指针极偶尔用于修改二级指针比如在函数内修改二维动态数组的结构但多数情况下可以用结构体取代。守则能少一级就少一级适度即可。四、复杂声明的阅读右左法则实战学了这么多指针、数组、函数它们混合在一起时声明会变得像天书。比如int*(*p[10])(int);这是什么鬼我们上篇提到的“右左法则”就是解咒的钥匙。右左法则The Clockwise/Spiral Rule 的简化找到变量名从它开始。先向右看遇到[ ]数组或( )函数就解析出来。再向左看遇到*指针、const等就解析出来。如果遇到括号()跳进去重复 2-3 步。一直处理到类型名然后继续往外层扩展。我们用几个例子练习。例一int *p[10]变量名是p。向右看[10]→p是数组有 10 个元素。再向左看*→ 元素是指针。再向左看int→ 指针指向int。结论p是一个有 10 个元素的数组每个元素是int*。指针数组例二int (*p)[10]变量名p被括号包着先处理括号内部。括号内向右没有东西。括号内向左*→p是指针。跳出括号向右看[10]→ 指向有 10 个元素的数组。向左看int→ 数组元素是int。结论p是一个指针指向有 10 个int的数组。数组指针例三int *(*p[10])(int)变量名p。向右看[10]→p是数组有 10 个元素。向左看*→ 元素是指针。再向右看(int)→ 这个指针指向函数该函数接收一个int参数。向左看*→ 函数返回指针。再向左int→ 指针指向int。结论p是一个有 10 个元素的数组每个元素是一个函数指针指向“接收一个int、返回int*”的函数。看其实并不神秘只是需要耐心拆解。例四int (*func(int))(void)这不是变量声明而是函数声明。func是函数名。向右看(int)→ 接收一个int参数。向左看*→ 这个函数返回一个指针。跳出向右看(void)→ 返回的指针指向一个函数该函数接收void。向左看int→ 该函数返回int。结论func是一个函数接收int参数返回一个函数指针该指针指向“接收void返回int”的函数。这种声明在实际项目中极其罕见若遇到通常都会用typedef简化。五、typedef是复杂声明的救星当你遇到需要反复使用的复杂指针类型时用typedef分步定义是提升代码可读性的最佳实践。比如上面那个“返回函数指针的函数声明”正常人都会拆解typedefint(*func_ptr)(void);// func_ptr 是函数指针类型func_ptrfunc(int);// func 是函数接收 int返回 func_ptr清晰无比。所以如果你发现自己在手写三级以上的指针或嵌套函数指针立刻停下来用typedef分步定义。这不仅是给自己看更是给未来维护你代码的人包括六个月后的你自己行善。六、常见错误与陷阱1. 解引用级别搞错inta10;int*pa;int**ppp;printf(%d\n,*pp);// 错误*pp 是 p地址不是 a应该**pp才是a。多级指针的解引用必须逐级剥开心里默念pp→*pp是一级指针 →**pp才是值。2. 忘记为二级指针分配行指针数组int**matrix;matrix[0]malloc(10*sizeof(int));// 错误matrix 未初始化必须先matrix malloc(rows * sizeof(int*));分配指针数组。3. 释放顺序错误int**matrixmalloc(rows*sizeof(int*));for(inti0;irows;i)matrix[i]malloc(cols*sizeof(int));free(matrix);// 错误先释放了行指针数组导致每行内存泄漏必须先释放每一行的内存再释放行指针数组。顺序反了就会泄漏内层数组。4. 混淆指针数组和数组指针再提醒int *arr[10]arr是数组元素是指针。int (*arr)[10]arr是指针指向数组。每次看到这种声明用右左法则读一遍别靠感觉猜。七、小结多级指针和复杂声明不是魔法而是一套有逻辑的体系一级指针T*存的是T的地址。二级指针T**存的是T*的地址常用于在函数内修改一级指针的值或构造动态锯齿数组。三级及以上指针极少出现在良好设计的代码中如果出现重新审视结构或用typedef化简。复杂声明用“右左法则”按部就班拆解或者直接用typedef分步构建可读的类型别名。至此我们已经完成了指针与内存管理阶段的所有核心内容。你已经有能力操作任何内存结构理解任何指针声明。下一阶段我们将进入结构化数据的世界——struct结构体、union共用体和enum枚举让你能把不同类型的数据打包在一起构建真正贴近现实的复杂数据模型。课后小练习写一个函数void create_matrix(int ***mat, int rows, int cols)在函数内部分配一个rows × cols的二维动态数组锯齿状通过三级指针参数传回给调用者。并在main中验证并释放。解释以下声明的含义并尝试声明对应的变量并简单使用int*(*fp)(int);int(*arr[5])(double);char*(*(*p)(void))[10];提示用右左法则然后尝试用typedef重写它们看看是否更易读。分析以下代码的错误int**pmalloc(3*sizeof(int));p[0][0]10;它错在哪里如何修正小挑战实现一个能够处理命令行参数中多级子命令的简单框架用二级指针argv和函数指针数组根据第一个参数的不同调用不同的处理函数。处理函数打印自己的名字即可。体会多级指针和函数指针的结合。我们下期见获取本系列示例代码请访问 GitCode 仓库。