C 语言的精髓在于指针,而指针函数、函数指针和const 修饰的指针是指针体系中既核心又易混淆的知识点。本文将从定义、用法、易错点和实战场景出发,全面解析这三大知识点。
一、指针函数:返回指针的函数
1. 定义与语法
指针函数,本质是函数,只是其返回值类型为指针(地址)。语法结构如下:
返回值类型 *函数名(参数列表);
// 例:返回int类型指针的函数
int *Add(int x, int y);
指针函数的核心特征:* 紧跟在返回值类型后,函数名是核心,优先级高于*(这也是和函数指针的关键区别)。
2. 最致命的错误:返回局部变量的地址
局部变量存储在栈区,函数执行结束后,栈区内存会被系统释放(相当于 “大厦被铲平”),此时返回的地址虽有效,但指向的内容已失效,访问会导致未定义行为(程序崩溃、输出随机值等)。
你看到一个大厦的路标(指针),根据上面的地址(指针的内容)找大厦,你到了地方(指针指向的内容)后,却发现大厦被铲车铲平了(指针指向的内容被释放了)

❌ 错误示例(用户原代码修正版):
#include <stdio.h>
int *Add(int x, int y) {
int sum = 0;
sum = x + y;
return ∑ // 错误:返回栈区局部变量地址
}
int main(void) {
int Num1 = 100, Num2 = 200;
int *ret = Add(Num1, Num2); // ret拿到了地址,但内容已释放
printf("ret = %d\n", *ret); // 输出随机值,行为未定义
return 0;
}
3. 指针函数的正确用法
想要安全返回指针,需让指针指向不会被释放的内存(数据区、堆区、全局区):
(1)返回字符串常量(数据区)
字符串常量存储在只读数据区,程序运行期间始终存在,可安全返回其地址(但注意:字符串常量不可修改)。
#include <stdio.h>
char* inputarray(void) {
char *str = "helloworld"; // 字符串常量,存储在数据区
return str; // 安全返回
}
int main() {
char *p = NULL;
p = inputarray();
// *p = 'H'; // 错误:只读数据区不可修改
printf("p = %s\n",p); // 正确输出:helloworld
return 0;
}
(2)返回动态分配的内存(堆区)
通过malloc/calloc分配的堆区内存,需手动free释放,函数结束后不会自动回收:
#include <stdio.h>
#include <stdlib.h>
int *GetSum(int x, int y) {
int *sum = (int *)malloc(sizeof(int)); // 堆区分配内存
*sum = x + y;
return sum; // 安全返回
}
int main() {
int *p = GetSum(10, 20);
printf("sum = %d\n", *p); // 正确输出:30
free(p); // 必须释放堆区内存,避免内存泄漏
p = NULL;
return 0;
}
(3)返回全局 / 静态变量地址
全局变量、static局部变量存储在全局 / 静态区,生命周期与程序一致:
int *Add(int x, int y) {
static int sum = 0; // static修饰,存储在静态区
sum = x + y;
return ∑ // 安全返回
}
4. 指针函数的实战场景:链式调用
很多库函数(如strcpy)都是指针函数,返回值可直接作为另一个函数的参数,实现链式调用:
#include <stdio.h>
#include <string.h>
int main(void) {
char src[32] = {0};
char dst[32] = {0};
// strcpy(src, "hello world")返回src的地址,作为第二个strcpy的参数
strcpy(dst, strcpy(src, "hello world"));
printf("dst = %s\n", dst); // 输出:hello world
return 0;
}
二、函数指针:指向函数的指针
1. 定义与语法
函数指针,本质是指针,只是其指向的不是普通变量,而是函数的入口地址。语法结构如下:
返回值类型 (*指针名)(参数列表);
// 例:指向“返回int、接收两个int参数”的函数的指针
int (*p)(int, int) = NULL;
⚠️ 关键:括号不能省略!如果写成int *p(int, int),就变成了 “返回 int 指针的函数”(指针函数)。
函数名的本质:函数名本身就是函数的入口地址,可直接赋值给函数指针。
2. 函数指针的基础使用
以四则运算为例,用函数指针指向不同的运算函数,实现灵活调用:
#include <stdio.h>
// 四则运算函数
int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }
int Mul(int x, int y) { return x * y; }
int Div(int x, int y) { return x / y; }
int main() {
int (*p)(int, int) = NULL; // 定义函数指针
int ret = 0;
// 1. 打印函数地址(函数名即地址)
printf("Add函数地址:%p\n", Add);
printf("Sub函数地址:%p\n", Sub);
// 2. 函数指针指向Add函数
p = Add;
// 3. 通过函数指针调用函数(两种写法等价)
ret = p(100, 100); // 推荐写法
// ret = (*p)(100, 100); // 等价写法,*可省略
printf("100+100 = %d\n", ret); // 输出:200
return 0;
}
3. 核心应用:函数指针作为函数参数(回调函数)
函数指针最强大的用法是作为函数参数,实现 “将函数作为参数传递”(回调函数思想),让代码解耦、灵活。
示例 1:可自定义运算规则的计算器
#include <stdio.h>
// 四则运算函数
int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }
int Mul(int x, int y) { return x * y; }
int Div(int x, int y) { return x / y; }
// 计算器函数:接收两个数 + 运算规则(函数指针)
void Calculator(int x, int y, int (*p)(int, int)) {
printf("运算结果:%d\n", p(x, y));
}
int main(void) {
int Num1 = 0, Num2 = 0;
char op = '\0';
printf("请输入运算式(如 10 + 20):");
scanf("%d %c%d", &Num1, &op, &Num2);
// 根据运算符,传递不同的运算函数
switch(op) {
case '+': Calculator(Num1, Num2, Add); break;
case '-': Calculator(Num1, Num2, Sub); break;
case '*': Calculator(Num1, Num2, Mul); break;
case '/': Calculator(Num1, Num2, Div); break;
default: printf("无效运算符!\n");
}
return 0;
}
示例 2:可自定义排序规则的冒泡排序
通过函数指针传递排序规则,让冒泡排序同时支持升序、降序、绝对值排序:
#include <stdio.h>
#include <stdlib.h>
// 输入数组
void InputArray(int *pArray, int len) {
for (int i = 0; i < len; i++) {
scanf("%d", &pArray[i]);
}
}
// 输出数组
void OutputArray(int *pArray, int len) {
for (int i = 0; i < len; i++) {
printf("%d ", pArray[i]);
}
printf("\n");
}
// 冒泡排序:排序规则由函数指针决定
void BubbleSortArray(int *pArray, int len, int (*pfun)(int, int)) {
for (int j = 0; j < len-1; j++) {
for (int i = 0; i < len-1-j; i++) {
// 排序规则交给外部函数
if (pfun(pArray[i], pArray[i+1]) > 0) {
int tmp = pArray[i];
pArray[i] = pArray[i+1];
pArray[i+1] = tmp;
}
}
}
}
// 排序规则函数
int AscSort(int x, int y) { return x > y ? 1 : 0; } // 升序
int DescSort(int x, int y) { return x < y ? 1 : 0; } // 降序
int AscAbsSort(int x, int y) { return abs(x) > abs(y) ? 1 : 0; } // 绝对值升序
int main(void) {
int a[5] = {0};
printf("请输入5个整数:");
InputArray(a, 5);
printf("绝对值升序排序结果:");
BubbleSortArray(a, 5, AscAbsSort);
OutputArray(a, 5);
return 0;
}
补充:函数指针数组(进阶)
如果有多个同类型的函数,可将函数指针存入数组,简化调用(比如替代 switch):
#include <stdio.h>
int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }
int Mul(int x, int y) { return x * y; }
int Div(int x, int y) { return x / y; }
int main() {
// 函数指针数组:存储4个运算函数的地址
int (*opFuncs[])(int, int) = {Add, Sub, Mul, Div};
char ops[] = "+-*/";
int num1 = 20, num2 = 5;
// 遍历调用所有运算
for (int i = 0; i < 4; i++) {
printf("%d %c %d = %d\n", num1, ops[i], num2, opFuncs[i](num1, num2));
}
return 0;
}
三、const 修饰的指针:
const(只读)用于修饰指针,核心是限制 “指针指向的内容” 或 “指针本身” 的修改权限,能有效防止误操作。
1. 三种 const 指针的核心区别
| 语法形式 | const 修饰对象 | 核心特性 | 初始化要求 |
|---|---|---|---|
const int *p / int const *p | *p(指针指向的内容) | 指针指向可改,内容不可改(只读内容) | 可选 |
int *const p | p(指针本身) | 指针指向不可改,内容可改(只读指针) | 必须初始化 |
const int *const p / int const *const p | *p + p | 指针指向和内容都不可改(双重只读) | 必须初始化 |
(1)const 修饰内容:const int *p / int const *p
这两种写法完全等价,重点是const靠近*,限制 “通过指针修改内容”,但指针本身可指向其他地址。
#include <stdio.h>
int main() {
int a = 10, b = 20;
const int *p = &a;
// *p = 100; // 错误:不能通过p修改a的值
p = &b; // 合法:指针可指向其他地址
printf("*p = %d\n", *p); // 输出:20
return 0;
}
💡 实战场景:函数参数中保护源数据(如strcpy的原型char *strcpy(char *dest, const char *src);,确保 src 内容不被修改)。
(2)const 修饰指针:int *const p
const靠近指针变量p,限制 “指针本身的指向”,必须初始化(否则后续无法赋值),但可修改指向的内容。
#include <stdio.h>
int main() {
int a = 10, b = 20;
int *const p = &a; // 必须初始化
*p = 100; // 合法:可修改a的值
// p = &b; // 错误:指针指向不可改
printf("a = %d\n", a); // 输出:100
return 0;
}
💡 类比:数组名本质是int *const类型(指向数组首地址,不可修改,但内容可改)。
(3)双重 const:const int *const p
既限制指针指向,又限制内容修改,必须初始化,是最严格的只读限制:
#include <stdio.h>
int main() {
int a = 10;
const int *const p = &a; // 必须初始化
// *p = 100; // 错误:内容不可改
// p = &b; // 错误:指向不可改
printf("*p = %d\n", *p); // 仅可读取,输出:10
return 0;
}
2. 记忆技巧
判断const修饰谁,只需看const靠近*还是指针变量:
- 靠近
*→ 修饰*p(内容不能改); - 靠近变量名 → 修饰
p(指针不能改); - 两边都有 → 内容和指针都不能改。
四、核心知识点总结
- 指针函数 vs 函数指针:
- 指针函数是 “返回指针的函数”,语法
int *func(int);(*归返回值); - 函数指针是 “指向函数的指针”,语法
int (*p)(int);(括号不可省,*归指针)。
- 指针函数是 “返回指针的函数”,语法
- 指针函数避坑:严禁返回栈区局部变量地址,可返回数据区、堆区、全局 / 静态区地址(堆区需手动释放)。
- const 指针核心:
const int *p(内容只读)、int *const p(指针只读)、const int *const p(全只读),核心是通过const限制修改权限,提升代码健壮性。 - 函数指针价值:作为函数参数传递(回调函数),可实现代码解耦,让函数支持自定义规则(如灵活排序、通用计算器)。
亲爱的朋友们,今天也是收获满满的一天呐,如果对你有帮助的话,请给博主点点赞!先谢谢大家啦!!
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2201_75336383/article/details/157186248



