| “ | 指针到底是个啥啊啊啊啊啊啊啊啊啊啊!!! | ” |
| ——刚学C语言没多久的小白 | ||
C语言学到一半,突然看到“指针”这玩意,谁知道它有什么用途?于是大家都跟着在课本找资料,但是课本太正式了难理解,就转去上网查吧——
难道你不是看别人的文章觉得你根本就不懂所以才来这里看的吗?
对编程语言的学习与教授,大部分人都会常犯最大的三个误区,分别是“默认谁都懂”、“默认别人跟你思维一致”、“机翻痕迹明显”。这三大误区都会导致精通率不能达到100%,我管这叫“学习陷阱”。
| “默认谁都懂” | 你给不会电脑的玩家讲组策略管理器 |
|---|---|
| “默认别人跟你思维一致” | 对习惯了Python(以“对象”为核心思维的编程语言)学C语言(以“过程”为核心思维的编程语言) |
| “机翻痕迹明显” | 南开大学的涂奉生、齐寅峰教授把“Robust”(程序运行刚性)翻译成“鲁棒性” 还有些人才为了让讲方言的人理解通透索性翻译成了“耐操性” |
| “译事三难:信、达、雅。求其信,已大难矣!顾信矣,不达,虽译,犹不译也,则达尚焉。”——严复 《天演论》译例言 | |
“指针”这个翻译,同时触犯了上面三种学习陷阱,你说你跟一个还没把内存学透甚至连数学都不会的学生这么科普,你能保证他完全理解吗?
既然你在这里是教师,你写的内容是以课程、百科、教科书甚至直接对话的形式传输给学生的,你不单只要让学生知道这个新内容,还要让他们明白这个内容是干什么用的、该怎么用、要注意哪些特殊情形。当然,你还要做好“学生把你的知识原封不动教给下一批学生”的准备。
名师出高徒,学生才会为你感到骄傲,如果你只是水任务水课程,要你这老师干什么!
C语言的Pointer,按照上文所说,确实是存放内存地址的变量,确实可被翻译成“指针”,但是他们对“Pointer”的解释不成功,导致很多人学得云里雾里。这里直接给出一个例子让你们结合生活实际理解什么是C语言的“Pointer”(指针),看过来。
- 在现实生活上举例
你在一个电商网站上购买一个物品下了订单,因为远隔千里,你和卖家都不能进行现场交易,需要把货品交给快递员派送。
快递员会收到来自卖家的货物,包装稳妥使其不被破坏,再根据你给出的收件地址,选择最为快捷的交通工具派送这包裹。
当你看到快递员送上来的时候,这“物品”往往被一个文件袋或是一个盒子包裹着,上面还贴着一张纸写着你的收货地址。
- 换在C语言里面
存放物品的包裹盒子就是变量,确保包裹里面的物品对得上订单的产品描述,对买家、卖家、订单和店铺信誉来说都重要。
写着收货地址的纸条就是指针,确保纸条上的地址对得上你家门口的门牌号,对派送包裹到你家里的快递员来说特别重要。
- 结论
指针是一种特殊用途的变量,用于存放其他变量在内存上的地址,对于一些要把数据复制到其他位置的程序来说特别重要。
不难发现,凡是涉及到数据复制、内容传递、代码位置跳转的程序都会大量使用指针,这些程序会在内存中的海量数据里,按照指针上存放的地址精准定位对应的变量,用于存放新数据、导出原数据,或者把这些数据放在与前端显示相关的变量里转化为可被用户理解或修改的数字、文本或各种媒体。
如果没有什么特殊情形,按照马斯洛需求层次理论,但凡动物甚至植物都有自己的安全需求,所以就连自己的家里都要注重家里的门窗能不能关死防止不法分子进来。但如果你愿意让朋友进屋拜访,或者快递员需要知道怎么把快递放到你手上,你就需要把你家的详细门牌号主动分享给你信任的人。
作为高级编程语言的C语言,其中的优势就是不需要用户刻意学习里面的内存分配并保证精通,因为编译器自己就能通过用户程序自动分配内存;反过来说,如果没有做好内存读写管理,长期保持固定的内存地址容易被不法分子逆向编译成功。
但有些需要存放海量数据的硬件(直接存储器存取,DMA)或软件(USART突发事件处理程序,俗称USART_IRQ)需要把数据准确复制到程序或功能要求的变量中,就需要向相关硬软件提供目标变量的绝对内存地址。
我们可以在变量前添加控制字&,用于获取该变量在内存中的绝对地址而不是该变量所存储的数值。
#include <stdio.h>
unsigned char val = 85;
//新建一个字节(8位)变量“val”,根据逆向编译,它被存放在了0x004F的绝对地址里面。
unsigned int a = val;
//新建单字(16位)变量“a”,初始值为“val”所存放的数值“85”
unsigned long b = &val;
//新建双字(32位)变量“b”,初始值为“val”在内存上的绝对地址“0x004F”
printf("变量 val 在内存上的地址是 %d, 其数值是 %x。\r\n", b, a);
/*输出结果:
“变量 val 在内存上的地址是 85,其数值是 0x004F。
”
*/
有了这样的控制字,我们就能在编译器保护所有程序不会越权访问变量的情况下,把需要代为读写的变量所在地址放在可被程序操作的变量上。
但如果没有特殊说明,编译器只会默认所有的变量只被当做文本或数值使用,只对面向当前程序的读写起作用。如果这个程序需要跨空间访问绝对地址,而被访问的绝对地址被其他程序所使用,就需要用到指针变量。