指针使用技巧:指针算术优化、函数指针应用、const指针的正确用法
指针,是C语言的灵魂,也是很多人的噩梦。我做了十几年嵌入式开发,见过太多因为指针翻车的案例。说实话,指针用好了,代码效率能上一个台阶;用不好,段错误能让你调试到怀疑人生。今天咱们就聊聊指针的三个核心技巧:指针算术优化、函数指针应用、还有const指针的正确用法。
核心观点:指针不是洪水猛兽,关键是要理解它的本质——地址的抽象。掌握了指针的算术、函数指针的灵活、const指针的保护机制,你的代码会变得更高效、更安全。
一、指针算术优化:别再用下标了
很多人写数组遍历,习惯用 arr[i]。这没问题,但效率上不是最优的。指针算术在底层就是地址加减,比下标索引少一次乘法运算。你想想看,循环几百万次,这个差距就出来了。
我在项目中遇到过这样一个场景:一个实时音频处理程序,需要遍历一个 10 万点的缓冲区。用下标索引时,CPU 占用率一直下不来。改成指针算术后,占用率直接降了 15%。
// 不推荐:下标索引
for (int i = 0; i < N; i++) {
buffer[i] = buffer[i] * 2;
}
// 推荐:指针算术
int *p = buffer;
int *end = buffer + N;
while (p < end) {
*p = *p * 2;
p++;
}
小技巧:指针算术的步长由指针类型决定。int* 每次加 1,地址增加 4 字节;char* 每次加 1,地址增加 1 字节。这个特性在操作结构体数组时特别有用。
还有一个容易忽略的点:指针算术可以用于结构体成员的偏移计算。用 offsetof 宏配合指针算术,可以写出非常灵活的序列化/反序列化代码。
#include <stddef.h>
typedef struct {
int id;
char name[32];
float score;
} Student;
// 计算成员偏移
size_t offset = offsetof(Student, score);
Student s;
float *pScore = (float *)((char *)&s + offset);
*pScore = 95.5f;
警告:指针算术最容易翻车的地方就是越界。我曾经在调试一个网络协议栈时,因为指针多加了 1,导致内存越界,整个系统随机崩溃。排查了整整两天才找到问题。所以,一定要用 end 指针做边界检查。
二、函数指针应用:让代码活起来
函数指针是什么?说白了,就是把函数当作变量来用。你可以把函数赋值给指针,传参,甚至放在数组里。这个能力让 C 语言有了面向对象的感觉。
我记得有一次写一个嵌入式菜单系统,菜单项有几十个,每个菜单对应不同的操作。如果用 switch-case,代码会膨胀到没法看。用函数指针数组,几行代码就搞定了。
// 定义函数指针类型
typedef void (*MenuHandler)(void);
// 菜单处理函数
void menu_open(void) { /* ... */ }
void menu_save(void) { /* ... */ }
void menu_exit(void) { /* ... */ }
// 函数指针数组
MenuHandler handlers[] = {
menu_open,
menu_save,
menu_exit
};
// 调用:根据菜单ID直接执行
int menu_id = get_menu_id();
if (menu_id >= 0 && menu_id < 3) {
handlers[menu_id](); // 动态调用
}
函数指针还有一个经典应用:回调函数。比如 qsort 的排序比较器,就是一个函数指针。你只需要提供一个比较函数,qsort 就能对任意类型的数据排序。
// 比较函数
int compare_int(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
// 使用 qsort
int arr[] = {5, 2, 8, 1, 9};
qsort(arr, 5, sizeof(int), compare_int);
个人经验:函数指针在状态机中特别好用。每个状态对应一个处理函数,状态切换就是换一个函数指针。这样写出来的状态机,逻辑清晰,扩展方便。我曾经用这个模式写过一个通信协议解析器,后来加新协议时,只需要新增一个函数,不用改原有代码。
三、const指针的正确用法:保护你的数据
const 和指针的组合,有四种写法,很多人搞混。我刚开始学的时候也晕,后来总结了一个规律:看 const 在 * 的哪一边。
| 写法 | 含义 | 示例 |
|---|---|---|
const int *p |
指向常量的指针(数据不可改) | *p = 10; // 错误 |
int * const p |
常量指针(地址不可改) | p = &b; // 错误 |
const int * const p |
指向常量的常量指针(都不可改) | 两者都不可改 |
int const *p |
同 const int *p |
数据不可改 |
为什么要在函数参数里用 const 指针?说白了,就是给调用者一个承诺:我不会改你的数据。这在 API 设计中非常重要。
// 好的设计:用 const 声明不会修改数据
void print_student(const Student *s) {
printf("ID: %d\n", s->id);
printf("Name: %s\n", s->name);
// s->score = 100; // 编译错误,保护数据
}
// 不好的设计:没有 const,调用者不放心
void print_student_bad(Student *s) {
printf("ID: %d\n", s->id);
// 调用者担心你会修改数据
}
建议:函数参数中,如果只是读取数据,一律用 const 指针。这不仅是好习惯,还能让编译器帮你检查错误。我见过太多因为忘记加 const,导致在函数内部意外修改了外部数据的 bug。
const 指针还有一个妙用:定义只读的全局配置。比如一个系统的参数表,用 const 修饰后,任何代码都不能修改它,保证了数据安全。
// 只读配置表
const int config_table[] = {
100, // 超时时间
200, // 缓冲区大小
50 // 最大连接数
};
// 任何试图修改 config_table 的代码都会编译失败
// config_table[0] = 200; // 错误!
避坑指南:我曾经在项目中遇到一个 const 指针的坑——用 const 指针指向一个非 const 变量,然后通过另一个非 const 指针修改它。这是合法的,但容易让人误解。记住:const 是编译期的约束,不是运行时的保护。
好了,指针的这三个技巧就聊到这里。指针算术让代码跑得更快,函数指针让代码更灵活,const 指针让代码更安全。这三个结合起来,你的 C 代码质量会有质的提升。