目录
一、内存、内存地址
计算机的CPU(中央处理器)在对数据进行处理时,需要的数据在内存中读取,数据也相应的会存放在内存之中,那么这些内存空间是如何进行管理的呢?
在生活中,有许多对于空间管理的例子,比如我们的宿舍楼,像我所住的宿舍楼一层就有几十个房间,一栋宿舍楼下来就有上百个房间,这就和内存空间类似,而我们就像一个个存放在各个空间里的“数据”,如何快速的在宿舍楼内找到我所住的房间呢?多简单,找门牌号就行了。给房间编上门牌号,这样就方便进行查找管理了。
计算机也是类似,其实也是把内存划分为一个个的内存单元,每个内存单元的大小取1个字节。每个内存单元也都有一个编号(这个编号就相当于宿舍房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到每一个内存空间。
对于计算机,我们把每个内存单元的编号叫做地址,也叫指针。
二、指针变量
指针,指针变量?有什么不同吗?
我们其实口头上说的指针是指指针变量,指针变量是用来存放指针(地址)的。指针变量是一个空间,指针只是一个空间的地址。
a.取地址操作符&
我们通过取地址操作符(&)拿到的地址是⼀个数值,比如:0x006FFD70,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在指针变量里。
int main()
{
int a = 10;
int* pa = &a;//取出a的地址并存储到指针变量pa中
return 0;
}
指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。
b.指针的类型
就是指当前指针变量存放的地址所对应的空间存放的是什么类型的数据。
通过观察可以发现,a类型为 int,a变量的地址类型即为 int*,我们可以这样认为 * 是pa作为指针的象征,int 是其指向对象的类型。那么同理,char类型变量的指针类型为char*……
c.解引用操作符 *
我们将地址保存起来,未来是要使用的,那怎么使用呢?
在现实生活中,我们使用地址要找到⼀个房间,在房间里可以拿去或者存放物品。C语言中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这里必须认识⼀个操作符叫解引用操作符 * 。(与乘号同形但意义不同,为单目操作符)
*pa 的意思就是通过pa中存放的地址,找到指向的空间,*pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0。
d.指针变量的大小
指针有那么多的类型,那是不是意味着指针变量大小各式各异呢,这里就有所误解了。
我们知道,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产生的2进制序列当做⼀个地址,那么一个地址就是32个bit位,需要4个字节才能存储。如果指针变量是用来存放地址的,那么指针变的大小就得是4个字节的空间才可以。同理64位机器,假设有64根地址线,⼀个地址就是64个二进制位组成的⼆进制序列,存储起来就需要8个字节的空间,指针变量的大小就是8个字节。
总的来说就是:指针变量的大小和机器有关,与所指对象类型无关。
三、函数传值调用和传地址调用
学习指针的目的是使用指针解决问题,那什么问题,非指针不可呢?
当要求写个函数来交换两个变量的值时,我们稍加思索,可以写成如下函数:
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
那么我们来进行测试一下,观察结果是否符合预期:
这是什么情况呢?为何没有发生交换?
在看看这样是否可行:
可以发现,函数逻辑并没有任何问题,却没有产生应有的效果,下面我们通过调试来一探究竟:
a变量与b变量的内存空间与函数内部的局部变量x变量和y变量是两块不同的空间集,x变量和y变量只是得到了a变量与b变量的值,而不代表a变量和b变量:
在main函数内部,创建了a和b,a的地址是0x098fa48,b的地址是0x0098fa3,在调用Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是x的地址是0x0098f964,y的地址是0x0098f968,x和y确实接收到了a和b的值,不过x的地址和a的地址不一样,y的地址和b的地址不一样,相当于x和y是独立的空间,那么在Swap1函数内部交换x和y的值,自然不会影响a和b,当Swap1函数调用结束后回到main函数,a和b的没法交换。这种调用函数的方式叫传值调用。
那该如何调用函数才能让函数直接操作a变量和b变量呢?一定想到了吧,传地址吧,这样就可以直接访问a变量和b变量了,不就直接操作了么。经思索写出如下函数:
void Swap2(int*px, int*py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
来测试看看是否符合预期:
简直完美,下面我们来看图理解一下为何可行:
传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。如果函数内部要修改主调函数中的变量的值,就需要传址调用。