目录
- 一、何为数组?
- 二、一维数组
-
- [1. 一维数组的创建和初始化](#1. 一维数组的创建和初始化)
-
- [1.1 字符数组的初始化](#1.1 字符数组的初始化)
- [2. 一维数组的使用](#2. 一维数组的使用)
-
- [2.1 使用下标打印数组元素](#2.1 使用下标打印数组元素)
- [2.2 使用下标输入数组元素](#2.2 使用下标输入数组元素)
- [3. 一维数组在内存中的存储](#3. 一维数组在内存中的存储)
- [4. 使用 sizeof 计算数组元素个数](#4. 使用 sizeof 计算数组元素个数)
- 三、二维数组
-
- [1. 二维数组的创建](#1. 二维数组的创建)
- [2. 二维数组的初始化](#2. 二维数组的初始化)
- [3. 二维数组的使用](#3. 二维数组的使用)
- [4. 二维数组在内存中的存储](#4. 二维数组在内存中的存储)
- 四、数组名的含义
-
- [1. 从函数传参解释数组名的含义](#1. 从函数传参解释数组名的含义)
一、何为数组?
在 C 语言中,数组是一组具有相同数据类型的元素的集合。数组是在已有的类型的基础上创建的复合类型,已有的类型可以是 C 语言内置的,也可以是自定义的类型。
二、一维数组
1. 一维数组的创建和初始化
(1)数组的创建
创建数组需要数组的大小和数组的类型,一般格式如下:类型 数组名[大小];
如下代码:
c
// 数组的创建和初始化的演示
int arr1[10];
double arr2[20];
char arr3[30];
上述代码分别创建了,大小为 10 的 int 数组 arr1,大小为 20 的 double 数组 arr2,大小为 30 的char 数组 arr3。
注意: 创建数组时,其大小只能使用常量表达式来指定。如果你的编译器支持 C99,那么可以使用变量来指定(变长数组)。
(2)数组初始化
数组的初始化使用花括号来完成,其中的元素使用逗号隔开。数组初始化又分为完全初始化和不完全初始化,完全初始化就是初始化的元素个数和数组的大小一致,不完全初始化就是初始化的元素个数少于数组的元素个数。初始化都是从前往后一一对应,不完全初始化剩余的元素均被设置为 0。如下代码:
数组初始化时可以不指定数组的大小,编译器会把该数组初始化元素的个数指定为其大小。如下代码:
使用 sizeof(数组名) / sizeof(首元素) 可以获得该数组的大小,当然只能在创建该数组的地方使用。
1.1 字符数组的初始化
由于字符数组可以存储字符串,所以字符数组的初始化方式有两种。如下代码:
c
char arr1[3] = { 'a', 'b', 'c' }; // 字符数组
char arr2[4] = "abc"; // 字符串
上述两个数组都是字符数组,但是只有第二个数组是字符串。因为字符串是以空字符结尾的,第一个数组末尾没有空字符。而第二个数组编译器会在末尾添加空字符。
从上述图片中,可以看到数组 arr2 的末尾有空字符。也可以通过打印字符串来进行验证,使用 printf() 函数打印字符串时,遇到空字符结束。如下代码:
2. 一维数组的使用
一维数组通过下标运算符来访问数组元素,且下标是从 0 开始的。如:
int arr[5] = {1, 2, 3, 4, 5};
arr[0] 就是该数组的第一个元素,arr[4] 就是该数组的最后一个元素。
2.1 使用下标打印数组元素
只要知道数组的大小,通过循环和一个整型变量从 0 递增到 size - 1,就可以访问这个数组的所有元素。如下代码:
2.2 使用下标输入数组元素
同理和上述代码类似,我们也可以对数组的元素进行逐个输入。如下代码:
3. 一维数组在内存中的存储
一维数组在内存中的存储是连续的,拿 int 数组来说,相邻元素之间相差 sizeof(int) 个字节。如:int arr[5],其中 arr[0] 和 arr[1] 之间相差四个字节。可以通过指针变量来打印地址进行验证,如下代码:
4. 使用 sizeof 计算数组元素个数
可以在创建该数组的代码块中,通过 sizeof(数组名) / sizeof(数组首元素) 来计算该数组的元素个数。前者是数组总大小,后者是数组每个元素的大小。如下代码:
三、二维数组
二维数组,实际上就是元素是数组的数组。我们可以理解为一表格,有行和列。但实际存储时,仍是连续的,先存储第一行然后存储第二行,以此类推。
1. 二维数组的创建
以 int 类型的二维数组为例,如下代码:
c
// 二维数组
int arr[3][4];
创建了一个包含 3 个元素的数组,每个元素是一个包含 4 个元素的 int 数组。把数组名和后面的方括号去掉,剩下的 int [4] 就是该数组元素的类型。其他数组以此类推。
2. 二维数组的初始化
二维数组可以通过两个下标相乘计算其元素总个数,所以可以像一位数组那样初始化,如下代码:
c
// 二维数组初始化
int arr[3][4] = { 1,2,3,4,5,6,7,8,9 };
和一维数组一样,这是不完全初始化,未初始化的元素值被设置为 0。可以想象成:
1 2 3 4
5 6 7 8
9 0 0 0
由于二维数组的元素为数组,也可以把数组当作一个元素(用花括号括起)初始化,如下代码:
c
int arr[3][4] = { {1,2,3}, {4,5,6}, {7,8,9} };
上述每个数组都采用部分初始化,每个数组的最后一个元素均为 0。
可以想象成:
1 2 3 0
4 5 6 0
7 8 9 0
一维数组初始化时可以省略数组大小,编译器根据数组初始化元素个数进行计算。而二维数组可以省略行数,但不能省略列数,编译器根据初始化元素总个数除以列数来计算行数。如下代码:
3. 二维数组的使用
一维数组可以通过下标进行访问,二维数组同样可以。二维数组使用两个下标进行定位,第一个下标代表行,第二个下标代表列,同样都是从 0 开始,只要是数组的下标都是从 0 开始。
(1)使用下标对二维数组进行输入
c
// 使用下标对二维数组进行输入
int arr[3][4];
int i;
for (i = 0; i < 3; ++i) // 控制行
{
int j;
for (j = 0; j < 4; ++j) // 控制每行的列
scanf("%d", &arr[i][j]);
}
(2)使用下标打印二维数组
4. 二维数组在内存中的存储
由一位数组在内存中是连续存储的,可以猜测二维数组在内存中也是连续存储的。因为二维数组本身也可以看成一维数组,只不过其元素也是数组。通过使用指针变量打印每个元素的地址进行验证。代码如下:
可以看到二维数组中的每个元素在内存中都是连续的存储的。如果把二维数组看成一维数组,那么每个数组之间相邻 16 字节。
四、数组名的含义
这里先给出结论,数组名实际上是数组首元素的地址。但是除了以下两种情况:
(1)sizeof(数组名)这里求得的是整个数组的大小
(2)&数组名 这里求得的是整个数组的地址
给出代码验证:
可以看到,数组名实际上就是数组首元素的地址。接下来给这三个变量加 1,如下:
可以看到 &arr + 1 增加了十六进制的 14 也就是 20,也就是整个数组的大小。而指针变量加 1,增加的是其指向对象的大小。所以,&数组名 取出的是整个数组的地址。
1. 从函数传参解释数组名的含义
我们都知道 C 语言函数传参是值传递,也就是被调函数中的函数参数实际上是主调函数中变量的副本。假设我们传参的时候传递了整个数组,那么当数组很大时所进行的拷贝就大大提高了时间和空间的消耗。而如果函数名是数组首元素的地址的话,又因为数组在内存中是连续存储的,我们只要传递该数组的元素个数就可以访问整个数组。这样极大地提高了时间和空间的使用率。
而实际上声明函数时,我们使用数组来接收实际上就是使用指针来接受,因为指针变量是存储地址的。也就是下面两个函数参数是等价的:
c
int Sum1(int arr[]);
int Sum2(int *pi);
使用数组来接收显得更加通俗易懂,但是其本质是一个指针。而我们使用的下标运算符实际上等价于指针移动后的解引用操作,如:arr[3] 等价于 *(arr+3) 。这个下标运算符可以称为语法糖,让我们更加容易使用数组。