指针(2):深度理解指针

我们继续开始指针的学习基于指针(1)对指针的了解我们继续开始指针的学习,这一节我们要正式的开始指针的学习之路了,我们会写一些函数指针这也是指针的魅力之处。

一,数组名的理解

我们先从我们熟悉的数组开始,说起数组名我们要先看一下数组传参的本质。

其实我们数组也是一种指针,如arr[1],也可以写成 *(arr+1) 。这既是指针的表示,那这里的数组名到第代表了什么呢?这里我们也可以看出arr这个数组名其实就是数组首元素的地址。

我们来用代码验证一下:
 

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

运行一下就会发现这个sizoef(arr)得出的结果是40,如果数组名是首元素的地址那么它的大小会是4或8。那这是怎么回事呢?

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

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

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

除了这两个情况之外其它的情况数组名都是表示数组首元素的地址。

这时有好奇的同学可以试一下下面的代码:

我们发现这三个的地址一模一样那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;
}

运行结果为:

这就体现了arr和&arr的区别,arr+1会跳4个字节,而&arr+1会跳40个字节这就证明了&arr取出的是整个数组的地址,+1跳过的是整个数组。

二,使用指针访问数组

#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);
		//scanf("%d",arr+i)也可以这样写
	}
	//输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

这里我们分析一下因为arr是首元素的地址,arr赋值给p那么p这个指针和arr是等价的可以用arr[i]可以访问数组,那么p[i]是不是也可以访问数组。

#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);
		//scanf("%d",arr+i)也可以这样写
	}
	//输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ", p[i]);
	}
	return 0;
}

这里的*(p+i)换成p[i]也是可以的这就说明这两个是等价的,同理arr[i]也可以替换成 *(arr+i)。也是转换成首元素的地址+偏移量取出元素的地址,再进行解引用来访问。

三,一维数组传参的本质

在以上的种种运行结果可以显示,一维数组传参的本质其实是传的是数组首元素的地址,通过这个地址可以找到数组中的所有元素。

#include<stdio.h>
void test1(int arr[])//参数写成数组形式本质上还是指针
{
	printf("%d\n", sizeof(arr));
}
void test2(int* arr)//参数写成指针方式
{
	printf("%d\n", sizeof(arr));//计算一个指针变量的大小
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	test1(arr);
	return 0;
}

总结:一维数组传参,形参可以写成数组方式也可以写成指针方式。

其中用sizeof(arr)计算的是一个地址的大小(单位字节)而不是数组大小(单位字节),正是因为函数的参数部分是本质是指针。

四,二级指针

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

答案是二级指针:

这里解释一下,图中的地址实际上可能并不是连续的地址,这里就是为了方便。

这就是二级指针,二级指针中存放的是一级指针的地址,可以同过二级指针修改一级指针的数据,进而控制a。

1.*ppa通过对ppa中的地址进行解引用,这样找到的是pa,*ppa其实访问的就是pa。

int b=20;
*ppa=&b;//等价于pa=&b

2.**ppa先通过*ppa找到pa,然后对pa进行解引用操作:*pa,那找到的是a。

**paa=30;
//等价于*pa=30
//等价于a=30

五,指针数组

指针数组是指针还是数组?

我们类比一下,整型数组,是存放整型的数组,字符数组是存放字符的数组。

指针数组就是存放指针的数组,所以指针数组其实是数组。

六,指针数组模拟二维数组

#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[] = { 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[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素。

上述的代码模拟出的二维数组并非完美的二维数组,因为每一个并非是连续的。

六,冒泡排序

这里讲一下冒泡排序,这是一个经典的排序方法,它的核心是两两相邻进行比较。

这里讲的冒泡排序后期会用指针的方式进行实现,所以这里尽量理解要不然后面会很难理解,我也会讲的详细一点。

#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-i-1 ; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
int main()
{
	int arr[] = { 3,1,4,2,6,5,8,9,7,0 };
	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;
}

这里要排序10个数两两比较就需要进行就要进行9次(sz-1),每一趟比较都会把最大的数排到相应的位置,剩余的数就会减少比较次数(sz-1-i)。如果遇到前一位比后一位大就交换,依次类推就可以完成排序。

下面给大家来个测试看看你能不能理解下面的代码改进方法:

#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])
			{
				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,4,2,6,5,8,9,7,0 };
	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,今天的分享就到此结束了,感谢你的阅读,也祝你知识越来越巩固,为正在努力前行的我们,加油祝贺。

  • 63
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值