数组两个特性
为什么很多编程语言中数组都是从 0
开始编号,首先先了解一下数组的概念。
数组 Array
是一种线性表数据结构,是一组连续的内存空间,用来存储一组具有相同类型的数据。数组具备以下特性:
线性表,是数据排列成像一条线一样的结构,每个线性表上的数据最多只有前和后两个方向。比如,除了数组,还有链表、队列和栈。
连续的内存空间和相同类型的数据,正因为这两个特点,才让数组的操作变得非常低效,要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。
正因为这两个特点,可以非常高效的通过下标进行随机访问。
顺序访问和随机访问的区别
顺序访问:链表在内存中不是按顺序存放的,而是通过指针连在一起,为了访问某一元素,必须从链头开始顺着指针才能找到某一个元素。
随机访问:数组在内存中是按顺序存放的,可以通过下标直接定位到某一个元素存放的位置。
寻址公式
一维数组寻址公式:
a[k]_address = base_address + k * type_size
二维数组寻址公式:
假设二维数组大小为 m*n
,那么寻址公式为:
a[i][j]_address = base_address + (i * n + j) * type_size
三维数组寻址公式:
假设是 m*n*q
,那么寻址公式为:
a[i][j][k]_address=base_address + (i * n * q + j * q + k) * type_size
其中, type_size
表示数组中每个元素的大小。
验证
例如,声明一个长度为 10
的 int
类型的数组。
int arr[10] = { 0 };
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
运行结果如下,
从运行结果可以看出,计算机给数组 arr
,分配了 40
字节的内存,首地址为 0x7ffeefbff4f0
,arr[0]
地址为:0x7ffeefbff4f0
,arr[9]
地址为:0x7ffeefbff514
,每个 int
有 4
个字节,故 arr[9]
结尾为 0x7ffeefbff514
。
在 C
语言中数组名代表首地址,即第一个元素的地址,a[0]
就是偏移为 0
的位置,a[k]
就表示偏移 k
个元素类型大小的位置。得出计算公式:
a[k]_address = base_address + k * type_size
结论
CPU
性能层面考虑:从数组存储的内存模型上来看,“下标”最确切的定义应该是offset
。前面也讲到,如果用 a
来表示数组的首地址,a[0]
就是偏移为 0
的位置即首地址,a[k]
就表示偏移 k
个 type_size
的位置。如果数组编号从 1
开始计数,那这个公式就会变为:
a[k]_address = base_address + k * type_size
a[k]_address = base_address + (k-1) * type_size
对比两个公式,如果从 1
开始编号,每次随机访问数组元素就多了一次减法运算,对于 CPU
来说就是多了一次减法指令,增加了性能开销。
历史原因层面考虑:C
语言设计者用 0
开始计数数组下标,之后的 Java
、JavaScript
等高级语言都效仿了 C
语言,或者说,为了在一定程度上减少 C
语言程序员学习 Java
的学习成本,因此继续沿用了从 0
开始计数的习惯。实际上,很多语言中数组也并不是从 0
开始计数的,比如 Matlab
。甚至还有一些语言支持负数下标,比如 Python
。