简单理解C指针(1)变量篇

为了让大家理解C语言的指针
我将一步一步剖析指针的本质
请各位耐心看

大酒店->内存

计算机的本质功能就是计算
人类计算需要 草稿纸 来临时记录数值
而计算机使用 “内存” 来存放临时的数值
这里说的“内存”就是所谓的“运行内存”

内存在计算机工作的原理
和酒店的管理是差不多的

房间->内存单元

假如有一家大酒店
这个酒店有很多间房
每个房间都有自己的门牌号
每间房最多可以住下8个人

内存同理,内存中也有很多“小格子”
每个小格子都有自己的门牌号
并且每个小格子也只能放下8位二进制
这些“小格子”就是“内存单元”
我们先不管这些专业名词
下文就把“内存单元”称为“小格子”

开房->申请内存

假如我想让6个人去住大酒店
这6个人怎么知道他们具体到住哪间房呢?
当然是我给他们定房间啦!
酒店前台查了一下,发现114号房是空的
所以就把114号房开给了我们

内存也一样,计算机运算的时候
想把6这个值临时放一下
应该放到哪里呢?
计算机查了一下,发现514号格子是能用的
所以打算把值临时存放在514号格子里面

门牌号->地址

这6个人知道他们要住114号房之后
他们从001开始找房间
直到找到114号房,然后住进去

计算机知道514号格子是空的时候
会自己在内存中找到514号小格子
然后把6放进去

查房->寻址

现在大酒店需要确定114号房到底住了多少人
那么大酒店会派出服务员
找到114号房
清点人数

内存把这一过程称为“寻址”
计算机想起来自己还有数据被存放在514号格子
计算机打算把这个值拿回来
所以计算机会找到514号格子
最后读取这个值

房间规格->数据类型

这下子大酒店来了一个旅游团的人
导游为了更好洗脑,不让他们分开住
这时候,酒店把连续的几间房串通
这样就有了一间大房让旅游团的人住

计算机中也有各种各样的数据
可能8位的格子不够用
所以计算机面对这些“特殊”类型
也会把内存中的连续几块格子
当作一个大格子来看

事实上,一般的编译器认为8位的数据类型只有两个:char 和 unsigned char
short:短整(数)形,16位
int:整(数)形,32位

房间名->别名

随着住酒店的人越来越多
现在房间号都排到1919810号了
住在1919810的客人老是看错门牌
总跑错到别人的房间
酒店为了解决这个问题
把一些重要客人的房间
添加了一个房间名
比如1919810号房,又称为
“超级无敌豪华总统套房”

对于计算机来说,记一两个数字没什么
但是对于写程序的人来说就是一个灾难
总不能写一个程序要记住一堆内存号吧
比如两个数值相加就变成了
把在0x3344ABAB号格子的值
和在0x622B2ABA的值相加
这些记一两个还好
写多了真的会乱
搞不好还把这些数字写错

这时候,人就把0x3344ABAB这个地址
起一个名字“num_1”
把0x622B2ABA这个地址
起一个名字“num_2”
那么把这两个格子相加就变成了
把在num_1的值和在num_2的值相加

找房间->别名寻址

住在“超级无敌豪华总统套房”的客人迷路的时候
问服务员“超级无敌豪华总统套房”在哪
服务员也很自然地把他带到了1919810号房
也就是“超级无敌豪华总统套房”

同理,学校里有个靓仔
学号是21050714
那么这个靓仔是他
21050714也是他

内存有一个小格子被称为“num_1”
实际上,这个小格子就是0x3344ABAB
不过要注意,这个数字只是这个小格子的号码
不是小格子里面的内容

就像“超级无敌豪华总统套房”只是
1919810号房的别名
1919810不是实际住的人数
“超级无敌豪华总统套房”住了6个人
为什么是6个人?
因为一开始我就是安排了6个人来住
“超级无敌豪华总统套房”啊

num_1,这个0x3344ABAB号小格子
里面存放了6,为什么是6?
因为计算机一开始就是想往某个内存格子
存放一个临时的数值6
刚刚好0x3344ABAB号小格子有位置
所以0x3344ABAB号小格子被存放了6
自然,读num_1这个小格子,得到的值也是6

房间规格+房间名->数据类型+别名=变量

绕了一大圈,终于讲到了变量
其实上面介绍的内容也间接在讲解变量的原理了

现在酒店又来了个2107班,这个旅游团有47人
酒店的每一个房间最多住8人
所以至少需要6间房才能住下
这班同学感情很好,大家想住一间大间
酒店很乐意,所以把这6间房打通成一个了大间
并且给这大房起了个别名
“有福同享,有难退群”
同学们也很开心

不知道大家有没有注意到一个问题
假如酒店打通的这6间房原来的门牌号分别是
8208、8209、8210、8211、8212、8213
那打通之后,这个门牌号叫啥啊?
酒店也想到了这个问题,既然是从8208开始打通的
总不能叫8210吧,所以最终这个房间的门牌号就是8208
当然,“有福同享,有难退群”也是8208

综上所述,现在酒店有一间房间
名字是“有福同享,有难退群”
房间规格是“6串大房”
门牌号是8208
实际人数是47

计算机这边同理,1个内存单元只能存放8位二进制的数值
所以最大的值应该是 2的8次方减1 ,也就是255
如果我想让计算机存放 1073741823 这个值怎么办?
把这个数值转换成二进制,你会发现这个值是30位的
也就是说,起码要4个8位的内存格子才能放得下
在计算机中,一般把占用4x8也就是32位的大格子
称为int,也就是整型
其实一般的运算int是完全够用的
所以int是最常用的变量类型

这时候,计算机会向内存申请,需要4个连续的格子
内存查了查,发现
0x66A384号
0x66A385号
0x66A386号
0x66A387号
这4个连续的格子可以用
那么就把这4个格子看作成一个大格子
这个大格子的门牌号就是0x66A384号

叫我记0x66A384号这种号码是不可能的
这辈子都不可能的,英语都记不住,还记这些
所以我给0x66A384号大格子起一个别名
“num_4”,这下好记多了
num_4这个格子,就是0x66A384号格子

综上所述,现在内存中有个大格子
名字->变量名是“num_4”
房间规格->数据类型是int(整型)
门牌号->地址是0x66A384号
人数->变量值是1073741823

所以我现在说我要读取num_4的值
实际上就是说我要读取0x66A384号内存格子的值
这个内存格子里面的值就是1073741823

现在你再看C程序:

int num_4 = 1073741823;

这段代码实际上就是让程序向内存申请一个大格子
然后把1073741823这个值保存到这个格子中

指针的本质

注意,一下内容可能开始会变枯燥
如果开始看不懂,请反复看,或者用草稿纸画画
“指针”这个说法会让很多初学者产生歧义
其实,“指针”就是“内存地址”!

接下来,我会继续用刚才提到的大格子
来解释指针到底怎么用
变量名:“num_4”
数据类型:int(整型)
地址:0x66A384号
变量值:1073741823

指针变量

上文的num_4变量,是一个专门放int型的变量
除此之外,还有:

类型 位数 描述
char 8位 字符型
short 16位 短整型
long 64位 长整型
float 32位 浮点数
double 64位 双精度浮点数

除此之外,还有一种特殊变量,叫“指针变量”
顾名思义,这种变量是用来存放“指针”的
声明的方法也简单,就是在数据类型的后面添加一个“*”

int * num_p;

这个“*”号在哪里都无所谓,比如下列代码
功能是一样的,都是声明一个名为num_p的int指针变量

int* num_p;
int * num_p;
int *num_p;

获取变量的地址

& 运算符就是用来获取一个变量的实际内存地址
还是我们的老熟人
变量名:“num_4”
数据类型:int(整型)
地址:0x66A384号
变量值:1073741823

num_4的实际内存地址就是0x66A384
所以,使用 &num_4 你就会得到这个值
一定要注意,0x66A384值在本文是虚构的
实际在计算机中,这个值是计算机向内存申请得到的
我们不能随便更改某个地址的数据
因为这样可能会导致严重的错误

获取某个地址的实际值

继续请上我们的老熟人num_4
为了好记,我执行了一句

num_4 = 123456;

变量名:“num_4”
数据类型:int(整型)
地址:0x66A384号
变量值:123456

现在我知道变量num_4的内存地址为0x66A384
该怎么得到num_4的值呢?
用酒店的说法就是去到那个格子查看对吧
C程序中,应该使用“*”号去查看
这个可不是乘号,虽然符号一样
但是在不同语句下,作用是不一样的
比如a*b是a乘b
但是*num_4是啥?
是0x66A384吗?
不不不,这你就搞错了
&num_4才是0x66A384
那是*num_4是123465吗?
当然不是,num_4才是123456

*num_4实际上就是把后面的值,当作地址
再通过这个地址得到这个地址的值
是不是很绕?没关系

现在有一个指针变量num_p

int * num_p;
num_p = &num_4;

num_p的值是num_4的地址,也就是&num_4
也就是0x66A384
注意,是num_p的值是0x66A384
也就是说,内存中又多了一个大格子
内容是0x66A384,别名叫num_p
那这个大格子的实际地址,也就是&num_p
这个值也是计算机安排的,可能是0x7723F45
也就是说,现在多了个新小子
变量名:“num_p”
数据类型:int*(整型指针)
地址:0x7723F45号
变量值:0x66A384(这个是num_4的地址)

一定一定要记住
因为刚才执行了一句num_p = &num_4;
所以num_p的值是num_4的地址
也就是说 num_p 的值是0x66A384

那么开始解答,*这个符号怎么用?

int num_new = *num_p;

现在又多了一个变量num_new,我们再看看这个是什么

变量名:“num_new”
数据类型:int(整型)
地址:0x2884C62号
变量值:123456

哎!发现了吗,num_new的值是123456

为什么呢?

我们来看看*num_p到底做了什么吧
其实*的作用真的很简单,就是获取某个地址的值
上熟人!
变量名:“num_4”
数据类型:int(整型)
地址:0x66A384号
变量值:123456
如果我跟你说*0x66A384其实就是123456你能搞懂吗?
为什么呢?
因为*是获取某个地址的值啊,*后面是0x66A384
也就是说,获取内存第0x66A384号格子的值
第0x66A384号格子的值不就是num_4的值吗?
而num_4的值不就是123456吗?
所以*0x66A384就是123465

再看
变量名:“num_p”
数据类型:int*(整型指针)
地址:0x7723F45号
变量值:0x66A384(这个是num_4的地址)
*num_p就是把num_p的值作为地址
根据这个地址获得一个值
num_p的值不就是0x66A384吗?
所以*num_p等于*0x66A384
而0x66A384是num_4的地址
所以获取内存第0x66A384号的值
就是获取num_4的值,也就是123456

可能你还是会很绕,接着看

int num_4 = 123456;		//所以num_4的值为123456
int *num_p = &num_4;	//所以num_p的值是num_4的地址
int num_new = *num_p;	//把num_p的值作为地址,获取这个地址的值
//所以最终num_new的值是123456

结语

指针是作为C的一种功能存在的,如果学会了使用指针,很多地方其实用指针很舒服的
可能第一次看还是很绕,因为我也是第一次写这种类型的文章,有错误的地方还得请大家多多指正!本篇是纯文字文章,可能看得很枯燥,以后我尽力画一些插图吧。
这篇只是指针的一篇开篇,后续我可能还会写关于指针的数组篇、结构体篇等!但愿我会更新它,感谢您的观看!