C语言指针进阶数组传参、指针函数、函数指针与const指针全解析在C语言的学习路径中指针始终是绕不开的核心难点很多开发者写了多年代码依然会在数组传参、函数指针这类场景里踩坑。今天我们就把最容易混淆的几个知识点串起来从一维整型数组传参讲到const指针结合可运行的代码示例帮你彻底理清逻辑。一、一维整型数组的指针传参很多新手第一次写数组传参时会疑惑为什么明明定义的是int arr[]函数内部用sizeof(arr)得到的却不是数组总长度而是指针的大小。这背后的核心原因是C语言中数组作为函数参数传递时会自动退化为指向数组首元素的指针。我们先看一个错误写法的示例c#include stdio.h// 错误示例无法在函数内获取数组真实长度void print_arr(int arr[]) {// 这里得到的是指针的大小32位系统为4字节64位系统为8字节printf(sizeof(arr) in func: %d\n, sizeof(arr));for(int i 0; i sizeof(arr)/sizeof(int); i) {printf(%d , arr[i]);}}int main() {int arr[] {1,2,3,4,5};printf(sizeof(arr) in main: %d\n, sizeof(arr));print_arr(arr);return 0;}运行这段代码你会发现main函数里sizeof(arr)得到的是205个int元素每个4字节但在print_arr函数里得到的却是8最终循环只会打印前1-2个元素完全不符合预期。正确的指针传参写法必须额外传入数组的长度参数c#include stdio.h// 标准写法用指针接收数组首地址同时传入数组长度void print_arr(int *arr, int len) {for(int i 0; i len; i) {printf(%d , arr[i]);}printf(\n);}int main() {int arr[] {1,2,3,4,5};int len sizeof(arr)/sizeof(arr);print_arr(arr, len);return 0;}这种写法的本质是把数组首元素的地址传递给函数函数内部通过指针偏移来访问数组的每一个元素既不会拷贝整个数组节省栈空间也能通过传入的长度参数准确遍历所有元素。二、一维字符型数组的指针传参字符数组的传参逻辑和整型数组基本一致但因为C语言约定了字符串以\0作为结束标志所以很多场景下我们不需要额外传入长度参数直接通过指针遍历到\0停止即可。最典型的例子就是标准库的strlen函数它的核心实现逻辑就是基于字符指针传参c#include stdio.h// 模拟实现strlen统计字符串长度int my_strlen(char *str) {int cnt 0;while(*str ! \0) {cnt;str;}return cnt;}int main() {char str[] hello c pointer;int len my_strlen(str);printf(string length: %d\n, len);return 0;}这里要特别注意一个常见坑如果传入的字符数组没有以\0结尾指针遍历就会越界访问内存直到遇到随机的\0才停止最终得到一个完全错误的长度甚至引发段错误。如果我们只是要读取字符串内容、不希望函数内部修改原数组就可以用const修饰指针指向的内容这也是标准库函数的通用写法c// 安全写法禁止函数内部修改传入的字符串int my_strlen(const char *str) {int cnt 0;while(*str ! \0) {cnt;str; // 允许修改指针本身的指向不允许修改*str的值}return cnt;}这种写法既保护了原字符串的内容不被意外修改也支持直接传入字符串常量作为参数兼容性更强。三、指针函数返回指针的函数指针函数本质是一个普通函数只是它的返回值类型是指针。它的声明格式很容易理解返回值类型 *函数名(参数列表)这里的*是和返回值类型结合的代表这个函数返回一个对应类型的指针。比如我们写一个函数拼接两个字符串的前半部分就可以让它返回字符指针c#include stdio.h#include string.h// 指针函数返回拼接后的新字符串指针char* concat_part(const char *s1, const char *s2) {// 注意这里用static修饰数组避免函数返回后栈空间被释放static char buf;int len1 strlen(s1);strncpy(buf, s1, len1/2);strcat(buf, s2);return buf;}int main() {char *res concat_part(hello, world);printf(%s\n, res); // 输出heworldreturn 0;}这里必须提醒一个高频坑绝对不要返回局部变量的指针。因为局部变量是在栈上分配的函数执行结束后栈空间就会被回收返回的指针会变成野指针后续访问会引发未定义行为。上面示例中用static修饰buf就是把变量的生命周期延长到整个程序运行周期避免了空间被释放的问题更稳妥的写法也可以在堆上动态分配内存由调用方负责释放。很多新手会把指针函数和接下来要讲的函数指针搞混核心区别就是看*有没有被括号包裹没有括号的是指针函数本质是函数返回值是指针被括号包裹的是函数指针本质是指针指向一个函数。四、函数指针指向函数的指针函数指针的核心是指针它存储的不是普通数据的地址而是函数的入口地址。在C语言中函数名本身就代表了函数的入口地址这和数组名代表首元素地址是类似的逻辑。声明函数指针的格式必须用括号把*和变量名包裹起来返回值类型 (*指针名)(参数列表)括号不能省略否则就变成了返回指针的指针函数。我们用一个简单的计算器示例来演示它的用法c#include stdio.h// 定义四个基础运算函数int add(int a, int b) { return a b; }int sub(int a, int b) { return a - b; }int mul(int a, int b) { return a * b; }int divi(int a, int b) { return a / b; }int main() {// 声明函数指针指向参数为两个int、返回值为int的函数int (*calc)(int, int);calc add;printf(10 3 %d\n, calc(10, 3));calc sub;printf(10 - 3 %d\n, calc(10, 3));calc mul;printf(10 * 3 %d\n, calc(10, 3));calc divi;printf(10 / 3 %d\n, calc(10, 3));return 0;}你会发现同一个函数指针变量可以先后指向不同的同类型函数这就是函数指针最强大的地方我们可以把同规则的函数统一管理不需要写大量重复的分支判断代码。实际开发中函数指针的应用非常广泛POSIX标准里的pthread_create线程创建函数就用函数指针接收线程执行函数的入口信号处理函数signal也是通过函数指针来指定收到对应信号后要执行的处理逻辑。我们还可以用typedef给函数指针类型起别名让代码更简洁c// 给函数指针类型起别名后续声明变量更方便typedef int (*CalcFunc)(int, int);// 后续直接用别名声明即可CalcFunc func_arr {add, sub, mul, divi};用函数指针数组我们就可以把多个同类型函数放到数组里通过下标直接调用代码的可扩展性会大大提升。五、const指针理清左定值、右定向规则const是C语言里用来限定不可修改的关键字它和指针结合之后的几种组合是很多人搞混的重灾区。这里我们用一个非常好记的口诀来总结左定值右定向意思是const在*的左边代表指针指向的值不能修改const在*的右边代表指针本身的指向不能修改。我们把三种常见组合逐一拆解const int *p 常量指针const在*的左边它修饰的是指针指向的数据。也就是说你不能通过*p修改p指向的内存里的值但p本身的指向可以改变cint a 10, b 20;const int *p a;// *p 100; // 错误不能修改指向的值p b; // 正确可以修改指针的指向int * const p 指针常量const在*的右边它修饰的是指针变量本身。也就是说p的指向一旦初始化就不能改变但你可以通过*p修改指向的内存里的值cint a 10, b 20;int * const p a;*p 100; // 正确可以修改指向的值// p b; // 错误不能修改指针的指向这里要注意指针常量必须在定义的时候就完成初始化否则后续没有机会给它赋值会直接报编译错误。const int * const p 指向常量的指针常量*的左右两边都有const相当于同时锁死了两个权限既不能修改指针的指向也不能通过*p修改指向的值是完全只读的指针cint a 10;const int * const p a;// *p 100; // 错误// p b; // 错误这个规则完全可以覆盖所有const和指针的组合场景再也不用死记硬背了。最后总结今天我们梳理的这几个知识点覆盖了C语言指针进阶里最核心的高频场景数组传参要记住数组退化为指针的特性永远记得给数组传长度指针函数的核心坑点是不要返回局部变量指针函数指针本质是存储函数入口地址是实现回调、多态的基础const指针用左定值右定向的口诀就能一秒判断权限。