1.内存和地址
1.1内存
在生活中,如果你想到宿舍楼去找你的好基友玩,如果没有房间号码,你就得一个一个去找,效率极其低下,但如果有房间号,你就可以立马找到,效率超高
对应到计算机中也是同样的道理,我们知道计算机CPU在处理数据时,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那是怎么管理这些内存呢,其实就是把内存划分为一个个的内存单元,每个内存单元的大小取1个字节
其中每个内存单元就相当于一个宿舍,一个人字节空间里面能放8个比特位,相当于宿舍八人间,每个人都是一个比特位。
每个内存单元都有一个编号,就相当于宿舍门牌号,有了这个编号,CPU就可以快速找到一个内存空间。我们通常把门牌号叫作地址,在计算机中我们也把内存单元的编号称为地址,而C语言给地址起了个新名字:指针
所以我们可以认为:内存单元的编号=地址=指针
1.2如何理解地址
CPU访问内存中的某个字节空间,应该知道这个字节空间在内存的1什么位置,而内存中字节空间是非常多的,所以就需要给每一个字节空间一个地址。
而计算机的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。第一,计算机内是有很多的硬件单元的,它们之间相互协同工作,进行数据传递。但是硬件与硬件之间是互相独立的,它们之间通过“线”连接。
CPU与内存之间也需要进行数据传递,它们之间通过地址总线进行连接,其中64位机器有64根地址总线,每根线有两个状态,表示0和1,那么一根线表示两种含义,两根线表示四种含义,64根地址线,就表示2^64种含义,每一个含义都代表一个地址,地址信息被传达给内存,就可以找到该地址对应的数据,再将数据通过数据总线传入CPU内寄存器。
2.指针变量和地址
2.1指针变量
理解了内存和地址的关系,为了存储地址,以便后面使用,我们需要创建一个东西来存放,就是:指针变量
这个变量就是用来存放地址的,存放在指针变量中的值都理解位为地址(指针)
如上图所示,我们创建了一个指针变量,有的人可能对图中的*和&有疑问
其实在C语言中*是在声明pa是一个指针(地址)变量,而int是在声明pa存储的地址对应的值是int类型的值
而&其实是取地址操作符,它负责把a所占4个字节中地址较小的字节的地址取出来,然后再赋值给pa指针变量
2.2解引用操作符
我们把地址存储起来,怎么使用它呢,在生活中,我们使用地址找到一个房间,然后在房间里可以拿或者放东西,在C语言中也是同理,我们只要拿到了指针(地址),就可以通过它找到地址指向的对象,怎么找到呢,要通过解引用操作符(*)。
上面的代码中*pa的意思就是通过pa中存放的地址,找到指向的空间,*pa其实就是变量a了,所以*pa=100就把a的值变为了100。
2.3指针变量的大小
因为指针变量存储的是地址,而根据前面的内容我们了解到,64位机器有64根地址总线,没跟地址线出现的电信号转换成数字信号后是0或1,我们把64根地址线产生的2进制序列当作一个地址,一个地址就是64个比特位,需要8个字节才能存储,所以指针变量的大小在64位机器中就是8个字节,同理在32位机器上就为4个字节
所以我们还可以知道,指针变量的大小和类型是无关的,因为地址总是64个比特位,只要是指针变量,在相同平台下,其大小是相同的。
3.指针变量类型的意义
有的人就会有疑问,既然指针变量大小都是一样的,那为什么还要分指针类型呢,下面就会来讲为什么要分类型
3.1指针的解引用
对比下面的代码和调试结果
我们可以看到,结果是不一样的,其实第一个代码将n的4个字节全部变为0,而第二个代码只是将n的第一个字节改为0
可以得到结论:指针的类型决定了对指针解引用时指向的空间有多大的使用权限(一次能操作几个字节),char*类型的指针解引用一次只能使用一个字节,int*类型的指针解引用一次能使用四个字节。
3.2指针+-整数
先看一段代码,观察地址的变化
我们可以看出,char*类型的指针变量+1跳过1个字节,int*类型的指针变量+1跳过了4个字节,这就是指针变量的类型不同带来的差异
结论:指针的类型决定了指针向前或向后走一步有多大
3.3void*指针
在指针类型中有一种特殊的类型是void*类型,理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址,都是void*类型指针不能直接进行指针的+-整数和解引用的运算。
我们可以看到,void类型的指针可以接收不同类型的地址,但是不能直接进行指针运算。
那void*类型的指针岂不是没用?并不是这样的
一般void*类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样可以实现泛型编程的效果,使得一个函数来处理多种类型的数据,后续我们会讲到。