数组是什么?为什么下标是从 0 开始的?
一、什么是数组?
从字面上看,就是一组数据的意思,它的作用就是用来存储一组数据。数组中存放的每一个数据,一般称为“元素”。
1.1. 声明一个数组
格式:
1 | 元素类型 数组名[元素的数量]; |
示例:
1 | // 声明一个数组ages,并且进行初始化,ages可以存放5个int类型的元素 |
1.2. 数组元素的访问
数组中的每一个元素都有一个唯一的索引(index,也叫做下标),是从 0 开始计算的。我们可以元素的索引访问元素。
示例:
1 | int ages[5] = {10, 20, 50, 40, 60}; |
上面示例数组的下标:
索引 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
元素 | 10 | 20 | 50 | 40 | 60 |
访问示例数组的元素:
1 | printf("%d\n", ages[1]); // 输出:20 |
示例数组的元素重新赋值后:
索引 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
元素 | 10 | 100 | 50 | 40 | 60 |
1.3. 数组的遍历
遍历的意思是:将数组里面的每一个元素都访问一遍。遍历数组常用手法就是循环遍历。
示例:
1 | int ages[5] = {10, 20, 50, 40, 60}; |
二、内存细节
数组占用内存大小 = 元素类型占用内存大小 * 元素个数。
示例:
1 | int ages[5] = {10, 20, 50, 40, 60}; |
分析元素内存地址:
1 | printf("&ages=%p\n", ages); |
从上面的内地地址可以看出:
- 数组元素的地址是连续分配的,且每个元素占用对应类型的字节数(如 int 类型占用 4 个字节);
- 数组的地址等于数组首元素的地址。
三、数组的初始化细节
- 如果没有初始化,数组元素的值是不确定的
1 | int a[3]; |
- 可以先声明,后初始化
1 | int a[3]; |
- 如果在声明的同时进行初始化,可以不指定元素的数量
1 | int a[] = {10, 20, 30}; |
- 可以在声明的时候只初始化部分元素,其他元素默认会初始化为 0
1 | int a[5] = {10, 20, 30}; |
- 可以在大括号里面指定索引(注意:指定索引时,一定要标注索引 0 从哪个位置开始,否则会数据异常)
1 | // 案例一: |
- 数组的初始化元素数量不能超过数组的长度
1 | // 编译器会警告 |
- 数组在声明的同时进行初始化,其长度不能是变量
1 | int count = 5; |
- 在数组声明以后,不能再通过大括号进行赋值
1 | int ages[3]; |
- 数组的索引越界后,访问的数值是不确定的
1 | int ages[3] = {10, 20, 30}; |
四、数组的索引为什么是从 0 开始的?
访问一个数组元素的值,是从 0 开始的,为什么呢?我们从内存角度分析下。
示例:
1 | int a[5] = {10, 20, 30, 40, 50}; |
从上面的代码输出可以看出,数组的地址是数组首元素的地址,
可以推算出:数组的元素地址 = 数组首元素地址 + 数组的索引 * 元素类型占用内存大小
数组 a 的内存地址:0x7fbff560
数组元素索引为 2 的地址:&a[2] = 0x7fbff560 + 1 * 4 = 0x7fbff564
假设:如果数组的索引从 1 开始
数组元素索引为 1 的地址(即首元素):&a[1] = 0x7fbff560 + 1 * 4 = 0x7fbff564
数组元素索引为 2 的地址:&a[2] = 0x7fbff560 + 2 * 4 = 0x7fbff568
数组 a 的内存地址:0x7fbff564
可以看出,如果元素所以从 1 开始,数组前面就会空出 4 个字节,浪费内存空间。
假如这时候把索引进行-1
操作呢?
数组元素索引为 1 的地址(即首元素):&a[1] = 0x7fbff560 + (1 - 1) * 4 = 0x7fbff560
数组元素索引为 2 的地址:&a[2] = 0x7fbff560 + (2 - 1) * 4 = 0x7fbff564
数组 a 的内存地址:0x7fbff560
发现,如果数组的索引从 1 开始,然后对索引进行-1
操作,确实解决了内存空间问题。但是,这样代码运行效率就变得有点低效了,因为每次都要进行减法运算。所以从 0 开始不仅可以解决内存空间问题,也能让代码快速高效地运行。