> 文档中心 > 【C语言】指针和数组的深入理解(第一期)

【C语言】指针和数组的深入理解(第一期)


篮球哥温馨提示:编程的同时不要忘记锻炼哦!

简单的程序员,用键盘敲出想要的生活


目录

1、指针是什么?

2、为什么要有指针?

3、指针的内存布局 

4、指针变量类型有什么作用?

5、指针的解引用

6、如何将数值存储到指定的内存地址? 


在开启这个章节之前,我看过很多书上都是先讲数组,后续又说数组名就是是地址,如果没有前期知识的铺垫,大多数都会想,地址是个啥玩意?到底什么是数组?什么是指针?他们之间又有什么样的关系?本章节我们将深入学习。

1、指针是什么?

在看这个问题之前,我们先引入一段简单的代码做铺垫:

int main(){int a = 10; //定义a变量开辟了一块空间并且初始化a = 20; //使用的是a的空间:左值int b = a; //使用的是a的内容:右值,此时a等价于20return 0;}

同过上面代码及注释,我们可以看出,同样一个 a 变量,在不同的应用场景中,a 的本身含义是不同的!

那么这个代码跟指针又有什么关系呢?

指针就是地址!就好比你 &a,取出变量 a 的地址,而这个就可以称作是指针!地址的本质上是数据!数据是可以被保存在变量空间里面的!

这里可能有人会说,你在胡说,那我之前就知道指针是存放地址的,你现在又告诉我指针就是地址?这直接把我搞糊涂了。这里你就不够严谨了!我们口头上说的指针,指的是指针变量!什么是指针变量?就是保存指针的变量,也可以说是保存地址的变量,这个变量就叫做指针变量!

从严格意义上,指针和指针变量是不同的,指针就是地址,而指针变量是C语言中的变量!要在特定区域开辟空间,用来保存地址数据,也可以被取出地址。

但是我们在口语化表达的时候,经常将这两个概念混合,是以前的书不够严谨?还是教学的人学的不够通透,或者是翻译外国资料的时候没翻译清楚?具体原因无从考证,在以后,我们分开理解,和别人讨论时,要明确概念,指针就是指针,指针变量就是指针变量!

有了上面的认识,我们再来看一段代码:

int main(){int a = 10;int* p = &a; //取出变量a的地址放到指针变量p中p = (int*)0x11223344; //使用p变量的空间,左值int* q = p; //使用p变量的内容:右值,此时的p等价于0x11223344return 0;}

 我们再次总结一下:指针就是地址!指针变量本质是变量,里面保存的是地址(指针)值!


2、为什么要有指针?

这里我们举个例子来说明,张三在寝室打游戏,到了中午肚子饿了,于是拿起手机点了份外卖,外卖小哥送到寝室楼下,一头雾水(假设没有门牌号的情况下),打电话问张三:“你在哪里啊?”,张三:“我在宿舍里啊”,“你在哪个宿舍啊?”,“我不知道该怎么表达,你一间间找吧”,外卖小哥很无奈,只能一间间的找,好不容易找到对应的房间,菜都凉了,外卖小哥成功获得了一个差评。

通过上述案例,证明了门牌号的重要性,显然而知没有门牌号是多么麻烦的一件事,门牌号归根到底就是为了提高查找效率!

那么我们类比到计算机中,CPU 在内存中寻址的基本单位是字节,在32位机器下,最多能识别 4G 的物理内存,既然 CPU 寻址是按照字节,但是内存又很大,所以可以看作众多字节的集合:

其实我们门牌号就可以等价于每个字节空间对应的地址,也就是该空间对应的指针

存在指针的意义就是为了提高CPU寻址效率。 

那么如何理解编址呢? 

我们得了解一个概念,计算机内部是有很多硬件单元的,而硬件单元是需要互相协同工作,最基本的他们之间要互相进行信息传递,而我们一个个硬件之间是相互独立的,总得想个办法把他们相关联起来,就举例CPU和内存之间要传递大量的数据,那么他们之间就会用“线”给连起来,今天我们只关心地址总线。

在CPU需要访问内存的某一个空间的时候,必须知道这个字节空间在内存的什么位置上,因为内存中字节很多,所以就如我们上面所说引入了地址,也就是给内存进行编址,在计算机中的编址,并不是把每个字节的地址信息保存下来,而是通过各硬件厂商出厂前就设计完成了。

硬件编址也是如此,32 位机器有 32 根地址总线,每根线可以表示两种形态 0,1【高电压低电压】,也就是说 1 根线能表示 2 种含义,2 根线表示 4 种含义,以此类推,32根线能表示 2^32 种含义,而每种含义则代表着一个地址,在地址信息被下达给内存,在内存的内部就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。(点到即可)

指针的大小?

有了上面的认识就理解指针的大小就很简单了,如果是 32 位机器我们要存一个地址数据是不是应该用 4 个字节,那如果是 64 位的环境就应该用 8 个字节大小空间来存放一个地址,那么我们的结论正不正确呢?

结论:指针变量是用来存地址的,地址的大小表示是根据操作环境变化的,所以要想放得下地址的数据大小,指针的变量大小也会随着系统环境而变化。 


3、指针的内存布局 

在很多教材上都会说某某指针指向了某某变量,是真的指向吗?既然指针变量是用来存地址的,那一个 int 占四个字节,他存的又是哪个字节的地址呢?下面我们来一一讨论。

先看一段代码:

int main(){int a = 10;int* p = &a;return 0;}

这里我们先来分析一下,创建了一个整型 a 变量,同时初始化为 10,接着我们创建了一个整型指针变量 p ,并取出变量 a 的地址赋值给指针变量 p,那么他们的内存布局是这样的:

所以我们常说的指针变量 p 指向变量 a 实质上是指针变量 p 中存放着 a 变量的起始地址,也是最小的地址。也能明白,取地址本质是取出变量的起始地址。如果有的老师画图把指向图画在变量中间的话,那么他显然画图画的很不严谨!


4、指针变量类型有什么作用?

变量有不同的类型,简而言之,我们指针有类型吗?有!

如果我们想要将一个 double 类型的数据放到一个变量中,我们会使用 double 类型,同理,如果我们要将一个 double 类型变量的地址放入一个指针变量中,是不是也应该用 double 类型的指针变量来存放呢?显然是的!

既然是这样,那么指针变量类型的意义在哪?我们举例来说明:

总结:指针的类型决定了指针向前或者向后走一步的举例有多大,这里我们在后面讲数组的时候也会一起讲,这里先做个铺垫。 


5、指针的解引用

在学习操作符的时候,我们已经接触过解引用 * 操作符了,那么如何理解这个 * 号呢?我们可以也可以拿宿舍的例子来说,你要进宿舍,发现宿舍门被锁了,肯定要拿出要是来开锁,锁的锁芯是不是很像这个 * 号?进寝室必须要用钥匙,那么我们去读写一块内存是不是也要一把这样的钥匙呢?

我们接着来看一段代码:

int main(){int a = 10;int* p = &a; //把a的地址取出来放到指针变量p中,以后可以通过p间接访问aint b = *p; //这里*p等价于a,把a变量的内容赋值给b*p = 20; //解引用找到a,把a变量的内容更改为20return 0;}

本段代码对 *p 完整的理解是,取出p中存放的地址,访问该地址指向的内存单元(空间或者内容),起始通过指针变量访问,本质是一种间接寻址方式!

总结:对指针变量解引用,就是指针变量所指向的目标。所以如上代码 *p 就是 a

对解引用概念有了解之后,我们再来看一个例子:

这里我们通过查看内存,发现了第一次 *pc 只修改了一个字节的内容,第二次的 *pi 修改了四个字节的内容,正好对应着他们分别是 char* 类型和 int* 类型

总结:指针的类型决定了,指针解引用的访问权限有多大! 


6、如何将数值存储到指定的内存地址? 

有了上面的对指针的研究,知道了指针的本质就是地址,地址也是数据,那么我们之前说指针解引用是间接访问,那么我们可以直接通过地址数据对变量进行直接访问吗?

int main(){int a = 10;//假设a变量的地址是0x12345678,那么访问a变量,还可以直接通过指针变量方式进行访问printf("%d\n", *(int*)0x12345678); //本质是一种直接寻址的方式*(int*)0x12345678 = 100; //本质是一种直接寻址的方式return 0;}

其实目前这种已经不可取了,现在主流的编译器和操作系统,为了安全已经有很多的内存保护机制,像 windows,linux 都有栈随机化的机制等,就好比你每次运行这个变量 a 的地址都是不一样的,在我们测试中,在windos平台和linux平台,使用C语言定义的局部变量在每次运行的时候都是不同的。


 Everyone is a star.

下期预告:指针与数组的深入理解(第二期)