深入理解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); // 传入指针的地址

p[0] = 100;

free(p);

return 0;

}

```

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

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

数组名就是指针吗?

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

```c

int arr[5] = {1,2,3,4,5};

int *p = arr; // arr 转换为 &arr[0]

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); // 等价于 arr[3]

```

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

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

```c

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

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

}

```

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

五、指针数组 vs 数组指针

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

写法 含义 记忆技巧

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

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

```c

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

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

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

int arr[3][5];

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(arr[i], arr[j]) > 0) {

int tmp = arr[i];

arr[i] = arr[j];

arr[j] = 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语言内存全景图:从代码到运行的完整旅程》


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

相关推荐
熬夜敲代码的猫3 小时前
C/C++:内存管理
c语言·c++·动态内存管理
云泽8084 小时前
第十五届蓝桥杯大赛软件赛省赛C/C++大学B组
c语言·c++·算法·蓝桥杯
luoqice4 小时前
利用flv库读取flv文件时长c程序
c语言·开发语言
浅时光_c5 小时前
12 函数
c语言
小文数模5 小时前
2026 年MathorCup(妈妈杯)数学建模竞赛C完整参考论文(第一版)
c语言·数学建模·matlab
汽车芯猿5 小时前
嵌入式 SHA-256 完全实现(附原码)(无 uint64_t,减少栈使用)
c语言·单片机
wuminyu5 小时前
专家视角看Java的线程是如何run起来的过程
java·linux·c语言·jvm·c++
码农的神经元5 小时前
2026 MathorCup 选题建议:A/B/C/D/E 题到底怎么选?
c语言·开发语言·数学建模
聆风吟º6 小时前
【C标准库】深入理解C语言strcmp函数:字符串比较的核心用法
c语言·开发语言·库函数·strcmp