深入理解指针【3】---(本章内容很重要!)

11 篇文章 0 订阅
1 篇文章 0 订阅

数组名的理解

前面,我们讲过,数组名就是数组首元素的地址。今天,我们就来进行详细的讲解。进行代码展示:

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

结果运行图:在这里插入图片描述
我们从结果运行图中,可以看出来,数组名的地址和数组首元素的地址相同。所以,数组名就是首元素的地址(数组名就是地址)。接下来,我们再写一个代码(能颠覆你三观的代码),进行代码展示:

//	写这样的代码:计算 sizeof(arr)的值。
//	这个代码咱们前面都见过,比如,int sz = sizeof(arr) / sizeof(arr[0])
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%zd\n", sizeof(arr));
	return 0;
}

结果运行图:在这里插入图片描述
我们之前讲过这个代码,sizeof(arr)测的是整个数组的大小,这样想很合情理。但是,我们讲过了,数组名就是地址,地址的空间大小跟数据类型无关,只与计算机平台有关,32位平台小为4个字节,64位平台下为8个字节。而sizeof(arr)不就是在测地址的字节吗?结果不就是4或者8吗?为什么会40呢?自相矛盾?数组名就是首元素的地址,这个说法没有错,只不过凡事都有例外——sizeof&这两个操作符是不适用的。除了这俩货,数组名就是首元素的地址这个规则在其它地方都能用(都成立)。sizeof咱们已经讲过了,接下来,咱们讲讲&这个例外。进行代码展示:

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

结果运行图:在这里插入图片描述
对于arr&arr[0]的地址相同,我们没什么可惊讶的。但是,&arr的地址竟然也和它们俩相同,这就很惊讶。我们都知道&取地址操作符,而**&arr就是取的整个数组的地址,难道整个数组的地址=数组名=数组首元素的地址吗?当然不是,虽然&arr的数值和它们俩相同,但是它和它们俩本质上是不同。 ** arr&arr[0]是可以互用的,但是&arr不能与它们俩互用。进行代码展示:

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

	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);

	return 0;
}

结果运行图:在这里插入图片描述
我们可以发现,arr+1&arr[0]+1跳过四个字节(原因:在前面的指针运算讲过)。而 &arr+1跳过了40个字节,就是跳过了整个数组。 在指针运算的那个章节里面,咱们讲过指针运算跨过多少个字节取决于数据类型,而 &arr+1的数据类型肯定不同(因为它跨过了整个数组),具体是什么类型,咱们后面讲。这就是它和它们的区别,虽然它和它们在数值上相同,但本质上是不同

使用指针访问数组

有了前面的知识铺垫,我们就可以使用指针对数组进行访问了。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	printf("更改前");
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	for (i = 0; i < sz; i++)
	{
		*(p + i) = -1;
	}
	printf("\n");
	printf("更改后");
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

结果运行图:在这里插入图片描述
我们知道p和arr是等价的(本身p是由arr赋值而来的,所以肯定相等)。前面,我们访问数组的时候,都是用arr[i]来进行访问。既然,p和arr是等价的,那么我们能否写成p[i]来进行访问呢?答案是肯定的,进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", p[i]);
	}
	return 0;
}

结果运行图:在这里插入图片描述
大家能看的出来,结果是一样的。看到这里,我们再来进行拓展一下。前面,我们用过arr[i] ,p[i]和*(p+i)对数组进行访问,我们会发现访问的结果是一样的。那么它们本质上是不是一回事呢?答案:本质上是一样的。 在访问数组时,编译器会把arr[i]转换成 *(p+i)进行编译因为arr[i]这种格式很符合人们的认知,所以C语言中就规定了以这种方式对数组进行访问 . *(p+i)才是本质。总结:arr[i] = *(p+i)= *(arr+i)。这里的p只是举个例子,换成什么都OK

一维数组传参的本质

前面,我们访问数组的时候都是在main函数里面进行访问。那么,今天我们就创建个子函数,在这个子函数里面进行访问。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void Printf(int arr[])
{
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	Printf(arr);
	return 0;
}

结果运行图:在这里插入图片描述
我们把给函数传的是数组,我们理所应当就该给Printf函数的参数类型为数组形的。我们既然把数组给传过去了,那么我们就应该能访问整个数组呀,为什么只访问了1个元素呢?原因很简单,前面咱们讲过——数组传参,传的是数组首元素的地址。也就是说,arr传过去的并不是整个数组,而是传过去一个地址。前面,我们讲过,地址的空间大小只与计算机平台有关。在32位平台下为4个字节,又因为int 也是4个字节。所以,sz的结果为1,即arr[0]就只访问了一个元素。进行代码展示:

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

结果运行图:在这里插入图片描述
结果与我们分析的是一样的。接下来,我们对代码进行更改:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void Printf(int arr[],int sz)
{
	int i = 0;
	for (i = 0; i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Printf(arr,sz);
	return 0;
}

结果运行图:在这里插入图片描述
在前面,我们讲了,数组传参传的是数组首元素的地址。那么,我们可以把子函数的参数写成指针(因为传过去的是地址)。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void Printf(int*p,int sz)
{
	int i = 0;
	for (i = 0; i < sz;i++)
	{
		printf("%d ", p[i]);
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Printf(arr,sz);
	return 0;
}

结果运行图:在这里插入图片描述
我们发现效果是一样的,也就是说 int arr []int *p是一样的。确实是一样的,int *pint arr[]的本质。因为当我们写成int arr []时,是很自然而然地就能想到的(因为我们传过去的是数组,自然而然就用数组来接收)。其实,编译器也会把int arr []转换成int *p来进行识别和处理的因为我们传过去的是地址,不是数组,所以子函数的形参部分是不会创建数组的,所以```[ ]``里面的数字可省略不写

冒泡排序

我们来写个程序,对数据进行排序。对数据进行排序的算法有很多,比如,1.冒泡排序 2.插入排序 3.选择排序 4.快速排序 5.归并排序。今天,我们来用冒泡排序。冒泡排序的核心思想:两两相邻的数据进行比较

//	我们随机输入5个数字,而且还是乱序的。最后我们得到从小到大的排序。
//	比如,输入:1 ,4, 6 ,5 ,9 
//       输出 :1 ,4 ,5 ,6 ,9

进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void change(int arr[], int sz)	//写成指针也是可以
{
	int x = 0;	//趟数
	int y = 0;	//排序对数
	for (x = 0; x < sz - 1; x++)	//趟数
	{
		for (y = 0; y < sz - 1 - x; y++)	//排序对数
		{
			if (arr[y] > arr[y + 1])
			{
				int c = arr[y + 1];
				arr[y + 1] = arr[y];
				arr[y] = c;
			}
		}
	}
}
int main()
{
	int arr[5] = {1,2,3,4,5};
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);
	}
	printf("排序前:");
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	printf("排序后:");
	change(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

结果运行图:在这里插入图片描述
这个代码不够完善,需要优化。因为当我们输入9 ,1 ,2 ,3 ,4 时,只需要一趟就能排完,但是按照上面的代码就要重复排序,代码执行效率不高。优化后的代码如下所示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void change(int arr[], int sz)	//写成指针也是可以
{
	int flage = 1; //假设一趟排完
	int x = 0;	//趟数
	int y = 0;	//排序对数
	for (x = 0; x < sz - 1; x++)	//趟数
	{
		for (y = 0; y < sz - 1 - x; y++)	//排序对数
		{
			if (arr[y] > arr[y + 1])
			{
				flage = 0;
				int c = arr[y + 1];
				arr[y + 1] = arr[y];
				arr[y] = c;
			}
		}
		if (flage == 1)
			break;
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);
	}
	printf("排序前:");
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	printf("排序后:");
	change(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

二级指针

前面,咱们讲过,指针变量也是变量。创建变量的本质就是向内存申请空间,既然申请了空间,那就有这个空间的地址。那么,我们就可以取出来这个指针变量的地址。如图所示:在这里插入图片描述
我们创建了int a =10,然后我们又创建了指针变量int *p=&a。前面,咱们已经讲过了int *的意义(对它的解读)。我们创建了一个指针变量(二级指针),用来存放指针变量的地址,而指针变量的类型为int *,又因为我们创建的变量来存放指针变量的地址,所以它也是一个指针变量,即int **ppa=&pa。按照这个思路,我们还可以创建三级,四级,……N级指针。二级指针往上,我们就不常用了。当我们对其指针所指的变量进行数据访问时,我们要进行多次*操作。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	int* *ppa = &p;
	printf("更改前:a=%d\n", a);
	*(*ppa) = 30;			//要加()
	printf("更改后:a=%d\n", a);

	return 0;
}

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

指针数组

指针数组是指针呢?还是数组呢?在回答这个问题之前,我们先类比一下。

//	整形数组:存放的元素是整形类型的数据
//	字符数组:存放的元素是字符类型的数据
		|						|
		|						|
		|						|
//	指针数组:存放的元素是指针(地址)类型的数据

所以指针数组是存放指针(地址)的数组。如图所示:在这里插入图片描述

指针数组模拟二维数组

我们学了指针数组,那么我们就用指针数组模拟二维数组。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 5,4,3,2,1 };
	int arr3[] = { 0,0,0,0,0 };
	int* arr[] = { arr1,arr2,arr3 };
	int i = 0;
	int j = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);		//指针数组的元素个数
	int sz1 = sizeof(arr1) / sizeof(arr1[0]);	//整形数组的元素个数
	for (i = 0; i < sz; i++)
	{
		for (j = 0; j < sz1; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

结果运行图:在这里插入图片描述
我们在这里只是模拟二维数组,并不是真正的而数组。因为真正的二维数组是在同一个连续的空间的。我们这三个数组分明不在同一个空间,所以只是模拟

数组名的理解返场:

开篇咱们讲了数组名的理解,里面有个遗留的问题——&arr+1为什么能跳过整个数组呢?它的数据类型到底是什么?今天我们就讲解一下。

//	int a=10;
//	int * p=&a;	//因为a是整形,所以就用 int *

//	char b='w';
//	char *pp=&b;	//因为b是字符形,所以就用char *

对于数组,同理得到:
//	int arr[10];
//	int *arr1[10] =&arr;   //因为arr是数组类型,所以就用数组类型——int arr [10]
						  // 然后再加个*,代表存放地址。
? ? ? ? ? ?
但事实就是这样的吗? 没有什么疑问吗?

int *arr1[10] =&arr 并不是整个数组的地址,这不就是咱们上面讲的指针数组吗,指针数组是用来存放数组的地址的(也就是数组名----数组首元素的地址),并不是存放&arr整个数组的地址。我们只需要这样改一下就OK了——int( *arr1)[10] =&arr (*arr1)说明arr1是个指针变量,然后(*arr1)[10]结合,表明所指的类型为数组。最后,(*arr1)[10]int 结合,表明所指的数组为整形数组。所以arr1的数据类型为int( *)[10] ,这也就是为什么&arr+1跳过40个字节了。总结当我们接收整个数组的地址时,数据类型为type (* name) [元素个数]

彩蛋时刻!!!

https://www.bilibili.com/video/BV15b42177rL/?spm_id_from=333.337.search-card.all.click&vd_source=7d0d6d43e38f977d947fffdf92c1dfad在这里插入图片描述
每章一句学习也是如图片里的跑步一样。感谢你能看到这里,点赞+关注+收藏+转发是对我最大的鼓励,咱们下期见!!!

  • 41
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值