指针的介绍(1)(包括对内存和地址的理解、相关操作符的介绍、指针变量的概念和意义,以及const的使用)



1 内存和地址

生活案例:
假设你住在宿舍楼A栋,整栋楼一共有100个宿舍,假如你有一个朋友第一次想来找你玩,顺着宿舍找你肯定是不现实的,所以,一般你肯定会考虑告诉他你的宿舍门牌号,比如住在5栋,宿舍门牌号为511,朋友得到了宿舍门牌号511,就能够快速的找到你。

我们都知道电脑上有内存,CPU,硬盘等等部件,CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中。内存空间的管理其实和上面的生活案例类似。

内存会被划分为一个个的内存单元,每个内存单元为1个字节。

如图所示:
在这里插入图片描述
每个内存单元类似于每个宿舍,一个字节能放8个比特位类似于一间宿舍里面住8个人,一个比特位类似于每个宿舍里的学生。

每个内存单元都有⼀个编号(这个编号就相当于宿舍房间的门牌号),有了内存单元的编号,CPU就可以快速找到该内存编号对应的内存空间。

在计算机中,内存单元的编号也称为地址。
在C语言中,地址有了新名字- ->指针。
简单来说,就是内存单元的编号== 地址 ==指针

2 指针变量和地址

2.1 取地址操作符(&)的介绍

在C语言中,创建变量就是在向内存申请空间,例如创建变量a:

#include <stdio.h>
int main()
{
	int a = 10;
	return 0;
}

我们可以通过调试来观察a的地址。此时是在x86(32位)环境下运行的结果
在这里插入图片描述
在这里插入图片描述
可以看到,在创建了整形变量a后,向内存申请了四个字节,来存放整数10,整数10转换成32位二进制(00000000 00000000 00000000 00001010),存入内存,右键可以发现,在我们观察的时候默认是16进制显示的。10的16进制表示为00 00 00 0a,内存分配地址优先分配高地址,从左往右分配地址,所以0a的地址是4个地址中最小的。
在这里插入图片描述

整形10本次运行占用的四个字节的地址分别为:(每次运行编译器会自动为变量分配地址,所以每次运行的地址可能都不相同)
0x0073FEB4
0x0073FEB5
0x0073FEB6
0x0073FEB7

如果想直接得到a的地址,这时就需要用到取地址操作符&,代码如下:

#include <stdio.h>
int main()
{
	int a = 10;
	printf("%p", &a);		//打印地址需要用到%p
	return 0;
}

结果如图:
在这里插入图片描述

在这里插入图片描述
)
在打印地址时,&a取出的是4个字节中地址最小的字节的地址,不过这也很好理解,给整形分配四个字节的地址是顺着分配的,每两个相邻的地址之间相差1个字节,所以有了起始地址,每次把地址加1,就能顺着找到其余的地址。

2.2 指针变量

取出的地址本质上也是一个数值,我们在后期需要使用到这个地址,这时,我们就需要把地址存储到指针变量中。
例如:

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	return 0;
}

指针变量是用来专门存储地址的,存储在指针变量中的值都会被理解成地址。假设本次运行时编译器为变量a分配的地址为0X0012FF40。
在这里插入图片描述
*是说明变量pa是一个指针变量,前面的int指的是pa指向的是一个整形类型的对象。

2.3 解引用操作符(*)的介绍

例如代码:

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	*pa = 0;		//(1)
	return 0;
}

(1)处代码就用到了解引用操作符*,pa的意思就是通过pa的地址找到地址指向的对象的空间,由上图可以看到,变量pa指向的其实是变量a的值10,所以pa=0就是把指向的变量a的值改成0,此时a的值就为0。

我们可以通过监视来观察地址的变化。
在这里插入图片描述
在执行完int a=10后,可以发现,编译器为变量a分配的地址为0x003ff8ac,pa为随机值。

在这里插入图片描述
接着往下执行,执行完int* pa = &a后,可以发现,此时pa存放的就是a的地址。

在这里插入图片描述

执行完*pa = 0后,*pa和a的值都为0.

现在看来貌似指针还把简单的题目弄复杂了,但是以后我们会遇到一些题目,只有通过指针才能解出来,任何知识肯定有它独到的地方,只有多学习,多练习,多积累,我们才能慢慢的理解和发现其中的奥妙。

2.4指针变量的大小

用代码来计算不同指针变量的大小
在这里插入图片描述
可以发现,在x86(32位)环境下,不同指针变量存放地址都需要四个字节的空间,这也很好理解,32位机器,就是用32个bit位来当做地址,一个地址32个bit位,需要4个字节存储,无论指针变量是什么类型的,存储地址都需要用32个bit位,如果是64位机器,例如x64环境下,就需要64个bit位来存储地址,也就是8个字节。

总结:
1.32位平台下地址是32个bit位,指针变量大小是4个字节
2.64位平台下地址是64个bit位,指针变量大小是8个字节
3.指针变量的大小与类型无关,在相同的平台下,无论什么类型存储地址都需要一样大的空间,指针变量的大小都是相同的

3.指针变量类型的意义

可能会有人疑惑,在相同平台下,指针变量大小都是一样的,那有什么意义呢?为什么还要有不同的指针变量呢?我们可以通过一些代码例子来观察不同指针变量的区别。

3.1指针解引用

我们要实现通过指针来改变一个变量的值,可以使用指针变量间接改变:

#include <stdio.h>
int main()
{
	int n = 0x12345678;
	int* p = &n;
	*p = 0;
	printf("%d", n);
	return 0;
}

可以通过调试内存观察结果
在这里插入图片描述

当我们执行完第一句,可以发现,12345678成功赋值到n里面去,并且编译器为n分配的地址为0x003BFDF8,当我们执行完第三句,n的4个字节成功被改成了0,最终输出0
在这里插入图片描述
在这里插入图片描述
但是,如果我们修改下代码,把int * 改成char * ,结果就不是0了,如图所示,这是为什么呢?
在这里插入图片描述
我们还是用内存来观察
在这里插入图片描述
可以发现,在p解引用后,n的四个字节只更改了第一个字节。

那我们大致可以得出结论:
指针类型决定了在解引用时访问的权限,如果是char类型,一次只能访问一个字节,如果是int类型,一次只能访问4个字节,以此类推。

3.2指针加减整数运算

#include <stdio.h>
int main()
{
	int n = 100;
	int* p1 = &n;
	char* p2 = &n;

	printf("n的地址为%p\n", &n);
	printf("p1的地址为%p\n", p1);
	printf("p2的地址为%p\n", p2);

	printf("p1+1的地址为%p\n", p1+1);
	printf("p2+1的地址为%p\n", p2+1);

	printf("p1+2的地址为%p\n", p1 + 2);
	printf("p2+2的地址为%p\n", p2 + 2);
	return 0;
}

在这里插入图片描述

可以发现,p1和p2都保存的n的地址,和n的地址是一样的,
但是int* 类型p1加1后,地址加了4个字节,但是char* 类型p2加1后,地址只加了1个字节,
但是int* 类型p1加2后,地址加了8个字节,但是char* 类型p2加2后,地址增加了2个字节,
也就是说,如果增加n,地址也会增加n倍类型对应的字节。

因此我们也可以大致得出结论:
指针变量决定了指针在进行加减操作时,每加减1跳过字节的长度。如果是int类型,一次加减1跳过4个字节,如果是char类型,一次加减1跳过1个字节,以此类推。

4 const修饰指针

变量本身是可以直接或间接进行修改的,但是如果我们加上了const修饰之后,就可以实现变量无法被直接修改。
例如代码:

#include <stdio.h>
int main()
{
	int n = 100;
	n = 20;
	printf("%d", n);
	return 0;
}

在这里插入图片描述

我们可以很好的直接修改n的值,但是如果加了const,就不一样了。

#include <stdio.h>
int main()
{
	const int n = 100;
	n = 20;
	printf("%d", n);
	return 0;
}

在这里插入图片描述
可以发现,如果给变量n加上了const修饰,就表示n的值无法被直接修改,如果要强行修改,编译器就会提示报错。

此时肯定会有人想,n的值不能直接修改,那我能不能用指针变量p得到n的地址,再间接修改n的值呢?

#include <stdio.h>
int main()
{
	const int n = 100;
	int* p = &n;
	*p = 20;
	printf("%d", n);
	return 0;
}

在这里插入图片描述

结果可以发现,通过p成功将n的值修改了,但是我们又不想让n的值被修改,显然对n加const修饰是不够的,所以我们就需要考虑对指针变量p加const修饰。

那么就有两种情况:
1.在 * 的左边(int的左边右边都可以)加const限制 * p
2.在 * 的右边,p的左边加const限制p

可以通过代码来观察区别。

#include <stdio.h>
int main()
{
	int n = 100;
	int m = 10;

	int* p = &n;
	*p = 20;
	p = &m;

	printf("%d", n);
	return 0;
}

我们首先把n的地址给p,通过p修改n的地址,再将p指向m的地址。

1.在 * 左边加const限制,可以发现,加了限制后,无法通过指针变量p间接改变n的值,但是还是可以改变p的指向为m的地址。所以在 * 左边加限制,限制的是*p。
在这里插入图片描述
2.在 * 右边,p左边加const限制 ,可以发现,加了限制后,和上面情况完全相反,可以通过p间接修改n的值,但是无法改变p的指向为m的地址。所以在 * 的右边加限制,限制的是p。
在这里插入图片描述
总结:
1.const能够修饰变量,使得这个变量无法被修改
2.当const放在 * 的左边的时候,限制的是 * 变量,限制指针所指向的内容,即不能通过指针来改变指针所指向内容的值,但是可以改变指针变量的指向。
3.当const放在 * 的右边的时候,限制的是指针变量本身,即不能修改指针变量的指向,但是可以通过指针变量修改当前所指向的内容。

所以当我们想要给变量加限制时,需要考虑好把const放置什么位置最合适,能满足我们的需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值