你写过多少次 for (int i = 0; i < arr.length; i++)?有没有哪一刻突然愣住:为啥非得从 0 开始?不是从 1 更自然吗?超市货架第一排标着“第1排”,电梯一楼叫“1F”,连小朋友数手指都是“1、2、3”……偏偏代码里,第一个元素偏偏是 arr[0]。
不是约定俗成,是算起来真方便
想象一个整型数组 int arr[5],存了 5 个数字,系统给它分配了一块连续内存,起始地址假设是 1000(单位:字节)。每个 int 占 4 字节。那么:
arr[0] → 地址 1000 + 0×4 = 1000
arr[1] → 地址 1000 + 1×4 = 1004
arr[2] → 地址 1000 + 2×4 = 1008
arr[3] → 地址 1000 + 3×4 = 1012
arr[4] → 地址 1000 + 4×4 = 1016看出来没?索引直接当“偏移量”用,不用额外减 1。要是从 1 开始,arr[1] 就得算 1000 + (1−1)×4,每次访问都多一次减法——在早期计算机资源紧张的年代,这可不是小开销。
C 语言定下的调子,后来者跟着走
Ken Thompson 和 Dennis Ritchie 设计 C 语言时,就选了这种“基地址 + 索引×元素大小”的简洁模型。C 直接把数组名当指针,arr[i] 在底层就是 *(arr + i)。这个 + 运算天然适配从 0 起跳。后来的 C++、Java、Python、JavaScript……几乎全盘继承。不是大家懒,是这套逻辑太顺了——指针移动、内存计算、循环边界,全都一气呵成。
边界检查也更干净
判断下标是否越界,只需一行:if (i >= 0 && i < length)。两个不等号方向一致,条件清晰。如果从 1 开始,就得写成 if (i >= 1 && i <= length),一边是 ≥,一边是 ≤,对称性差了点,手滑写错的概率反而高一点。
别较劲“自然”,较劲“一致”
有人问:“那 Python 的切片 [1:4] 不是取第1到第3个吗?这不是又绕回来了?” 其实不是。切片 [start:end] 的 end 是“不包含”的上界,整个区间是左闭右开:[0:5] 刚好覆盖全部 5 个元素,长度 = 5 − 0 = 5。这种设计让长度计算、拼接、分割都无需加减 1,数学上更自洽。
说白了,从 0 开始不是为了反人类,而是为了让机器算得快、让人写得稳、让不同语言之间少点意外。你习惯了,它就成了呼吸一样的存在——就像键盘上的 Caps Lock 总在左下角,第一次觉得别扭,用久了反而离不开。