c语言中中文可以比较大小吗_指针,很难吗?| 工程师给你详细解释!

导读:指针是 C 语言的灵魂,该如何真正理解并运用呢?这篇文章告诉你答案!终于到了 C 语言中最为重要的指针环节了。之前一直以积累为主,不敢写,或者说不愿意写,因为没有足够的高度写出来的东西很多都是片面的,当然现在我也不敢说我目前写出来的就一定是全面的,只是对于普通的程序员来说,也算比较全面了吧!当然因为东西太多,有可能会有忘记的地方,到时候再更新补充吧。这里将数组和指针放在一块介绍,是因为他们很像,很像的东西放在一块比较能找出它们的差异性了,以后使用的时候也就不容易犯迷糊了。并且这里将穿插测试代码和从汇编的角度去理解 C 语言,这样可能更容易掌握指针。注意,所有的测试工作都是在 KEIL 、STM32开发平台下的,其他平台不敢保证这是对的。先概括一下本文大纲(如果道友看到的不全,请关注公众号查看): 1、解析指针的过程与意义(重点) 2、指针大小、强制转换、二级指针、结构体指针、函数指针 3、指针和数组 4、指针与寄存器 5、指针与外设 6、溢出与使用效率问题

解析指针的过程与意义(重点)

什么是指针?鱼鹰不想用教科书式的语句去介绍,这样就失去了本篇笔记的意义了。现在我们想象这样一个场景,广场里有一个个由学生组成的方阵,每个方阵有大有小,这些学生由教官统一指挥(军训场景)。 c0fbac722254f624c1cd90ec29aef006.png 现在教官想要通知第一方阵的2号同学唱首军歌该怎么做?正常情况下应该是教官眼睛先看向第一方阵的位置,然后看向2号同学(发现是黄四郎),“抬起头来,唱首《中国军魂》” ,实际上他想走个“走个虎虎生风,走个一日千里,走个恍如隔世” (教官寻找2号同学的过程其实就是结构体内数据寻址的过程)。 e0267c042e222a6ad725a253a5482f21.png一个、两个人的通知,对教官来说不算啥,但是如果有很多人需要分别通知到呢?怎么办?分级管理。 64c4e459e51e904f2ef2a8d40d3e65e5.png既然力不从心,那么我就找几个助手呗(也是学生,但不属于各个方阵内的学生,上图蓝色部分学生,学生助手比较特殊,占用位置空间大小是方阵内学生的4倍,毕竟是小官了)。刚开始这些 学生助手满脑子都是在想着怎么偷懒,不想军训,怎么办,只能先来个思想教育(指针清零),然后让每个助手记忆各自方阵的成员(指针赋值),还有可能时间紧,思想教育都省了,直接记忆。一段时间后,每个助手对方阵内的各个成员都熟悉起来了。熟悉到什么程度呢?你随便问一个位置他都知道那里站的是谁,比如10号位置就是张三,12号位置是李四。但是你要问他其它方阵内的同学,他就支支吾吾了。现在假设他们管理的方阵如下: c7fa13667d17dbb0ec7b189ea90b8df8.png现在管理很明确了,咱再来通知一遍!为了通知黄四郎唱军歌给教官听,教官就得先告诉助手学生A,再让A同学通知2号唱歌。其他方阵内的通知也是同样道理。这样一来,想通知哪个方阵的哪位同学,只要通知管理该方阵的助手即可,该助手自会通知到具体的同学的。如此这般, 教官只要管理六个人,就能把六个方阵的所有学生都管理起来了,大大减少了教官的管理负担,但是好处仅是如此吗?非也!现在假设第一方阵的学生都有一个替身(可理解为 影分身,特点就是基因相同,也长得一样,比如1号同学很聪明,那它的替身也聪明,3号同学比较笨,它的替身也比较笨,但是它们的记忆可能不一样): 9826f0d24e4b1d2d4f8b1215b8795945.png现在助手人数只有那么多,不够该咋办?从道理上讲,应该找管理第一方阵的助手A代理 替身第一方阵,为啥,因为他最熟悉第一方阵的人员配置啊(比如他知道21号同学有事请假了,所以 替身第一方阵也没有它的替身,这样教官要找21号时助手A就可以马上他说不在了),但是学生助手A不愿意啊,这怎么行,一个人管两个方阵,会累死人的,可教官不管,毕竟上哪找一个能很快熟悉第一方阵的人啊。但是既想让助手A管理分身方阵,又要照顾助手A的心情,不能有让他抗拒心理,咋办?很快,教官想出了办法:催眠(换句话说,就是修改记忆)。教官是个催眠高手,很快助手A就被催眠了,虽然第一方阵和替身方阵长得一模一样,但是它们站的位置是不一样的,所以他认定了离教官远的那个方阵是他管理的方阵,而旁边那个才是分身方阵。 f9840b64d1b42a730b44d4c60f7e7c5e.png现在继续通知2号黄四郎(“咋又是我啊”),不过这一次通知的是他的替身,这下该怎么做呢。因为事先助手A已经被催眠了,所以当教官告诉助手A要找黄四郎时,助手A自然而然的通知了替身方阵内的黄四郎,而且他很高兴的执行了,因为不用多管一个方阵了。当教官要通知真正的黄四郎时,又得催眠一遍,让助手A认定离教官近的是自己管理的方队,没办法,缺人嘛,只能教官辛苦一下了。现在咱们再假设一种情况,第四方阵的替身组成了一个新方阵,有几个推迟上学的赶回来军训了,教官将他们安排在第四方阵后面: 06920510216567d7a4c3289703435737.png同样的,还是缺人,这个时候应该催眠谁呢?当然是学生助手D了,为啥,因为第四方阵他最熟悉啊, 第四方阵(部分替身)和 第四方阵只有两点不同:第一:站的位置不一样。第二:多了后面部分迟到同学。催眠他去管理这部分学生是不错的选择,但是因为学生D只对原生的第四方阵熟悉,对后面赶来的同学不熟悉,无法管理,也不知道原来30号同学后面还有一堆人(嗯,近视了),所以教官问催眠后的助手D方阵共有几人时,只会说共有30人,4人未到。 ed08928541ca6e3799cbe4b585af75b8.png所以教官被学生D误导了,他很相信学生D报告的结果,但是如果他去替身方阵后面查看的话,会发现那里还站着不少人呢!上述场景描述应该很容易就理解了,那么如何和我们的C语言指针联系起来呢?教官是CPU,一个个学生所站的位置就是内存空间,而学生是这块空间的属性(聪明还是笨,或者其他属性),而学生的记忆就是内存空间的内容。那么方阵是什么?由程序定义的数据结构。这个数据结构需要占用空间,有大有小,也可能内部空缺(内存对齐需要)。那么那些助手又是什么?本篇的主角: 指针。虽然它担任着管理任务,但是它的本质还是学生,只是赋予了管理职责(它也可以只管理一个学生(字节),不一定是方阵(结构体等))。那么那两个假设在C语言中又代表了什么? 重新赋值和 强制转换赋值(强行把一个大方阵交给一个只能管理小方阵的助手,但是他自己本身是不知道自己管理了超出自己能力的方阵的)。现在我们再往细了说,和实际C语言代码联系起来: 9b9041861cc34b6562e2f728c3fb7c9d.png上图定义了三个方阵,每个方阵的结构是不太一样的(关于typedef和struct关键字可看鱼鹰相关笔记),为了更好的理解接下来的知识,以方阵 1 为例介绍上述代码的含义。注释“方阵蓝图”部分,就如注释一般,就是一个蓝图,它只是告诉编译器,这个方阵1应该怎么安排, 但实际上根本还不存在这个东西,这就是一张蓝图,只用于参考之用(可理解为建筑工人拿着一张建筑图,准备按建筑图的模样建造一栋房子,但还没开始建)。那怎么利用这个蓝图搭建一个方阵出来呢(按建筑图搭建出一个实实在在的房子)?在C语言里面只需要一句话即可完成,就是上图中另一个方框内的代码。那么第一个假设在C语言里是怎样的呢?首先需要一个替身方阵,这个方阵和方阵1应该是一样的,因为方阵 1 是按照蓝图设计的实物(在内存中占有空间,而方阵蓝图不存在内存中),所以咱们可以用蓝图继续建一个方阵出来: 86c2b0685b85ab8cb2e03afb5249b692.png可以看到这个 替身方阵1(SquareMatrix_1_StandIn)和 方阵1的创建过程没有两样,换句话说,两者是等价的,只有一点区别,就是占用的内存位置不一样(可理解为你在 北京建了一个房子,然后又在 深圳按照北京的房子样式又建了一栋一模一样的房子,区别就是现实中你可以直接以北京的房子为蓝图建造,而在C语言中, 你必须先有一个蓝图,才能建一栋北京的房子(SquareMatrix_1)和深圳的房子(SquareMatrix_1_StandIn))。现在我们理解了 替身方阵和 方阵之间的关系,再来说说前面所说的催眠问题,在C语言中是如何实现的呢?首先需要一个助手,这样才有催眠对象嘛。这个助手有什么特别(属性)呢:他只能管理以方阵1为蓝图构建的方阵。在C语言怎么达到这个要求呢? b6130fd3dca6fe0f29b1d58d8440145c.png这样符合要求的助手A就诞生了。现在教练(CPU)让他管理去管理方阵1: 3f2cc0a561f97011fdfdae9b082faecb.png可以看到,在实际代码中根本没有CPU(教官)的身影,但它却无处不在,因为代码就是靠CPU一步步执行的。现在又想让他管理替身方阵,就得催眠一下: 3513c7a3a2360fa32f0ea6b05fcc62d9.png可以看到,你理解的催眠在C代码上和开始教练就让助手A管理方阵1没啥区别,从宏观上理解,开始就让助手A管理方阵1也可以认为是一次催眠操作,只是在这次催眠之前助手A是没有任何管理对象的。从这里也可以理解, 单独的没有指向一块内存的指针是没有意义的,就和光杆司令一样,只有一个司令,没有兵,怎么打仗,这样理解之后,你就不会让光杆司令(没有分配士兵)去打仗了,而只要有一个小兵,那么司令就能指挥了。那么第二个假设在C语言中又是怎么回事呢? 67cc68cb1a87ac09518929f04467db9d.png按照C语言要求,必须先有一个蓝图(专业术语称为“声明”),然后才能创建一个新方阵出来,并且需要一个能管理方阵2的助手B: dad9aa717af1e98cb96ceaec59960658.png因为在诞生助手B的时候, 基因上决定了他只能管理方阵1,对使用新蓝图SquareMatrix_2_PartTypedef创建的SquareMatrix_2_Part是无法管理的,但是因为SquareMatrix_2_Part前面部分是利用方阵2的蓝图构建的,所以让他管理前面部分的倒是没有问题,这个和催眠又有点差别: a02763dcce3dcf2628d7fc9ef32ff857.png前面说了C语言,现在又不得不说一说汇编语言与编译器。那么怎么理解它们之间的关系呢?C语言和汇编语言之间隔了一个编译器。用通俗的话介绍就是,一个不懂鸟语的人(C程序员),要和鸟(单片机)对话,就要一个懂鸟语的人做翻译官,通过这个翻译官将人类的语言(C语言)和转化成鸟语(汇编语言),从而实现对话。只不过,他们的交流是一次性完成的,就是说不懂鸟语的人把所有要说的话(C语言代码)一次性告诉翻译官,翻译官翻译好了之后(汇编语言代码),再告诉鸟(单片机),“你应该干啥、不该干啥”,小鸟记住了之后,就一遍遍按照要求重复执行了。从这里可以明白, 单片机存放的是汇编代码(更准确是0、1的二进制),而不是我们程序员看到的C语言代码。另外还可以知道的一点就是, 与其说我们程序员是在和单片机打交道,不如说同时在和单片机与编译器打交道,既要了解单片机(鸟)能干什么,也要清楚编译器的规则,不能让翻译官译错了你的意思,否则你说往东,他还以为你说往西呢!看过一个故事,说Unix操作系统的创造者,总是能很快的黑进任何一个Unix系统,别人一直以为是Unix代码中留下了暗门,但查了很久也没发现,后来才知道是编译源码的编译器留下了暗门。不管这个故事是否真实,但编译器的重要性毋庸置疑。现在我们再简单聊聊编译器的一点好处,让我们知道为什么需要编译器。了解汇编的人都知道,单片机的内存都是程序员分配的,而且是直接和内存地址打交道。比如1号内存放一个同学,2号内存放另一个同学(所有内存空间都进行了编号,就是地址),找人的时候就通过这个编号找。但是数字记忆不是人类的强项,所以就给这些编号命名了一个别名,比如1号叫做张三,2号是李四,当叫2号唱歌时,只要叫李四即可。 ec3f07dd11a658663eb2e4366a1aee59.png但事实上很少有人这么干!毕竟如果只是取个别名也没方便到哪里去。更多的时候,我们都是告诉编译器,我们需要两个空间,一个空间放张三,一个空间放李四,但具体放在哪,我不管: 2231b19b1b12ffa42bb4e4f15c120a00.png 034ebca38ed38d40d4bec852fd1cc253.png编译后,从 .map 文件中(如何打开该文件可参考鱼鹰以前的笔记)可知道,编译器将这两个命名为张三和李四的空间放在了0x20000018 和 0x20000019 (注意这里没有4字节对齐)里面(事实上,每次改变代码后编译,这些空间地址可能会发生变化,不变的是这个空间名,你总是能通过这个空间名去访问一块内存,只是可能两次编译后再访问时,它所在的空间地址不一样罢了,一般来说,这没什么大不了的,毕竟每次使用这块空间前都会进行初始化)。所以在这件事上,编译器为我们做了两件事:第一,分配内存地址;第二,命名这块空间名字(事实上还有第三件事情,规定这个空间只能放char类型数据)。而张三、李四这个名字不仅代表了两块空间,还有一个额外的空间属性要求:只能存放char类型数据,操作这些空间时一定要注意这一点(有些错误操作可能编译器会发出提示信息,而有些操作编译器发现不了,所以需要额外注意)!其实引入编译器的好处不止于此,这里只是简单介绍,不深入讲述。现在我们再来从汇编语言的角度去看指针赋值与强制转化过程。 9c94efcd0b6fb6cae0dac5b5478593d6.png从上面可以看到,所谓的 指针赋值和 强制转化,在汇编代码的层面上可以说完全一样,都 只是赋值操作,都是将 方阵的地址赋值给寄存器R0, 方阵指针的地址给寄存器R1,最后方阵的地址赋值给方阵指针所在的地址(从这可以了解到,内存和内存之间不可以直接操作,必须通过寄存器中转,这就是为什么明明只有一条C语言代码,汇编语句却有多条的原因之一了,C语言封装了很多操作细节,虽然我们可以不去深究,但必须了解它的存在)。以第一条C语句为例,用示意图表示(只把涉及到的内存空间画出,填充颜色部分为实际内存空间,未填充部分用于说明空间地址或者寄存器,xxxxxxxx表示不必关心原来的内容是什么,红色表示操作后发生的变化): 4c7b9c33e1857521ef9e801eec2bc34b.png而操作 结构体内的变量 Student_12如下: 3e2d21bc359804fb48ddf7c88470057f.png 8c77334a695a58a430d01a72cd55535d.png 首先把0 赋值给R0,把指针所在的空间地址(0x080010CC处存在一个地址)赋值给R1,再把 R1存在的地址的内容 赋值给R1(这时这个R1存放的就是方阵结构体的首地址),最后把R0赋值给相对R1地址偏移4的Student_12中。示意图如下: 75043c167d8608cd069ff73e67d8e40e.png而直接操作结构体的方式如下(不采用指针): 152f9372dd0f9a06277d4b0cd172661f.png首先将0x01赋值给R0,然后将0x080010CC处的内容赋值给R1,最后把 R1的内容当做地址,并将R0赋值给这个地址相对偏移0x04的地址处。示意图如下: d5781ded90f92e0b6e1a60df1a626447.png从中对比两者操作可以发现,当不使用指针操作0x200000 54空间时,只需要三条语句,而使用指针,因为涉及到对0x200000 10内存空间的操作,多增加了一条指令,即从0x200000 10 (指针)处获得操作 基地址0x200000 50,再做最终的赋值操作,除此之外的操作指令都是类似的。 ab180544520b929d51d4b02a8116bd7a.png                    (通过KEIL查看结构体内的变量在内存中的地址)从上面也可了解到,所谓的指针,只是人为的把内存里面的内容当做地址而已,因为你把存在0x20000010处的0x200000 50当做了地址去操作,才存在如下关系: d9aaa91523cc59e9cb3209267a685441.png你使用C语言去操作0x20000010时才会影响到0x200000 50处的变量。但是如果你不把它0x200000 50当做地址,而只是当做普通数据处理也是可以的: 4bea3dc513198cb48a715d66b7158079.png(事实上,当增加变量data 编译之后,SquareMatrix_1_Assistant的内容不再是0x20000050,而是变成了0x20000058,这里我们假设编译器为之前的变量分配的空间地址不变)当然你现在又希望把这个数据当成地址,咋办? 9919a91a564be126fa8c18315756a9f3.png这样一来,不仅把数据0x20000050当成了地址,还改变了可访问的数据大小,即只能访问int大小数据,结构体内的其他数据无法访问: f439911ada6eaaf21a60536b52f82a89.png我们尝试对0x20000058这个地址赋值: 4827fad017089f2daceb30fcb091f6b4.png当你理解了上述内容,你会发现,指针,不过如此!其实通过以上内容的讲述,我们也可以总结使用一块内存需要注意的三个要素: 地址、基因(属性)、内容。所谓地址,就是这个空间所在的地址;属性,就是规定这块内存能存放什么东西,char还是int,或者其他自定义类型等等;而内容就是内存中存放的东西,这是程序员真正需要的东西,前两者都是为其服务的。那这个和指针有啥关系,别忘了前面所说,指针也是一块内存,只是这个内存的属性规定了两个,第一,存放指针(这个也只是普通数据,只是需要按照C语言要求处理),第二,这个指针指向的内容是char、int……数据类型而已(也可能会规定其他属性,这里不讨论),从内存空间的角度上,所谓的指针和普通的数据类型没有本质区别。那么学习指针有什么好办法吗?鱼鹰认为,除了要深刻理解指针外,还有就是要像鱼鹰一样,画图去表示它们之间的关系(不用像前面一样画的那么清楚,画个示意图即可),只有这样,你才不会被那些指向关系搞得稀里糊涂。也只有这样,你才能在代码中灵活运行指针去做你任何想做的事情。

这篇笔记修修改改不知道多少次,原以为能比较快就能写好的,但事实上花了好几天才写完,因为鱼鹰要尽可能的将故事贴合实际的 C语言运行情况,所以花了不少时间去思考,但真正难的还在于如何把心中所想画出相应示意图,这个是最耗费时间的。

尽管如此,指针这一块还是没有完成,道友看了前面大纲也可了解,这只是第一点内容,后面还有五点没写,以后有时间再说吧。

如果对道友有帮助的话,记得转发、分享哦!

-THE END-

来源于微信公众号「鱼鹰谈单片机」

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值