C语言基础---15.指针&数组名&数组地址&变量对应的加减法---图解篇

最终学习C/C++,遇到很多疑难杂症。希望用自己的理解和图形化的方式,把核心的问题表达清楚,即使是小白,也能看明白什么原理!(不忘初心,晚上加鸡腿!)

在这里插入图片描述

  • pa是指针,指向了数组a,pa对应的是首元素的地址
  • 数组a有10个元素,都初始化好了
  • int类型的数组,每个元素占用4个字节的大小,内存中每4个字节内存存储了一个元素,共计40个字节大小,右边暂时省略了!
  • 数组名a的值是首元素的地址a[0]的值,虽然a和a[0]表达的不一样,但是值都一样的

1.指针与整数的加减(指针的偏移)

指针的偏移,其实是地址的偏移,所以无论加减,其实本质都是地址位置的移动,*p + 1 的本质是 *(p+1),因为先移动的位置,然后再去取值。所以 *(p+1)也更容易理解

#include <stdio.h>

int main() {

	int* pa;
	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	pa = &a;					// 将指向指针数组a,其实默认指向a[0]这个元素
	printf("%d  \n", *pa);
	printf("%d  \n", *(pa + 1));	// 打印数组后面一个元素的值(内存中的概念是:从当前指针所指向的内存单元开始,往后拖动4个字节,然后再取4个字节的值,最终算出这个元素的值)
	printf("%d  \n", *(pa + 2));	// 同理,往指针后面移动8个字节之后,再取连续4个字节的内存,算出的值
	printf("================\n");
	pa = &a[2];  				// 将指针指向数组的第三个元素,a[2]
	printf("%d  \n", *pa);
	printf("%d  \n", *(pa + 1));	// 
	printf("%d  \n", *(pa + 2));
	return 0;
};


如上代码中:pa = &a[2]; 其实就是把指针指向了第三个元素(索引为2),入下图所示

在这里插入图片描述


1
2
3
================
3
4
5

如上可以说明:

  • 指针指向的数组,其实就是把指针指向数组默认的首元素,通过指针的偏移,就可以找到最终每个元素的值了。(也可以通过a[i]这种方式获取值的大小,但这不是我们将要说明的重点)
  • 指针既然可以指向第一个元素,也可以指向其他元素,那该如何获取前面的值呢?

指针与整数的减法?

int main() {

	int* pa;
	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	pa = &a[2];
	printf("%d  \n", *pa);
	printf("%d  \n", *(pa + 1));
	printf("%d  \n", *(pa + 2));
	printf("=================\n");
	printf("%d  \n", *(pa - 1));				// 左偏移1个元素
	printf("%d  \n", *(pa - 2));				// 左偏移2个元素
	return 0;
};


在这里插入图片描述

3
4
5
=================
2
1

总结:

  • 指针指向的数组地址,可以通过 pa = &a[i]形式修改,指向数组a的第i+1个元素
  • p+1,相当于把指针右移1个元素




2.数组名与整数的加减

#include <stdio.h>

int main() {

	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	printf("%d  \n", *a);
	printf("%d  \n", *(a + 1));		// 数组名a代表的是首元素的地址
	printf("%d  \n", *(a + 2));
	return 0;
};

1
2
3

通过如上发现:

  • 1.数组名的加法,与指针变量的加法类似,a + 1 与 p + 1 都是右移一个元素,运算完再用 * 取值,最终就可以获取到元素的值了!
  • 2.注意,数组名a代表的是首元素的地址,&a可以代表 数组的地址。不同点在于,a的长度是sizeof(int)的字节长度,&a代表的是sizeof(a)的字节长度。




3.数组名转换为整数,再与整数的加减(数组名转换后的计算)

int main() {

	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* pa = (int*)((int)a + 1);
	printf("%x  \n", *pa);

	return 0;
};

编译环境为32位,一般默认为小端存储方式。

  • 即数组的低位,存储在内存的低位置;数组的高位存储在内存的高位置。
  • 首元素1,对应的二进制为00 00 00 01 ,因为01是低位,所以存储在最左边。同理,最高位00,则存储在最右边。
  • 最终在内存看到的效果是,存储的数据是倒着来的,也应该倒着读数据。

模拟数组a的内存结构如下:

ptr2 对应的是指针pa
在这里插入图片描述

表达式 (int * ) ( (int)a + 1)的功能为:

  • 先将数组名a,强制转换为int类型。因为a代表的是数组的首元素地址,例如0x001,转换为int类型即为1
  • (int)a + 1,得到一个整数,即为2
  • 最后这个整数地址,再强制转换为指针变量(再转为16进制),即为ox0002,其实就是之前首元素的第二个字节位置
2000000

为了加深理解,如下代码看着估计更清晰明了:

int main(void)

{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)((int)a + 1);
	int* ptr2 = (int*)((int)a + 2);
	int* ptr3 = (int*)((int)a + 3);
	int* ptr4 = (int*)((int)a + 4);
	int* ptr5 = (int*)((int)a + 5);
	printf("%x \n" ,*ptr1);
	printf("%x \n" ,*ptr2);
	printf("%x \n" ,*ptr3);
	printf("%x \n" ,*ptr4);
	printf("%x \n" ,*ptr5);

	return 0;
}


2000000			// 2 00 00 00	
20000			// 2 00 00
200				// 2 00
2				// 2
3000000			// 3 00 00 00

如上代码如果还未明白,可移步如下地址:
https://blog.csdn.net/RationalGo/article/details/17341083?utm_source=blogxgwz0





4.&数组名与整数的加减(数组地址的偏移)

#include <stdio.h>

int main() {

	int* pa;
	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	pa = &a;
	printf("%x   \n", &a);
	printf("%x   \n", &a[1]);
	printf("%x   \n", &a[2]);
	printf("%x   \n", &a + 1);		// 注意:此处&a代表的是整个数组的地址,长度是40个字节
	return 0;
};



3dfe08			// a[0]地址
3dfe0c			// a[1]地址
3dfe10			// a[2]地址
3dfe30			// &a + 1地址

通过结果表明:

  • 相邻元素之间的地址确实相差4个字节,32位系统中,int类型确实占用4个字节大小
  • 3dfe30与3dfe08两个地址相差是40,也就是说,&a+1向右边偏移了40个字节,就是整个数组的长度。(如果数组a的长度为5,那么将移动20个字节)




根据如上的原理,那么如下的代码中,*(p_last - 1),最终能打印什么呢?


int main() {

	int* pa;
	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	pa = &a;
	printf("%x   \n", &a);
	printf("%x   \n", &a[1]);
	printf("%x   \n", &a[2]);
	printf("%x   \n", &a + 1);
	int* p_last = &a + 1;
	printf("%d  \n", *(p_last - 1));
		
	return 0;
};


在这里插入图片描述

50f6b4
50f6b8
50f6bc
50f6dc
10

根据如上2个案例的说明:

  • int* p_last = &a + 1,说明指针p_last指向的位置,在紧邻着数组a(占用40个字节)的右边,如上图所示位置
  • *(p_last - 1)指针p_last向左偏移4个字节,然后取值,最终查看到的内容为10
  • 上面 (p_last - 1) 如果更换为p_last -1 最终会打印乱码,所以案例一中的原理我猜想也是*(p-1)是最合适的写法




5.变量名与整数的加减

#include <stdio.h>

int main() {

	int a = 20;
	double b = 30.22;
	printf("%p  \n", &a);
	printf("%p  \n", &a+0x1);		// 0x1 其实就是0x00000001
	printf("%p  \n", &b);
	printf("%p  \n", &b+0x1);
	return 0;
};

0075F724
0075F728
0075F714
0075F71C

  • 根据如上代码发现:
  • 变量名加一(0x1),最终移动的字节数和变量名的类型有关,int占4个字节、double占8个字节,所以分别移动4个和8个字节。





以上所有案例总结:

  • 假设数组名a,a+1移动几个字节数,由数组的类型决定的,1 * sizeof(数组类型)
  • &a+1,移动的大小等于整个数组所占字节大小
  • int(a) + 1,这个代表将首数组首元素的地址加上1,最终计算的地址,一般还需要再换位换指针变量
    假设变量名b,则b+0x1移动的字节数,也是有这个变量的类型决定的
    假设指针变量p,则p+ 1,代表p指向的内容移动一个大小。如果p指向一维数组,则p移动一个元素。如果p指向结构体,则p + 1 移动的位置大小是这个结构体所占的字节大小

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello-alien

您的鼓励,是我最大的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值