前文我们已经初步学习了指针,本篇文章将结合几个重要的例子来加深对指针的理解。
一、理论知识整理
1. 指针变量的字节数
所有指针变量在内存中占的字节数都是相同的,永远是2个字节(16位地址)。
无论定义的是int *p1、float *p2还是double *p3。
p1、p2、p3 这三个指针变量本身都只占2个字节。
类比理解:就像一栋楼的门牌号,不管房间多大,门牌号都是固定位数的(比如都是3位数)。但门牌号背后的房间大小是不一样的。
2. 指针前面类型的真正含义
定义指针时前面写的类型(int、float、double),不是指针变量本身的类型,而是:
这个指针将来要指向的那个内容变量的类型,也就是它打开的"房间"的大小。
| 指针定义 | 指针本身占字节 | 指向的变量占字节 | 指向的变量类型 |
|---|---|---|---|
int *p1 | 2字节 | 2字节 | int型 |
float *p2 | 2字节 | 4字节 | float型 |
double *p3 | 2字节 | 8字节 | double型 |
核心规则:同类型互指。
什么类型的指针,就要得什么类型变量的地址。eg:
int型指针→只能得int型变量的地址
float型指针→只能得float型变量的地址
double型指针→只能得double型变量的地址
3. 指针口诀四(完整三句话)
这是做指针的核心口诀,必须背熟:
第一句:地址变量得地址,得谁地址指向谁。
指针变量拿到哪个变量的地址,就指向哪个变量。
第二句:有星为内容值,不是读就是写。放在赋值号左边为写,其他都为读。
*p出现在赋值号左边→写操作(往p指向的内存里写值)
*p出现在其他任何地方→读操作(读出p指向的内存里的值)
第三句:无星为地址,地址的赋值意味着改指向。
p1 = p2没有星,操作的是地址本身,执行后p1的指向改变了,和p2指向同一个地方。
4. 指针作为函数参数(形参如何影响实参)
C语言永远是单向传值:实参→形参,形参的值不能直接传回给实参。
但是,通过传地址(传指针),形参可以间接地修改实参的值。需要同时满足两个条件:
条件一:把地址传给对方(实参是指针/地址)
主函数把变量的地址(钥匙)交给子函数
条件二:对方要用指针运算符*进行读写
子函数拿到钥匙后,要
*p进门去修改房间里的东西
只传地址但不用
*运算→只是交换了形参自己的指向,不会影响实参。 传地址且用了*运算→真正修改了主函数中变量的值。
指针的优势:return语句最多只能返回一个值,而通过指针可以同时"返回"多个修改后的值。
5. 指针变量必须初始化后才能使用
指针变量不仅仅要定义,还要初始化,定义且初始化后才能用。
未初始化的指针是野指针,里面的地址是随机的,可能指向:
用户区(碰巧没事)
系统区(写操作→篡改系统数据→死机/崩溃)
对野指针进行写操作是极度危险的,在实际运行中会触发内存访问错误。
6. 指针题的画图方法
内容变量(普通变量)→画房子,里面写值
指针变量→画指向箭头,得谁地址就指向谁
函数调用时,主函数和子函数的同名变量是彼此独立、互不影响的,画图时要分开画
二、VS2019示例
此处列举四道经典例题。
例题1:指针传参修改实参(基础题)
题目:主函数x=1, y=2,调用子函数时传&y和&x(注意顺序反了),子函数先输出*x和*y,再执行*x=3; *y=4;,主函数最后再输出x和y。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> // 子函数:形参x、y都是指针变量 void fun(int *x, int *y) { // 有星为内容值,不在赋值号左边 → 读 // x得到的是主函数y的地址,所以*x读的是主函数y的值=2 // y得到的是主函数x的地址,所以*y读的是主函数x的值=1 printf("%d%d", *x, *y); // 输出 21 // 有星为内容值,在赋值号左边 → 写 *x = 3; // 把3写入x指向的房子(主函数的y),y变成3 *y = 4; // 把4写入y指向的房子(主函数的x),x变成4 } int main() { int x = 1, y = 2; // 注意:传参顺序是 &y, &x(反的) // 实参&y传给形参x,实参&x传给形参y fun(&y, &x); // 子函数中*x=3改了主函数的y → y=3 // 子函数中*y=4改了主函数的x → x=4 printf("%d%d", x, y); // 输出 43 return 0; } // 最终输出:2143例题2:swap交换(正确版—用*交换内容)
题目:输入两个数,通过swap函数交换它们的值。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> // 正确的swap:用临时普通变量temp,通过*交换指向的内容 void swap(int *p1, int *p2) { int temp; // 普通变量,存内容值 temp = *p1; // 读:读出p1指向的值,给temp *p1 = *p2; // 读+写:读出p2指向的值,写入p1指向的单元 *p2 = temp; // 读+写:读出temp的值,写入p2指向的单元 } int main() { int a, b; int *p1 = &a, *p2 = &b; printf("请输入两个整数(空格分隔):"); scanf("%d%d", p1, p2); // p1、p2本身就是地址,不用再加& // 输入 2 5 → a=2, b=5 printf("交换前:a=%d, b=%d\n", a, b); // a=2, b=5 swap(p1, p2); // 传地址(p1、p2里存的是a和b的地址) printf("交换后:a=%d, b=%d\n", a, b); // a=5, b=2 return 0; } // 输入:2 5 // 输出:交换前 a=2,b=5 交换后 a=5,b=2为什么能交换成功?
- 主函数把a和b的地址(钥匙)传给了swap的形参p1、p2
- swap里用了
*运算,拿着钥匙进门,真正修改了a和b房子里的内容 - 形参p1、p2虽然在函数结束后被释放,但它们对a、b的修改已经留在了内存里
例题3:swap交换(错误版 — 只交换地址,不用*)
题目:本例跟例题2看起来很像,但swap里用的是int *temp,交换的是p1、p2本身(地址),没有用*。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> // 错误的swap:用临时指针变量temp,交换的是地址本身(指向) void swap(int *p1, int *p2) { int *temp; // 临时指针变量 temp = p1; // 无星为地址 → 改指向:temp指向p1原来指向的地方(a) p1 = p2; // 无星为地址 → 改指向:p1现在指向p2原来指向的地方(b) p2 = temp; // 无星为地址 → 改指向:p2现在指向temp指向的地方(a) // 结果:形参p1和p2的指向交换了,但a和b房子里的内容根本没动! } int main() { int a, b; int *p1 = &a, *p2 = &b; printf("请输入两个整数(空格分隔):"); scanf("%d%d", p1, p2); // 输入 2 5 → a=2, b=5 printf("交换前:a=%d, b=%d\n", a, b); // a=2, b=5 swap(p1, p2); printf("交换后:a=%d, b=%d\n", a, b); // 还是 a=2, b=5!没有交换成功 return 0; } // 输入:2 5 // 输出:交换前 a=2,b=5 交换后 a=2,b=5(没变!)为什么交换失败?
- 虽然传了地址(满足条件一)
- 但swap里全程没有用
*运算(不满足条件二)- 只是把形参p1、p2这两把"钥匙"交换了,根本没有进门去改房间里的东西
- 形参被释放后,主函数的a、b完全不受影响
记住:想让形参影响实参,传地址只是必要条件,还必须在子函数里用
*去读写。
例题4:swap交换(错误版—野指针,运行崩溃)
题目:本例跟例题2类似,但swap里用int *temp且未初始化,直接*temp = *p1。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> // 极度危险的swap:temp是未初始化的野指针 void swap(int *p1, int *p2) { int *temp; // 只定义了指针temp,没有初始化!temp里是随机地址 *temp = *p1; //危险!把p1指向的值写入temp指向的随机地址 // 如果temp碰巧指向系统区,就会篡改系统内存 → 死机/崩溃 *p1 = *p2; *p2 = *temp; } int main() { int a, b; int *p1 = &a, *p2 = &b; printf("请输入两个整数(空格分隔):"); scanf("%d%d", p1, p2); printf("交换前:a=%d, b=%d\n", a, b); swap(p1, p2); // 运行到这里会触发内存访问错误,程序崩溃 printf("交换后:a=%d, b=%d\n", a, b); return 0; } // 运行结果:程序崩溃(VS2019会报"写入访问权限冲突"或类似错误)为什么崩溃?
int *temp;只定义不初始化,temp是野指针,指向随机内存*temp = *p1;对野指针进行写操作,把值写到了随机地址- 如果那个随机地址是系统保护区,操作系统会直接终止程序(内存访问违规)
- 即使碰巧没崩溃,也可能篡改了其他重要数据,后果不可预测
正确做法:temp用普通变量(如例题2),或者如果一定要用指针,必须先让temp指向一个合法变量:
int t; int *temp = &t; // 先初始化,让temp指向t *temp = *p1; // 安全,写入的是t的内存三、四道例题对比总结
| 例题 | swap里temp类型 | 有没有用* | 结果 | 原因 |
|---|---|---|---|---|
| 例1 | — | 有* | 成功修改实参 | 传地址+用*读写 |
| 例2 | int temp(普通变量) | 有* | 交换成功 | 传地址+用*改内容 |
| 例3 | int *temp(指针) | 无*(只操作地址) | 交换失败 | 传了地址但没用*,只改了形参指向 |
| 例4 | int *temp(未初始化指针) | 有* | 程序崩溃 | 野指针写操作,访问非法内存 |
学习过程有诸多不足之处,欢迎来私信与我一同交流学习,共同进步!