
4-1 指针⏱本节预计用时30分钟在讲数组那一节我们对指针做了简单的介绍数组传参传的不是复制品而是存首元素地址的变量也就是指针。指针就是存地址的变量就是这么简单。指针的语法比较绕但我相信学完这一讲你能够看懂大部分使用指针的代码另外的就是熟练度问题了。1 两个符号 和 *1.1—— 取地址每个变量在内存里都有个门牌号之前我们把变量比作格子现在比作房子也就是地址。用变量名就能查到它的数字编号是多少#includestdio.hintmain(){intx42;printf(x 的值%d\n,x);// 42printf(x 的地址%p\n,x);// 0x7ffd...0x开头表示十六进制数// %p 是专门打印地址的格式符。return0;}运行情况每次运行输出地址可能不一样这是因为运行结束之后房子被系统回收了下次它再分配的可能不一样。1.2 声明指针变量声明时在变量名前加个*intx42;int*px;// p 是一张小纸条记下了 x 的门牌号第一次看基本必懵逼别怕继续用房子来想x是一栋房子里面住着42x是这栋房子的门牌号一串地址数字int *p x;的意思是再造一个格子p这个格子专门用来装门牌号现在把x的门牌号塞进去。所以p里装的数字和x完全一样——p就是存着 x 门牌号的那个格子。打个比方x是房子x是门牌号p就是那张记着门牌号的小纸条。在1.1的示例里加入int *p x;printf(x 的地址%p\n, p);#include stdio.h int main() { int x 42; int *p x; printf(x 的值%d\n, x); printf(x 的地址%p\n, x); printf(x 的地址%p\n, p); return 0; }运行情况1.3*—— 解引用*p的意思是顺着纸条找到 x 的房子看里面的住户。不光能看还能改intx42;int*px;printf(%d\n,*p);// 42 —— 看房子的住户*p100;// 把 x 房的值改成 100printf(%d\n,x);// 100 —— x 真被改了所以指针的全部就两件事取地址问门牌号*解引用顺着门牌号找到房子看或改里面的值这里有一个容易乱的点*在1.2是声明用的意思是我要造一个装地址的指针变量和这里的顺着地址去查变量不一样。在1.2的示例里加入printf(x 的值%d\n, *p);#include stdio.h int main() { int x 42; int *p x; printf(x 的值%d\n, x); printf(x 的地址%p\n, x); printf(x 的地址%p\n, p); printf(x 的值%d\n, *p); return 0; }运行情况1.4 野指针 和 NULL野指针就是声明了、却没给它一个明确地址的指针。它里面装着的是一个随机的垃圾编号你根本不知道它指向哪int*p;// p 里是垃圾值野指针*p42;// 顺着一个随机编号去写 42程序很可能崩溃就像你手里有张空白纸条却硬要按上面的门牌号去找房子——大概率走错门甚至闯进别人家。解决办法声明时先把它指向空。C 里用NULL表示这个指针现在谁都不指int*pNULL;// 明确告诉编译器p 现在谁都不指if(p!NULL){// 安全习惯用它之前先问一句你指着没*p42;}NULL就是一个特殊值等于 0意思是空、没有指向。声明指针时赋NULL不给它留下垃圾编号的机会解引用前先判断NULL确认它真的指着东西再动手2 一张图弄清楚地址: 0x100 0x104 0x108 ┌─────────┬─────────┬─────────┐ │ 42 │ │ 0x100 │ └─────────┴─────────┴─────────┘ x p 住户42 记着0x100x住在0x100里面是42p住在0x108里面是0x100就是 x 的门牌号*p 看地址0x100那房子的住户 42*p 100 把0x100那房子改写成100→ x 变成 1003 传地址函数就能改外面的变量回忆函数那章的值传递传进去的是复印件的值函数里改了外面纹丝不动。想把外面的变量也改掉就把门牌号传进去让函数顺着地址改原件#includestdio.hvoidadd10(int*p){*p10;// 顺着 p 改外面那个变量}intmain(){intn5;printf(%d\n,n);// 5add10(n);printf(%d\n,n);// 15return0;}传地址 → 就能通过地址改外面的变量。这也是数组传参能改外面的原因——数组名本身就是首元素地址传数组本质就是传指针。运行情况4 数组名就是指针数组名是存放着首元素地址的指针常量。#includestdio.hintmain(){inta[5]{10,20,30,40,50};printf(%p\n,a);// 数组名 首元素地址printf(%p\n,a[0]);// 和 a[0] 打印出来一样int*pa;// 数组名可以直接塞进指针printf(%d\n,a[2]);// 30printf(%d\n,*(a2));// 30 —— a[2] 就是 *(a2)printf(%d\n,p[2]);// 30 —— 指针也能用 []return0;}运行情况记住三点数组名是首元素的地址a和a[0]是同一个数字所以int *p a;合法。a[i]本质就是*(ai)先首地址加偏移量再解引用。[]只是语法糖——所以只要手里有地址p[2]和a[2]都好使。数组名是指针常量改不了它相当于是const int *a不能a、不能a p门牌号改不了p是变量可以p往后挪。看到数组名就看成首元素的门牌号。以后传数组、遍历数组还有学生系统里scanf(%31s, stu.name)不用写都是因为这个。5 总结概念含义代码地址变量的门牌号x→ 0x7fff…指针存地址的变量int *p x;*p顺着地址看/改房子里的值*p→ 42传地址函数能改外面add10(n);数组名首元素的地址能当指针用int *p a;野指针声明后没赋地址装着垃圾值int *p;别乱用NULL空指针谁都不指int *p NULL;用前先判断指针 存地址的变量本身没魔法取地址*解引用——全部就这两个操作传地址能改外面——数组传参能改外面的本质数组名 首元素地址a[i]就是*(ai)传数组就是传指针声明指针先赋NULL用前先判断躲开野指针的坑到这里你已经会用指针指一个变量、一个数组了。可真实的数据往往不是一个数而是一坨——比如一个学生就有学号、姓名、三门成绩。下一讲我们专门解决这件事用指针指一整个结构体看懂它在内存里怎么摆。学完那一讲我们就正式动手做学生成绩管理系统。