[指针]从浅到深理解指针-第二课

一、数组名的理解

指针第一课中,我们在指针运算那一节中使用指针访问数组的内容时,有这样的代码:

int arr[] = {1,2,3,4,5,6,7,8,9,10};
int* p = &arr[0];//取出了数组的首元素的地址

这里我们使用 &arr[0] 的方式拿到了数组第⼀个元素的地址,但是其实数组名arr本来就是地址,而且是数组首元素的地址,我们来做个测试。

//代码一
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p1 = &arr[0];
	int* p2 = &arr;
	printf("arr[0]首元素的地址:   %p\n", &arr[0]);
	printf("arr数组名的地址:      %p", arr);

	return 0;
}

在这里插入图片描述
通过测试,我们可以知道,arr数组名确实是首元素的地址

这时候有人会有疑问?数组名如果是数组首元素的地址,那下面的代码二怎么理解呢?
//代码二
#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("%d\n", sizeof(arr));//结果为40
 return 0;
}

相信有人觉得结果是4,毕竟arr是数组名,是首元素的地址,而地址的大小取决于在哪个环境下的,在x86(默认)中地址的字节大小为4字节,那么arr的大小也为4字节。

可是真的如此吗?通过测试,我们得到的结果为40,那么这里我们把数组名作为首元素地址就理解不了这个例子。那么我们该如何理解?

其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有两个例外
• sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节,如代码二
• &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
除此之外,任何地方使用数组名,数组名都表示首元素的地址。如代码三

//代码三
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"    , &arr+1);
	return 0;
}

在这里插入图片描述

从上面的代码三和运行结果,我们可以看到&arr (&数组名)的地址与数组首元素地址一样相等,但是含义不一样,因为指针类型不一样。如果我们还记得指针类型的意义,我们就知道指针的类型会影响步长(指针±整数),即影响指针的偏移量,看代码三,&arr+1后跳过了整个数组即40个字节,而arr+1跳过了一个元素即4个字节。所以&arr和arr的指针类型不一样。 比如&arr[0]和arr的类型都为int * ,而&arr的类型为 int (*) [10] 。

我们想一想,我们取出的地址是要干什么?当然是为了存放起来方便使用它,而指针变量又是存放地址的变量,那么指针变量和存放的地址就是同一个意思。看代码:

int a = 10;//其中a在内存中是不会存放a的
//a这个变量只是让我们程序员知道此时的a就是代表着10,即10的数据类型是int

int* p = &arr[0];//同理:此时p就代表着&arr[0](也就是首元素的地址)
//那么此时指针类型(地址类型)就是 int *
int* p = arr;//与上面一样

(这就和数组的类型以及数组元素的类型有关了,这里就先给出结果)
在这里插入图片描述


二、使用指针访问数组

有了前面知识的支持,再结合数组的特点,我们就可以很方便的使用指针访问数组了。

//代码四
#include <stdio.h>
int main()
{
 int arr[10] = {0};
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 
 //输⼊
 for(i=0; i<sz; i++)
 {
 scanf("%d", &arr[i]);
 }
 
 //输出
 for(i=0; i<sz; i++)
 {
 printf("%d ", arr[i]);
 }
 return 0;
}

代码四中,我们先使用最初的方式写入和访问数组

//代码五
#include <stdio.h>
int main()
{
 int arr[10] = {0};
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 
 //输⼊
 int* p = &arr[0];//也可以使用arr赋值给p
 for(i=0; i<sz; i++)
 {
 scanf("%d", p+i);//这里不需要&符号了,因为p+i就是地址
 }
 
 //输出
 for(i=0; i<sz; i++)
 {
 printf("%d ", *(p+i));
 }
 return 0;
}

代码五,我们使用了指针来写入和访问数组

比较代码四和五,我们可以深层得到一定理解,我们在用指针访问数组时可以用下面的代码来进行访问:

*(arr+i)= arr[i];//编译器在编译的时候,也是把arr[i]转化为*(arr+i)来计算的
*(p+i)= p[i];//因为p指针和arr指针都指向数组的首元素,那么可以用arr[i]索引的方式来访问数组
//那么就可以使用p[i]索引的方式来访问数组
//在计算机中,我们知道+支持交换律
*(p+i) = p[i]= *(i+p) = i[p];

即p就是arr,arr就是p

总结:
在这里插入图片描述


三、一维数组传参的本质

#include <stdio.h>
void test(int arr[]) //那么形参用数组接受,[]里可以写数字,也可以写任何整数(10,1000,2)
//还可以不写,因为本质是指针,不是数组,写了也没用,没有意义,
{
 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;
}

在这里插入图片描述

通过数组名传值,我们可以看到,计算的元素个数居然不一样,一个为10,另一个为1。那我们反推一下看,既然sz2 = 1,那么sizeof(arr)的大小就应该是4个字节,4 / 4 = 1。可为什么函数test()里的sizeof(arr)的大小为4个字节呢?那么这就和一维数组传参的本质有关。

数组名是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。 所以形参即使写成数组的形式,本质上也是一个指针变量。
所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
函数参数部分int arr[]本质上是 int * arr


四、 冒泡排序

#include<stdio.h>
#include<string.h>
input_arr(int* p, int sz)//输入函数
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}

}

output_arr(int* p, int sz)//打印函数
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
}

paixu_arr(int* p, int sz)//排序的基本思想是:相邻的二个数比较大小,符合条件的交换,
{                           //一趟完成一个数,10个数字的排序,需要9趟,因为9个数排序好位置,第10个数自动排序好
	int i = 0;                        //并且每次完成一趟后,下一趟需要比较的数字就少一个
	for ( i = 0; i < sz-1; i++)//每一趟
	{
		int j = 0;
		for( j=0;j<sz-1-i;j++)//每一趟完成一个数字的排序
		{ 
		    if (*(p+j) > *(p + 1+j))//升序排序;注意不是*p > *(p+j)
		    //因为开始时j=0,即*p和*(p+j)是一样的意思,如果是写成数组形式就不容易错,p[j]>p[j+1]
		    {
			    int temp = *(p+j);
			    *(p+j) = *(p + 1+j);
			    *(p +1+ j) = temp;
		    }
		}
	}

}

int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	input_arr(arr, sz);//输入
	output_arr(arr, sz);//输出

	paixu_arr(arr, sz);//排序
	output_arr(arr, sz);//输出
	return 0;
}

下面对代码进行一些健壮

#include<stdio.h>
#include<string.h>
input_arr(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
	}

}

output_arr(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
}

int count = 0;//计算比较了几次
bubble_arr(int* p, int sz)
{                           
	int i = 0;
	
	int flag = 1;//假定已满足排序
	for (i = 0; i < sz - 1; i++)//每一趟
	{
		int j = 0;
		
		for (j = 0; j < sz - 1 - i; j++)//每一趟完成一个数字的排序
		{
			count++;
			if (p[j] > p[j+1])//升序排序
			{
				int flag = 0;//说明没有排序好
				int temp = p[j];//交换
				p[j] = p[j + 1];
				p[j + 1] = temp;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}

}

int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	input_arr(arr, sz);//输入
	output_arr(arr, sz);//输出

	bubble_arr(arr, sz);//排序
	output_arr(arr, sz);//输出
	printf("%d", count);//计算比较了几次
	return 0;
}

添加代码flag的意义是,假如数据本来就是已经给定排序好的(升序),那么我们如果还是按最初的代码进行,比较浪费时间,会进行45次的比较次数,而添加flag后,我们先假定flag =1 ,假如没有进入if条件里,则flag一直等于1,不会等于0,那么在一趟中,没有数据交换,也就代表着数据已经排序好了。此时就可以跳出循环,避免再下一趟比较排序。


五、二级指针

我们前面学习的都是一级指针,那么什么是二级指针呢?
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是二级指针

int main()
{
	int a = 10;
	int* pa = &a;//pa是一级指针变量
	int** ppa = &pa;//paa是二级指针变量
	return 0;
}

图片中就是对二级指针的基本理解
在这里插入图片描述

那么怎么对二级指针变量进行使用呢?其实很简单,我们在一级指针时使用了* pa可以找到pa所指向的空间的值10,那么 *paa找到的是paa所指向空间的值(该值是一个地址,指向a),那么再次解引用得到 **paa找到的是 *paa所指向的空间的值为10。

* ppa = pa;//说白了就是这个等式 
** paa = *pa = a; 
提个小点:二级指针和二维数组没有对应关系!!!

六、 指针数组

指针数组是什么?是指针?还是数组? 不急先让我们看看代码

char arr[10];//字符数组  - 存放字符的数组
int arr[5];//整型数组  - 存放整型的数组

通过上面的代码类比,我们应该知道了,指针数组 — 存放指针的数组,数组的每一个元素是指针。

char* arr[5];//存放char* (字符指针)的数组
int* arr[6];//存放 int* (整型指针)的数组

那么如何来应用指针数组呢?

int a = 10;
int b = 20;
int c = 30; 
int* pa = &a;
int* pb = &b;
int* pc = &c;

如果我们想把a,b,c的地址都存放起来,使用pa,pb, pc指针变量来存放就显得有些繁琐了。这时就可以使用指针数组来快捷存放地址。

int a = 10;
int b = 20;
int c = 30; 
int* arr[3] = {&a , &b , &c};

存放后,我们也可以使用学过的知识来把值打印出来

int main()
{
	int a = 10;
	int b = 20;
	int c = 30; 
	int* arr[3] = {&a , &b , &c};
	int i = 0;
	for(i = 0;i < 3;i++)
	{
	printf("%d ",*(arr[i]));//结果是: 10 20 30
	}
	return 0;
}

七、用指针数组模拟二维数组

int main()
{
	int arr1[9] = { 1,2,3,4,5,6,7,8,9 };
	int arr2[9] = { 4,5,6,7,8,9,1,2,3 };
	int arr3[9] = { 7,8,9,4,5,6,1,2,3 };

	int* parr[3] = { arr1,arr2,arr3 };//指针数组,存放了三个一维数组的各个首元素地址

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 9; j++)
		{
			printf("%d ", parr[i][j]);//*(*(prr+i)+j)
		}
		printf("\n");
	}

	return 0;
}

在这里插入图片描述

parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型⼀维数组中的元素。
上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。


谢谢观看!希望以上内容帮助到了你,对你起到作用的话,可以一键三连加关注!你们的支持是我更新地动力。
因作者水平有限,有错误还请指出,多多包涵,谢谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值