很多同学都觉得指针是C语言的难点,难以突破,实际上指针并没有那么的难,通过这一节,你将发现指针原来如此简单。
一、内存和地址的理解
为了方便大家理解指针,笔者将通过问题引导的方式带领大家来理解计算机的内存。
Q1:在介绍指针之前,我们首先要介绍计算机的内存,那么什么是内存呢?
内存是电脑上特别重要的存储器之一,计算机中所有程序的运行都是在内存中进行的,内存常见的空间的空间容量有4GB、8GB 。
Q2:内存空间那么大,电脑是如何有效使用管理内存的呢?
在这里,我们可以结合生活中的例子进行联想,用我们的国土面积和内存进行类比。
我们的国家领土有960W平方公里,在那么大的地理面积上,我们是如何去规划呢?比如在淘宝上买件衣服,我们是怎么管理我们的居住地址,让快递员在那么大的地域范围内找到的我们的家呢?很自然的我们就想到了省、市、县……没错,我们就是通过省市县镇村单元楼房间号,最终归结到每一个小的房间里,由此来找到我们的地址。
所以为了有效使用管理内存,内存也是划分为一个个小的内存单元(类似生活中每个人的房间),为了能够有效的访问到内存的每个单元,我们给每个内存单元编号(类似生活中每个人的房间号),这些编号我们称之为该内存单元的地址,通过内存单元的地址我们可以有效地找到并访问管理每个小的内存单元。
Q3:内存单元的编号是如何产生的呢?(地址是怎么产生的呢?)
在32位机器上,我们有32根地址线(物理的电线,有正电负电之分,每根地址线产生的电信号转换为0/1二进制),转换为32个二进制数字组成的序列
00000000000000000000000000000000
00000000000000000000000000000001
00000000000000000000000000000010
······
1111 1111 1111 1111 1111 1111 1111 1111
每一个由32根地址线组成的序列就是每一个内存单元的编号,对应着内存单元的地址。32个二进制数字可以产生上面2^32种不同的地址编号,因此32根地址线可以管理2^32个内存单元。由此每个内存单元的编号地址就产生了。
Q4:一个内存单元应该是多大的空间?
如果1个内存单元是1bit,32位机器上可以管理2^32个内存单元,那么就是管理2^32bit的空间,即总共管理0.5GB个空间(管理总空间太小,不合适,生活中32位机器中内存1G,2G很常见);并且数据类型中最小的char需要1byte=8bit,若一个内存单元是1bit,则每创建一个字符char就要消耗8个地址编号,也不合适,太浪费地址编号了!
如果1个内存单元是1KB,而一个char只需要1个byte,剩下的1023byte会造成浪费,试想一下偌大的紫禁城(类似1KB)如果只有你一个人(类似1byte)居住,未免也太浪费内存空间了吧!
如果1个内存单元是1个byte,则总共管理4GB个空间,我们生活中也常见到1G,2G,4G,再大的内存空间其实不是特别的多;1个int类型用掉了4个地址编号;1个char类型用掉了1个地址编号。
综合考虑,一个内存单元是1个字节比较合适。
二、指针的概念
通过上面问题引入我们已经知道,我们的计算机也是对内存进行有效地管理。
首先把内存划分成若干个小的格子,每个小的格子大小是一个字节,这些小的格子被称为内存单元。
接下来我们给每个内存单元都给一个编号,这些编号是由32根或64根地址线产生的。
最终通过这些编号我们就可以访问每一个内存单元,我们效仿生活中的方式,将这些编号叫做这些内存单元的地址。
每个变量的创建都要在内存开辟空间,所以变量都有地址。
那么怎么取出变量的地址呢?这里我们就需要用到取地址操作符&。
int main()
{
int num = 10; // 向内存申请4个字节的空间,里面存放10
# // 取出num的地址,&--取地址操作符
printf("%p\n", &num); // 打印地址,%p--以地址的形式打印
// 当变量超过1个byte时,取地址拿到的是第一个byte的地址(较小的地址),后面的根据创建变量类型顺藤摸瓜
return 0;
}
地址也是数字啊,得想办法把它存起来,那地址如何存储?需要定义指针变量。
int main()
{
int* pa = &a;
// pa是一个存放地址的变量,这样的变量就称为指针变量
// 如何去定义一个pa变量呢?用int*
// *告诉我们创建的变量是指针变量,int告诉我们指针指向的对象是int类型
// 通过pa我们可以找到a,所以我们可以简单的认为pa指向了a
return 0;
}
注解:
pa是一个存放地址的变量,这样的变量就称为指针变量
如何去定义一个pa变量呢?用int*
*告诉我们创建的变量是指针变量,int告诉我们指针指向的对象是int类型
通过pa我们可以找到a,所以我们可以简单的认为pa指向了a
我们可以通过地址找到地址指向的这块空间,我们形象的也把这个地址叫做指针;把地址存在一个变量里面,我们把这个变量称为指针变量
地址就是指针,指针就是地址;我们生活中也会口语化把指针变量称为指针,注意区分
三、指针的使用实例
将地址存在指针变量里面有什么用呢?我们就是为了有朝一日能通过存的这个变量的地址来找到这个变量。
这里我们就需要用到解引用操作符*来帮助我们找到指针所指向的内存空间。
int main()
{
int num = 10;
int* p = #
*p = 20; // *解引用操作符,通过*可以找到所指向的内存空间
return 0;
}
通过上面的代码,我们发现取地址操作符&和解引用操作符*才让我们的指针才有意义。
以整形指针举例,可以推广到其他类型,如:
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'q';
printf("%c\n", ch);
return 0;
}
四、指针变量的大小
下面我们来看一下每个指针变量类型所占内存空间的大小是多少呢?
int main()
{
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(short*));
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(double*));
return 0;
}
打印的结果竟然全是4,那么为什么这里指针类型的大小全是4呢?
32位机器有32根地址线,即由32个0/1组成(32bit),32bit构成地址编号,地址的大小是4个字节,而指针变量是用来存放地址的,所以指针变量的大小也是4个字节
地址几个字节,指针变量类型就是几个字节,指针大小在32位平台是4个字节,64位平台是8个字节。