【C语言】指针第二弹(指针数组、数组指针、数组传参)

一. 指针数组

指针数组就是存放指针变量的数组,指针数组的本质是数组,而非指针

1.1 定义和初始化

定义:int* arr[3]  //arr是存放整型指针的数组,包含3个元素

初始化:int* arr[3] = { &a, &b, &c },arr中的三个元素分别指向存储a、b、c三个整型变量的内存地址。

指针数组定义并初始化的通用格式:指针类型  数组名[数组包含元素的个数]

指针类型包括:int*、char*、double*、float* 、short* 等等

1.2 指针数组的应用

可通过对指针数组中的元素进行解应用操作,以此打印几个数组中的每个元素。如下代码所示,将整形数组a、b、c首元素地址放入整型数组指针(int* arr[3])中,再对指针数组arr中每个指针元素进行 +整数 运算 和解应用操作,依次打印a、b、c三个数组。

#include<stdio.h>
int main()
{
	//定义并初始化a、b、c三个数组
	int a[5] = { 1,2,3,4,5 };
	int b[5] = { 2,3,4,5,6 };
	int c[5] = { 3,4,5,6,7 };

	int* arr[3] = { a,b,c }; //将三个数组首元素地址存入指针数组arr中
	int i = 0;
	int j = 0;

	//外循环代表abc三个数组,内循环代表每个数组中的元素
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			//arr[1]、arr[2]、arr[3]分别代表数组abc首元素地址
			//arr[i]+j代表每个数组中各个元素的地址
			//*(arr[i]+j)可获得数组中的元素
			printf("%d ", *(arr[i] + j));
		}
		printf("\n");
	}
	return 0;
}

注:数组名在一般情况下表示数组首元素的地址,但在两种特殊情况下例外。

这两种特殊情况是:

(1)sizeof(数组名) —— 计算整个数组的大小

(2)&数组名 —— 取出整个数组的地址(对于整个数组的地址,本文第二节会讲到)

对于上段代码中的语句printf("%d ", *(arr[i] + j)),可以将*(arr[i] + j)写为 arr[i][j],以模拟二维数组的形式来打印每个数组中的元素。计算机识别arr[i][j]的底层逻辑就是*(arr[i] + j)。

对于二维数组,数组名也是数组首元素地址。但要注意与一维数组进行区分,二维数组的首元素是第一行的所有元素的集合(可以将二维数组的每一行理解为一个一维数组)。

二. 数组指针

数组指针存放一个数组的地址。本质为指针,而非数组。

2.1 数组指针的定义和初始化

int arr[10] = {1, 2, 3, 4, 5};
int (*parr)[10] = &arr; //数组指针,存放一个数组的地址

数组指针初始化通用格式: 数组元素类型  (*指针变量名) [数组包含元素个数]

注意:在定义数组指针变量时,*一定要和指针变量名一同用括号括起来,如(*parr)的形式,如果不加括号,则指针变量名会首先与后面进行结合,从而错误的将数组指针定义为指针数组。

2.2 对数组名和&数组名的区分

通过对数组的学习,我们了解到,数组名即为数组首元素的地址。如代码2.1所示,分别用%p的格式来打印arr和parr(parr为指向整型数组arr的数组指针),打印结果相同。那么,就引出问题,下面代码中的arr和&arr是等价的吗?答案显然是否定的。

代码2.1
#include<stdio.h>
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int(*parr)[5] = &arr;
	printf("%p\n", arr);
	printf("%p\n", parr); //两个printf打印结果相同
	return 0;
}

为了辨析arr和&arr的区别,我们对代码2.2进行分析,第二个printf的打印结果比第一个printf大4,第三个printf的打印结果比第一个大printf大20。由此可见,对于arr和&arr,走一步(+1)跨过的步长不同。arr跨过的步长由数组元素的类型来决定(数组元素类型占几个字节的空间arr+1跨过的步长就是多少),&arr跨过的步长由数组arr中的元素类型和其所包含的元素个数共同决定(&arr+1走过的步长为 sizeof(数组元素类型) * 数组元素个数)。在代码2.2中,数组arr中的元素类型为int型,一个int型数据占用4byte的内存空间,arr中包含四个整型元素,故&arr + 1 能跨过的步长为4 \times 4=16。图2.1为代码2.2的一次运行的结果。

代码2.2
#include<stdio.h>
int main()
{
	int arr[4] = { 1,2,3,4 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);  //打印结果比第一个printf大4
	printf("%p\n", &arr + 1); //打印结果比第一个printf大16
	return 0;
}
图2.1  代码2.2的一次运行结果

注意:代码2.1和2.2的每次运行结果会有所不同。这是因为在代码运行的过程中,由编译器将数据放入到内存中去,每次运行编译器在内存中的那个区域放置数据程序员是不可知的,每次运行放置的位置也会不一样。

2.3 数组指针的应用 

2.3.1 通过数组指针遍历一维数组中的每个元素。

代码演示:

代码2.3
#include<stdio.h>
int main()
{
	int arr[10] = { 10,9,8,7,6,5,4,3,2,1 }; //定义并初始化数组
	int(*parr)[10] = &arr; //定义指向数组arr的数组指针
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(*parr + i)); //打印10 9 8 7 6 5 4 3 2 1
	}
	return 0;
}

在代码2.3中,通过printf("%d ", *(*parr + i)) 命令来遍历打印数组中的每个元素。其中:对数组指针解应用(*parr)获得arr,即获得数组首元素地址,*(*parr + i)与*(arr + i)等价,通过解引用获得数组中的每个元素。

2.3.2 通过数组指针传参打印二维数组中的每个元素

代码演示:

代码 2.4
#include<stdio.h>
//定义二维数组打印函数Print
//函数返回类型为void,p是二维数组首元素的地址,row是二维数组的行数,col是列数
void Print(int(*p)[5], int row, int col)
{
	int i = 0;
	int j = 0;
    //外循环为行,内循环为列
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *((*p + i) + j));
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	Print(arr, 3, 5); // 调用二维数组打印函数
	return 0;
}

二维数组的数组名也代表数组首元素的地址,二维数组的首元素是数组第一行的元素,二维数组arr可以理解为几个一维数组组成的数组。在代码2.3中,,arr[1]、arr[2]、arr[3]可以分别理解为二维数组第一行、第二行、第三行对应一维数组的数组名。在函数调用时将二维数组数组名作为实参传递给函数,相当于给函数传了个数组指针。

三. 数组传参的方法

在设计函数时,难免会将数组和指针变量作为实参传递给函数,那么,该如何设计函数来确保传参正确呢?

3.1 一维数组传参

提出下面问题,在代码3.1中,哪种传参方式是可行的,那种是不可行的?

#include <stdio.h>
void test(int arr[])  //方法1
{}
void test(int arr[10])  //方法2
{}
void test(int *arr)  //方法3
{}
void test2(int *arr[20])  //方法4
{}
void test2(int **arr)  //方法5
{}
int main()
{
    int arr[10] = {0};
    int *arr2[20] = {0};
    test(arr);
    test2(arr2);
    return 0;
}

方法1、2、4、5是可行的,方法3不可行。 

解析:要求进行一维数组传参时,定义函数时形式参数有数组形式和指针形式两种写法。方法1和方法2将形式参数写为了数组形式,一维数组在定义时元素个数可以省略,故方法1和方法2均是可行的。方法3、4、5将形式参数写为指针形式,arr2是包含20个整型指针的指针数组,方法3传入整型指针变量,与main函数中arr2的类型冲突,不可行;方法4是直接传入指针数组,可行;方法5将形式参数写为二级指针的形式,可行,详解见图3.1。

图3.1 对于一维数组传参方法5的图解

3.2 二维数组传参 

首先还是先来看一组问题,代码3.2中,那种二维数组传参的方式可行,那种不可行?

void test(int arr[3][5]) //方法1
{}
void test(int arr[][]) //方法2
{}
void test(int arr[][5]) //方法3
{}

void test(int *arr) //方法4
{}
void test(int* arr[5]) //方法5
{}
void test(int (*arr)[5]) //方法6
{}
void test(int **arr) //方法7
{}
int main()
{
    int arr[3][5] = {0};
    test(arr);
    return 0;
}

方法1、3、6是可行的,方法2、4、5、7不可行。

解析:二维数组的数组名是首元素地址,二维数组首元素即第一行元素,因此,二维数组的数组名本质上的类型是数组指针。二维数组在定义时,可以省略行数,但不能省略列数,因此,方法1、3正确,方法2错误。对于方法4,形参arr是整型指针,与主函数中arr的类型相冲突,因此方法4不可行;对于方法5,arr是指针数组,而非数组指针,与主函数中arr类型冲突,不可行;对于方法6,前面提到过二维数组的数组名本质上就是一个数组指针,将形参写为数组指针的形式,显然方法6是可行的;对于方法7,二级指针与二维数组毫无关联,不可行。

3.3 总结

一维数组传参的几种正确形式:

<1>  test(int arr[5]) —— 直接传入数组并给出数组中含有几个元素

<2>  test(int arr[ ]) —— 直接传入数组,省略数组中含有的元素个数

<3>  test(int* arr) —— 形参写为指针形式,传入数组首元素地址

二维数组传参的几种正确形式:

<1> test(int arr[3][5]) —— 直接传入二维数组,行数和列数均不省略

<2> test(int arr[ ][5]) —— 直接传入二维数组,省略行数,不省略列数

<3> test(int (*p)[5]) —— 以数组指针的形式传参

二维数组传参的几种典型错误形式:

<1> test(int arr[ ][ ]) —— 将二维数组的列数省略,二维数组列数不可省略

<2> test(int* arr[5]) —— 传入指针数组

<3> test(int **arr) —— 传入二级指针

全文结束,感谢大家的阅读,敬请批评指正。 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值