深入理解指针————Part2

前言

本篇博客继续为大家介绍指针的内容,前面为大家介绍过了指针的一部分基础内容,今天这篇博客将为大家进一步剖析指针的进阶内容,如果你点开了这篇文章,麻烦各位大佬高抬贵手,一键三连,多多支持,下面进入正文部分。

1. 数组名的理解

这里咱们先看一小段代码

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];

大家可以看到,这段代码之前讨论用指针访问数组的元素时提到过,它是取出了数组首元素的地址;下面我想请大家观察下面的代码

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("arr = %p\n", arr);
 return 0;
}

大家发现,我们分别打印了两个地址,大家可以猜猜这段代码的运行结果

大家可以看到,打印的结果完全一样,这就说明一个问题:数组名就是数组首元素(第⼀个元素)的地址。 

下面。可能会有同学提出疑问,如果数组名表示首元素的地址,那下面这段代码应该如何理解?

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("%d\n", sizeof(arr));
 return 0;
}

大家发现,我将数组名放入了sizeof操作符中,我们知道,sizeof是用来求数据类型的长度的,单位是字节,那么这段代码的结果是什么呢?

大家可以发现,结果是40,那么这里就有人产生了疑问,上面说数组名表示首元素的地址,那结果应该为4或者8呀,为什么结果是40呢?

这里就要为大家介绍很重要的两个点;

• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表示整个数组,计算的是整个数组的大小,单位是字节。

• &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)

除此之外,任何地方使用数组名,数组名都表示首元素的地址。

说到这里,大家应该就可以理解为什么上面的结果为40了;sizeof计算的是整个数组的元素的大小,整型数组每一个元素都是整型。一共有10个元素,所以结果为40。

下面呢,再来讨论一个问题,上面说到,整个数组的地址和数组首元素的地址是有区别的,那么它们具体的区别是什么呢?大家先来看下面的代码

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("arr = %p\n", arr);
 printf("&arr = %p\n", &arr);
 return 0;
}

这段代码的运行结果是什么呢?

 大家发现,结果还是一样的,这时有人就纳闷了,三个结果一样,那有啥区别呢?

大家不要着急,咱们再来看下面的代码

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("&arr[0]+1 = %p\n", &arr[0]+1);
 printf("arr = %p\n", arr);
 printf("arr+1 = %p\n", arr+1);
 printf("&arr = %p\n", &arr);
 printf("&arr+1 = %p\n", &arr+1);
 return 0;
}

这段代码的运行结果就可以体现出它们的区别,大家仔细看下面的运行结果

 

大家观察地址的变化,前四行的变化是一样的,因为&arr[0]与arr都表示取出首元素的地址,让他们+1,地址向后跳4个字节;但是大家再来看最后两行代码, 我们让&arr+1,结果大家会发现,地址向后跳了40个字节。这时,它们之间的区别就展现出来了;arr+1或者&arr[0]+1是向后跳过一个整型(即4个字节),而&arr+1是跳过整个数组(即40个字节),这里它们之间的区别大家一定要理解清楚!

2. 使用指针访问数组

这里我们知道了数组名表示首元素的地址后,我们再来使用指针访问一下数组

#include<stdio.h>
int main()
{

	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));

	}
}
#include<stdio.h>
int main()
{

	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", arr + i);
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));

	}
}

这里为大家展示了使用指针来访问数组,大家注意上面两段代码是等价的,因为p和arr是等价的;那么问题来了,我们前面在学习数组的时候,我们使用arr[i]来访问数组,那么我们现在能不能使用p[i]来访问呢?大家请看下面的代码

#include<stdio.h>
int main()
{

	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", p[i]);

	}
}

大家可以看到,用p[i]也同样可以访问数组;所以我们可以得到一个结论:本质上p[i]是等价于*(p+i)。同理,arr[i]等价于*(arr+i).

3. 一维数组传参的本质

⾸先从⼀个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把函数传给⼀个函数后,函数内部求数组的元素个数吗?大家请看下面的代码

#include <stdio.h>
void test(int* arr)
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test(arr);
	return 0;
}

大家可以猜猜这段代码的运行结果

大家发现,sz2和sz1的值不一样,sz1相信大家都能理解,它代表数组的元素个数,但是为什么sz2不是10呢?这里就要为大家介绍数组传参的本质了:数组传参本质上是传递了数组首元素的的地址,所以在上面的代码中,我们给test函数传递的是数组首元素的地址,那这个时候sizeof(arr)求的就是首元素地址的大小,也就是4个字节(X86环境),sizeof(arr[0])是首元素,它是整型元素,大小是4个字节。所以sz2的值就为1。

4. 冒泡排序

关于冒泡排序,相信大家都有所了解,它的核心思想在于相邻两个元素之间互相比较;

void bubble_sort(int* arr, int sz)//参数接收数组元素个数 
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1;//假设这⼀趟已经有序了 
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				flag = 0;//发生交换就说明,无序 
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		if (flag == 1)//这⼀趟没交换就说明已经有序,后续无需排序了 
			break;
	}
}
int main()
{
	int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

这里我们实现了冒泡排序,上面展示了升序的排法;当然,还有降序的排法

void bubble_sort(int* arr, int sz)//参数接收数组元素个数 
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1;//假设这⼀趟已经有序了 
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] < arr[j + 1])
			{
				flag = 0;//发生交换就说明,无序 
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		if (flag == 1)//这⼀趟没交换就说明已经有序,后续无需排序了 
			break;
	}
}
int main()
{
	int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

降序只需要将大于号改为小于号就OK了,这里就不作赘述了。

5. 二级指针

大家知道,指针又叫指针变量,那么既然是变量,就有它自己的地址;那么这个地址存放在哪里呢?这里就需要引进二级指针了。大家来看下面的图

上图清晰地表示了,一级指针和二级指针间的关系,相信大家应该可以理解了;那么,当我们对ppa解引用时,得到的就是pa;再对pa解引用,就找到了a。

二级指针的内容比较简单,它就是一级指针的”栖息地“,希望大家可以理解。

6. 指针数组

我们前面学过整型数组,字符数组,整型数组是存放整型的,字符数组是存放字符的,那么类比一下,指针数组就是用来存放指针的也就是说指针数组的每个元素都是一个地址,且指向一块区域;具体大家来看下面的图;

7. 用指针数组来模拟二维数组

#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	//数组名是数组首元素的地址,类型是int*的,就可以存放在parr数组中 
	int* parr[3] = { arr1, arr2, arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

大家发现,我们并没有创建二维数组,但是这里我们实现了二维数组的效果,这就是指针数组的作用。我们创建的parr数组就是一个指针数组,它的类型是int*类型,用来存放整型指针。parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数组中的元素。

这里大家需要注意,上述的代码模拟出⼆维数组的效果,实际上并非完全是⼆维数组,因为每⼀行并非是连续的。

8. 总结

本篇博客为大家介绍了指针的进阶内容,主要包括一维数组的传参,冒泡排序,指针数组等内容,这些都属于进阶内容,希望大家理解它们的定义和用法,后面我们遇到的考察指针的题目里,这些点都是很重要的知识,最后,希望本篇博客的内容可以为大家带来帮助,如有错误,欢迎评论交流,谢谢!

  • 34
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值