正文开始
一、内存和地址
再讲之前,我们以一个生活中的案列来理解什么是内存和地址。
假设有一栋宿舍楼,楼上有100个房间,但是房间没有门牌号,这时有个朋友来找你,只能一间一间的找,这样效率就很低,如果可以根据楼层的情况,按照楼层给每层的每个房间编号如:、
一楼:101 102 103 104
二楼: 201 202 203 204
有了房间编号,你的朋友就会很快的找到你。
对比计算机,我们都知道计算机中的cpu在处理数据时,要从内存中读取数据,处理完毕后放回内存中,我们买电脑时,电脑上的内存有8G/16G/32G等,那么计算中的这些内存空间是如何进行管理的?
其实就是把这些内存划分为一个一个内存单元,每个内存单元大小是1个字节。这里要对字节这个内存单元大小有所概念。如下
1byte(字节) = 8bit(比特)
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
注:一个比特位可以存储一个二进制位(0或1)
也就是每个内存单元相当于一个学生宿舍,一个宿舍可以住八个人,相当于一个字节空间可以存放八个比特位。每个内存单元也有编号就相当于房间编号。cpu可以通过内存单元编号快速找到该内存空间。生活中比如你点外卖要填写你家地址的楼层的几单元门牌号是多少,在计算机中也一样把内存单元的编号称为地址。在C语言中取名叫指针。 总结就是:房间编号==内存单元编号==地址==指针
了解了地址与内存的关系,下面讲解一下如何理解编址
1.1编址
cpu在访问内存单元时,首先要知道其地址,但内存单元地址有很多,如果不对它们进行排列,就跟宿舍房间没有编号一样,所要要对其进行编制。计算机的编制,并不是每个记录下来,而是由硬件设计完成,就如同钢琴中的多来买法索莱希,虽然没有具体编号,但是演奏者可以精确的找到钢琴的每一个键位,因为制造商已经把它设计好了,并且演奏者都知道,也就是一种约定俗成的东西。接下来先来了解一下计算机中的硬件如何进行数据传输,以及数据处理。
cpu | 1 | 内存 | |
0 | |||
1 | |||
0 | |||
地址总线 | |||
0 | |||
1 | |||
0 | |||
控制总线 | |||
1 | |||
0 | |||
数据总线 | |||
1 |
计算机中的硬件是协调工作的而连接它们的桥梁就是线,这里重点讲解地址总线。假如一个计算机是32位机器,相当于有32跟地址总线,每根线有0/1两种脉冲,那么一根线就能表示0或1其中一种电脉冲,那么32根线,每个都有两种状态也就是2^32次方,每一个就代表一个地址。地址信息被下达给cpu在内存中就能找到相应的地址对应的数据,然后将数据通过数据总线传递给cpu内的寄存器。
二、指针变量和地址。
2.1&取地址操作符
在上面已经理解了内存与地址的关系,回到C语言中,请看如下代码。
这里创建了变量a,申请了4个字节空间,来存放10,图中4个内存空间都有地址如图红色框所示。
那么如何获取a的地址呢?这里就要学习一个取地址操作符了。
如图所示&a表示取出a所占4个字节中较小字节地址。
2.2拆解指针类型
*是指针变量和解引用操作符,&a是将a的地址取出来,有时候需要把它存储起来,方便后期的使用,那么这个地址存储在哪里呢?答:指针变量,入下代码所示。
指针变量也是一种变量,用来存放地址的,存放在指针变量中的值也可以理解为地址。
将上图所示代码拆分来看。
1*表示pa是指针变量
2int表示pa指向变量的a的类型是int。
*也可以作为解引用操作符如下代码所示
三、指针变量的大小。
以一段代码演示
注:X86是32位平台
X64是64位平台
由此可知,指针变量的大小与所属类型是无关的,只要是指针变量,在相同的平台下大小是一样的。
四、指针加减整数
前三个地址一样,后三个分别对它们进行加1操作,int类型的站4个字节所以加1跳过4个字节,而char类型占1个字节,加以跳过一个字节。
总结就是
五、void* 指针一种特殊的指针类型(无具体类型,可接受任意类型地址,缺点不能进行+-和解引用操作)
一般这种类型使用在函数参数部分,用来接收不同类型的指针变量地址,可以实现一种泛型编程的效果,使得一个函数可以处理多种数据类型。
六、const修饰指针
6.1const修饰变量
变量是可以修改的,也可以通过指针解引用修改,如果要加上一些限制,让变量不能被修改,const就发挥了作用。请看下面代码。
这里const修饰了变量a,加上语法的限制,如果此时修改了变量a的值就会报错。那么如果想要修改a的值怎么办呢?其实可以利用指针。但是这样会打破语法规则。如下所示。
这样确实可以修改,如果不想打破const的语法限制,让a变量无法修改应该怎么办呢?
6.2const修饰指针变量
const可以放在*的左边或者*的右边,只不过含义不一样。请看下面代码。
情况1:const放在*左边时
限制的是指针指向的内容,也就是不能通过指针来改变它所指向的内容,但是可以修改指针变量本身。
情况2:const放在*右边时
限制的指针变量本身,不能改变其指向,但是可以改变其指向的内容。
6.3*两边都有const
这样都不能被修改。
七、指针运算
7.1指针加整数
7.2指针减整数
7.3指针减指针
这里用一个模拟strlen函数来举例。
指针减去指针就是计算元素之间的个数,因为数组的地址是连续的。
八、野指针
含义:野指针就是指向的位置是不确定的。就像一条野狗一样,乱咬人。
8.1野指针的成因
形成野指针的成因有很多种,这里举例讲解三种
1:指针未初始化
2指针越界访问
这里循环了11次但是数组只有10个所以出现了指针的越界访问情况,就会出现一个随机值。
3指针指向的空间释放
8.2野指针的避免
1指针初始化
如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL. NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。
2⼩⼼指针越界
如果指针指向那块空间就访问那块空间,不要访问空间以外的内存。
3指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性
4避免返回局部变量的地址
九、传值调用与传址调用
这里调用函数,发现a和b的值并没有进行交换。这里我们调试来看看。
通过调试可以看到a和b确实传递了Swap函数,形参x和y也接收了a和b的址,x和y也确实交换了,但注意请看a和b的地址,与x和y的地址很明显不一样,说明形参x和y是一块独立的空间,形参的改变并没有影响实参,所以a和b的值并没有交换。这就叫传值调用。如何解决这里问题呢?请看下列代码。
这里只需要将a和b的地址传递给函数的参数,通过指针来间接操作a和b这样就完成了交换。这就是传址调用
总结:传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所 以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改 主调函数中的变量的值,就需要传址调⽤。