有希带你深入理解指针(2)

前言😶‍🌫️

距离上次更新好几天了,十分抱歉!是我打字太慢了😫,呜呜~~,现在我们接着上次的内容接着学习指针内容吧!😆
在这里插入图片描述

1.数组名的理解🥰

当我们要访问一个数组时,我们首先要得到首元素的地址。我们现在可能会使用&arr[0]来拿到数组首元素的地址,其实数组名本来就是首元素的地址,我们可以测试看看😤!
在这里插入图片描述
但是在这里,有两个例外,此时数组名不是首元素的地址:😫

  1. sizeof(数组名) —— 这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
  2. &数组名 —— 这里的数组名表示整个数组,取出的是整个数组的地址

现在我们对第一种情况进行分析(环境:VS2022 Debug X64),如图:
在这里插入图片描述
这里想必不少小伙伴们就会想:你刚才不是说过数组名是首元素的地址吗?🤨那么在此环境下,一个地址应该是8个字节,怎么会是40呢?😲
所以在此条件下,数组名表示的是整个数组的大小,是与一般情况不符的。🤓

第二种情况:(环境:VS2022 Debug X86)
在这里插入图片描述

此时大家可能会这样思考:&arr[0]和arr的结果一样是因为它们得到的都是首元素的地址,那&arr按照结果来看也是首元素的地址吗?🫡其实&arr应该是数组的地址,我们依据下图来理解。
在这里插入图片描述
在这里插入图片描述
在这里我们会发现,它们虽然在加1之前地址是一样的,但是在加1后有着不同的变化。&arr[0]和arr加1后增加了4个字节,相当于跳过了一个元素。&arr加1跳过了40个字节,相当于跳过了整个数组

所以在此条件下,数组名表示的是整个数组的大小,是与一般情况不符的。

知识回顾:😶‍🌫️
我们刚才加1跳过几个字节是由什么决定的呢?
答案:指针类型,指针类型决定了指针±整数时的步长。我们以上所说的&arr[0]和arr都是int * 类型的,所以在加1后增加4个字节(一个元素)。那&arr是什么类型的呢,别着急我们以后会讲到。🤫

2.使用指针访问数组👻

先对一般情况下,我们怎么访问数组进行讨论。

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		scanf("%d", &arr[i]);
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

以上的代码,我们是通过下标的方式来访问数组的。采用该方法是依据我们知道数组里面的元素是有下标的,那学习了指针的知识后,我们不妨试试使用指针来完成该操作。我们使用一个整型的指针来访问数组,此时对该指针解引用访问4个字节,加1后我们跳过4个字节,到达下一个元素的位置,并不断遍历这个过程,最后我们就搞定了这10个元素。😋

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

在这里插入图片描述
我们现在理解了这个代码后,我想问:int * p =arr这里,我把数组首元素的地址赋给了p,现在p和arr是等价的吗?此时是等价的。所以我们把printf("%d ", * (p + i))中的p换为arr试试看。😁
在这里插入图片描述
显然我们的猜想是正确的!现在我们结合以上的代码我们会发现arr[i]、 * (arr+i)和 * (p+i)在此处的效果是一样的。

知识延申:😎
arr[i]和 * (arr+i)是完全等价的。对于arr[i]这种写法,编译器在执行的时候,也会转换成 * (arr+i)的方式。
我们不难发现数组的底层也是通过指针方式对数组进行访问的。🤤

理解以上知识后,我们再试试别的写法,例如把 * (p+i)的位置直接换为p[i]试试。
在这里插入图片描述
我们刚才不是说p和arr是等价的,可以看为数组首元素的地址。所以这里用该方式也可以实现。到这里,我想大家应该会灵活的使用指针访问数组了吧。当然我们刚才一直在改printf那里的内容,scanf那里也可以改,如图:
在这里插入图片描述
难道我们就只能这样使用指针访问数组吗?我们现在脑洞大开一下:🤐
先用一个例子铺垫:
已知arr[i]和 * (arr+i)是完全等价的。 * (arr+i)是具有交换律的,它和 * (i + arr)是一样的。
在这里插入图片描述
可能大家会问为什么有交换律?arr本身就是一个地址值,值和i相加的时候,我们根据类型决定了加几个字节,对于加法交换并不会产生影响,所以理论上是完全没有问题的。🤯

那arr[i]可以改写为i[arr]吗?🤪
在这里插入图片描述
这样写是可行的,[ ]其实只是一个操作符而已,如1 +2等同于2 + 1一样。在编译器在执行的时候,i[arr]也会转换成 * (arr+i)的方式。但是我不推荐这样写,毕竟不好理解嘛。

3.一维数组传参本质🥳

在这里插入图片描述
上图就是数组传参,在这个函数这里我们写一个数组名arr,但是不要写arr[10],arr[10]并不是它的数组名,[10]代表该数组有10个元素。如果写为arr[10],我们相当于传过去一个元素,下标为11的元素。

现在我们用test函数来计算数组中的元素个数:(环境:VS2022 Debug X86)

在这里插入图片描述
这里我用了两个方式来计算数组的大小,sz1的计算方法我们十分熟悉,但为什么我们把该功能分装到一个test函数时却不能实现原本的功能,我们可能会想test(arr)里的arr到底传递了什么内容呢?🫠
根据显示内容1,我们可以猜测:sizeof(arr[0])计算的是下标为0的元素大小,一定是4。只有sizeof(arr)也为4,最终答案才能为1。这里其实传递的是数组首元素的地址,我们前面不是提到了数组名就是首元素的地址,在test(arr)里面的arr不是特殊情况,到了我们分装的函数里,sizeof(arr)计算的其实是一个地址的大小,最终答案是1也就说得通了。🧐

总结,数组传参传递的是数组首元素的地址

现在我们清楚此时传递的是一个地址,我们并不需要创建一个数组来接收,即函数形参的部分是不会真实的创建一个数组,那么就不需要数组的大小。😋

我们传递的是一个地址,那么我们就可以用一个指针变量来接收,依照例子中数组的类型,我们假设用int * p 来接收。我们使用int arr[ ]的目的是方便理解。🥲

在了解到通过该方式我们是计算不了数组中的元素个数后 ,我们往往在主函数中计算好数组中元素的个数,并与数组首元素的地址一块传递到函数中,便于我们遍历数组。😮‍💨

#include <stdio.h>
void test(int arr[],int sz1)
{
	int i = 0;
	for (i = 0; i < sz1; i++)
	{
		printf( "%d  ", arr[i]);
	}
}

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,sz1);
	return 0;
}

在这里插入图片描述

4.冒泡排序😴

这里我们将运用一维数组传参的知识。
冒泡排序的核心思想就是:两个相邻元素相比较,有需要的话,进行交换。
我们对一组比较极端的数据进行排序,{9,8,7,6,5,4,3,2,1},我们需要将其改为升序。😝
在这里插入图片描述
在这里,我们让最大的数字一直“冒泡”到它应该出现的位置上,这个过程被称为一趟冒泡排序。之后我们需要对8进行操作一直到达它应该在的位置(这个过程中不需要对9进行操作)。经过所有趟冒泡后,我们得到1 2 3 4 5 6 7 8 9的结果,对于9个数字我们需要8趟冒泡排序,对于n个数字,我们需要n - 1趟冒泡排序。🤑

我们首先完成一趟的内容,再加趟数,很显然这是一个循环嵌套的过程。用 j 作为下标,j + 1作为下一个元素的下标。

#include <stdio.h>
void Bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < ; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}



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

此时代码是不完整的,一趟内部我们需要比较多少对呢?对于9个数字,我们第一趟需要比较8对,并以此递减,所以我们在第二个for循环里写 j < sz - 1 - i。现在我们写一个函数来打印我们的结果。😋

#include <stdio.h>
void Bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j <sz-1-i ; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

void Print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

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

在这里插入图片描述
此时完成我们的代码之后,我们的代码已经可以进行冒泡排序了。但是在这里我们可以对其进行优化。假设我们的数字是{9 ,1,2,3,4,5,6,7,8},那么进行一次冒泡排序之后我们的结果就已经正确,但是我们的代码此时还会不断的进行这个过程,直到完成整个过程。不如引入一个标志(我们这里定为flag),如果后面的数字有序,就不再进行这过程。代码演示如下:

#include <stdio.h>
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-1-i ; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

void Print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

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

5.二级指针😘

在这里插入图片描述

p是指针变量,一级指针变量。

pp是二级指针变量。
现在我们逐步理解:
a的地址存放到p中,p要存放a的地址需要向内存申请空间,该空间也具有自己的地址,p的地址可以存放到pp中。我们不难发现二级指针变量的作用就是存放一级指针变量的地址。🤯

我们可以通过下图来理解这个符号:
在这里插入图片描述
同理,三级指针或者往上都可以这样理解,只是三级指针都用得比较少。
注意:二级指针与二维数组是没有什么直接的联系的,不要混淆!

那我们可以通过pp,逐步找到p,最后找到a呢?肯定是可以的!😝
在这里插入图片描述
在这里,我们对pp不断的解引用,最后找到了a。

6.指针数组😌

顾名思义,就是存放指针的数组。例:

char* arr1[9];存放字符指针的数组
int* arr2[6];存放整型指针的数组

现在我们以整型指针为例:
在这里插入图片描述
在该整型指针中,我们存放了a,b,c的地址,并在后面解引用将其打印出来。

7.指针数组模拟二维数组😆

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[] = { arr1,arr2,arr3 };
	return 0;
}

这里我们放置了三个数组arr1、arr2和arr3,我们把这三个数组名放在了arr这个数组中。我们说过数组名就是首元素的地址,所以在arr数组中我们放置了三个数组首元素的地址,那么这个数组就是指针数组。😺

我们可以通过下图进行理解:
在这里插入图片描述
此时我们添加打印的功能,我们将其打印出来:

#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* arr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++) 
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述
我们这时可以发现我们是用二维数组的方式访问元素的。现在我们已经完成使用指针数组模拟二维数组这个目标了!👻

好了我们本次讲解就到这里了,指针的内容还没有结束,我们下期blog继续!😆大家如果感兴趣,请一键三连!😘如果存在问题,各位大佬请在评论区斧正!🥰十分感谢大家的支持!
在这里插入图片描述

评论 93
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值