深入理解C语言指针:从一级指针到函数指针

前言

指针是C语言的灵魂,也是让无数初学者"从入门到放弃"的罪魁祸首。但说实话,指针本身并不复杂------复杂的是它背后所代表的内存地址这一抽象概念。

一旦你理解了"指针就是一个存放地址的变量",你会发现指针不仅不恐怖,反而是C语言最优雅的设计之一。

今天,我们从最基础的一级指针开始,一路走到函数指针和复杂声明解析,帮你彻底拿下指针。

一、指针的本质:地址的容器

```c

int a = 42;

int *p = &a;

```

在内存中,变量 a 占据4个字节(假设32位系统),这4个字节的起始地址就是 &a。而指针变量 p 存储的就是这个地址。

关键理解:

· p 存的是地址(如 0x7ffd1234)

· *p 是"解引用",意思是"去 p 存储的地址那里,取出该地址上的值"

画个图就清晰了:

```

内存地址: 0x7ffd1230 0x7ffd1234 0x7ffd1238

+----------+----------+----------+

| ? | 42 | ? |

+----------+----------+----------+

a 在这里

p = 0x7ffd1234,p自己也有自己的地址

```

二、一级指针:最基础的指针

常见用法

```c

// 1. 修改外部变量(函数内修改函数外的值)

void swap(int *a, int *b) {

int tmp = *a;

*a = *b;

*b = tmp;

}

// 2. 返回多个结果

int divide(int a, int b, int *remainder) {

if (b == 0) return 0;

*remainder = a % b;

return a / b;

}

// 3. 避免大结构体拷贝(传指针比传值高效)

void process_struct(struct BigData *data) {

// 只传8字节的指针,而非整个结构体

}

```

空指针与野指针

类型 定义 后果

空指针 int *p = NULL; 解引用会段错误(至少能发现)

野指针 int *p; 未初始化 解引用可能修改随机内存,极难调试

建议:声明指针时立即初始化,无法确定值时初始化为 NULL。

三、二级指针:指向指针的指针

二级指针常用于需要在函数内修改指针本身的场景。

```c

// 场景:动态分配内存后,返回给调用者

void allocate_memory(int **ptr, size_t size) {

*ptr = (int*)malloc(size * sizeof(int));

// 调用者传的是 &p,所以 *ptr 就是调用者的 p

}

int main() {

int *p = NULL;

allocate_memory(&p, 10); // 传入指针的地址

p0 = 100;

free(p);

return 0;

}

```

为什么要用二级指针? C语言是值传递。在函数内修改 p 本身(而非 *p),必须传递 &p。

四、指针与数组:暧昧不清的关系

数组名就是指针吗?

不是。数组名在大多数表达式中会被隐式转换为指向首元素的指针,但有例外:

```c

int arr5 = {1,2,3,4,5};

int *p = arr; // arr 转换为 &arr0

printf("%zu\n", sizeof(arr)); // 20 = 5*4,数组名没转换

printf("%zu\n", sizeof(p)); // 8(64位系统),指针的大小

```

指针运算

```c

int *p = arr;

p++; // 移动 sizeof(int) = 4 字节

*(p+2); // 等价于 arr3

```

公式:arri 完全等价于 *(arr + i)

数组作为函数参数:秘密就是指针

```c

void func(int arr\[\]) { // 编译器自动改写为 int *arr

printf("%zu", sizeof(arr)); // 输出 8,不是20!

}

```

记住:数组形参本质上就是指针,所以无法在函数内获取数组长度,需要额外传入 size。

五、指针数组 vs 数组指针

这是面试常考题,区分关键在于优先级:\[\] 优先级高于 *。

写法 含义 记忆技巧

int *p5 指针数组:5个元素,每个都是 int* p 先和 \[\] 结合

int (*p)5 数组指针:指向一个长度为5的int数组 (*p) 表示 p 是指针

```c

// 指针数组:常用于字符串数组

char *strs\[\] = {"hello", "world", "c"};

// 数组指针:常用于二维数组

int arr35;

int (*p)5 = arr; // p 指向第一行

```

六、函数指针:把函数当作数据

函数也有地址,可以存储在指针中,实现回调、策略模式等。

```c

// 声明一个函数指针:指向 返回int、参数(int,int) 的函数

int (*p)(int, int);

// 赋值

int add(int a, int b) { return a + b; }

p = add;

// 调用

int result = p(3, 5); // 等价于 add(3,5)

```

实用场景:回调函数

```c

// 通用的排序函数,让用户决定比较规则

void sort(int *arr, int size, int (*cmp)(int, int)) {

for (int i = 0; i < size; i++) {

for (int j = i+1; j < size; j++) {

if (cmp(arri, arrj) > 0) {

int tmp = arri;

arri = arrj;

arrj = tmp;

}

}

}

}

int asc(int a, int b) { return a - b; }

int desc(int a, int b) { return b - a; }

// 使用

sort(myarr, 10, asc); // 升序

sort(myarr, 10, desc); // 降序

```

函数指针的复杂声明(读懂即可)

```c

// 信号处理函数:void (*signal(int sig, void (*handler)(int)))(int)

// 用 typedef 简化

typedef void (*sighandler_t)(int);

sighandler_t signal(int sig, sighandler_t handler);

```

七、const 与指针:四种组合

这是初学者最容易混淆的地方,记住一条规则:const 修饰谁,谁就不能改。

写法 含义 能否改指向 能否改值

const int *p 指向常量的指针 ✅ 可以 ❌ 不行

int const *p 同上 ✅ 可以 ❌ 不行

int * const p 常量指针 ❌ 不行 ✅ 可以

const int * const p 两者都不可改 ❌ 不行 ❌ 不行

记忆口诀:const 在 * 左边修饰值,在 * 右边修饰指针本身。

八、常见陷阱与调试技巧

  1. 解引用未初始化的指针

```c

int *p;

*p = 10; // 错误!p 指向哪里?

```

  1. 返回局部变量的地址

```c

int* bug() {

int a = 10;

return &a; // 错误!函数返回后 a 被销毁

}

```

  1. 释放后继续使用

```c

free(p);

*p = 10; // 未定义行为

p = NULL; // 释放后立即置空是个好习惯

```

  1. 调试技巧

· 打印指针值:printf("p = %p\n", p);

· 使用 Valgrind 检测非法内存访问

· 地址消毒剂:gcc -fsanitize=address -g

结语

指针确实需要时间去消化,但一旦掌握了它,你就真正拥有了C语言。记住三个核心概念:

  1. 指针就是地址

  2. 解引用就是去那个地址取值

  3. 传指针是为了让函数能够修改外部变量

剩下的,无非是反复练习、反复调试。下一篇文章,我们将进入 C语言内存布局,看看程序在运行时的代码、数据、堆、栈是如何组织的。

下一篇预告:《C语言内存全景图:从代码到运行的完整旅程》


有任何指针方面的问题,欢迎在评论区留言讨论!

相关推荐
caishenzhibiao6 小时前
市场同步系统 同花顺期货通指标
java·c语言·c#
say_fall7 小时前
【Linux系统编程】文件操作基础:C标准库、系统调用、fd是什么和fd与FILE*的关系
android·linux·c语言
Lee_jerome19 小时前
《C/C++编译全家桶:g++四阶段、.a与.so区别、CMake构建、ARM交叉编译,这篇全讲透了》
c语言·开发语言·arm开发·c++·编译·cmake·cmakelists
浩浩kids21 小时前
C•exercises
c语言
春生野草21 小时前
个人笔记——C语言字符串、树
c语言·开发语言·笔记
ysu_03141 天前
用Vibe-Coding从零实现俄罗斯方块
c语言·游戏程序
冻柠檬飞冰走茶1 天前
PTA基础编程题目集 7-24 约分最简分式(C语言实现)
c语言·开发语言·数据结构·算法
weixin_585951171 天前
CH592F 2.4G 低功耗
c语言·开发语言
盒子里的加菲猫1 天前
什么?不会C语言也可以搭建窗口级应用程序?(Electron)
c语言·开发语言·electron
caishenzhibiao1 天前
无极多空共振 同花顺期货通指标
java·c语言·c#