二维数组以及C99中的变长数组(如何在VS2022中使用苹果的clang编译器)
一、二维数组的创建
1.1 二维数组的概念
在上一篇文章中所写的称为一维数组,数组的元素都是内置类型的,如果我们把一维数组作为数组的元素,这时候就是二维数组,二维数组作为数组元素的数组被称为三维数组,二维数组以上的数组统称为多维数组。
为了更好地理解二维数组,我这里举一个生活中的例子,一个班有5个人,你要储存3个班的成绩,看下图第三个表:
第一行第一个班5人,依次下推,一维数组可以存一个班级的数据,二维数组就可以存多个班级的数据了。
1.2 二维数组的创建
那么我们如何定义二维数组呢?语法如下:
1 type arr_name[常量值1][常量值2];
2
3 例如
4 int arr[3][5];
5 double data[2][8];
上述代码出现的信息:
- 3表示数组有3行
- 5表示每一行有5个元素
- int 表示数组的每个元素是整型类型
- arr 是数组名,可以根据自己的需要指定名字
data数组意思基本一致。
二、二维数组的初始化
在创建变量或者数组的时候,给定一些初始值,被称为初始化。
那么二维数组如何初始化的呢?
二维数组的初始化也像一维数组一样,是使用大括号初始化的。
2.1 不完全初始化
1 int arr1[3][5]={1,2};
2 int arr2[3][5]={0};
这里3行5列15个元素,结果我只放1,2。这没放满呀,就是不完全初始化。
这里的arr1的意思就是1,2,放到第一行前两个元素,后面的元素全部置0,arr2相同的原理,这里附监视内容:
2.2 完全初始化
这里我们再看完全初始化
2.3 按行初始化
1 int arr4[3][5] = { {1,2},{3,4},{5,6} };
在这里的arr4这串代码中,正常来说,它的初始化方式应该是1,2,3,4,5放到第一行,6放到第二行第一个位置。
有的兄弟就说了,主播主播,我有强迫症,想让他雨露均沾平均分配可以吗,当然是可以的,当我们在{ }中,再使用{ }将想要均分的数据括起来后,就可以实现了:
2.4 初始化时省略行,但是不能省略列
之前不是说了一维数组只要初始化了就可以省略掉[ ]内数字的大小,数组的大小会根据初始化的内容来判断,那么二维数组可以吗?
二维数组也是可以的,只不过略有不同,那就是二维数组初始化时只能省略行,但是不能省略列。
接下来讲完二维数组在内存中的存储方式,这里会再加以解释。
三、二维数组的使用
3.1 二维数组的下标
二维数组访问也是使用下标的形式的,二维数组是有行和列的,只要锁定了行和列就能唯一锁定数组中的一个元素。
C语言规定,二维数组的行是从0开始的,列也是从0开始的,这里我们用i代表行,用j代表列
图中最左侧绿色的数字表示行号,第一行蓝色的数字表示列号,都是从0开始的,比如,如果想要7,就说第二行第四列。
3.2 二维数组的输入和输出
访问二维数组的单个元素已经知道了,那如何访问整个二维数组呢?
其实我们只要能够按照一定的规律产生所有的行和列的数字就可以了,以下面这串代码为例:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{int arr[3][5] = { 0 };//给数组输入值int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){scanf("%d", &arr[i][j]);}}//把数组的内容打印出来for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[i][j]);}printf("\n");}return 0;
}
这里\n的意思是每一行打印出来换行,这里也可以换行输入,原因涉及到scanf,可以看我之前数据变量那一块的文章。
这是换行输入的结果,是不是和不换行打印出来的效果是一样的呢?
那我们能不能按列打印呢?
当然是可以的,话不多说,展示代码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{int arr[3][5] = { 0 };//给数组输入值int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){scanf("%d", &arr[i][j]);}}//把数组的内容打印出来 - 按照列打印for (i = 0; i < 5; i++){int j = 0;for (j = 0; j < 3; j++){printf("%d ", arr[j][i]);}printf("\n");}return 0;
}
上面的代码展示的效果便是按行输入按列打印,简单的把第二个for循环里的条件互换一下就可以了。
当然也可以按列输入按列打印,这里就不作展示了。
四、二维数组在内存中的存储
像一维数组一样,我们如果想研究二维数组在内存中的存储方式,我们也可以打印出数组所有元素的地址,代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{int arr[3][5] = { 0 };//给数组输入值int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);}}return 0;
}
这里稍作解释:
地址打印用%p,这里&是地址打印的时候进行取地址。
printf这行代码的意思是i行j列的下标i,j的地址。
我们进行运行,依旧使用x86的环境,因为打印出来的地址内容更简洁,不懂可以看一维数组那一块:
从输出结果来看,每一行内部的每个元素都是相邻的,地址之间相差4个字节,跨行位置处的两个元素(如:arr[0][4]和arr[1][0]之间也差4个字节,足以说明二维数组中每个元素都是连续从放的。
这里为什么说相差四个字节呢?请看图:
这是抄我上个文章的内容的哈哈,实在懒了,十六进制逢16进1,自己算一下就懂了,这里再附加两张计算图吧:
第一个表是我们以为的二维数组在内存中的存放形式,第二个表则是真正的存放形式,即连续存放,就算不同行之间也是连续的。
这里我们就具体说一下二维数组能省略行,不能省略列的具体原因
int arr6[][5] = { 1,2,3,4,5,6,7 };
这里的这个[5]是非常重要的,因为你知道一行是5个元素,所以1,2,3,4,5就必然是第一行,这样6,7就必然在第二行,因为他们是连续存放的,这也是我放到这里解释的原因,只有知道一行有几个元素的时候,这个6才知道从哪开始放。
根据前面对二维数组的讲解,我们是不是可以这样理解:
图中每一个红放开括起来的内容都可以看成一个一维数组,所以这里就可以这么说,二维数组其实是一维数组的数组(二维数组的每个元素可以看成个一维数组)
假设这是一个一维数组的话,这个数组的数组名叫什么?
这里可以称为arr[0],我们在访问它的第一行的每个元素的时候,我们都是arr[0][j],j为一个数字,j的取值范围0-4。每个元素通过[j]定位,那我们就可以认为arr[0]就是这一行的数组名,因为数组名加小标得以访问这个数组的每个元素嘛,后面的也依次类推,整个二维数组的数组名是arr,这里讲了每一行的数组名。
五、C99中的变长数组
在之前数组的创建中我们可以得知,无论是一维数组还是二维数组,数组在创建的时候[ ]中都要用常量值,那么能不能用变量值呢?
可以看到是不可以的,系统会产生报错。
其实在C99标准之前,C语言在创建数组的时候,数组大小的指定只能使用常量、常量表达式,或者如果我们初始化数据的话,可以省略数组大小。
这样的语法限制,就让创建数组不够灵活了,有时候数组大浪费空间,有时候数组笑了又不够用。
C99中给一个变长数组的新特性,允许我们可以使用变量指定数组大小,但是呢,通过上面的代码,大家发现VS上是不能这样玩的呀,其实真正的原因是VS2022默认使用的msvc这个编译器,msvc不支持C99中变长数组,gcc是支持的,苹果的clang也是支持的,而VS2022中可以使用clang,这里教大家如何使用:
首先在电脑上搜索这样一个程序
等内容加载出来后点击修改
把这里勾选上,然后点击修改
等程序安装好
安装完成后,正常的创建项目,想要让项目里的代码支持C99的变量数组,我们点中项目名称,右击鼠标,点击属性。
然后配置属性-常规-平台工具集,点击平台工具集右边的Visual Studio 2022改为LLVM(clang-cl),之后点击应用,确定就可以了这时候再编译变量数组就不会出现问题了
接下来我们用代码实践:
这样是不是就可以使用变量数组了呢
上面示例中,数组arr就是变长数组,因为它的长度取决于变量n的值,编译器没法事先确定,只有运行时才知道n是多少。
变长数组的根本特征,就是数组长度只有运行时才能确定,所以变长数组不能初始化。它的好处是程序员不必在开发时,随意为数组指定一个估计的长度,程序可以在运行时为数组分配精确的长度。有一个比较迷惑的点,变长数组的意思是数组的大小是可以使用变量来指定的,在程序运行的时候,根据变量的大小来指定数组的元素个数,而不是说数组的大小是可变的。数组的大小一旦确定就不能再变化了。
当然变长数组当然也不是完美的,毕竟不能想改就改,一旦输入确定的数组长度就不能再做更改了,后面我会写关于动态内存管理的问题,动态内存管理就可以做到内存大小的调整。
好了今天的技术分享就到这里了,本来是下周一要考试的,但是因为运动会放了3天假,左右权衡了一下还是觉得可以写的哈哈,明后天主播就要好好复习了,喜欢作者的文章不要忘记一键三连呀!