C语言指针进阶内容讲解-成长路上必看


前言: 本文内容相较于初学者可能会有些难度,适合已经对C语言指针有过一点了解,或者指针部分基础薄弱的老铁们学习!耐心看完一定会有所收获的。

1,指针数组

关于指针数组,我们首先一定要先认清它的含义是什么?从名字中不难得出,指针数组,是一个存放指针的数组,指针是这个数组的类型,就像整形数组和字符数组一样

int arr[10];-—》整形数组
char ch[20];—》字符数组

那么既然指针数组也是数组,那它也一定有自己的命名格式

int* arr[10];
char* ch[10];

指针类型加上数组名就是指针数组的命名格式,arr是数组名,由于[]的优先级比* 高,所以arr先和[]组成一个数组,数组的类型是int *
我们参考数组,数组有一维和二维之分,那么指针数组也有一级和二级,甚至三级之分

int** arr[10];—》二级指针数组

没错,在一级的基础上多加一个 * 便是二级指针的命名
那么C语言中指针数组是怎么使用的呢?

#include <stdio.h>

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

	int* p[5] = { arr, arr1, arr2, arr3, arr4};

	int i = 0;
	for (i = 0; i < 5; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("%d ", *(*(p + i) + j));
			//*(p+i)找到是指针数组的元素,再加j找到的是对应数组的元素
			//*(*(p + i) + j)再进行解引用找到对应数组的元素
		}
		printf("\n");
	}
	return 0;
}

当我们要使用多个数组时,一个一个的进行打印的话,代码会很臃肿,代码量也会增多,这时创建一个指针数组,利用指针数组去找到对应的数组会提高代码的灵活性,也会方便日后的修改
运行结果:
在这里插入图片描述

2,数组指针

2.1数组指针的定义

数组指针的概念也是从名字就可以得知,是一个指向数组的指针,大家一定不要和指针数组搞混了

数组指针指向数组的一个指针
指针数组存放指针的一个数组

他俩的本质区别还是很大的,我们先来回归一个指针

指向int类型的指针是int *p;
指向char类型的指针是char *p;
指向floar类型的指针是float* p;

那么同样的,指向数组的指针如下:

int (*p)[10];
char (*p)[10]

p是数组名,由于()的优先级最高,所以p先和 * 结合组成一个指针,指针指向的内容是int [10]类型的数组。

为了防止大家搞混指针数组和数组指针的概念,这里我们再次将它们俩放在一起看一下

指针数组:int* arr[10];
	是一个大小为10的数组,数组存放int*的指针
数组指针:int (*p)[10];
	是一个指针,指向的是一个大小为10的int类型的数组

2.2数组名详解

我们通过一段代码来观察数组名

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

	printf("arr:   %p\n", arr);
	printf("arr+1: %p\n", arr+1);
	printf("&arr:  %p\n", &arr);
	printf("&arr+1:%p\n", &arr+1);

	printf("P:     %p\n", p);
	printf("P+1:   %p\n", p+1);

	return 0;
}

运行结果
在这里插入图片描述
我们知道,数组名是首元素的地址,所以arr+1跳过4个字节(int类型的字节大小是4)指向数组中的第二个元素。
通过运行结果来看&arr与arr的地址是相同的,但是加一之后的结果却是不同的,从004FF89C到004FF8C4,增加了10,即数组的大小,所以虽然arr和&arr的地址相同,但是本质却是不同的

&arr表示的是整个数组的地址
arr表示的是数组首元素的地址

那么我们再看,p和p+1的结果与&arr和&arr+1的结果是相同的,这也证明了,数组指针指向的是数组的地址,并非首元素的地址
了解完以上内容之后,我们再来看一下,关于数组指针到底是怎么使用的?

2.3数组指针的使用

数组指针的使用,多用在二维数组传参时,当然一维数组传参时,也是可以使用的,我们先从一维数组开始讲起

void print(int (*p)[5], int sz)//使用数组指针来接收
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", (*p)[i]);
	}
}
//打印数组内容
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	
	print(arr, 5);

	return 0;
}

这里我们不再使用数组或者指针作为形参,而是改用我们刚学的数组指针。
这里int (*p)[5]中的5可有可无,不过最好还是写上。

然后就是在打印数组内容的时候要注意一点的是,要先对p解引用(*p)之后再通[]找到对应的元素下标,千万不能写成 *(p+i),因为数组指针指向的是数组的地址,而不是首元素地址,这样写将造成不可预料的结果
如果在上面的代码中想要不使用[]也是可以的,应该写成

printf("%d ", *((*p) + i));

先对p解引用找到数组,然后再加上i,再解引用找到对应的元素,输出的结果也是一样的

接下来我们再来看一段二维数组的代码

#include <stdio.h>

void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			//printf("%d ", arr[i][j]);
			printf("%d ", *(*(arr + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr1(arr, 3, 5);
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收
	print_arr2(arr, 3, 5);
	return 0;
}

print_arr1是使用同等大小的二维数组作为形参接受,没什么好说的,唯一值得注意的一点是,二维数组作为形参时,列的大小是不能省略的也就是

void print_arr1(int arr[3][5], int row, int col)
行(3)可以省略,但是列(5不能省略)

这和二维数组的创建是一个道理,编译器可以通过你有几列来自动确定行数,但是不能不知道列数

print_arr2便是使用了一个数组指针作为形参

void print_arr2(int(*arr)[5], int row, int col)
arr是数组指针变量名,指向的是一个大小为10的int类型的数组

要注意的是,数组指针在指向一个二维数组的时候,指向的其实是第一行的地址,并非第一个元素的地址
在这里插入图片描述
所以我们在接下来打印的时候,就要格外注意一下

printf("%d ", arr[i][j]);基础写法--没问题

我们来讲一下这个写法

printf("%d ", *(*(arr + i) + j));

首先*(arr+i)找到对应的行数,之后再再加上列(j)找到对应元素的地址,之后再解引用找对应的元素。
知道之后逻辑就是很简单的了

3,数组参数&指针参数

在写代码的时候,难免会将【数组】或者【指针】作为实参传给函数,那么函数的参数该如何设计呢?

3.1一维数组传参

先来看一下一维数组的情况
我们来判断一下test函数的形参设计的是否合理

#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int* arr)//ok?
{}
int main()
{
	int arr[10] = { 0 };
	test(arr);
	return 0;
}

arr是一个大小为10的一维数组
所以函数的形参同样设计成一维数组时肯定可以,形参arr的大小可有可无。
一维数组使用一级指针也是ok的,本身test(arr)传的就是函数的地址,使用一级指针接受,没毛病。

void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
int main()
{
	int* arr2[20] = { 0 };
	test2(arr2);
	return 0;
}

这里创建的是一个大小为20的一级指针数组。
同样使用一个大小相同的一级指针数组来接受,肯定也是没问题的,道理和使用数组来接受数组一样。
那么使用

void test2(int** arr)

是否也行呢?
不难看出int** arr是一个二级的指针,test2传过去的是一个一级指针,那么使用一个二级指针来接受也是可以的
在这里插入图片描述

3.2二维数组传参

接下来看一下二维数组的情况

void test(int arr[3][5])//ok?1
{}
void test(int arr[][])//ok?2
{}
void test(int arr[][5])//ok?3
{}
void test(int* arr)//ok?4
{}
void test(int* arr[5])//ok?5
{}
void test(int(*arr)[5])//ok?6
{}
void test(int** arr)//ok?7
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

第一个test和第三个test肯定是可以的没问题,第二个test因为缺少列数,所以是错误的。

第四个test函数参数是一个int类型的一级指针,可以使用一级指针来接受二维数组的地址吗?
并不可以,因为我们知道,二维数组在传参的时候,数组名代表的是一行的地址(一个一维数组的地址),你用一个int类型的指针来接受一个数组肯定是行不通的

第五个test是个指针数组,指针数组存放的是指针,而我们二维数组名代表的是一个一维数组的地址,所以这种写法肯定也是不行,类型不匹配!

第六个test参数是数组指针,我们知道二维数组其实可以看成是由多个一维数组构成的,那么这里的arr就可以看成是由三个一维数组,每个数组有五个元素构成的二维数组,而二维数组名代表的就是第一个数组的地址,所以使用数组指针作为函数参数也是可以的。

第七个test的形参是一个二级指针,首先我们要搞清楚二级指针是用来存放一级指针的地址的,而我们test传过去的是一个二维数组名(一个一维数组的地址),那么肯定也是会发生类型不匹配。
所以在使用指针接收二维数组时,只有第六个test是可以的,其他都不行。

3.3一级指针传参

了解完了一维数组和二维数组传参,我们现在来看一下
一级指针和二级指针传参的情况
#include <stdio.h>
void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

这里我们在设计函数的参数时,一级指针传参就还是用一级指针来接收一级指针就可以了, 代码不会有任何毛病,然后遍历整个数组就可以将数组的内容全部打印出来。但是有一个很值得思考的问题

当我们函数形参是一级指针的时候,函数的实参可以是什么?
假设我们设计了一个函数如下:

int test(int* pt);

大家可以思考几分钟…
该函数的实参可以是一下几种类型:
1.一个整形变量的地址

 int a = 0;
 test(&a);

2.一个一级指针(如上面代码所示)
3.一个数组名

int arr[10];
test(arr);

数组名其实是首元素的地址,所以我们用一个一级的指针来接受也是可以的

3.4二级指针传参

同样的,当二级指针传参的时候,也用二级指针来接收就好了

#include <stdio.h>
void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
	return 0;
}

二级指针用来存放一级指针的地址,所以test(&p)也是没问题的

同样的一个问题:当函数的形参设计成二级指针时,函数的形参可以是什么呢?

首先二级指针和一级指针的地址肯定是没问题的(见上面代码区)
那么还有什么呢?
我们知道二级指针可以用来存放一级指针的地址,那联合我们上面讲过的指针数组,我们说指针数组是用来存放指针的数组,那么数组名便是首元素(第一个指针)的地址,所以形参还可以是同类型的指针数组名,如下图:
在这里插入图片描述

4,函数指针

因为内容太多,所以有关函数指针的内容,后期我会单出一篇博客来讲解,见谅!

希望这边文章能给大家带来帮助,欢迎大家评论留言Bey!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南山忆874

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值