1. 二维数组的概念

二维数组是相同数据类型元素按行和列排列的矩形集合,可以把二维数组写成行和列的排列形式来理解二维数组的逻辑结构。

站在一维数组的基础上理解二维数组,可以理解为:二维数组是把一维数组作为数组的元素。

2. 二维数组的创建和初始化

语法如下:

type arr_name[常量值1][常量值2];

创建示例:

int arr[2][3];

表示定义了一个2行3列的数组。

完全初始化
​
#include<stdio.h>
int main()
{
    //程序会根据2行3列的方式依次进行初始化
    int arr[2][3] = { 1,2,3,4,5,6 };
    return 0;
}

​
指定行和列进行初始化
​
#include<stdio.h>
int main()
{
    int arr1[2][3] = { {1,2,3},{4,5,6} };
    int arr2[2][3] = { {1,2},{4} };
    return 0;
}

​
不完全初始化
​
#include<stdio.h>
int main()
{
    int arr1[2][3] = { 1,2,3,4 };
    int arr2[2][3] = { 0 };
    return 0;
}

​
注意:

二维数组定义的时候可以省略行,不能省略列。(程序会根据列自动推导出行)

​
#include<stdio.h>
int main()
{
    int arr[][3] = { 1,2,3,4,5,6 };
    return 0;
}

​

3. 二维数组的使用

3.1 二维数组下标

数组arr[][3]={1,2,3,4,5,6}下标如下:

0 1 2
0 1 2 3
1 4 5 6

绿色即列下标,蓝色即行下标

定义该二维数组如下:

​
#include<stdio.h>
int main()
{
    int arr[2][3] = { 1,2,3,4,5,6 };
    return 0;
}

​
二维数组访问规则

二维数组的访问也是通过[]的方式进行访问,如上图所示如果要确定数据3的位置,我们可以通过行和列确定。行为0,列为2. 在C语言中确定方式为:数组名[行][列]的方式。

访问示例:
  • arr[0][2]: 对应数据为:3
  • arr[1][0]: 对应数据为:4
  • arr[1][2]: 对应数据为:6

3.2 通过数组下标访问元素

3.2.1 访问单独数据
​
#include<stdio.h>
int main()
{
    int arr[2][3] = { 1,2,3,4,5,6 };
    printf("%d\n",arr[0][2]);
    printf("%d\n",arr[1][0]);
    printf("%d\n",arr[1][2]);
    return 0;
}

​
3.2.2 访问数组当中的所有数据
​
​
#include<stdio.h>
int main()
{
    int arr[2][3]={1,2,3,4,5,6};
    for(int i=0;i<2;i++)
    {
         for(int j=0;j<3;j++)
         {
                printf("%d ",arr[i][j]);
         }
         printf("\n");
    }
    return 0;
}

​
3.2.3 给数组输入元素

4. 二维数组在内存当中的存储

二维数组的内存特性

数组本身就是一块连续的存储空间,所以无论是一维数组还是二维数组,我们通过打印地址可以发现内存确实是连续的空间。

​
#include<stdio.h>
int main()
{
    int arr[2][3] = {0};
    int row = 2;
    int col = 3;
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
        }
    }
    return 0;
}

​

输出结果(地址可能不同):
&arr[0][0] = 1827994312

&arr[0][1] = 1827994316

&arr[0][2] = 1827994320

&arr[1][0] = 1827994324

&arr[1][1] = 1827994328

&arr[1][2] = 1827994332

整型的二维数组,每个元素4个字节。所以各地址之间间隔为4。

在一开始的概念当中我们提到过,二维数组是把一维数组作为数组的元素。

可以类似理解二维数组里是把很多相同大小的一维数组分到不同的行数中。

例如我们把最开始的二维数组表格

0 1 2
0 1 2 3
1 4 5 6

可以理解成两个大小为3的两个连续的一维数组

【拓】字符数组:初始化方式决定内存布局

1. 字符列表式:char crr[] = {'a','b','c','d'};
   · 内存布局:精确存放 4 个字符:[a] [b] [c] [d]。
   · 输出特性:由于末尾没有自动添加字符串结束标志 \0,若直接使用 printf("%s", crr),系统将持续读取内存直至随机遇到 \0,可能导致输出内容包含尾随乱码。
   · 安全建议:若需以字符串形式输出,应指定数组大小并为 \0 预留位置,如 char crr[5] = {'a','b','c','d'};,此时未初始化的第 5 个元素默认为 \0。
2. 字符串常量式:char crr2[] = "abcd";
   · 内存布局:编译器自动在末尾追加结束符,实际存储为:[a] [b] [c] [d] [\0]。
   · 输出特性:%s 精确识别 \0 位置,输出行为符合预期,无越界风险。

5. 二维数组习题练习

1. 编写一个程序,实现一个3×3矩阵的转置操作。矩阵转置是指将矩阵的行和列互换,即原矩阵的第i行第j列元素变为转置矩阵的第j行第i列元素。

原始矩阵:
1 2 3
4 5 6
7 8 9

转置后的矩阵:
1 4 7
2 5 8
3 6 9

​

​
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int arr[3][3] = {
		{1,2,3},
		{4,5,6},
		{7,8,9}
	};
	printf("转置之前:\n");
	for (int i = 0; i <3 ; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d", arr[i][j]);
		}
        printf("\n");
	}
	printf("\n");
	for (int i = 0; i < 3; i++)
	{
		for (int j = i + 1; j < 3; j++)
		{
			int lc = arr[i][j];
			arr[i][j] = arr[j][i];
			arr[j][i] = lc;
		}
	}
	printf("转置之后: \n");
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d", arr[i][j]);
		}
         printf("\n");
	}
	printf("\n");
	return 0;
}

在这个矩阵转置的代码里,内层循环的  j  不从 0 开始,而是从  i+1  开始,是为了避免重复交换。
 
如果  j  从 0 开始,当  i  增大到一定程度时,就会把之前已经交换过的元素再交换回来,这样就做了无用功,还会破坏矩阵转置的结果。
 
举个例子:
 
- 当  i=0  时, j  从 1 开始,交换  matrix[0][1]  和  matrix[1][0] 。
- 当  i=1  时, j  从 2 开始,交换  matrix[1][2]  和  matrix[2][1] 。
- 这样就只对矩阵上三角( i < j )的元素进行一次交换,保证每个元素对只交换一次,完成矩阵转置。

6. C99中的变长数组

在C99标准之前,C语言在创建数组的时候,数组大小的指定只能使用常量、常量表达式,或者如果我们初始化数据的话,可以省略数组大小。

int arr1[10];
int arr2[3+5];
int arr3[] = {1,2,3};

这样的语法限制,让我们创建数组就不够灵活,有时候数组大了浪费空间,有时候数组又小了不够用的。

C99中给一个变长数组(variable-length array,简称 VLA)的新特性,允许我们可以使用变量指定数组大小。请看下面的代码:

int n = a + b;
int arr[n];

上面示例中,数组arr就是变长数组,因为它的长度取决于变量n的值,编译器没法事先确定,只有运行时才能知道n是多少。
变长数组的根本特征,就是数组长度只有运行时才能确定,所以变长数组不能初始化

变长数组的好处是:

  • 程序员不必在开发时,随意为数组指定一个估计的长度,程序可以在运行时为数组分配精确的长度。
  • 有一个比较迷惑的点,变长数组的意思是数组的大小是可以使用变量来指定的,在程序运行的时候,根据变量的大小来指定数组的元素个数,而不是说数组的大小是可变的。数组的大小一旦确定就不能再变化了。

在VS2022上,虽然支持大部分C99的语法,但没有支持C99中的变长数组,没法测试。
下面是在gcc编译器上测试的示例:

​
#include <stdio.h>
int main()
{
    int n = 0;
    scanf("%d", &n);//根据输入数值确定数组的大小
    int arr[n];
    int i = 0;
    for (i = 0; i < n; i++)
    {
        scanf("%d", &arr[i]);
    }
    for (i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

​
注意:

需要注意的是部分OJ题上是支持的,我们可以直接定义和使用。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐