七、C语言指针
指针是 C 语言赋予程序员的上帝之手,它允许我们直接操作内存。用好了,它是神兵利器;用不好,它是程序崩溃的根源。这次将带你深入内存,理解指针的本质。
思维导图


一、 指针基础概念
1.1 什么是地址?
计算机内存就像一个巨大的公寓楼,每个字节都有一个唯一的门牌号,这就是地址。
变量名只是门牌号的别名,指针则是专门用来存放门牌号的变量。
1.2 语法结构:定义与赋值
语法:类型 *指针变量名;
int a =10;int*p;// 1. 定义:p 是一个指向 int 的指针 p =&a;// 2. 赋值:把 a 的地址给 p(p 指向 a)int*q =&a;// 3. 定义并初始化(推荐写法)1.3 两个核心运算符
& (取地址):获取变量在内存中的位置。&a 得到的是一个十六进制的地址值。* (解引用/间接访问):拿着钥匙开门。访问指针指向的那个位置里的数据。
代码示例:用指针修改变量
int a =10;int*p =&a;// p 存储了 a 的地址printf("a 的地址: %p\n",(void*)p);printf("通过指针看 a: %d\n",*p);*p =20;// 核心操作!远程修改 a 的值printf("a 现在是: %d\n", a);// 输出 201.4 指针类型决定视野
为什么指针需要类型?int * 和 char * 存的都是地址(在 64 位系统上都是 8 字节),区别在哪?
区别在于解引用时的视野:
int *p:*p一次性读取4 个字节。char *q:*q一次性读取1 个字节。
代码验证:
int x =0x12345678;char*p =(char*)&x;printf("%x",*p);// 在小端序机器上,输出 78 (只读了最低字节)二、 致命三剑客:NULL、野指针、悬空指针
2.1 NULL 指针
NULL 是一个宏,通常定义为 (void*)0不指向任何有效内存。
最佳实践: 定义指针时如果暂时不用,务必初始化为 NULL。
2.2 野指针
定义了指针但没初始化。它指向哪里?只有上帝知道(垃圾值)。
int*p;// 野指针!// *p = 100; // 极度危险!可能覆盖关键系统数据导致崩溃2.3 悬空指针
指针指向的内存已经被释放,但指针还在。这是最难调试的 Bug 之一。
经典错误:返回局部变量地址
int*get_val(){int x =10;return&x;// 错误!函数结束 x 就销毁了}intmain(){int*p =get_val();// p 变成了悬空指针// printf("%d", *p); // 未定义行为,数据可能已经被覆盖}三、 指针实战:交换两个数
这是理解地址传递最经典的代码。
代码示例:
// 接收地址,拥有“远程修改”的能力voidswap(int*a,int*b){int temp =*a;// 取出 a 指向的值*a =*b;// 把 b 指向的值赋给 a 指向的地方*b = temp;// 把 temp 赋给 b 指向的地方}intmain(){int x =5, y =10;swap(&x,&y);// 必须传地址!printf("x=%d, y=%d\n", x, y);// 输出 x=10, y=5return0;}四、 指针与数组
4.1 数组名的真面目
在表达式中,数组名 arr 会退化为指向首元素的指针。
核心等价公式:
arr[i] ≡ *(arr + i)
4.2 用指针遍历数组
这种写法在嵌入式开发和操作系统内核中非常常见,效率极高。
代码示例:
int arr[]={10,20,30};int*p = arr;// 指向开头for(int i =0; i <3; i++){// 写法 1:下标法(最直观)printf("%d ", arr[i]);// 写法 2:指针算术法(偏移)printf("%d ",*(p + i));// 写法 3:指针移动法(步进)printf("%d ",*p++);// 取值后,指针指向下一个}五、 指针算术与边界
5.1 +1 到底加多少?
指针加 1,不是地址值加 1,而是跳过一个数据类型的大小。
char *p: p+1 地址增加1 字节。int *p: p+1 地址增加4 字节。double *p: p+1 地址增加8 字节。
代码验证步长:
int nums[]={1,2};int*p = nums;printf("当前: %p\n",(void*)p); p++;printf("加一: %p\n",(void*)p);// 观察地址差值,一定是 45.2 两个指针相减
如果两个指针指向同一个数组,p1 - p2 得到的是它们之间相隔的元素个数(不是字节数)。这是实现 strlen 的基础。
手写 strlen (指针版):
size_tmy_strlen(constchar*s){constchar*start = s;while(*s !='\0'){ s++;// 指针一直往后走}return s - start;// 尾地址 - 头地址 = 长度}六、 const 与指针(绕口令时间)
这是面试必考题,记住这句口诀:
const 在 * 左边,锁的是物(内容)
const 在 * 右边,锁的是人(指针)
6.1 三种形态对比
int x =10;int y =20;// 1. 指向常量的指针 (Pointer to Const)// "我不能改里面的值,但我可以指向别人"constint*p1 =&x;// *p1 = 30; // 错!内容被锁 p1 =&y;// 对!指针没锁// 2. 常量指针 (Const Pointer)// "我可以改里面的值,但我不能指向别人"int*const p2 =&x;*p2 =30;// 对!内容没锁// p2 = &y; // 错!指针被锁// 3. 指向常量的常量指针 (双重锁定)// "我都不能改"constint*const p3 =&x;七、 练习题
题目 1: 64 位系统下,int *p 和 char *q 占用的内存大小分别是多少?
题目 2:int a = 10; int *p = &a;,*&a 的结果是什么?&*p 的结果是什么?
题目 3: 为什么指针被释放(free)后,最好立即置为 NULL?
题目 4: 若 short *p = 0x1000; (short占2字节),p + 2 的地址值是多少?
题目 5:int arr[5]; int *p = arr;,表达式 p[2] 合法吗?它等价于什么?
题目 6: 下面代码输出什么?
int a[]={1,2,3,4};int*p = a;printf("%d",*p++);printf("%d",*p);题目 7: 解析声明:const char * p 和 char * const p 的区别。
题目 8: 如何定义一个指针,指向一个 int 类型的指针(二级指针)?
题目 9: 只有 void* 指针可以直接赋值给其他类型的指针而不需要强转(在 C 中),对吗?
题目 10: 下面代码有什么致命问题?
int*p;*p =100;题目 11: 两个指针相加(p1 + p2)有意义吗?两个指针相减(p1 - p2)有意义吗?
题目 12: 在大端序(Big Endian)机器上,int a = 0x12345678; char *p = (char*)&a;,*p 的值是多少?
题目 13: 什么是“野指针”?与“悬空指针”有何区别?
题目 14: 函数参数 void func(const int *p) 里的 const 想表达什么意图?
题目 15:int a[3][4]; int *p = a; 这种赋值会报警告吗?为什么?
八、 解析
题 1 解析
答案: 都是 8 字节。
详解:
指针也是变量,用来存地址。在 64 位系统上,地址总线宽度是 64 位,所以所有类型的指针大小都是 8 字节。
题 2 解析
答案:10 和 a 的地址。
详解:
*和&互为逆运算,相互抵消。
题 3 解析
答案: 防止悬空指针(Dangling Pointer)误用。
详解:
释放内存后,指针变量里的地址值还在。如果不置空,后续代码可能会误以为这块内存还能用,导致难以排查的 Bug。
题 4 解析
答案:0x1004。
详解:
步长 = sizeof(short) = 2。0x1000 + 2 * 2 = 0x1004。题 5 解析
答案: 合法。等价于 arr[2] 或 *(p+2)。
详解:
指针和数组名在下标访问上是语法糖关系。[] 运算符本质上就是指针加法后解引用。题 6 解析
答案: 12。
详解:
*p++先取值*p(1),然后p自增指向下一个。第二次打印*p就是 2。
题 7 解析
答案:
前者指针指向的内容不可改(内容只读);后者指针本身的指向不可改(指针只读)。
题 8 解析
答案:int **pp;。
详解:
二级指针,存放的是一级指针的地址。
题 9 解析
答案: 对。
详解:
void* 是通用指针,C 语言允许它隐式转换为任意类型指针(注意:C++ 不允许)。题 10 解析
答案: 野指针解引用。
详解:
p 没有初始化,指向随机地址。写入可能导致程序立即崩溃。题 11 解析
答案: 相加无意义;相减有意义。
详解:
两个地址相加没有物理意义。同类型指针相减表示中间相隔的元素个数(不是字节数)。
题 12 解析
答案:0x12。
详解:
大端序:高位字节存低地址。p指向首字节(低地址),所以拿到的是高位数据0x12。
题 13 解析
答案:
野指针:未初始化的指针。
悬空指针:指向已释放内存的指针。
题 14 解析
答案: 只读承诺。
详解:
告诉调用者:“把你的数据传给我,我保证只读取,绝不修改。”这是一种契约。
题 15 解析
答案: 会报警告。
详解:
a的类型是int (*)[4](指向包含4个int的数组的指针),p的类型是int *。虽然数值可能一样,但步长不同,类型不兼容。
日期:2025年2月12日
专栏:C语言