理解C语言指针
C语言指针理解
本人在初学的时候认为c语言中指针很好理解,但身边好多同学一直在说老师讲的指针太抽象了,看不到,摸不着,非常难理解,甚至学了4年计算机,毕业了,不少同学还说不清楚指针是什么,遇到指针的问题必定出错,这里简单介绍一下。
引言
c语言中有很多抽象的东西,而指针就是其中一个,学好 c 语言就要学会计算机的思维:透过表面看本质。
比如下面一行代码中 两个 ! 运算符
int x = 0;
int a = !!0; //看这里
表面上:第2行的两个 ! 运算符抵消了,相当于没有。但这只是结论。
本质上:对0进行了两次 ! 运算。这才是本质。
很多同学整理错题的时候也是只整理表面上的结论,所以导致同一类型的题目一再二,二再三 出错。
正题
为什么说指针好理解?首先要有抽象或者说本质的概念。
感觉这个世界里非常多相似的东西,上了十几年的学,就算学新知识,也能在已有的知识体系上找到很多的相似的东西,学习指针时,可以拿已有的概念做参照来学。
下面我先给它一个定义,让你能想象的出它大概是什么,干嘛的。
指针像商品的条形码,像汽车的车牌,像寝室的门号,又像人的身份证号…等等。你可以把他理解为一个标志,它并不代表实际内容,只有一个值,但我们可以根据它来找到对应的真实存在的对象。
同样的,指针并不保存所指对象的完整信息,但是我们可以根据一个映射(map)关系,来根据指针中的值,来找到对应真正的对象。看以下丑图:
比如我有个程序:
int main(){
fun1();
...
}
void fun1(){
fun2();
...
}
void fun2(){
...
}
那他在内存中的某一时刻(正在执行fun2函数)可能是这样的:
而你定义的变量或者指针,在内存中的的栈中,具体到某个栈帧里。
可以看到栈内存的分配非常像一只只集装箱,集装箱里又装了一个个快递…
其中栈和堆只是个名字,认为划分的两个区域,为了方便程序内存管理。
- 栈中的内存不需要手动回收
比如 int a = 1;就不用手动 free(a) 操作
因为他会随着所在函数的结束,整个函数占用的栈内存会自动释放掉,实际上是编写 c 语言的人帮你释放掉了。 - 堆内存需要手动回收,比如 xxx* p = malloc(…); 相当于在堆内存中开辟了一块空间,在栈内存中新建了一个变量,把堆内存的地址的值赋值给栈内存中的变量(指针),在使用完毕后需要手动释放掉堆内存的地址,因为不过不主动释放,虽然栈中指针所占内存被自动回收了,但堆内存并没有被标记为释放,而又无法通过栈中的值找到这块堆内存,即发生内存泄漏,这块堆内存在该程序结束前将一直无法使用。
- 指针和实际指向的对象是两个东西。
废话解释:
像商品的条形码 纯净的商品是不带条形码的,可以根据条形码找到对应的商品
像汽车的车牌 汽车生产出来也是不带车牌的,但车牌可以方便我们识别一辆车
像寝室的门号 寝室本不需要门号就能使用,门号只是方便我们找到他
像人的身份证号 人在一诞生是没有身份证号的,是出生后才有
回归正题
比如我有以下代码
int a = 0; // 定义一个int类型变量a
int* point_a; // 定义一个指向 int类型变量 的指针
point_a = &a; //把 变量a 的内存地址 赋值给 point_a
//以下为比喻-------------
Car aodi = ....; // 有一辆奥迪车,它的颜色是...长...宽.........
Car* carNum; // 生产一个车牌
carNum = &aodi; // 把这个车牌挂到车上
其中,无需纠结比喻部分合不合理,主要看 c 代码即可。
看一下内存中是如何分配的
可以看到指针的值其实是一个内存地址值,而不是具体值。具体的值保存在变量 a 中。
因此,当你进行以下地址计算时:
printf("%d", &a); // a 变量的地址, 0x8004
printf("%d", point_a); // 变量 point_a 的值 0x8004
printf("%d", a); // 变量 a 的值 0
printf("%d", *point_a); // 变量 point_a 值被按照int解析后的值 0
c 语言中 * &的意思:
首先:内存中的状态只能表示 0 或 1,因此获取内存中变量的值的真正含义,需要根据类型来解析
变量前符号 | 简介 | 详情 |
---|---|---|
什么都不加 | 变量的值 | 根据变量的类型,解析变量所在内存地址中的值 |
& | 变量地址 | 获得变量的内存地址 |
* | 寻址并解析 | 寻找地址等于变量值的内存,根据变量类型解析这块内存中的值 |
以上是指针知识的大概介绍(是什么)
大概对指针有点概念后,还需要知道他存在的意义(为什么存在)和用法(怎么用)
存在意义
主要是为了减轻程序员的负担。
虽然 c 还是不 java,python,js 等写起来爽,几乎不需要关心内存了,但他相比于汇编语言,机器代码,有这种思想,在当时已经非常先进和高级了。
因为如果让程序员直接管理 地址,解析地址中的 0 或 1,那不得累死?所以出现了指针这个东西,极大地方便了程序员来管理内存,在写代码时无需关心底层内存是如何分配的,可以更好的专注于功能的设计。
题外话
当然在现在,c 语言毕竟使用时还需要手动释放堆内存,允许直接访问内存地址,操作不当则很容易使得系统或者其他程序出错(如修改某游戏所占内存中的值,简称外挂),内存自动分配和回收的语言更加受到开发者的喜爱。
如何使用
以上是本质,如果只看现象不看本质,在做题时候稍微有点坑,便反应不过来。
比如一般写代码时候 总是让某一类型的指针指向特定类型的变量,如下
//易读写法
int i = 0;
int* point_i = &i;
char c = ' ';
char* point_c = &c;
//实际中经常会简写为
int i = 0, *pi = &i;
char c = ' ', pc = &c;
其实以上都是非常规范的写法,是指针的最简单的使用,大部分出现在入门阶段。
深入分析
我们通过现象看本质知道。
析地址符 * 可以按照后面变量的类型来进行解析内存等于其后变量值的内容
假设可以以下操作
char c = 'a';
char* point_c = &c;
short* point_s = &c;
int* point_i = &c;
那我们就知道这三个指针变量 的值相同,类型不同,因类型不同,解析出来的表现也不同。
‘a’ 在内存中存的内容实际是其 ascll 码值,即 61 ,char 类型占用的内存为 1 字节即 8 位,则在内存中形式为 000111101,
那么 *point_c 的流程:
- 根据 point_c 的值获得内存地址
- 根据 1 得到的内存地址找到对应的内存
- 获取找到内存的类型
- 检查是否为自身或为(point_c ) 类型的子类,如果不是继续判断能否进行强制类型转换
- 如果是,根据所得的地址,按照自身 (point_c) 类型,获取该区域内存的值(char类型,读取1个字节),000111101
- 按照 自身 (point_c) 类型解析(转换为 ascll 码所代表的 字符) ,转换成 ‘a’
*point_s 的过程与上类似,我们这里做个假设第 4 步通过了,会发生什么?
直接来到第 5. 步:
- 按照自身类型(point_s),从该区域读,连续读 2 个字节 000111101 xxxxxxxx
- 按照 short 类型解析,大部分计算机内存中内存存储会按照高位在前,低位在后存储,因此:
- 其读到的数据用二进制表示为 xxxxxxxx 000111101
- 把它认为成一个 short 类型数据读取
其他情况本质上都是这样的
提示
初学者最重要的就是要理解 指针 和 它所指变量是 两个变量。
本篇仅面向初学者,只介绍了指针的读取,有不理解的地方欢迎留言。
还没有评论,来说两句吧...