初识C语言·指针(2)

目录

1 数组名的理解

2 指针访问数组

3 一维数组传参的本质

4 冒泡排序

5 二级指针

6  指针数组

7 指针数组模拟实现二维数组


1 数组名的理解

上一篇文章提及,数组名是首元素的地址,可是,不管什么情况都是这样的吗?

先看一串代码。

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

打印地址的时候,数组名确实是首元素地址,结果一样。

那么既然数组名是首元素地址,是不是说sizeof(arr)的大小是4 / 8呢?

结果是整个数组的大小,那么就说明了sizeof()里面的数组名不是首元素地址的含义,代表的是整个数组。

数组名是首元素地址的说法也是对的,只是有两个特例

一个是sizeof(数组名),代表的是整个数组的大小。

一个是&数组名,代表取出整个数组的地址。

需要区分的是,取出整个数组的地址与取出首元素地址是不一样的,具体差别看这段代码。

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

可以看到,打印arr和&arr的时候是没有区别的,因为打印的都是首元素地址,但是进行指针+1的运算之后,arr  + 1打印出来的是第二个元素的地址,而&arr + 1打印的是数组最后一个元素的地址的后一块空间,arr + 1是跳过了一个整型,&arr + 1是跳过了整个数组,这就是取出了首元素地址和取出了整个数组地址的区别。

总结一下,数组名不是首元素地址的情况只有两种,遇到sizeof(数组名)和&数组名 的时候,这两个情况应该多加注意。


2 指针访问数组

有了前面知识的铺垫,我们就可以通过指针来访问数组了。比如实现输入,实现打印等操作。

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

p存首元素的地址,利用解引用操作,得以访问数组。

那么这里我就提问了,平常的写法是

printf("%d ",arr[i]);

arr是数组名,现在也不是两种特殊情况,那么可不可以理解为指针[i]呢?毕竟arr代表的就是首元素地址,那么我们可以写成p[i]吗?答案是肯定的。

注意一点就是这里就不需要解引用操作符了。

那么我又又又提问了,刚才的写法是*(p + i),可以代替为p[i],那我可以写成*(I + p)吗?肯定可以,那么我可以写成i[p]吗?试试。

哦吼,看来可以,那么i[arr]可以吗?当然也是可以的。

但是实际上,i[p],i[arr],都是比较少见的写法,我们权当了解一下就行了,平时还是写*(p + i),或者p[i],这两个写法是常见的写法。


3 一维数组传参的本质

实际上在扫雷的篇章的时候,我们就已经了解了一维数组传参传的是首元素地址,这里就不再多解释了,来两段代码,看看能不能找出差异,如果能,说明你就了解了。

void test(int arr[])
{
	for (int i = 0; i < sizeof(arr); i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	test(arr);
	return 0;
}
void test(int arr[],int sz)
{
	for (int 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]);
	test(arr,sz);
	return 0;
}

4 冒泡排序

了解了一维数组传参的本质,我们现在来了解一下冒泡排序。

冒泡嘛,泡都是一个一个冒的,排序也是一个一个排的,冒泡排序的本质思想就是,两两相邻的元素进行比较。从左到右依次比完为1趟,一趟下来就能确定某个元素的相对位置,冒泡要么就是升序排列要么就是降序排列,所以每趟下来能暂时确定相对位置。

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,7,5,8,9,0,2,4,6};
 int sz = sizeof(arr)/sizeof(arr[0]);
 bubble_sort(arr, sz);
for(i=0; i<sz; i++)
 {
 printf("%d ", arr[i]);
 }
 return 0;
}

我们写一个冒泡排序的函数,通过一维数组传参的方式,使用地址修改该数组,传过去的是首元素地址,所以我们选在主函数的位置把数组的元素个数传过去。

在第一个for循环里面,我们确定的是总共需要多少趟就能排列完成,假定一个数组有10个元素,我们只需要排列9次就可以,因为冒泡排序是两两比较的。

在第二个for循环里面,我们确定的是在该趟数下,需要比较多少次,可能你会好奇为什么是j < sz - 1 - i,实际上,你也可以不用选择- i,这样你比较的话就是老老实实的从第一个元素开始,每个都进行一次比较,但是实际上,如前面所说,每趟都能确定某些元素的相对位置,也就是说,随着趟数的增加,实际需要比较,换位置的元素是越来越少的。所以我们需要比较的次数就随之递减,因为每次能至少确定一个元素的相对位置,所以每次比较的次数 - 1,所以代码是j < sz - 1 -i。

if里面就是两个变量交换的步骤,不用过多介绍。

但是实际上,某些极端情况确实就需要满满的比较上9趟,大多数情况下,可能在第5趟就比较完了,可能第一趟就比较完了,甚至可能都不需要比较,那么我们如何优化呢?

优化的点就是,如何确定在一趟中有没有进入到交换变量的if里面去,所以这里有一个常用的方法,标志变量,这样使用的。

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-i-1; 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,7,5,8,9,0,2,4,6};
 int sz = sizeof(arr)/sizeof(arr[0]);
 bubble_sort(arr, sz);
 for(i=0; i<sz; i++)
 {
 printf("%d ", arr[i]);
 }
 return 0;
}

可以看到,我们多加了一个变量,flag,假定这一趟已经有序了,那么flag就不变,在判断flag的if里面就break,跳出循环了,如果进入的交换变量的循环,那么flag就改变,第二趟的时候flag的值重新变为1,进入第二趟的循环,判断。

当然,这里有个易错点就是flag的位置,容易放在最外层那个for循环的外面,这样flag就不能重新回到1了,即便某一趟有序,还是要老老实实的比较,就比较浪费时间。


5 二级指针

我们说一个变量,在内存中存储的时候就会有自己的“房间”,有自己的“门牌号”,那么指针变量也是变量,它也会有自己的地址,我们用另一个指针变量来存储这个指针变量,新的存储指针变量的指针变量就被称为二级指针。

int main()
{
	int num = 10;
	int* pa = &num;
	int** pb = &pa;
	return 0;
}

这里的pb就是二级指针,就会有人问了,会不会有三级指针,四级指针,以至于100级指针,当然是有点,但是实际生活中,三级指针都很少用到了,二级指针是比较常见的,所以包括三级往上,我们称为多级指针。

你看,二级指针有两个*,一级指针有一个*,所以不完全归纳法可以知道,多少级指针就有多少个*。

我们现在就先简单介绍一下二级指针的概念,具体使用放到后面介绍.。

这里来一段代码过过瘾就行

int main()
{
	int num = 10;
	const int* const p1 = &num;
	int** p2 = &p1;
	**p2 = 20;
	printf("%d ", num);
	return 0;
}

6 指针数组

首先提个问题,指针数组是数组还是指针。

想不清楚没关系,看这个。

int arr[10] = { 0 };

这是数组吧?是什么数组?整型数组吧?那你说指针数组是指针还是数组?

当然是数组了,只是里面存放的是指针而已。

那这个就厉害了,我们调用这个数组的时候,可以通过访问这个数组来使用里面存放的指针,这么说你可能不大明白,但是剧透一下,函数名也是地址,多的就不透露了,后面会提到,

先看一下指针数组,模拟实现二维数组吧。

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

先看看效果图。

我们定义三个整型数组,一个指针数组,指针数组里面存放的是三个整型数组的数组名,第一个for循环用来确定调用几次指针数组,第二个for循环用来确定调用几次整数数组的数组名。

可能唯一比较绕的点就是parr[i][j],不妨这样来看,parr[1]就是arr1,然后就是arr1[j],这样够明了的吧,当然这里用解引用操作的话也行,但是我认为没有方括号看着顺眼,

*(*(parr + i) + j),这就是解引用的写法,看起来可能比较绕,*(parr + i)就是arri,再解引用一下就是arri[j]了,不理解的话,再看几遍!


感谢阅读!

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值