第九章

你说好想回来听我的小故事, 可也想去看看落基山. 你去吧女儿. 20个小时的飞机对谁都是禁闭. 你说最近总是不能再无忧无虑. 说常困惑自己会怎样怎样. 我说女儿啊时间是河秒就是沙随它去吧. 记得你爱堆沙堡, 看着浪把它们一个个卷走, 然后你只是捧起沙, 它们就那样随意流下, 也能堆成个弯月般的小沙丘. 你总是能很开心, 不去在意是什么力那么轻易的夷平了你费力构筑的城堡. 你好像天生就知道, 不管将来会怎样怎样, 随心所欲, 随遇而安的道理.

去外边玩带好map地图. 不要只靠手机GPS.

落基山几点了? 时间是MST还是PST? 这老美的时区对华人来说真是个joke. 看俺们多简单, 无论你在哈密太原还是昆明泰安, 青岛银川还是包头武汉, 全都是北京时间. 我们想当然的事情在美国可大不同. 匹斯堡市的火车站有6个时间, 每个铁路公司的火车时刻表都用自己的标准时间!当然这是100多年前, 但至今美国也没能统一, 仍在使用着4个时间. 当跨越那3条时间变更线时, 可以想象人们是多么混乱. 要是你刚好站在那条线上, 你可以同时拥有两个正确的本地时间. 当然, 这仍是一大进步, 也是电报和铁路带来的时间需要. 在之前, 美国使用着8000多个不同的本地时间.

交通旅行还可以用小时来安排时间. 到了电脑联网的时代, 整个地球表面的机器时间都统一在UTC, 也就是一个基于国际原子钟的时间标准. 这个钟可不是一个, 而是全球70个国家实验室的200多台原子钟在人造卫星的协同下精确对表得到的.

我知道最不可思议的地图时区程序是1992年ioccc国际乱C大奖得主westley.c:

$ cat westley.c
           main(l
      ,a,n,d)char**a;{
  for(d=atoi(a[1])/10*80-
 atoi(a[2])/5-596;n="@NKA\
CLCCGZAAQBEAADAFaISADJABBA^\
SNLGAQABDAXIMBAACTBATAHDBAN\
ZcEMMCCCCAAhEIJFAEAAABAfHJE\
TBdFLDAANEfDNBPHdBcBBBEA_AL\
 H E L L O,    W O R L D! "
   [l++-3];)for(;n-->64;)
      putchar(!d+++33^
           l&1);}

这是个完整的C程序, 运行时给出纬度和经度则标出其位置, 如北京是北纬40度东经116度, 显示的结果如下. 注意我用红笔标出的引号是北京的位置.

$./a.out 40 116| fold -w 80

              !!!!!!!!!!! !!!            !!!   !!!!!!!                          
! !!!!!!!!!!!!!!!!!  !!!!! !    !      !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!        
 !!!!!!!!!!!!!!!!!!! !!!!          !  !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!         
          !!!!!!!!!!!!!!            !!!!!!! !!!!!!!!!!!!!!!"! !!    !           
             !!!!!!!!!             !! !   !!!!!!!!!!!!!!!!!!!!  !               
     !        !!!!  !              !!!!!!!!!!!!!!!!!!!!!!!!!!                   
                !!!!!             !!!!!!!!!!!!!   !!!   !!! !                   
                     !!!!!         !!!!!!!!!!      !     ! !  !                 
                     !!!!!!!!          !!!!!                    !!              
                      !!!!!!            !!!! !              !!!!!               
                       !!!!              !!                !!!!!!!!             
                       !!                                   !!  !!     !        
                       !                                                        

好在Go不许我们这样写程序. Go的原则是程序首先是给人看的, 其次才是给机器用的. 所以Go的写作风格不是随意的, 例如, {必须在行尾. 甚至有个gofmt的命令自动帮你排版. Rob建议所有的Go程序都用gofmt以达至风格的统一.

我们需要一些原始的C知识才能知道westley.c在干什么. 由于Go是C二代, 知道C和C的问题才能比较出Go的基因为什么要改变.

main也是每个C程序的入口. 后面括号里的l,a,n,d是main用到的变量, 即Go的var. 其中a的胎型在其后给出是char**,也就是Go的[]string的意思. l,n,d未明确胎型所以默认为整数型也就是Go的int.

C的函数体也是放在{}里, 但C没有行的概念, 所以每个语句都要分号结束. for也是C的循环语句, 但C还有while和do while两种循环, 在Go里都是for了. 并且C的循环控制要放在括号里, Go的不用.

C没有包的概念, atoi函数在标准库里, 所以不需import, 而且C的叫#include, 注意开始的#号, 代表的是预处理的意思. C可以有很多这样的预处理, 例如#define, #if, #else, #ifdef等, Go一概取消.

a[1]和a[2]是运行此程序时的命令行参数, 即纬度和经度. 是两个字串, 所以由atoi转换为整数计算得到d的值. 另外, C的数组也是从0开始, a[0]是程序名, 即a.out. 但和Go不同, C数组的下标可以越界, 这也是电脑病毒产出的根源. 因为病毒也是程序, 它会偷偷把自己放在某个数组的后边, 再改掉其进攻程序下标值使其执行自己, 从而把病毒当作自己的一部分不加区分. 更常见的是, 数组没有越界保护, 程序的漏洞, 也就是程序员没有发现的逻辑错误, 不能被及时察觉, 导致危险的结果. 这些危机在Go里都不存在, 所以Rob说Go是安全的语言.

和Go一样, for的控制有三部分由分号隔开, 初始;循环;增量. 此处初始是d=纬度/10*80-经度/5-开始位置. 除10是因为每十度一行, 乘80是每行80个字符. 因为没有加换行符, 需要在80列的终端显示(例如用vi), 或像我这样, 用‘ |fold -w 80 ’处理.

l 是main的第一个参数, 其值是2, 也就是程序命令行参数的个数. l++使其每次循环加一, l++-3使其从0开始. Go也可以用++和--来自动加一减一, 但只能单独使用, 不能像C那样和其它运算混在一起导致程序员搞不清顺序犯错误.

"@NKACLCCGZAAQBEAADAFaISADJABBA^SNLGAQABDAXIMBAACTBATAHDBANZcEMMCCCCAAhEIJFAEAAABAfHJETBdFLDAANEfDNBPHdBcBBBEA_AL H E L L O,    W O R L D! "

这是个无名的字串, [l++-3]下标取到的字节值给n. 它代表的是世界地图!循环从第一个字符到最后能自动结束, 是因为和Go的字串不同, C的字串尾隐含了一个值为0的字节, 可以方便的知道字串结束在哪里, 但同时要求字串中间不可以有值为0的字节. 当n为0时, for循环结束.

每次for循环执行另一个for循环for(;n-->64;). 内层的循环控制显示多少个字符. 这需要知道每个字符所代表的数是什么. C使用ASCII, 所以@是64, N是78, K是75等等. 和Go不同, C隐含的把字符转换为整数. Go必须明确的用int()转换, 麻烦了一些, 但避免了程序员犯糊涂的错误. 明确转换也是Go的一个原则.

putchar是C的另一个标准库函数, 用来显示字符, 此处为空白, 叹号或引号, 其值分别是32, 33和34. 所以有!d+++33^l&1. 要知道它在干什么需要知道C的运算优先级, 太过复杂. 我们只要知道结果就好了. Go的运算优先级得到了简化.

把这个程序用Go写一遍应该不难了吧.