Go程序设计语言导读

简介:
前  言
The Go Programming Language
“Go是一种开源的程序设计语言,它意在使得人们能够方便地构建简单、可靠、高效的软件。”(来自Go官网golang.org)
Go在2007年9月形成构想,并于2009年11月发布,其发明人是Robert Griesemer、Rob Pike和Ken Thompson,这几位都任职于Google。该语言及其配套工具集使得编译和执行既富有表达力又高效,而且使得程序员能够轻松写出可靠、健壮的程序。
Go和C从表面上看起来相似,而且和C一样,它也是专业程序员使用的一种工具,兼有事半功倍之效。但是Go远不止是C的一种升级版本。基于多种其他语言,它取其精华,去其糟粕。它实现并发功能的设施是全新的、高效的,实现数据抽象和面向对象的途径是极其灵活的。它还实现了自动化的内存管理,或称为垃圾回收。
Go特别适用于构建基础设施类软件(如网络服务器),以及程序员使用的工具和系统等。但它的的确确是一种通用语言,而且在诸多领域(如图像处理、移动应用和机器学习)中都能发现它的身影。它在很多场合下用于替换无类型的脚本语言,这是由于它兼顾了表达力和安全性:Go程序通常比动态语言程序运行速度要快,由于意料之外的类型错误而导致崩溃的情形更是少得多。
Go是个开源项目,所以其编译器、库和工具的源代码是人人皆可免费取得的。来自全世界的社区都在积极地向这个项目贡献代码。Go的运行环境包括类UNIX系统——Linux、FreeBSD、OpenBSD和Mac OS X,还有Plan 9和Microsoft Windows。只要在其中一个环境中写了一个程序,那么基本上不加修改它就可以运行在其他环境中。
本书旨在帮助读者立刻开始使用Go,以及熟练掌握这门语言,并充分地利用Go的语言特性和标准库来撰写清晰的、符合习惯用法的、高效的程序。
Go的起源
和生物学物种一样,成功的语言会繁衍后代,这些后代语言会从它们的祖先那里汲取各种优点;有时候,语言间的“混血”会产生异常强大的力量;在一些罕见情况下,某个重大的语言特性也可能凭空出现而并无先例。通过考察语言间的影响,我们可以学得不少知识,比如语言为什么会变成这个样子,以及它适合用于哪些环境,等等。
下图展示了更早出现的程序设计语言对Go产生的最重要影响。
Go有时会称为“类C语言”或“21世纪的C”。从C中,Go继承了表达式语法、控制流语句、基本数据类型、按值调用的形参传递和指针,但比这些更重要的是,继承了C所强调的要点:程序要编译成高效的机器码,并自然地与所处的操作系统提供的抽象机制相配合。
可是,Go的家谱中还有其他祖先。产生主要影响的是由Niklaus Wirth设计的、以Pascal为发端的一个语言支流。Modula-2启发了包概念。Oberon消除了模块接口文件和模块实现文件之间的差异。Oberon-2影响了包、导入和声明的语法,并提供了方法声明的语法。

Go的另一支世系祖先——它使得Go相对于当下的程序设计语言显得卓然不群,是在贝尔实验室开发的一系列名不见经传的研究用语言。这些语言都受到了通信顺序进程(Communicating Sequential Process,CSP)的启发,CSP由Tony Hoare于1978年在发表的关于并发性基础的开创性论文中提出。在CSP中,程序就是一组无共享状态进程的并行组合,进程间的通信和同步采用通道完成。不过,Hoare提出的CSP是一种形式语言,仅用于描述并发性的基本概念,并不是一种用来撰写可执行程序的程序设计语言。
Rob Pike等人开始动手做一些实验,尝试把CSP实现为真正的语言。第一种这样的语言称为Squeak(“和鼠类沟通的语言”),它是一种用于处理鼠标和键盘事件的语言,其中具有静态创建的通道。紧接着它的是Newsqueak,它具有类C的语句和表达式语法,以及类Pascal的类型记法。它是一种纯粹的函数式语言,具有垃圾回收功能,同样也以管理键盘、鼠标和窗口事件为目标。通道变成了“一等”值(first-class value),它可以动态创建并用变量存储。
Plan 9操作系统将这些思想都纳入一种称为Alef的语言中。Alef尝试将Newsqueak改造成一种可用的系统级程序设计语言,但垃圾回收功能的缺失使得它在处理并发性时捉襟见肘。
Go中的其他结构也会不时显示出某些并非来自祖先的基因。例如,iota多多少少有点APL的影子,而嵌套函数的词法作用域则来自Scheme(以及由之而来的大部分语言)。在Go语言中,也可以发现全新的变异。Go中新颖的slice不仅为动态数组提供了高效的随机访问功能,还允许旧式链表的复杂共享机制。另外,defer语句也是Go中新引入的。
Go项目
所有的程序设计语言都反映了其发明者的程序设计哲理,其中相当大的一部分是对于此前语言已知缺点的应对措施。Go这个项目也诞生于挫败感,这种挫败感来源于Google的若干复杂性激增的软件系统。(而且这个问题绝非Google所独有的。)
“复杂性是以乘积方式增长的。”Rob Pike如是说。为了修复某个问题,一点点地将系统的某个部分变得更加复杂,这不可避免地也给其他部分增加了复杂性。在不断要求增加系统功能、选项和配置,以及快速发布的压力之下,简单性往往被忽视了(尽管长期来看,简单性才是好软件的不二法门)。
要实现简单性,就要求在项目的一开始就浓缩思想的本质,并在项目的整个生命周期制定更具体的准则,以分辨出哪些变化是好的,哪些是坏的或致命的。只要足够努力,好的变化就既可以实现目的,又能够不损害Fred Brooks所谓软件设计上的“概念完整性”。坏的变化就做不到这一点,而致命的变化则会牺牲“简单性”去换得浅薄的“方便性”。但是,只有通过设计上的简单性,系统才能在增长过程中保持稳定、安全和自洽。
Go项目不仅包括该语言本身及其工具和标准库,还有决不能忽视的一点,就是它保持极端简单性的行为文化。在高级语言中,Go出现得较晚,因而有一定后发优势,它的基础部分实现得不错:有垃圾回收、包系统、一等公民函数、词法作用域、系统调用接口,还有默认用UTF-8编码的不可变字符串。但相对来说,它的语言特性不多,而且不太会增加新特性了。比如,它没有隐式数值类型强制转换,没有构造或析构函数,没有运算符重载,没有形参默认值,没有继承,没有泛型,没有异常,没有宏,没有函数注解,没有线程局部存储。这门语言成熟而且稳定,并且保证兼容更早版本:在旧版本的Go语言中写的程序,可以在新版本的编译器和标准库下编译与运行。
Go的类型系统足可以使程序员避免在动态语言中会无意犯下的绝大多数错误,但相对而言,它在带类型的语言中又算是类型系统比较简单的。其实现方法有时候会导致类型框架林立却彼此孤立的“无类型”程序设计风格,并且Go程序员在类型方面不会像C++或Haskell程序员那样走极端——反复表达类型安全性以证明语言是基于类型的。但在实际工作中,Go却能为程序员提供只有强类型的系统才能实现的安全性和运行时性能,而不让程序员承担其复杂性。
Go提倡充分利用当代计算机系统设计,尤其强调局部性的重要意义。其内置数据类型和大多数库数据结构都经过仔细设计,力求以自然方式工作,而不要求显式的初始化或隐式的构造函数。这么一来,隐藏在代码中的内存分配和内存写入就大大减少了。Go中的聚合类型(结构体和数组)都以直接方式持有其元素,与使用间接字段的语言相比,它需要更少的存储空间以及更少的分配操作和指针间接寻址操作。正如前面提到的那样,由于现代计算机都是并行工作的,因此Go具有基于CSP的并行特性。Go还提供了变长栈来运行其轻量级线程,或称为goroutine。这个栈初始时非常小,所以创建一个goroutine的成本极低,创建100万个也完全可以接受。
Go标准库常常称作“自带电池的语言”,它提供了清晰的构件,以及用于I/O、文本处理、图形、加密、网络、分布式应用的API,而且对许多标准文件格式和协议都提供了支持。Go的库和工具充分地尊重惯例,避免了配置和解释,从而简化了程序逻辑,提高了多种多样的Go程序之间的相似性,使得它更容易学习和掌握。采用go工具构建的项目,仅使用文件和标识符的名字(在极少情况下使用特殊注释),就可以推断出一个项目使用的所有库、可执行文件、测试、性能基准、示例、平台相关变体,以及文档。Go的源代码中就包含了构建的规格说明。 
本书结构
我们假定你已用一两种其他语言编过程序,可能是像C、C++或Java那样的编译型语言,也可能是像Python、Ruby或JavaScript那样的解释型语言,所以本书不会像针对一个零基础的初学者那样事无巨细地讲述所有内容。表面上的语法大体雷同,变量、常量、表达式、控制流和函数也一样。
第1章是关于Go的基础结构的综述,通过十几个完成日常任务(包括读写文件、格式化文本、创建图像,以及在Internet客户端和服务器之间通信)的程序来介绍这门语言。
第2章讲述Go程序的组成元素——声明、变量、新类型、包和文件,以及作用域。第3章讨论数值、布尔量、字符串、常量,还解释如何处理Unicode。第4章描述复合类型,即使用简单类型构造的类型,形式有数组、map、结构体,还有slice(Go中动态列表的实现)。第5章概述函数,并讨论错误处理、宕机(panic)和恢复(recover),以及defer语句。
可以看出,第1~5章是基础性的,其内容是任何主流命令式语言都有的。Go的语法和风格可能与其他语言有所不同,但大多数程序员都能很快掌握这些内容。余下的章节重点讨论Go语言中与惯常做法有一定区别的内容,包括方法、接口、并发、包、测试和反射。
Go以一种不同寻常的方式来诠释面向对象程序设计。它没有类继承,甚至没有类。较复杂的对象行为是通过较简单的对象组合(而非继承)完成的。方法可以关联到任何用户定义的类型,而不一定是结构体。具体类型和抽象类型(即接口)之间的关系是隐式的,所以一个具体类型可能会实现该类型设计者没有意识到其存在的接口。第6章讲述方法,第7章讲述接口。
第8章介绍Go的并发性处理途径,它基于CSP思想,采用goroutine和通道实现。第9章则讨论并发性中基于共享变量的一些传统话题。
第10章讨论包,也就是组织库的机制。该章也说明如何高效地利用go工具,仅仅这个工具,就提供了编译、测试、性能基准测试、程序格式化、文档,以及完成许多其他任务的功能。
第11章讨论测试,在这里Go采取了显著的轻量级途径,避免了重重抽象的框架,转而使用简单的库和工具。测试库提供了一个基础,在其之上根据需要可以构建更复杂的抽象。
第12章讨论反射,即程序在执行期间考察自身表示方式的能力。反射是一种强大的工具,不过要慎重使用它,该章通过演示如何用它来实现某些重要的Go库,解释了如何统筹兼顾。第13章解释低级程序设计的细节(它运用unsafe包来绕过Go的类型系统),以及什么时候适合这样做。
每章都配以一定数量的练习,可以用来测试你对Go的理解,或者探索对书中示例的扩展和变形。
除了最简单的示例代码以外,书中所有的示例代码都可以从gopl.io网站的公开Git仓库下载。每个示例以其包的导入路径开头和命名,从而能够方便地使用go get命令获取、构建和安装。你需要选取一个目录作为你的Go工作空间,并使GOPATH环境变量指向它。在必要时,go工具会创建该目录。例如:

要运行这些例子,至少需要使用1.5版本的Go语言。

如果你的计算机上的go工具版本太旧或者缺失,请按https://golang.org/doc/install上的步骤操作。
更多信息来源
关于Go的更多信息,最好的来源就是Go的官方网站:https://golang.org。其中列出了文档供读者访问,包括Go程序设计语言规范、标准包等。其中还列出Go语言教程,指导如何撰写Go程序,以及如何撰写好的Go程序,还有大量在线文本和视频资源,这些都是本书的主要补充资源。位于blog.golang.org的Go博客发布的是关于Go的最好文章,内容涉及该语言当下的状态、未来的计划、会议方面的报告,还有Go相关的大量话题的深度解读。
Go官网在线访问最有用的一个方面(这也是纸质书的一个令人遗憾的限制),就是提供了从描述Go程序的网页上直接运行的能力。这种功能由位于play.golang.org的Go训练场(Playground)提供,也可以嵌入其他页面,比如golang.org的首页,或者由godoc工具提供的文档页面。
训练场为读者对简短的程序执行简单的实验提供了方便,有助于读者检验自己对语法、语义和库包的理解,并且它在很多方面取代了其他语言中的读取–求值–输出循环(Read-Eval-Print Loop,REPL)。它的永久URL对于共享Go代码段、报告bug或提出建议都很有用。
在训练场的基础之上,位于tour.golang.org的Go Tour就是一系列简短的交互式课程(内容是Go语言的基础思想和结构),也是学习整门语言的系统资源。
训练场和Go Tour的主要缺点在于它只允许导入标准库,并且很多库特性(比如网络库)都出于可操作性或安全原因限制使用。而要编译和运行每个程序,都要求Internet连接。所以,欲进行更详尽的实验,需要在本机上运行Go程序。幸运的是,下载过程相当简单,从golang.org获取Go的安装版本并开始撰写和运行你自己的Go程序,用不了几分钟。
由于Go是个开源项目,因此你可以从https://golang.org/pkg上在线读取标准库中的任何类型或函数的代码,每个供下载的版本都同样包含这些代码。请使用这些代码来弄明白某些程序的运行原理、回答关于程序细节的问题,也可以用它们来学一学专家是如何写出一流的Go代码的。
致谢
Go团队的核心成员Rob Pike和Russ Cox仔细通读了初稿数次,他们从遣词造句到整体结构都对本书提出了重要的建议。在准备本书的日语版时,柴田芳树所做的贡献大大超过了他负担的义务,他的火眼金睛发现了英语版中的上下文不一致性,以及代码中的错误。非常感谢Brian Goetz、Corey Kosak、Arnold Robbins、Josh Bleecher Snyder以及Peter Weinberger对全书初稿进行彻底的审查并提出批判性的建议。
感谢Sameer Ajmani、Ittai Balaban、David Crawshaw、Billy Donohue、Jonathan Feinberg、Andrew Gerrand、Robert Griesemer、John Linderman、Minux Ma、Bryan Mills、Bala Natarajan、Cosmos Nicolaou、Paul Staniforth、Nigel Tao以及Howard Trickey提供的诸多有用建议。也感谢David Brailsford和Raph Levien的排版建议。
Addison-Wesley出版社的编辑Greg Doench策划了本书,而且一直不断地给予帮助。Addison-Wesley的制作团队——John Fuller、Dayna Isley、Julie Nahil、Chuti Prasertsith以及Barbara Wood——非常杰出,给予作者大量的支持。
Alan Donovan想要感谢Google的Sameer Ajmani、Chris Demetriou、Walt Drummond以及Reid Tatge让他腾出时间来写作这本书,还要感谢Stephen Donovan的建议和及时的鼓励。最重要的是,感谢他的妻子Leila Kazemi无限的热情和长期的支持,谅解了他在家庭生活中的疏忽。
Brian Kernighan对他的朋友和同事深表谢意,他们对Kernighan花费了很长时间以通俗易懂的语言写作本书表现出了极大的耐心和理解。尤其是他的妻子Meg,她为Kernighan的写作以及太多的其他事务提供了不懈的支持。

目  录

第1章 入门  
第2章 程序结构  
第3章 基本数据  
第4章 复合数据类型  61
4.1 数组  61
4.2 slice  63
4.2.1 append函数  66
4.2.2 slice就地修改  69
4.3 map  71
4.4 结构体  76
4.4.1 结构体字面量  78
4.4.2 结构体比较  80
4.4.3 结构体嵌套和匿名成员  80
4.5 JSON  82
4.6 文本和HTML模板  87
第5章 函数  92
5.1 函数声明  92
5.2 递归  93
5.3 多返回值  96
5.4 错误  98
5.4.1 错误处理策略  99
5.4.2 文件结束标识  101
5.5 函数变量  102
5.6 匿名函数  104
5.7 变长函数  110
5.8 延迟函数调用  111
5.9 宕机  115
5.10 恢复  118
第6章 方法  120
6.1 方法声明  120
6.2 指针接收者的方法  122
6.3 通过结构体内嵌组成类型  124
6.4 方法变量与表达式  127
6.5 示例:位向量  128
6.6 封装  130
第7章 接口  133
7.1 接口即约定  133
7.2 接口类型  135
7.3 实现接口  136
7.4 使用flag.Value来解析参数  139
7.5 接口值  141
7.6 使用sort.Interface来排序  144
7.7 http.Handler接口  148
7.8 error接口  152
7.9 示例:表达式求值器  154
7.10 类型断言  160
7.11 使用类型断言来识别错误  161
7.12 通过接口类型断言来查询特性  162
7.13 类型分支  164
7.14 示例:基于标记的XML解析  166
7.15 一些建议  168
第8章 goroutine和通道  170
8.1 goroutine  170
8.2 示例:并发时钟服务器  171
8.3 示例:并发回声服务器  174
8.4 通道  176
8.4.1 无缓冲通道  177
8.4.2 管道  178
8.4.3 单向通道类型  180
8.4.4 缓冲通道  181
8.5 并行循环  183
8.6 示例:并发的Web爬虫  187
8.7 使用select多路复用  190
8.8 示例:并发目录遍历  192
8.9 取消  195
8.10 示例:聊天服务器  198
第9章 使用共享变量实现并发  201
9.1 竞态  201
9.2 互斥锁:sync.Mutex  205
9.3 读写互斥锁:sync.RWMutex  208
9.4 内存同步  208
9.5 延迟初始化:sync.Once  210
9.6 竞态检测器  212
9.7 示例:并发非阻塞缓存  212
9.8 goroutine与线程  218
9.8.1 可增长的栈  219
9.8.2 goroutine调度  219
9.8.3 GOMAXPROCS  219
9.8.4 goroutine没有标识  220
第10章 包和go工具  221
10.1 引言  221
10.2 导入路径  221
10.3 包的声明  222
10.4 导入声明  223
10.5 空导入  223
10.6 包及其命名  225
10.7 go工具  226
10.7.1 工作空间的组织  227
10.7.2 包的下载  228
10.7.3 包的构建  229
10.7.4 包的文档化  231
10.7.5 内部包  232
10.7.6 包的查询  233
第11章 测试  235
11.1 go test工具  235
11.2 Test函数  236
11.2.1 随机测试  239
11.2.2 测试命令  240
11.2.3 白盒测试  242
11.2.4 外部测试包  245
11.2.5 编写有效测试  246
11.2.6 避免脆弱的测试  247
11.3 覆盖率  248
11.4 Benchmark函数  250
11.5 性能剖析  252
11.6 Example函数  254
第12章 反射  256
12.1 为什么使用反射  256
12.2 reflect.Type和reflect.Value  257
12.3 Display:一个递归的值显示器  259
12.4 示例:编码S表达式  263
12.5 使用reflect.Value来设置值  266
12.6 示例:解码S表达式  268
12.7 访问结构体字段标签  271
12.8 显示类型的方法  273
12.9 注意事项  274
第13章 低级编程  276
13.1 unsafe.Sizeof、Alignof 和Offsetof  276
13.2 unsafe.Pointer  278
13.3 示例:深度相等  280
13.4 使用cgo调用C代码  282
13.5 关于安全的注意事项  286
相关文章

相关实验场景

更多