深入理解指针(2)--新手小白都能明白的指针解析

深入理解指针(2)–新手小白都能明白的指针解析

前言

OK啊,我们也是在五一假期结束之前,又见了一面,我挺开心的,不知道你们开心吗

好了,回归正题,我相信你们在深入理解指针(1)中已经多多少少了解到了指针,接下来,我们再认识指针吧

在这里,你会学习到 对数组名的解释 用指针访问数组 … 还有很多好玩的知识

那么正片开始喽

1. 数组名的理解

在深入理解指针(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;
}

在这里插入图片描述

我们发现数组名和数组首元素的地址打印出的结果⼀模⼀样,数组名就是数组首元素(第⼀个元素)的地址

那我们说如果数组名是数组首元素的地址,那么它是不是应该和数组首元素地址一样也是4个字节呢

数组名如果是数组首元素的地址,那下面的代码怎么理解呢:

#include<stdio.h>

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

在这里插入图片描述

哎,为什么两个数好像没有按照我们的想法来输出啊,这是为什么,那是不是数组名根本就不是数组首元素的地址啊

其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
  • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)

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

这时有困惑的同学,再试⼀下这个代码:

#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;
}

在这里插入图片描述

从这里我们看到这三者确实都是数组首元素的地址

也就是说 &arr[0] arr &arr 指向的都是首元素的地址

画个图就能更好理解

在这里插入图片描述

三个打印结果⼀模⼀样,这时候我们又纳闷了,那arr和&arr有啥区别呢

接下来让我们看一下下面的一段代码:

#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;
}

在X86的输出结果如下:

在这里插入图片描述

我们说 &arr[0] arr &arr 指向的都是首元素的地址

为什么 +1 之后又会不一样呢

通过输出结果,我们会发现

  • &arr[0]和&arr[0]+1相差4个字节
  • arr和arr+1 相差4个字节

是因为 &arr[0] 和 arr 都是首元素的地址,+1就是跳过⼀个元素

  • &arr 和 &arr+1相差40个字节

因为&arr是数组的地址,+1 操作是跳过整个数组的

在这里插入图片描述

哦,原来如此,这下我们就应该清楚数组名的意义了吧,嗯很棒

这个时候又有小伙伴会有疑问,前面我们有提过arr是 int* 的类型的,所以它跳过了4个字节,&arr[0]也是 int* 类型的,所以它也跳过4个字节,那&arr一下跳过40个字节,它是什么类型的呢?这个问题我们会在后面给大家解答的,大家不要着急哦

2. 使用指针访问数组

我们之前访问数组都是通过下标的方式来访问的

就像这样一样

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

那现在我们已经学习了指针,对指针有了一定的认识,我们可以通过指针来访问数组

像这样

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

这个代码搞明白后,如果我们再分析一下,能不能使用指针给数组输入10个值呢,当然可以了

#include<stdio.h>

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

	//输入10个值
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
        //scanf("%d", &arr[i]);//使用下标的方式
	}

	//输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *p);
        //也可以写成  *(p+i)  *(arr+i)   或者写成p[i]
	}
	return 0;
}

我们可以看到数组名arr是数组首元素的地址,可以赋值给p,其实数组名arr和p在这里是等价的,可以使用arr[i]可以访问数组的元素,也可以用p[i]访问数组

总结

arr[i] == *(arr+i) == *(p+i) == p[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;
}

看看结果:

在这里插入图片描述

我们会发现在函数内部是没有正确获得数组的元素个数,那为什么, se2 为什么会是1呢

这就要学习数组传参的本质了,上个小节我们学习了:数组名是数组⾸元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组首元素的地址

所以我们在接收的时候就应该用一个指针 int* arr 来接收,而不是用数组来接收

//void test(int arr[])//参数写成数组形式,本质上还是指针
//{
//	printf("%d\n", sizeof(arr));
//}

void test(int* arr)//参数写成指针形式
{
	printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	test(arr);
	return 0;
}

总结

  • 一维数组传参的时候,传过去的是数组首元素的地址
  • 形参的部分可以写成指针的形式,也可以写成数组的形式,但本是上都是指针,写成数组的形式是为了方便理解

4. 冒泡排序

假设我们有一个无序的数组,要求把这个数组排成有序的,我们该怎么实现呢,大家知道排序有很多方法,今天我们主要给大家介绍一下冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较

我们来画个图帮助我们理解

在这里插入图片描述

我们就这样两两进行比较,一直走到和最后一个元素进行比较,我们将这个就叫一趟冒泡排序,当完成第一趟冒泡排序时,我们就可以看到已经将所有元素中最大的那个数字排到了最后,所以一趟冒泡排序就搞定了一个元素的顺序

那我来看看代码的实现:

//冒泡排序
#include<stdio.h>

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)//冒泡排序的趟数
	{
		//一趟冒泡排序
		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(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };//现在我们给定的是一个升序的数组,我们来给它排成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);//打印排序前
	bubble_sort(arr, sz);//冒泡排序实现
	print(arr, sz);//打印排序后
	return 0;
}

运行结果所示:

在这里插入图片描述

我们发现已经完成了一个数组的排序

我们在没有学习指针前这样写,没什么问题,那现在我们学习了指针,是不是能用一下指针呢,让我们来实现一下吧

//冒泡排序(指针)
#include<stdio.h>

void bubble_sort(int* arr, int sz)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)//冒泡排序的趟数
	{
		//一趟冒泡排序
		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(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };//现在我们给定的是一个升序的数组,我们来给它排成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);//打印排序前
	bubble_sort(arr, sz);//冒泡排序实现
	print(arr, sz);//打印排序后
	return 0;
}

嗯,这下我们就完成了冒泡排序

但是我们会发现,这样写代码的效率是不是有点太低了,要是我们要排序的数组已经是有序的,是不是还要一个一个比,这样的话效率就显得有些低了,那我们是不是还可以把我们的代码给优化一下

那怎么优化呢

是不是可以定义一个 “1” , 要是 “1” 没变就说明是有序的,要是 “1” 变了就是无序的

怎么定义呢,请看以下代码:

//冒泡排序(优化)
#include<stdio.h>

void bubble_sort(int* arr, int sz)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)//冒泡排序的趟数
	{
		//一趟冒泡排序
		int flag = 1;//我们规定,有序为1,无序为0;

		for (j = 0; j < sz - 1 - i; j++)
		{
			//如果满足前一个数小于后一个数就交换
			if (*(arr + j) > *(arr + j + 1))
			{
				flag = 0;//一旦发生交换我们就让flag为0
				int tmp = *(arr + j);
				*(arr + j) = *(arr + j + 1);
				*(arr + j + 1) = tmp;
			}
		}
		//如果进行了一趟冒泡排序之后,flag仍然为1,
		//就证明这个数组已经是有序的了,我们就跳出循环。
		if (flag == 1)
		{
			break;
		}
	}
}
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };//现在我们给定的是一个升序的数组,我们来给它排成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);//打印排序前
	bubble_sort(arr, sz);//冒泡排序实现
	print(arr, sz);//打印排序后
	return 0;
}

这样就优化好了

怎么样,冒泡排序是不是非常地简单

5. 二级指针

那接下来一级指针讲的差不多了,就讲讲让人头大的二级指针

我们说,之前在深入理解指针(1)中一级指针指向的是元素的地址

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里

这就是 二级指针 ,二级指针就是用来存放一级指针变量的地址的

int a = 10;
int * pa = &a;
int ** ppa = &pa;

什么意思,还是画个图帮助理解

在这里插入图片描述

这个 int ** ppa 我们就可以理解为指向 int * pa 的指针

那我们能不能通过二级指针 int ** ppa 来找到a呢,答案肯定是可以

来看看代码实现:

#include<stdio.h>

int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;
	**pp = 20;
	printf("%d\n", a);
	return 0;
}

运行结果所示:

在这里插入图片描述

嗯,没什么问题

6. 指针数组

大家看到这个名字是不是就开始疑惑了,指针数组到底是指针呢,还是数组呢,我们来类比一下.

整型数组 int arr[10] 就是存放整型的数组

字符数组 char arr[10] 就是存放字符的数组

指针数组 是不是就是存放指针的数组呢,对的

指针数组的每个元素都是用来存放地址(指针)的

在这里插入图片描述

指针数组的每个元素是地址,又可以指向一块区域

所以啊,大家遇到自己不熟悉的知识的时候,很多时候都可以用类比的方式给推出七七八八

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 用来存放三个数组的地址,然后我们通过arr[i]来访问指针数组,假设此时i=0,从图中我们可以看到 parr[0] 指向的就是数组 arr1 ,然后我们再通过访问 arr1 的下标来获取 arr1 数组中的元素

结语

好了,感谢您能看到这里,今天的学习到这里也就结束了,希望这篇文章对您有用

一起期待**深入理解指针(3)**吧,溜了溜了,我们下个星期再见吧

  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值