漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?

漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片

当我们想要写一个循环体 , 期望执行10次的时候 , 我们会使用以下方式:
for(inti=0i&lt10i++){
}
可以看到 , 为了保证循环10次 , 我们定义了一个整数变量从0开始 。
还有 , 当我们定义数组的时候 , 在常见的C语言、Java、Python等语言中 , 都是使用下标0来表示第一个元素的 。
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
从0开始更优雅
在一文中我们分析过 , Dijkstra通过分析 , 得出在进行范围表达的时候 , 使用左闭右开的方式更加合理 。
但是 , Dijkstra在分析出2≤i&lt13这种形式更加合理之后 , 他有陷入了另外一个思考 , 那就是:
当处理长度为N的序列时 , 到底第一个元素的下标使用0还是1更加合适?
关于这个分析 , 他的出发点很简单 , 那就是哪种方式更加漂亮 , 更加优雅 。
他认为 , 使用左闭右开的表达方式 , 当下标从1开始时 , 下标范围为1&lt=i&ltN+1;当下标从0开始时则是0&lt=i&ltN;
而显然后面这种表达式更加漂亮、优雅一些 。 所以 , 他建议我们使用0作为第一个下标 。
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
计数表示偏移量
很多人学习编程都是从C语言开始的 , 那么 , C语言就是一个典型的0-base语言(以0作为计数的开始) , 其实 , 这一约定早在BCPL时代就是这样的了 。
在C语言还不叫C语言 , 还叫BCPL的时候 , 他的作者马丁·理察德就设计了数组从0开始的索引方式 。
当我们在BCPL(C语言)中定义数组intarr[8]的时候 , 编辑器会在内存中开辟一块空间(这个空间中可能包含多个内存单元)供该数组使用 。
为了能让数组找到编译器为自己开辟的空间 , 会把这块内存空间中第一个内存单元的地址(0X0000001)赋值给这个数组 , 当我们使用&amparr的时候 , 就可以拿到这块地址 。
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
BCPL最初是用IBM7094机器编译的;它在编译时会优化这些数组索引提供的指针反参考运算(indirection) , 即可以通过指针取出地址中存储的值 , 这个特性也一直延续到今天 。
有了指针之后 , 我们可以使用int*pr=arr的方式初始化一个指针 , 那么 , 这时候 , 指针pr也会指向数组的内存空间的第一个内存单元的地址 。
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
那有了数组和指针 , 想要使用这块内存第一个内存单元存储一个变量的时候 , 就需要想办法表示这第一个空间 。
那么 , BCPL的作者采用了0作为数组第一个元素的下标 , 因为他认为 , 数组的下标应该和指针的偏移量是相对应的 。 这样在使用第一个内存单元的时候 , 直接使用arr[0]或者*(p+0)就可以了 。
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
因为指针*(p+0)这种表达形式中的0表示的是偏移量 , 所以 , 无论数组的下标从几开始 , *(p+0)都是用于存取内存中的p+0位址的值 , 也就是0X0000001这块内存单元的值 。
试想一下 , 如果使用1作为数组的起始下标 , 那么arr1就应该指向0X0000001这块内存 , 但是*(p+1)按照偏移量的计算方式 , 需要指向0X0000005这块内存 。 这种情况下 , 如果想要让*(p+1)和arr[1]指向同一块内存 , 就需要额外做一次减法指令 。
因为几乎所有计算机结构 , 都借由位址和偏移量来表示直接引用内存 , 所以 , 像C语言这种使用0做为数组的第一个下标使得语言的实现上更加容易 。
但是值得一提的是 , 在C语言流行起来之前 , 还是有很多1-base的编程语言的 , 如FORTRAN、BASIC等编程语言的数组下标都是从1开始的 。
随着C语言的发扬光大 , 很多语言都参考了C语言的做法 。
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
Python作者的解释
关于这个问题 , 之前也有网友在Twitter上询问过Python之父——GuidovanRossum , 他给出过正面回答 , 我把回答内容的翻译版贴在下面:
我记得自己就这个问题思考过很久;Python的祖先之一ABC语言 , 使用的索引是从1开始的(1-basedindexing) , 而对Python语言有巨大影响的另一门语言 , C语言的索引则是从0开始的 。
我最早学习的几种编程语言(Algol,Fortran,Pascal)中的索引方式 , 有的是1-based的 , 有的是从定义的某个变量开始(variable-basedindexing) 。 而我决定在Python中使用0-based索引方式的一个原因 , 就是切片语法(slicenotation) 。
让我们来先看看切片的用法 。 可能最常见的用法 , 就是“取前n位元素”或“从第i位索引起 , 取后n位元素”(前一种用法 , 实际上是i==起始位的特殊用法) 。 如果这两种用法实现时可以不在表达式中出现难看的+1或-1 , 那将会非常的优雅 。
使用0-based的索引方式、半开区间切片和缺省匹配区间的话(Python最终采用这样的方式) , 上面两种情形的切片语法就变得非常漂亮:a[:n]和a[i:i+n] , 前者是a[0:n]的缩略写法 。
如果使用1-based的索引方式 , 那么 , 想让a[:n]表达“取前n个元素”的意思 , 你要么使用闭合区间切片语法 , 要么在切片语法中使用切片起始位和切片长度作为切片参数 。
半开区间切片语法如果和1-based的索引方式结合起来 , 则会变得不优雅 。
而使用闭合区间切片语法的话 , 为了从第i位索引开始取后n个元素 , 你就得把表达式写成a[i:i+n-1] 。
这样看来 , 1-based的索引方式 , 与切片起始位+长度的语法形式配合使用会不会更合适?这样你可以写成a[i:n] 。 事实上 , ABC语言就是这样做的——它发明了一个独特的语法 , 你可以把表达式写成a@i|n 。
但是 , index:length这种方式在其它情况下适用吗?说实话 , 这点我有些记不清了 , 但我想我是被半开区间语法的优雅迷住了 。
特别是当两个切片操作位置邻接时 , 第一个切片操作的终点索引值是第二个切片的起点索引值时 , 太漂亮了 , 无法舍弃 。
【漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?】例如 , 你想将一个字符串以i , j两个位置切成三部分 , 这三部分的表达式将会是a[:i] , a[i:j]和a[j:] 。
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片
漫话:如何给女朋友解释为什么计算机从0开始计数,而不是从1开始?
文章图片


    推荐阅读