Git 前时代:使用 CVS 进行版本控制
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201812/06/184906o6039mi866ammq61.jpg">Media</a>GitHub 网站发布于 2008 年。如果你的软件工程师职业生涯跟我一样,也是晚于此时间的话,Git 可能是你用过的唯一版本控制软件。虽然其陡峭的学习曲线和不直观地用户界面时常会遭人抱怨,但不可否认的是,Git 已经成为学习版本控制的每个人的选择。Stack Overflow 2015 年进行的开发者调查显示,69.3% 的被调查者在使用 Git,几乎是排名第二的 Subversion 版本控制系统使用者数量的两倍。<a href="#fn1">1</a> 2015 年之后,也许是因为 Git 太受欢迎了,大家对此话题不再感兴趣,所以 Stack Overflow 停止了关于开发人员使用的版本控制系统的问卷调查。GitHub 的发布时间距离 Git 自身发布时间很近。2005 年,Linus Torvalds 发布了 Git 的首个版本。现在的年经一代开发者可能很难想象“版本控制软件”一词所代表的世界并不仅仅只有 Git,虽然这样的世界诞生的时间并不长。除了 Git 外,还有很多可供选择。那时,开源开发者较喜欢 Subversion,企业和视频游戏公司使用 Perforce (到如今有些仍在用),而 Linux 内核项目依赖于名为 BitKeeper 的版本控制系统。其中一些系统,特别是 BitKeeper,会让年经一代的 Git 用户感觉很熟悉,上手也很快,但大多数相差很大。除了 BitKeeper,Git 之前的版本控制系统都是以不同的架构模型为基础运行的。《<a href="https://ericsink.com/vcbe/index.html">Version Control By Example</a>》一书的作者 Eric Sink 在他的书中对版本控制进行了分类,按其说法,Git 属于第三代版本控制系统,而大多数 Git 的前身,即流行于二十世纪九零年代和二十一世纪早期的系统,都属于第二代版本控制系统。<a href="#fn2">2</a> 第三代版本控制系统是分布式的,第二代是集中式。你们以前大概都听过 Git 被描述为一款“分布式”版本控制系统。我一直都不明白分布式/集中式之间的区别,随后自己亲自安装了一款第二代的集中式版本控件系统,并做了相关实验,至少明白了一些。我安装的版本系统是 CVS。CVS,即 “并发版本系统Concurrent Versions System” 的缩写,是最初的第二代版本控制系统。大约十年间,它是最为流行的版本控制系统,直到 2000 年被 Subversion 所取代。即便如此,Subversion 被认为是 “更好的 CVS”,这更进一步突出了 CVS 在二十世纪九零年代的主导地位。CVS 最早是由一位名叫 Dick Grune 的荷兰科学家在 1986 年开发的,当时有一个编译器项目,他正在寻找一种能与其学生合作的方法。<a href="#fn3">3</a> CVS 最初仅仅只是一个包装了 RCS(修订控制系统Revision Control System) 的 Shell 脚本集合,Grune 想改进这个第一代的版本控制系统。 RCS 是按悲观锁模式工作的,这意味着两个程序员不可以同时处理同一个文件。需要编辑一个文件话,首先得向 RCS 系统请求一个排它锁,锁定此文件直到完成编辑,如果你想编辑的文件有人正在编辑,你就必须等待。CVS 在 RCS 基础上改进,并把悲观锁模型替换成乐观锁模型,迎来了第二代版本控制系统的时代。现在,程序员可以同时编辑同一个文件、合并编辑部分,随后解决合并冲突问题。(后来接管 CVS 项目的工程师 Brian Berliner 于 1990 年撰写了一篇非常易读的关于 CVS 创新的 <a href="https://docs.freebsd.org/44doc/psd/28.cvs/paper.pdf">论文</a>。)从这个意义上来讲,CVS 与 Git 并无差异,因为 Git 也是运行于乐观锁模式的,但也仅仅只有此点相似。实际上,Linus Torvalds 开发 Git 时,他的一个指导原则是 WWCVSND,即 “CVS 不能做的What Would CVS Not Do”。每当他做决策时,他都会力争选择那些在 CVS 设计里没有使用的功能选项。<a href="#fn4">4</a> 所以即使 CVS 要早于 Git 十多年,但它对 Git 的影响是反面的。我非常喜欢折腾 CVS。我认为要弄明白为什么 Git 的分布式特性是对以前的版本控制系统的极大改善的话,除了折腾 CVS 外,没有更好的办法。因此,我邀请你跟我一起来一段激动人心的旅程,并在接下来的十分钟内了解下这个近十年来无人使用的软件。(可以看看文末“修正”部分)CVS 入门CVS 的安装教程可以在其 <a href="https://www.nongnu.org/cvs/">项目主页</a> 上找到。MacOS 系统的话,可以使用 Homebrew 安装。由于 CVS 是集中式的,所以它有客户端和服务端之区分,这种模式 Git 是没有的。两端分别有不同的可执行文件,其区别不太明显。但要开始使用 CVS 的话,即使只在你的本地机器上使用,也必须设置 CVS 的服务后端。CVS 的后端,即所有代码的中央存储区,被叫做存储库 repository。在 Git 中每一个项目都有一个存储库,而 CVS 中一个存储库就包含所有的项目。尽管有办法保证一次只能访问一个项目,但一个中央存储库包含所有东西是改变不了的。要在本地创建存储库的话,请运行 init 命令。你可以像如下所示在家目录创建,也可以在你本地的任何地方创建。<pre class="prettyprint linenums">$ cvs -d ~/sandbox init</pre>CVS 允许你将选项传递给 cvs 命令本身或 init 子命令。出现在 cvs 命令之后的选项默认是全局的,而出现在子命令之后的是子命令特有选项。上面所示例子中,-d 标志是全局选项。在这儿是告诉 CVS 我们想要创建存储库路径在哪里,但一般 -d 标志指的是我们想要使用的且已经存在的存储库位置。一直使用 -d 标志很单调乏味,所以可以设置 CVSROOT 环境变量来代替。因为我们只是在本地操作,所以仅仅使用 -d 参考来传递路径就可以,但也可以包含个主机名。此命令在你的家目录创建了一个名叫 sandbox 的目录。 如果你列出 sandbox 内容,会发现下面包含有名为 CVSROOT 的目录。请不要把此目录与我们的环境变量混淆,它保存存储库的管理文件。恭喜! 你刚刚创建了第一个 CVS 存储库。检入代码假设你决定留存下自己喜欢的颜色清单。因为你是一个有艺术倾向但很健忘的人,所以你键入颜色列表清单,并保存到一个叫 favorites.txt 的文件中:<pre class="prettyprint linenums">blueorangegreendefinitely not yellow</pre>我们也假设你把文件保存到一个叫 colors 的目录中。现在你想要把喜欢的颜色列表清单置于版本控制之下,因为从现在起的五十年间你会回顾下,随着时间的推移自己的品味怎么变化,这件事很有意思。为此,你必须将你的目录导入为新的 CVS 项目。可以使用 import 命令:<pre class="prettyprint linenums">$ cvs -d ~/sandbox import -m "" colors colors initialN colors/favorites.txtNo conflicts created by this import</pre>这里我们再次使用 -d 标志来指定存储库的位置,其余的参数是传输给 import 子命令的。必须要提供一条消息,但这儿没必要,所以留空。下一个参数 colors,指定了存储库中新目录的名字,这儿给的名字跟检入的目录名称一致。最后的两个参数分别指定了 “vendor” 标签和 “release” 标签。我们稍后就会谈论标签。我们刚将 colors 项目拉入 CVS 存储库。将代码引入 CVS 有很多种不同的方法,但这是 《<a href="http://shop.oreilly.com/product/9780974514000.do">Pragmatic Version Control Using CVS</a>》 一书所推荐方法,这是一本关于 CVS 的程序员实用指导书籍。使用这种方法有点尴尬的就是你得重新检出check out工作项目,即使已经存在有 colors 此项目了。不要使用该目录,首先删除它,然后从 CVS 中检出刚才的版本,如下示:<pre class="prettyprint linenums">$ cvs -d ~/sandbox co colorscvs checkout: Updating colorsU colors/favorites.txt</pre>这个过程会创建一个新的目录,也叫做 colors。此目录里会发现你的源文件 favorites.txt,还有一个叫 CVS 的目录。这个 CVS 目录基本上与每个 Git 存储库的 .git…
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201812/06/184906o6039mi866ammq61.jpg">Media</a>GitHub 网站发布于 2008 年。如果你的软件工程师职业生涯跟我一样,也是晚于此时间的话,Git 可能是你用过的唯一版本控制软件。虽然其陡峭的学习曲线和不直观地用户界面时常会遭人抱怨,但不可否认的是,Git 已经成为学习版本控制的每个人的选择。Stack Overflow 2015 年进行的开发者调查显示,69.3% 的被调查者在使用 Git,几乎是排名第二的 Subversion 版本控制系统使用者数量的两倍。<a href="#fn1">1</a> 2015 年之后,也许是因为 Git 太受欢迎了,大家对此话题不再感兴趣,所以 Stack Overflow 停止了关于开发人员使用的版本控制系统的问卷调查。GitHub 的发布时间距离 Git 自身发布时间很近。2005 年,Linus Torvalds 发布了 Git 的首个版本。现在的年经一代开发者可能很难想象“版本控制软件”一词所代表的世界并不仅仅只有 Git,虽然这样的世界诞生的时间并不长。除了 Git 外,还有很多可供选择。那时,开源开发者较喜欢 Subversion,企业和视频游戏公司使用 Perforce (到如今有些仍在用),而 Linux 内核项目依赖于名为 BitKeeper 的版本控制系统。其中一些系统,特别是 BitKeeper,会让年经一代的 Git 用户感觉很熟悉,上手也很快,但大多数相差很大。除了 BitKeeper,Git 之前的版本控制系统都是以不同的架构模型为基础运行的。《<a href="https://ericsink.com/vcbe/index.html">Version Control By Example</a>》一书的作者 Eric Sink 在他的书中对版本控制进行了分类,按其说法,Git 属于第三代版本控制系统,而大多数 Git 的前身,即流行于二十世纪九零年代和二十一世纪早期的系统,都属于第二代版本控制系统。<a href="#fn2">2</a> 第三代版本控制系统是分布式的,第二代是集中式。你们以前大概都听过 Git 被描述为一款“分布式”版本控制系统。我一直都不明白分布式/集中式之间的区别,随后自己亲自安装了一款第二代的集中式版本控件系统,并做了相关实验,至少明白了一些。我安装的版本系统是 CVS。CVS,即 “并发版本系统Concurrent Versions System” 的缩写,是最初的第二代版本控制系统。大约十年间,它是最为流行的版本控制系统,直到 2000 年被 Subversion 所取代。即便如此,Subversion 被认为是 “更好的 CVS”,这更进一步突出了 CVS 在二十世纪九零年代的主导地位。CVS 最早是由一位名叫 Dick Grune 的荷兰科学家在 1986 年开发的,当时有一个编译器项目,他正在寻找一种能与其学生合作的方法。<a href="#fn3">3</a> CVS 最初仅仅只是一个包装了 RCS(修订控制系统Revision Control System) 的 Shell 脚本集合,Grune 想改进这个第一代的版本控制系统。 RCS 是按悲观锁模式工作的,这意味着两个程序员不可以同时处理同一个文件。需要编辑一个文件话,首先得向 RCS 系统请求一个排它锁,锁定此文件直到完成编辑,如果你想编辑的文件有人正在编辑,你就必须等待。CVS 在 RCS 基础上改进,并把悲观锁模型替换成乐观锁模型,迎来了第二代版本控制系统的时代。现在,程序员可以同时编辑同一个文件、合并编辑部分,随后解决合并冲突问题。(后来接管 CVS 项目的工程师 Brian Berliner 于 1990 年撰写了一篇非常易读的关于 CVS 创新的 <a href="https://docs.freebsd.org/44doc/psd/28.cvs/paper.pdf">论文</a>。)从这个意义上来讲,CVS 与 Git 并无差异,因为 Git 也是运行于乐观锁模式的,但也仅仅只有此点相似。实际上,Linus Torvalds 开发 Git 时,他的一个指导原则是 WWCVSND,即 “CVS 不能做的What Would CVS Not Do”。每当他做决策时,他都会力争选择那些在 CVS 设计里没有使用的功能选项。<a href="#fn4">4</a> 所以即使 CVS 要早于 Git 十多年,但它对 Git 的影响是反面的。我非常喜欢折腾 CVS。我认为要弄明白为什么 Git 的分布式特性是对以前的版本控制系统的极大改善的话,除了折腾 CVS 外,没有更好的办法。因此,我邀请你跟我一起来一段激动人心的旅程,并在接下来的十分钟内了解下这个近十年来无人使用的软件。(可以看看文末“修正”部分)CVS 入门CVS 的安装教程可以在其 <a href="https://www.nongnu.org/cvs/">项目主页</a> 上找到。MacOS 系统的话,可以使用 Homebrew 安装。由于 CVS 是集中式的,所以它有客户端和服务端之区分,这种模式 Git 是没有的。两端分别有不同的可执行文件,其区别不太明显。但要开始使用 CVS 的话,即使只在你的本地机器上使用,也必须设置 CVS 的服务后端。CVS 的后端,即所有代码的中央存储区,被叫做存储库 repository。在 Git 中每一个项目都有一个存储库,而 CVS 中一个存储库就包含所有的项目。尽管有办法保证一次只能访问一个项目,但一个中央存储库包含所有东西是改变不了的。要在本地创建存储库的话,请运行 init 命令。你可以像如下所示在家目录创建,也可以在你本地的任何地方创建。<pre class="prettyprint linenums">$ cvs -d ~/sandbox init</pre>CVS 允许你将选项传递给 cvs 命令本身或 init 子命令。出现在 cvs 命令之后的选项默认是全局的,而出现在子命令之后的是子命令特有选项。上面所示例子中,-d 标志是全局选项。在这儿是告诉 CVS 我们想要创建存储库路径在哪里,但一般 -d 标志指的是我们想要使用的且已经存在的存储库位置。一直使用 -d 标志很单调乏味,所以可以设置 CVSROOT 环境变量来代替。因为我们只是在本地操作,所以仅仅使用 -d 参考来传递路径就可以,但也可以包含个主机名。此命令在你的家目录创建了一个名叫 sandbox 的目录。 如果你列出 sandbox 内容,会发现下面包含有名为 CVSROOT 的目录。请不要把此目录与我们的环境变量混淆,它保存存储库的管理文件。恭喜! 你刚刚创建了第一个 CVS 存储库。检入代码假设你决定留存下自己喜欢的颜色清单。因为你是一个有艺术倾向但很健忘的人,所以你键入颜色列表清单,并保存到一个叫 favorites.txt 的文件中:<pre class="prettyprint linenums">blueorangegreendefinitely not yellow</pre>我们也假设你把文件保存到一个叫 colors 的目录中。现在你想要把喜欢的颜色列表清单置于版本控制之下,因为从现在起的五十年间你会回顾下,随着时间的推移自己的品味怎么变化,这件事很有意思。为此,你必须将你的目录导入为新的 CVS 项目。可以使用 import 命令:<pre class="prettyprint linenums">$ cvs -d ~/sandbox import -m "" colors colors initialN colors/favorites.txtNo conflicts created by this import</pre>这里我们再次使用 -d 标志来指定存储库的位置,其余的参数是传输给 import 子命令的。必须要提供一条消息,但这儿没必要,所以留空。下一个参数 colors,指定了存储库中新目录的名字,这儿给的名字跟检入的目录名称一致。最后的两个参数分别指定了 “vendor” 标签和 “release” 标签。我们稍后就会谈论标签。我们刚将 colors 项目拉入 CVS 存储库。将代码引入 CVS 有很多种不同的方法,但这是 《<a href="http://shop.oreilly.com/product/9780974514000.do">Pragmatic Version Control Using CVS</a>》 一书所推荐方法,这是一本关于 CVS 的程序员实用指导书籍。使用这种方法有点尴尬的就是你得重新检出check out工作项目,即使已经存在有 colors 此项目了。不要使用该目录,首先删除它,然后从 CVS 中检出刚才的版本,如下示:<pre class="prettyprint linenums">$ cvs -d ~/sandbox co colorscvs checkout: Updating colorsU colors/favorites.txt</pre>这个过程会创建一个新的目录,也叫做 colors。此目录里会发现你的源文件 favorites.txt,还有一个叫 CVS 的目录。这个 CVS 目录基本上与每个 Git 存储库的 .git…
IBM 029 型打孔机
我知道这很学院派,可一行超过 80 个字符的代码还是让我抓狂。我也在网上见过不少人认为即使在现代的视网膜屏幕下也应当采用行长度为 80 个字符的标准,可他们都不理解我对破坏这一标准的怒火,就算多 1 个字符也不行。在这一标准的黄金时期,一行代码的长度几乎不会超过 80 个字符的限制。在那时,这一限制是物理的,没有第 81 列用于存放第 81 个字符。每一个试图把函数名起的又长又臭的程序员都会在短暂的愉悦后迎来更多的麻烦,而这仅仅是因为没有足够的空间放下整个函数的声明。这一黄金时期也是打孔卡punch card编程时期。在 20 世纪 60 年代,IBM 打孔卡设立了标准,这个标准就是打孔卡的宽度为 80 列。这个 80 列标准在后来的电传打字机和哑终端时期得以延续,并逐渐成为操作系统中隐藏的细节。时至今日,即使我们用上了更大、更好的屏幕,偏向于使用更长的标识符而不是类似 <code>iswcntrl()</code> 这样令人难以猜测的函数名,可当你打开新的终端模拟器窗口时,默认的宽度依然是 80 个字符。从 Quora 上的很多问题中可以发现,很多人并不能想象如何使用打孔卡给计算机编程。我承认,在很长的一段时间里我也不能理解打孔卡编程是如何工作的,因为这让我想到就像劳工一样不停的给这些打孔卡打孔。当然,这是一个误解,程序员不需要亲自给打孔卡打孔,就像是火车调度员不用亲自扳道岔。程序员们有打孔机card punch machines(也被称为键控打孔机key punches),这让他们可以使用打字机式的键盘给打孔卡打孔。这样的设备在 19 世纪 90 年代时就已经不是什么新技术了。那时,最为广泛使用的打孔机之一便是 IBM 029 型打孔机。就算在今天,它也许是最棒的打孔机。<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201812/25/144551dtit8dpgprga5p3n.jpg">Media</a>IBM 029 型打孔机在 1964 年作为 IBM 的 System/360 大型电脑的配件发售的。System/360 是计算系统与外设所组成的一个系列,在 20 世纪 60 年代晚期,它几乎垄断了整个大型计算机市场。就像其它 System/360 外设一样,029 型打孔机也是个大块头。那时,计算机和家具的界限还很模糊,但 029 型打孔机可不是那种会占领你的整张桌子的机器。它改进自 026 型打孔机,增加了新的字符支持,如括号,总体上也更加安静。与前辈 026 型所展出 20 世纪 40 年代的圆形按钮与工业化的样貌相比,029 型的按键方正扁平、功能按键还有酷炫的蓝色高亮提示。它的另一个重要买点是它能够在数字区numeric field左侧自动的填充 0 ,这证明了 JavaScript 程序员不是第一批懒得自己做左填充left-padding的程序员。(LCTT 译注:这项功能需要额外的 4 张 <a href="https://en.wikipedia.org/wiki/IBM_Standard_Modular_System">标准模块系统卡</a>SMS card才能使用。例如设置数字区域长度为 6 列时,操作员只需要输入 73 ,打孔机会自动填充起始位置上的 4 个 0 ,故最终输出 000073。<a href="https://en.wikipedia.org/wiki/Keypunch#IBM_029_Card_Punch">更多信息</a>)等等!你说的是 IBM 在 1964 年发布了全新的打孔机?你知道那张在贝尔实验室拍摄的 Unix 之父正在使用电传打字机的照片吗?那是哪一年的来着?1970?打孔机不是应该在 20 世纪 60 年代中期到晚期时就过时了吗?是的,你也许会奇怪,为什么直到 1984 年,IBM 的产品目录中还会出现 029 型打孔机的身影 <a href="#fn1">1</a>。事实上,直到 20 世纪 70 年代,大多数程序员仍然在使用打孔卡编程。其实二战期间就已经有人在用电传打字机了,可那时并没能普及。客观的讲,电传打字机几乎和打孔卡一样古老。也许和你想象的恰恰相反,并不是电传打字机本身限制了它的普及,而是计算时间。人们拒绝使用电传打字机的原因是,它是可交互的,它和计算机使用“在线”的传输方式online mode of communication。在以 Unix 为代表的分时操作系统被发明前,你和电脑的交互会被任何人的使用而打断,而这一点延迟通常意味着几千美元的损失。所以程序员们普遍选择离线地使用打孔机编程,再将打孔卡放入大型计算机中,作为批任务batch job执行。在那时,还没有即廉价又可靠的存储设备,可打孔卡的廉价优势已经足够让它成为那时最流行的数据存储方式了。那时的程序是书架上一摞打孔卡而不是硬盘里的一堆文件。那么实际使用 IBM 029 型打孔机是个什么样子呢?这很难向没有实际看过打孔卡的人解释。一张打孔卡通常有 12 行 80 列。打孔卡下面是从 1 到 9 的数字行digit rows,打孔卡上的每一列都有这些行所对应的数字。最上面的三行是空间行zone rows,通常由两行空白行和一行 0 行组成。第 12 行是打孔卡最顶部的行,接下来是 11 行,随后是从数字 0 到 9 所在的行。这个有点让人感到困惑的顺序的原因是打孔卡的上边缘被称为12 边12 edge、下边缘被称为 9 边9 edge。那时,为了让打孔卡便于整理,常常会剪去打孔卡的一个角。<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201812/25/144552j4ce2ze22qc8cdc5.png">Media</a>(LCTT 译注:可参考<a href="https://zh.wikipedia.org/wiki/EBCDIC">EBCDIC 编码</a>)在打孔卡发明之初,孔洞的形状是圆形的,但是 IBM 最终意识到如果使用窄长方形作为孔洞,一张卡就可以放下更多的列了。每一列中孔洞的不同组合就可以表达不同的字符。像 029 型这样的拥有人性化设计的打孔机除了完成本质的打孔任务外,还会在打孔卡最上方打印出每一列所对应的字符。输入是数字就在对应的数字行上打孔。输入的是字母或符号就用一个在空间列的孔和一或俩个在数字列的孔的组合表示,例如字母 A 就用一个在第 12 空间行的空和一个数字 1 所在行的孔表示。这是一种顺序编码,在第一台打孔机被发明后,也叫 Hollerith 编码。这种编码只能表示相对较小的一套字符集,小写字母就没有包含在这套字符集中。如今一些聪明的工程师可能想知道为什么打卡不干脆使用二进制编码 —— 毕竟,有 12 行,你可以编码超过 4000 个字符。 使用 Hollerith 编码是因为它确保在单个列中出现不超过三个孔。这保留了卡的结构强度。二进制编码会带来太多的孔,会因为孔洞过于密集而断裂。打孔卡也有不同。在 20 世纪 60 年代,80 列虽然是标准,但表达的方式不一定相同。基础打孔卡是无标注的,但用于 COBOL 编程的打孔卡会把最后的 8 列保留,供标识数保存使用。这一标识数可以在打孔卡被打乱 (例如一叠打孔卡掉在地上了) 后用于自动排序。此外,第 7 列被用于表示本张打孔卡上的是否与上一张打孔卡一起构成一条语句。也就是说当你真的对 80 字符的限制感到绝望的时候,还可以用两张卡甚至更多的卡拼接成一条长语句。用于 FORTRAN 编程的打孔卡和 COBOL 打孔卡类似,但是定义的列不同。大学里使用的打孔卡通常会由其计算机中心加上水印,其它的设计则会在如 <a href="http://www.jkmscott.net/data/Punched%20card%20013.jpg">1976 年美国独立 200 周年</a> 的特殊场合才会加入。最终,这些打孔卡都要被计算机读取和计算。IBM 出售的 System/360 大型计算机的外设 IBM 2540 可以以每分钟 1000 张打孔卡的速度读取这些卡片<a href="#fn2">2</a> 。IBM 2540 使用电刷扫过每张打孔卡,电刷通过孔洞就可以接触到卡片后面的金属板完成一次读取。一旦读取完毕,System/360 大型计算机就会把每张打孔卡上的数据使用一种定长的 8 位编码保存,这种编码是扩增二进式十进交换码Extended Binary Coded Decimal Interchange Code,简写为 EBCDIC 编码。它是一种二进制编码,可以追溯自早期打孔卡所使用的 BCDIDC 编码 —— 其 6 位编码使用低 4 位表示数字行,高 2 位表示空间行。程序员们在打孔卡上编写完程序后,会把卡片们交给计算机操作员,操作员们会把这些卡片放入 IBM 2540 ,再把打印结果交给程序员。那时的程序员大多都没有见过计算机长什么样。程序员们真正能见到的是很多打孔机。029 型打孔机虽然不是计算机,但这并不意味着它不是一台复杂的机器。看看这个由密歇根大学University of Michigan计算机中心在 1967 年制作的<a href="https://www.youtube.com/watch?v=kaQmAybWn-w">教学视频</a>,你就能更好的理解使用一台 029 型打孔机是什么情形了。我会尽可能在这里总结这段视频,但如果你不去亲自看…
我知道这很学院派,可一行超过 80 个字符的代码还是让我抓狂。我也在网上见过不少人认为即使在现代的视网膜屏幕下也应当采用行长度为 80 个字符的标准,可他们都不理解我对破坏这一标准的怒火,就算多 1 个字符也不行。在这一标准的黄金时期,一行代码的长度几乎不会超过 80 个字符的限制。在那时,这一限制是物理的,没有第 81 列用于存放第 81 个字符。每一个试图把函数名起的又长又臭的程序员都会在短暂的愉悦后迎来更多的麻烦,而这仅仅是因为没有足够的空间放下整个函数的声明。这一黄金时期也是打孔卡punch card编程时期。在 20 世纪 60 年代,IBM 打孔卡设立了标准,这个标准就是打孔卡的宽度为 80 列。这个 80 列标准在后来的电传打字机和哑终端时期得以延续,并逐渐成为操作系统中隐藏的细节。时至今日,即使我们用上了更大、更好的屏幕,偏向于使用更长的标识符而不是类似 <code>iswcntrl()</code> 这样令人难以猜测的函数名,可当你打开新的终端模拟器窗口时,默认的宽度依然是 80 个字符。从 Quora 上的很多问题中可以发现,很多人并不能想象如何使用打孔卡给计算机编程。我承认,在很长的一段时间里我也不能理解打孔卡编程是如何工作的,因为这让我想到就像劳工一样不停的给这些打孔卡打孔。当然,这是一个误解,程序员不需要亲自给打孔卡打孔,就像是火车调度员不用亲自扳道岔。程序员们有打孔机card punch machines(也被称为键控打孔机key punches),这让他们可以使用打字机式的键盘给打孔卡打孔。这样的设备在 19 世纪 90 年代时就已经不是什么新技术了。那时,最为广泛使用的打孔机之一便是 IBM 029 型打孔机。就算在今天,它也许是最棒的打孔机。<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201812/25/144551dtit8dpgprga5p3n.jpg">Media</a>IBM 029 型打孔机在 1964 年作为 IBM 的 System/360 大型电脑的配件发售的。System/360 是计算系统与外设所组成的一个系列,在 20 世纪 60 年代晚期,它几乎垄断了整个大型计算机市场。就像其它 System/360 外设一样,029 型打孔机也是个大块头。那时,计算机和家具的界限还很模糊,但 029 型打孔机可不是那种会占领你的整张桌子的机器。它改进自 026 型打孔机,增加了新的字符支持,如括号,总体上也更加安静。与前辈 026 型所展出 20 世纪 40 年代的圆形按钮与工业化的样貌相比,029 型的按键方正扁平、功能按键还有酷炫的蓝色高亮提示。它的另一个重要买点是它能够在数字区numeric field左侧自动的填充 0 ,这证明了 JavaScript 程序员不是第一批懒得自己做左填充left-padding的程序员。(LCTT 译注:这项功能需要额外的 4 张 <a href="https://en.wikipedia.org/wiki/IBM_Standard_Modular_System">标准模块系统卡</a>SMS card才能使用。例如设置数字区域长度为 6 列时,操作员只需要输入 73 ,打孔机会自动填充起始位置上的 4 个 0 ,故最终输出 000073。<a href="https://en.wikipedia.org/wiki/Keypunch#IBM_029_Card_Punch">更多信息</a>)等等!你说的是 IBM 在 1964 年发布了全新的打孔机?你知道那张在贝尔实验室拍摄的 Unix 之父正在使用电传打字机的照片吗?那是哪一年的来着?1970?打孔机不是应该在 20 世纪 60 年代中期到晚期时就过时了吗?是的,你也许会奇怪,为什么直到 1984 年,IBM 的产品目录中还会出现 029 型打孔机的身影 <a href="#fn1">1</a>。事实上,直到 20 世纪 70 年代,大多数程序员仍然在使用打孔卡编程。其实二战期间就已经有人在用电传打字机了,可那时并没能普及。客观的讲,电传打字机几乎和打孔卡一样古老。也许和你想象的恰恰相反,并不是电传打字机本身限制了它的普及,而是计算时间。人们拒绝使用电传打字机的原因是,它是可交互的,它和计算机使用“在线”的传输方式online mode of communication。在以 Unix 为代表的分时操作系统被发明前,你和电脑的交互会被任何人的使用而打断,而这一点延迟通常意味着几千美元的损失。所以程序员们普遍选择离线地使用打孔机编程,再将打孔卡放入大型计算机中,作为批任务batch job执行。在那时,还没有即廉价又可靠的存储设备,可打孔卡的廉价优势已经足够让它成为那时最流行的数据存储方式了。那时的程序是书架上一摞打孔卡而不是硬盘里的一堆文件。那么实际使用 IBM 029 型打孔机是个什么样子呢?这很难向没有实际看过打孔卡的人解释。一张打孔卡通常有 12 行 80 列。打孔卡下面是从 1 到 9 的数字行digit rows,打孔卡上的每一列都有这些行所对应的数字。最上面的三行是空间行zone rows,通常由两行空白行和一行 0 行组成。第 12 行是打孔卡最顶部的行,接下来是 11 行,随后是从数字 0 到 9 所在的行。这个有点让人感到困惑的顺序的原因是打孔卡的上边缘被称为12 边12 edge、下边缘被称为 9 边9 edge。那时,为了让打孔卡便于整理,常常会剪去打孔卡的一个角。<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201812/25/144552j4ce2ze22qc8cdc5.png">Media</a>(LCTT 译注:可参考<a href="https://zh.wikipedia.org/wiki/EBCDIC">EBCDIC 编码</a>)在打孔卡发明之初,孔洞的形状是圆形的,但是 IBM 最终意识到如果使用窄长方形作为孔洞,一张卡就可以放下更多的列了。每一列中孔洞的不同组合就可以表达不同的字符。像 029 型这样的拥有人性化设计的打孔机除了完成本质的打孔任务外,还会在打孔卡最上方打印出每一列所对应的字符。输入是数字就在对应的数字行上打孔。输入的是字母或符号就用一个在空间列的孔和一或俩个在数字列的孔的组合表示,例如字母 A 就用一个在第 12 空间行的空和一个数字 1 所在行的孔表示。这是一种顺序编码,在第一台打孔机被发明后,也叫 Hollerith 编码。这种编码只能表示相对较小的一套字符集,小写字母就没有包含在这套字符集中。如今一些聪明的工程师可能想知道为什么打卡不干脆使用二进制编码 —— 毕竟,有 12 行,你可以编码超过 4000 个字符。 使用 Hollerith 编码是因为它确保在单个列中出现不超过三个孔。这保留了卡的结构强度。二进制编码会带来太多的孔,会因为孔洞过于密集而断裂。打孔卡也有不同。在 20 世纪 60 年代,80 列虽然是标准,但表达的方式不一定相同。基础打孔卡是无标注的,但用于 COBOL 编程的打孔卡会把最后的 8 列保留,供标识数保存使用。这一标识数可以在打孔卡被打乱 (例如一叠打孔卡掉在地上了) 后用于自动排序。此外,第 7 列被用于表示本张打孔卡上的是否与上一张打孔卡一起构成一条语句。也就是说当你真的对 80 字符的限制感到绝望的时候,还可以用两张卡甚至更多的卡拼接成一条长语句。用于 FORTRAN 编程的打孔卡和 COBOL 打孔卡类似,但是定义的列不同。大学里使用的打孔卡通常会由其计算机中心加上水印,其它的设计则会在如 <a href="http://www.jkmscott.net/data/Punched%20card%20013.jpg">1976 年美国独立 200 周年</a> 的特殊场合才会加入。最终,这些打孔卡都要被计算机读取和计算。IBM 出售的 System/360 大型计算机的外设 IBM 2540 可以以每分钟 1000 张打孔卡的速度读取这些卡片<a href="#fn2">2</a> 。IBM 2540 使用电刷扫过每张打孔卡,电刷通过孔洞就可以接触到卡片后面的金属板完成一次读取。一旦读取完毕,System/360 大型计算机就会把每张打孔卡上的数据使用一种定长的 8 位编码保存,这种编码是扩增二进式十进交换码Extended Binary Coded Decimal Interchange Code,简写为 EBCDIC 编码。它是一种二进制编码,可以追溯自早期打孔卡所使用的 BCDIDC 编码 —— 其 6 位编码使用低 4 位表示数字行,高 2 位表示空间行。程序员们在打孔卡上编写完程序后,会把卡片们交给计算机操作员,操作员们会把这些卡片放入 IBM 2540 ,再把打印结果交给程序员。那时的程序员大多都没有见过计算机长什么样。程序员们真正能见到的是很多打孔机。029 型打孔机虽然不是计算机,但这并不意味着它不是一台复杂的机器。看看这个由密歇根大学University of Michigan计算机中心在 1967 年制作的<a href="https://www.youtube.com/watch?v=kaQmAybWn-w">教学视频</a>,你就能更好的理解使用一台 029 型打孔机是什么情形了。我会尽可能在这里总结这段视频,但如果你不去亲自看…
YAML:可能并不是那么完美
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201901/07/235036g8naktbcmzmnrkpm.jpg">Media</a>我之前写过<a href="http://arp242.net/weblog/JSON_as_configuration_files-_please_dont.html">为什么将 JSON 用于人类可编辑的配置文件是一个坏主意</a>,今天我们将讨论 YAML 格式的一些常见问题。默认情况下不安全YAML 默认是不安全的。加载用户提供的(不可信的)YAML 字符串需要仔细考虑。<pre class="prettyprint linenums">!!python/object/applyMedias.systemargs: ['ls /']</pre>用 print(yaml.load(open('a.yaml'))) 运行它,应该给你这样的东西:<pre class="prettyprint linenums">bin etc lib lost+found opt root sbin tmp var sysboot dev efi home lib64 mnt proc run srv usr0</pre>许多其他语言(包括 Ruby 和 PHP <a href="#fn1">1</a> )默认情况下也不安全(LCTT 译注:这里应该说的是解析 yaml)。<a href="https://github.com/search?q=yaml.load&type=Code&utf8=%E2%9C%93">在 GitHub 上搜索 yaml.load</a> 会得到惊人的 280 万个结果,而 <a href="https://github.com/search?q=yaml.safe_load&type=Code&utf8=%E2%9C%93">yaml.safe_load</a> 只能得到 26000 个结果。提个醒,很多这样的 yaml.load() 都工作的很好,在配置文件中加载 yaml.load() 通常没问题,因为它通常(虽然并不总是!)来自“可靠源”,而且很多都来自静态的 YAML 测试文件。但是,人们还是不禁怀疑在这 280 万个结果中隐藏了多少漏洞。这不是一个理论问题。在 2013 年,<a href="https://www.sitepoint.com/anatomy-of-an-exploit-an-in-depth-look-at-the-rails-yaml-vulnerability/">正是由于这个问题,所有的 Ruby on Rails 应用程序都被发现易受</a>远程代码执行攻击。有人可能会反驳说这不是 YAML 格式的错误,而是那些库实现错误的的问题,但似乎大多数库默认不是安全的(特别是动态语言),所以事实上这是 YAML 的一个问题。有些人可能会反驳认为修复它就像用…
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201901/07/235036g8naktbcmzmnrkpm.jpg">Media</a>我之前写过<a href="http://arp242.net/weblog/JSON_as_configuration_files-_please_dont.html">为什么将 JSON 用于人类可编辑的配置文件是一个坏主意</a>,今天我们将讨论 YAML 格式的一些常见问题。默认情况下不安全YAML 默认是不安全的。加载用户提供的(不可信的)YAML 字符串需要仔细考虑。<pre class="prettyprint linenums">!!python/object/applyMedias.systemargs: ['ls /']</pre>用 print(yaml.load(open('a.yaml'))) 运行它,应该给你这样的东西:<pre class="prettyprint linenums">bin etc lib lost+found opt root sbin tmp var sysboot dev efi home lib64 mnt proc run srv usr0</pre>许多其他语言(包括 Ruby 和 PHP <a href="#fn1">1</a> )默认情况下也不安全(LCTT 译注:这里应该说的是解析 yaml)。<a href="https://github.com/search?q=yaml.load&type=Code&utf8=%E2%9C%93">在 GitHub 上搜索 yaml.load</a> 会得到惊人的 280 万个结果,而 <a href="https://github.com/search?q=yaml.safe_load&type=Code&utf8=%E2%9C%93">yaml.safe_load</a> 只能得到 26000 个结果。提个醒,很多这样的 yaml.load() 都工作的很好,在配置文件中加载 yaml.load() 通常没问题,因为它通常(虽然并不总是!)来自“可靠源”,而且很多都来自静态的 YAML 测试文件。但是,人们还是不禁怀疑在这 280 万个结果中隐藏了多少漏洞。这不是一个理论问题。在 2013 年,<a href="https://www.sitepoint.com/anatomy-of-an-exploit-an-in-depth-look-at-the-rails-yaml-vulnerability/">正是由于这个问题,所有的 Ruby on Rails 应用程序都被发现易受</a>远程代码执行攻击。有人可能会反驳说这不是 YAML 格式的错误,而是那些库实现错误的的问题,但似乎大多数库默认不是安全的(特别是动态语言),所以事实上这是 YAML 的一个问题。有些人可能会反驳认为修复它就像用…
JSON 的兴起与崛起
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201901/14/115006ibcnakngxqxi6lnl.png">Media</a>JSON 已经占领了全世界。当今,任何两个应用程序彼此通过互联网通信时,可以打赌它们在使用 JSON。它已被所有大型企业所采用:十大最受欢迎的 web API 接口列表中(主要由 Google、Facebook 和 Twitter 提供),仅仅只有一个 API 接口是以 XML 的格式开放数据的。<a href="#fn1">1</a> 这个列表中的 Twitter API 为此做了一个鲜活的注脚:其对 XML 格式的支持到 2013 年结束,其时发布的新版本的 API 取消 XML 格式,转而仅使用 JSON。JSON 也在程序编码级别和文件存储上被广泛采用:在 Stack Overflow(LCTT 译注:一个面向程序员的问答网站)上,现在更多的是关于 JSON 的问题,而不是其他的数据交换格式。<a href="#fn2">2</a><a href="https://dn-linuxcn.qbox.me/data/attachment/album/201901/14/115020iuvmnv5hfaaaovrq.svg">Media</a>XML 仍然在很多地方存在。网络上它被用于 SVG 和 RSS / Atom 信息流。Android 开发者想要获得用户权限许可时,需要在其 APP 的 manifest 文件中声明 —— 此文件是 XML 格式的。XML 的替代品也不仅仅只有 JSON,现在有很多人在使用 YAML 或 Google 的 Protocol Buffers 等技术,但这些技术的受欢迎程度远不如 JSON。目前来看,JSON 是应用程序在网络之间通信的首选协议格式。考虑到自 2005 年来 Web 编程世界就垂涎于 “异步 JavaScript 和 XML” 而非 “异步 JavaScript 和 JSON” 的技术潜力,你可以发现 JSON 的主导地位是如此的让人惊讶。当然了,这可能与这两种通信格式的受欢迎程度无关,而仅反映出缩写 “AJAX” 似乎比 “AJAJ” 更具吸引力。但是,即使在…
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201901/14/115006ibcnakngxqxi6lnl.png">Media</a>JSON 已经占领了全世界。当今,任何两个应用程序彼此通过互联网通信时,可以打赌它们在使用 JSON。它已被所有大型企业所采用:十大最受欢迎的 web API 接口列表中(主要由 Google、Facebook 和 Twitter 提供),仅仅只有一个 API 接口是以 XML 的格式开放数据的。<a href="#fn1">1</a> 这个列表中的 Twitter API 为此做了一个鲜活的注脚:其对 XML 格式的支持到 2013 年结束,其时发布的新版本的 API 取消 XML 格式,转而仅使用 JSON。JSON 也在程序编码级别和文件存储上被广泛采用:在 Stack Overflow(LCTT 译注:一个面向程序员的问答网站)上,现在更多的是关于 JSON 的问题,而不是其他的数据交换格式。<a href="#fn2">2</a><a href="https://dn-linuxcn.qbox.me/data/attachment/album/201901/14/115020iuvmnv5hfaaaovrq.svg">Media</a>XML 仍然在很多地方存在。网络上它被用于 SVG 和 RSS / Atom 信息流。Android 开发者想要获得用户权限许可时,需要在其 APP 的 manifest 文件中声明 —— 此文件是 XML 格式的。XML 的替代品也不仅仅只有 JSON,现在有很多人在使用 YAML 或 Google 的 Protocol Buffers 等技术,但这些技术的受欢迎程度远不如 JSON。目前来看,JSON 是应用程序在网络之间通信的首选协议格式。考虑到自 2005 年来 Web 编程世界就垂涎于 “异步 JavaScript 和 XML” 而非 “异步 JavaScript 和 JSON” 的技术潜力,你可以发现 JSON 的主导地位是如此的让人惊讶。当然了,这可能与这两种通信格式的受欢迎程度无关,而仅反映出缩写 “AJAX” 似乎比 “AJAJ” 更具吸引力。但是,即使在…
计算机实验室之树莓派:课程 1 OK01
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201901/19/163408xpyibrbyq5d4q5cf.jpg">Media</a>OK01 课程讲解了树莓派如何入门,以及在树莓派上如何启用靠近 RCA 和 USB 端口的 OK 或 ACT 的 LED 指示灯。这个指示灯最初是为了指示 OK 状态的,但它在第二版的树莓派上被改名为 ACT。1、入门我们假设你已经访问了<a href="https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/downloads.html">下载</a>页面,并且已经获得了必需的 GNU 工具链。也下载了一个称为操作系统模板的文件。请下载这个文件并在一个新目录中解开它。2、开始现在,你已经展开了这个模板文件,在 source 目录中创建一个名为 main.s 的文件。这个文件包含了这个操作系统的代码。具体来看,这个文件夹的结构应该像下面这样:<pre class="prettyprint linenums">build/ (empty)source/ main.skernel.ldLICENSEMakefile</pre>用文本编辑器打开 main.s 文件,这样我们就可以输入汇编代码了。树莓派使用了称为 ARMv6 的汇编代码变体,这就是我们即将要写的汇编代码类型。扩展名为 .s 的文件一般是汇编代码,需要记住的是,在这里它是 ARMv6 的汇编代码。首先,我们复制下面的这些命令。<pre class="prettyprint linenums">.section .init.globl _start_start:</pre>实际上,上面这些指令并没有在树莓派上做任何事情,它们是提供给汇编器的指令。汇编器是一个转换程序,它将我们能够理解的汇编代码转换成树莓派能够理解的机器代码。在汇编代码中,每个行都是一个新的命令。上面的第一行告诉汇编器 <a href="#fn1">1</a> 在哪里放我…
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201901/19/163408xpyibrbyq5d4q5cf.jpg">Media</a>OK01 课程讲解了树莓派如何入门,以及在树莓派上如何启用靠近 RCA 和 USB 端口的 OK 或 ACT 的 LED 指示灯。这个指示灯最初是为了指示 OK 状态的,但它在第二版的树莓派上被改名为 ACT。1、入门我们假设你已经访问了<a href="https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/downloads.html">下载</a>页面,并且已经获得了必需的 GNU 工具链。也下载了一个称为操作系统模板的文件。请下载这个文件并在一个新目录中解开它。2、开始现在,你已经展开了这个模板文件,在 source 目录中创建一个名为 main.s 的文件。这个文件包含了这个操作系统的代码。具体来看,这个文件夹的结构应该像下面这样:<pre class="prettyprint linenums">build/ (empty)source/ main.skernel.ldLICENSEMakefile</pre>用文本编辑器打开 main.s 文件,这样我们就可以输入汇编代码了。树莓派使用了称为 ARMv6 的汇编代码变体,这就是我们即将要写的汇编代码类型。扩展名为 .s 的文件一般是汇编代码,需要记住的是,在这里它是 ARMv6 的汇编代码。首先,我们复制下面的这些命令。<pre class="prettyprint linenums">.section .init.globl _start_start:</pre>实际上,上面这些指令并没有在树莓派上做任何事情,它们是提供给汇编器的指令。汇编器是一个转换程序,它将我们能够理解的汇编代码转换成树莓派能够理解的机器代码。在汇编代码中,每个行都是一个新的命令。上面的第一行告诉汇编器 <a href="#fn1">1</a> 在哪里放我…
Go 编程语言的简单介绍
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201902/09/000738div64l4tidkdv6vj.png">Media</a>(以下内容是我的硕士论文的摘录,几乎是整个 2.1 章节,向具有 CS 背景的人快速介绍 Go)Go 是一门用于并发编程的命令式编程语言,它主要由创造者 Google 进行开发,最初主要由 Robert Griesemer、Rob Pike 和 Ken Thompson 开发。这门语言的设计起始于 2007 年,并在 2009 年推出最初版本;而第一个稳定版本是 2012 年发布的 1.0 版本。<a href="#fn1">1</a>Go 有 C 风格的语法(没有预处理器)、垃圾回收机制,而且类似它在贝尔实验室里被开发出来的前辈们:Newsqueak(Rob Pike)、Alef(Phil Winterbottom)和 Inferno(Pike、Ritchie 等人),使用所谓的 Go 协程goroutines和信道channels(一种基于 Hoare 的“通信顺序进程”理论的协程)提供内建的并发支持。<a href="#fn2">2</a>Go 程序以包的形式组织。包本质是一个包含 Go 文件的文件夹。包内的所有文件共享相同的命名空间,而包内的符号有两种可见性:以大写字母开头的符号对于其他包是可见,而其他符号则是该包私有的:<pre class="prettyprint linenums">func PublicFunction() { fmt.Println("Hello world")}func privateFunction() { fmt.Println("Hello package")}</pre>类型Go 有一个相当简单的类型系统:没有子类型(但有类型转换),没有泛型,没有多态函数,只有一些基本的类型:基本类型:int、int64、int8、uint、float32、float64 等structinterface:一组方法的集合map[K, V]:一个从键类型到值类型的映射[number]Type:一些 Type 类型的元素组成的数组[]Type:某种类型的切片(具有长度和功能的数组的指针)chan Type:一个线&#…
<a href="https://dn-linuxcn.qbox.me/data/attachment/album/201902/09/000738div64l4tidkdv6vj.png">Media</a>(以下内容是我的硕士论文的摘录,几乎是整个 2.1 章节,向具有 CS 背景的人快速介绍 Go)Go 是一门用于并发编程的命令式编程语言,它主要由创造者 Google 进行开发,最初主要由 Robert Griesemer、Rob Pike 和 Ken Thompson 开发。这门语言的设计起始于 2007 年,并在 2009 年推出最初版本;而第一个稳定版本是 2012 年发布的 1.0 版本。<a href="#fn1">1</a>Go 有 C 风格的语法(没有预处理器)、垃圾回收机制,而且类似它在贝尔实验室里被开发出来的前辈们:Newsqueak(Rob Pike)、Alef(Phil Winterbottom)和 Inferno(Pike、Ritchie 等人),使用所谓的 Go 协程goroutines和信道channels(一种基于 Hoare 的“通信顺序进程”理论的协程)提供内建的并发支持。<a href="#fn2">2</a>Go 程序以包的形式组织。包本质是一个包含 Go 文件的文件夹。包内的所有文件共享相同的命名空间,而包内的符号有两种可见性:以大写字母开头的符号对于其他包是可见,而其他符号则是该包私有的:<pre class="prettyprint linenums">func PublicFunction() { fmt.Println("Hello world")}func privateFunction() { fmt.Println("Hello package")}</pre>类型Go 有一个相当简单的类型系统:没有子类型(但有类型转换),没有泛型,没有多态函数,只有一些基本的类型:基本类型:int、int64、int8、uint、float32、float64 等structinterface:一组方法的集合map[K, V]:一个从键类型到值类型的映射[number]Type:一些 Type 类型的元素组成的数组[]Type:某种类型的切片(具有长度和功能的数组的指针)chan Type:一个线&#…