第二部分—C语言提高篇_6. 多维数组

本文详细介绍了C语言中一维数组和多维数组的概念,包括数组名的性质、作为函数参数的处理,以及多维数组的表示和指针的使用。强调了数组名在表达式中等同于指向首元素的常量指针,讨论了数组与指针的区别,并指出在函数参数中数组实际传递的是指针。此外,文章还探讨了二维数组的三种参数形式。
摘要由CSDN通过智能技术生成

6.1 一维数组

  1. 元素类型角度:数组是相同类型的变量的有序集合
  2. 内存角度:连续的一大片内存空间

 

 在讨论多维数组之前,我们还需要学习很多关于一维数组的知识。首先让我们学习一个概念。

 6.1.1 数组名

 考虑下面这些声明:

int a;

int b[10];

我们把a称作标量,因为它是个单一的值,这个变量是的类型是一个整数。我们把b称作数组,因为它是一些值的集合。b[0]表示数组b的第1个值,b[4]表示第5个值。每个值都是一个特定的标量。

那么问题是b的类型是什么?它所表示的又是什么?一个合乎逻辑的答案是它表示整个数组,但事实并非如此。在C中,在几乎所有数组名的表达式中,数组名的值是一个指针常量也就是数组第一个元素的地址。它的类型取决于数组元素的类型:如果他们是int类型,那么数组名的类型就是“指向int的常量指针”;如果它们是其他类型,那么数组名的类型也就是“指向其他类型的常量指针”。

请问:指针和数组是等价的吗?

答案是否定的。数组名在表达式中使用的时候,编译器才会产生一个指针常量。那么数组在什么情况下不能作为指针常量呢?在以下两种场景下:

  1. 当数组名作为sizeof操作符的操作数的时候,此时sizeof返回的是整个数组的长度,而不是指针数组指针的长度。
  2. 当数组名作为&操作符的操作数的时候,此时返回的是一个指向数组的指针,而不是指向某个数组元素的指针常量。
int arr[10];
//arr = NULL;                                         //arr作为指针常量,不可修改
int *p = arr;                                         //此时arr作为指针常量来使用
printf("sizeof(arr):%d\n", sizeof(arr));              //此时sizeof结果为整个数组的长度

输出结果

sizeof(arr):40

6.1.2 下标引用

int arr[] = { 1, 2, 3, 4, 5, 6 };

*(arr + 3) ,这个表达式是什么意思呢?

首先,我们说数组在表达式中是一个指向整型的指针,所以此表达式表示arr指针向后移动了3个元素的长度。然后通过间接访问操作符从这个新地址开始获取这个位置的值。这个和下标的引用的执行过程完全相同。所以如下表达式是等同的:

*(arr + 3)

arr[3]

问题数组下标可否为负值?请阅读如下代码,说出结果:

int arr[] = { 5, 3, 6, 8, 2, 9 };
int *p = arr + 2;
printf("*p = %d\n", *p);
printf("*p = %d\n", p[-1]);

输出结果

*p = 6
*p = 3

故可以为负数。

6.1.3 数组和指针

指针和数组并不是相等的。为了说明这个概念,请考虑下面两个声明:

int a[10];

int *b;

声明一个数组时,编译器根据声明所指定的元素数量为数组分配内存空间,然后再创建数组名,指向这段空间的起始位置。声明一个指针变量的时候,编译器只为指针本身分配内存空间,并不为任何整型值分配内存空间,指针并未初始化指向任何现有的内存空间。

因此,表达式*a是完全合法的,但是表达式*b却是非法的。*b将访问内存中一个不确定的位置,将会导致程序终止。另一方面b++可以通过编译,a++却不行,因为a是一个常量值。

6.1.4 作为函数参数的数组名

当一个数组名作为一个参数传递给一个函数的时候发生什么情况呢?我们现在知道数组名其实就是一个指向数组第1个元素的指针,所以很明白此时传递给函数的是一份指针的拷贝。所以函数的形参实际上是一个指针。但是为了使程序员新手容易上手一些,编译器也接受数组形式的函数形参。因此下面两种函数原型是相等的:

int print_array(int *arr);

int print_array(int arr[]);

我们可以使用任何一种声明,但哪一个更准确一些呢?答案是指针。因为实参实际上是个指针,而不是数组。同样sizeof arr值是指针的长度,而不是数组的长度。

现在我们清楚了,为什么一维数组中无须写明它的元素数目了,因为形参只是一个指针,并不需要为数组参数分配内存。另一方面,这种方式使得函数无法知道数组的长度。如果函数需要知道数组的长度,它必须显式传递一个长度参数给函数。

6.2 多维数组

如果某个数组的维数不止1个,它就被称为多维数组。接下来的案例讲解以二维数组举例。

void test01()
{
	//二维数组初始化
	int arr1[3][3] = 
    {
		{ 1, 2, 3 },
		{ 4, 5, 6 },
		{ 7, 8, 9 }
	};
	int arr2[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int arr3[][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	//打印二维数组
	for (int i = 0; i < 3; i++)
    {
		for (int j = 0; j < 3; j ++)
        {
			printf("%d ",arr1[i][j]);
		}
		printf("\n");
	}
}

输出结果

1 2 3
4 5 6
7 8 9

6.2.1 数组名

一维数组名的值是一个指针常量,它的类型是“指向元素类型的指针”,它指向数组的第1个元素。多维数组也是同理,多维数组的数组名也是指向第一个元素,只不过第一个元素是一个数组。例如:

int arr[3][10]

可以理解为这是一个一维数组,包含了3个元素,只是每个元素恰好是包含了10个元素的数组。arr就表示指向它的第1个元素的指针,所以arr是一个指向了包含了10个整型元素的数组的指针。

6.2.2 指向数组的指针(数组指针)

数组指针,它是指针,指向数组的指针。

数组的类型由元素类型数组大小共同决定:int array[5]  的类型为  int[5];C语言可通过typedef定义一个数组类型:

定义数组指针有一下三种方式:

//方式一
void test01()
{
	//先定义数组类型,再用数组类型定义数组指针
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	//有typedef是定义类型,没有则是定义变量,下面代码定义了一个数组类型ArrayType
	typedef int(ArrayType)[10];     //int ArrayType[10],定义一个数组,数组名为ArrayType	
	ArrayType myarr;                //等价于 int myarr[10];
	ArrayType* pArr = &arr;         //定义了一个数组指针pArr,并且指针指向数组arr
	for (int i = 0; i < 10;i++)
    {
		printf("%d ",(*pArr)[i]);
	}
	printf("\n");
}


//方式二
void test02()
    {
	int arr[10];
	typedef int(*ArrayType)[10];         //定义数组指针类型
	ArrayType pArr = &arr;               //定义了一个数组指针pArr,并且指针指向数组arr
	for (int i = 0; i < 10; i++)
    {
		(*pArr)[i] = i;
	}
	for (int i = 0; i < 10; i++)
    {
		printf("%d ", (*pArr)[i]);
	}
	printf("\n");
}


//方式三
void test03()
{
	int arr[10];
	int(*pArr)[10] = &arr;
	for (int i = 0; i < 10; i++)
    {
		(*pArr)[i] = i;
	}
	for (int i = 0; i < 10; i++)
    {
		printf("%d ", (*pArr)[i]);
	}
	printf("\n");
}

 6.2.3 指针数组(元素为指针)

6.2.3.1 栈区指针数组 

//打印数组
void array_print(char** arr, int len)
{
	for (int i = 0; i < len; i++)
	{
		printf("%s\n", arr[i]);
	}
	printf("----------------------\n");
}

void test()
{
	//主调函数分配内存
	char* p[] = { "bbb", "aaa", "ccc", "eee", "ddd" };	//指针数组
	int len = sizeof(p) / sizeof(char*);
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(char*));
	printf("%d\n", len);
	//打印数组
	array_print(p, len);
	//对字符串进行排序
	array_sort(p, len);
	//打印数组
	array_print(p, len);
}

输出结果

20
4
5
bbb
aaa
ccc
eee
ddd
----------------------
aaa
bbb
ccc
ddd
eee
----------------------

6.2.3.2 堆区指针数组

//分配内存
char** allocate_memory(int n)
{
	if (n < 0 )
    {
		return NULL;
	}
	char** temp = (char**)malloc(sizeof(char*) * n);
	if (temp == NULL)
    {
		return NULL;
	}
	//分别给每一个指针malloc分配内存
	for (int i = 0; i < n; i ++)
    {
		temp[i] = malloc(sizeof(char)* 30);
		sprintf(temp[i], "%2d_hello world!", i + 1);
	}
	return temp;
}

//打印数组
void array_print(char** arr,int len)
{
	for (int i = 0; i < len;i++)
    {
		printf("%s\n",arr[i]);
	}
	printf("----------------------\n");
}

//释放内存
void free_memory(char** buf,int len)
{
	if (buf == NULL)
    {
		return;
	}
	for (int i = 0; i < len; i ++)
    {
		free(buf[i]);
		buf[i] = NULL;
	}
	free(buf);
}

void test()
{
	int n = 10;
	char** p = allocate_memory(n);
	//打印数组
	array_print(p, n);
	//释放内存
	free_memory(p, n);
}

输出结果

 1_hello world!
 2_hello world!
 3_hello world!
 4_hello world!
 5_hello world!
 6_hello world!
 7_hello world!
 8_hello world!
 9_hello world!
10_hello world!
----------------------

6.2.4二维数组三种参数形式

6.2.4.1 二维数组的线性存储特性

void PrintArray(int* arr, int len)
{
	for (int i = 0; i < len; i++)
    {
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//二维数组的线性存储
void test()
{
	int arr[][3] = 
    {
		{ 1, 2, 3 },
		{ 4, 5, 6 },
		{ 7, 8, 9 }
	};

	int len = sizeof(arr) / sizeof(int);
	//如何证明二维数组是线性的?
	//通过将数组首地址指针转成Int*类型,那么步长就变成了4,就可以遍历整个数组
	int* p = (int*)arr;
	for (int i = 0; i < len; i++)
    {
		printf("%d ", p[i]);
	}
	printf("\n");
	PrintArray((int*)arr, len);
}

输出结果

1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9

6.2.4.2 二维数组的3种形式参数

//二维数组的第一种形式
void PrintArray01(int arr[3][3])
{
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	printf("------------\n");
}


//二维数组的第二种形式
void PrintArray02(int arr[][3])
{
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	printf("------------\n");
}

//二维数组的第三种形式
void PrintArray03(int(*arr)[3])      //二维数组传过来的地址是指向数组第一行的地址
{
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	printf("------------\n");
}

void test()
{
	int arr[][3] =
	{
		{ 1, 2, 3 },
		{ 4, 5, 6 },
		{ 7, 8, 9 }
	};
	PrintArray01(arr);
	PrintArray02(arr);
	PrintArray03(arr);
}

输出结果

1 2 3
4 5 6
7 8 9
------------
1 2 3
4 5 6
7 8 9
------------
1 2 3
4 5 6
7 8 9
------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值