Go语言程序设计(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-02 01:25:26

点击下载

作者:[英]MarkSummerfield

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Go语言程序设计

Go语言程序设计试读:

译者序

关注过我的人可能都知道,我在新浪微博、《Go语言编程》一书中都非常高调地下了一个论断:Go语言将超过C、Java,成为未来十年最流行的语言。

为什么我可以如此坚定地相信,选择Go语言不会有错,并且相信Go语言会成为未来10年最流行的语言?除了Go语言的并发编程模型深得我心外,Go语言的各种语法特性显得那么深思熟虑、卓绝不凡,其对软件系统架构的领悟,让我深觉无法望其项背,处处带给我惊喜。

Go语言给我的第一个惊喜是大道至简的设计哲学。

Go语言是非常简约的语言。简约的意思是少而精。少就是指数级的多。Go语言极力追求语言特性的最小化,如果某个语法特性只是少写几行代码,但对解决实际问题的难度不会产生本质的影响,那么这样的语法特性就不会被加入。Go语言更关心的是如何解决程序员开发上的心智负担。如何减少代码出错的机会,如何更容易写出高品质的代码,是 Go 设计时极度关心的问题。

Go语言追求显式表达。任何封装都是有漏洞的,最佳的表达方式就是用最直白的表达方式,所以也有人称Go语言为“所写即所得”的语言。

Go语言也是非常追求自然(nature)的语言。Go不只是提供极少的语言特性,并极力追求语言特性最自然的表达,也就是这些语法特性被设计成恰如多少人期望的那样,尽量避免惊异。事实上,Go语言的语法特性上的争议是非常少的。这些也让Go语言的入门门槛变得非常低。

Go语言给我的第二个惊喜是最对胃口的并行支持。

我对服务端开发的探索,始于Erlang语言,并且认为Erlang风格并发模型的精髓是轻量级进程模型。然而,Erlang 除了语言本身不容易被程序员接受外,其基于进程邮箱做消息传递的并发编程模型也小有瑕疵。我曾经在C++中实现了一个名为CERL的网络库,刚开始在C++中完全模仿Erlang风格的并发编程手法,然而在我拿CERL库做云存储服务的实践中,发现了该编程模型的问题所在并做了相应的调整,这就是后来的CERL 2.0版本。有意思的是,CERL 2.0与Go语言的并行编程思路不谋而合。某种程度上来说,这种默契也是我创办七牛时,Go语言甚至语法特性都还没有完全稳定,我们技术选型就坚决地采纳了Go语言的重要原因。

Go语言给我的第三个惊喜是接口。

Go语言的接口,并非是你在Java和C#中看到的接口,尽管看起来有点像。Go语言的接口是非侵入式的接口,具体表现在实现一个接口不需要显式地进行声明。不过,让我意外的不是Go的非侵入式接口。非侵入式接口只是我接受Go语言的基础。在接口(或契约)的表达上,我一直认为Java和C#这些主流的静态类型语言都走错了方向。C++的模板尽管机制复杂,但是走在了正确的方向上。C++0x(后来的C++11)呼声很高的concept提案被否,着实让不少人伤了心。但Go语言的接口远不是非侵入式接口那么简单,它是Go语言类型系统的纲,这表现在以下几个方面。(1)只要某个类型实现了接口要的方法,那么我们就说该类型实现了此接口。该类型的对象可赋值给该接口。(2)作为1的推论,任何类型(包括基础类型如bool、int、string等)的对象都可以赋值给空接口interface{}。(3)支持接口查询。如果你曾经是Windows程序员,你会发现COM思想在Go语言中通过接口优雅呈现。并且Go语言吸收了其中最精华的部分,而COM中对象生命周期管理的负担,却因为Go语言基于gc方式的内存管理而不复存在。

Go语言给我的第四个意外惊喜是极度简化但完备的面向对象编程(OOP)方法。

Go语言废弃大量的OOP特性,如继承、构造/析构函数、虚函数、函数重载、默认参数等;简化的符号访问权限控制,将隐藏的this指针改为显式定义的receiver对象。Go语言让我看到了OOP编程核心价值原来如此简单——只是多数人都无法看透。

Go语言带给我的第五个惊喜是它的错误处理规范。

Go语言引入了内置的错误(error)类型以及defer关键字来编写异常安全代码,让人拍案叫绝。下面这个例子,我在多个场合都提过:

f, err := os.Open(file)

if err != nil {

...// 错误处理

return

}

defer f.Close()

...// 处理文件数据

Go语言带给我的第六个惊喜是它功能的内聚。

一个最典型的案例是Go语言的组合功能。对于多数语言来说,组合只是形成复合类型的基本手段,这一点只要想想C语言的struct就清楚了。但Go语言引入了匿名组合的概念,它让其他语言原本需要引入继承这样的新概念来完成事情,统一到了组合这样的一个基础上。

在C++中,你需要这样定义一个派生类:

class Foo : public Base {

...

};

在Go语言中你只要:

type Foo struct {

Base

...

}

更有甚者,Go语言的匿名组合允许组合一个指针:

type Foo struct {

*Base

...

}

这个功能可以实现C++中一个无比晦涩难懂的特性,叫“虚拟继承”。但同样的问题换成从组合角度来表达,直达问题的本质,清晰易懂。

Go语言带给我的第七个惊喜是消除了堆与栈的边界。

在 Go语言之前,程序员清楚地知道哪些变量在栈上,哪些变量在堆上。堆与栈是基于现代计算机系统的基础工作模型上形成的概念,Go语言屏蔽了变量定义在堆上还是栈上这样的物理结构,相当于封装了一个新的计算机工作模型。这一点看似与 Go语言显式表达的设计哲学不太一致,但我个人认为这是一项了不起的工作,而且与Go语言的显式表达并不矛盾。Go语言强调的是对开发者的程序逻辑(语义)的显式表达,而非对计算机硬件结构的显示表达。对计算机硬件结构的高度抽象,将更有助于Go语言适应未来计算机硬件发展的变化。

Go语言带给我的第八个惊喜是Go语言对C语言的支持。

可以这么说,Go语言是除了 Objective-C、C++这两门以兼容 C为基础目标的语言外的所有语言中,对C语言支持最友善的一个。什么语言可以直接嵌入C代码?只有Go。什么语言可以无缝调用C函数?只有Go。对C语言的完美支持,是Go快速崛起的关键支撑。还有比C语言更让人觊觎的社区财富吗?那是一个取之不尽的金矿。

总而言之,Go语言是一门非常具有变革性的语言。尽管40年(从1970年C语言诞生开始算起)来出现的语言非常之多,各有各的特色,让人眼花缭乱。但是我个人固执地认为,谈得上突破了C语言思想,将编程理念提高到一个新高度的,仅有Go语言而已。

Go语言很简单,但是具备极强的表现力。从目前的状态来说,Go语言主要关注服务器领域的开发,但这不会是Go语言的完整使命。

我们说 Go语言适合服务端开发,仅仅是因为它的标准库支持方面,目前是向服务端开发倾斜:

● 网络库(包括socket、http、rpc等);

● 编码库(包括json、xml、gob等);

● 加密库(各种加密算法、摘要算法,极其全面);

● Web(包括template、html支持)。

而作为桌面开发的常规组件:GDI和UI系统与事件处理,基本没有涉及。

尽管Go还很年轻,Go语言1.0版本在2012年3月底发布,到现在才近1年,然而Go语言已经得到了非常普遍的认同。在国外,有人甚至提出“Go语言将制霸云计算领域”。在国内,几乎所有你听到过名字的大公司(腾讯、阿里巴巴、京东、360、网易、新浪、金山、豆瓣等),都有团队对Go语言做服务端开发进行了小范围的实践。这是不能不说是一个奇迹。

Go语言是一门前途非常光明的语言,很少有语言在如此年轻的时候就得到如此热捧。

但因为年轻,导致了Go语言的书籍哪怕在全球都非常稀少。这本书由知名技术作家Mark Summerfield撰写,它会让你了解Go语言,按Go语言的方式思考,以及使用Go语言来编写高性能软件。一直以来,Summerfield的教学方式都是深入实践的。每一章节都提供了多个活生生的代码示例,它们都是经过精心设计的用于鼓励读者动手实验并且能够帮助读者快速掌握如何开发的。许式伟2013年6月

致谢

我写每一本技术书时都得到过来自他人的帮助与建议,本书也不例外。

我想特别感谢两个之前没有Go语言编程经验的程序员朋友——asmin Blanchette和Trenton Schulz。他们两个曾多年为我的书贡献诸多。他们对本书的反馈也让本书能更符合程序员初学Go语言时的需求。

来自Go语言核心开发者Nigel Tao的反馈也让本书受益良多。虽然我并未完全采纳他的所有建议,但是他的反馈总是能够提点我,进而给代码以及书的内容带来极大的改进。

此外,我得到过其他许多人的帮助,包括Go语言初学者David Boddie。他提供了一些有价值的反馈。同时,Go语言的开发者Ian Lance Taylor特别是Russ Cox为我解决了很多代码以及概念上的问题,他们提供的清晰准确的解释对本书的精确性有极大的贡献。

在撰写本书时,我在golang-nuts这个邮件列表里提了许多问题,每次提问总能从众多回邮件者那里收到深思熟虑且实用的回复。同时,Safari上的本书初稿读者也给了我许多反馈,从而让本书中的一些讲解清晰了很多。

意大利的软件公司 www.develer.com 以 Giovanni Bajo 个人的名义,给我提供免费的Mercurial代码库托管服务,让我在写作的漫长过程中能够静心思考。谢谢Lorenzo Mancini为我设置整个环境然后帮我打理它。同时,我也非常感谢Anton Bowers以及Ben Thompson,自2011年初起,我的网站www.qtrac.eu就托管在他们的网络服务器上。

谢谢Russel Winder在他的博客www.russel.org.uk上讨论软件专利的事情,附件B中有许多思想是从他那里来的。

然后,我要一如既往地感谢lout排版系统的作者Jeff Kingston,我所有的书以及许多其他写作项目都是用这个系统排版而成的。

特别感谢我的责任编辑Debra Willians Cauley,是他将本书成功带给出版社,同时也在本书的写作过程中提供了支持与实际帮助。

同时也感谢出版经理 Anna Popick,他再次将书的出版过程管理得如此好,也感谢校对人员Audrey Doyle的出色工作。

与以往一样,我还要感谢我的妻子Andrea,谢谢她的爱与支持。

引言

本书介绍如何使用Go语言的语言特性以及标准库中的常用包来进行地道的Go语言编程。同时,本书也设计成在学会 Go语言后依然有用的参考资料。为了实现这两个目标,这本书覆盖面非常广,尽量保证每一章只涵盖一个主题,各章之间会进行内容上的交叉引用。

从语言的设计精神来说,Go语言与 C 语言非常相似,是一门精小而高效的语言,它有便利的底层设施,如指针。不过Go语言还提供了许多只在高级或者非常高级的语言中才有的特性,如 Unicode 字符串、强大的内置数据结构、鸭子类型、垃圾收集和高层次的并发支持,使用通信而非常规的共享数据和锁方式。另外,Go语言还提供了一个庞大且覆盖面全的标准库。

虽然所有的Go语言特性或者编程范式都会以完整可运行的示例来详细讲解,但是本书还是假设读者有主流编程语言的经验,比如C、C++、Java、Python或其他类似的语言。

要学好任何一门语言,使用它进行编程都是必经之路。为此,本书采用完全面向实战的方式,鼓励读者亲自去练习书中的例子,尝试着去解决练习题中给出的问题,自己去写程序,以获得宝贵的实践经验。正如我以前写的书一样,本书中所引用的代码片段都是“活代码”。也就是说,这些代码自动提取自.go源文件,并直接嵌入到提供给出版商的PDF文件中,故此不会有剪切和粘贴错误,可以直接运行。只要有可能,本书都会提供小而全的程序或者包来作为贴近实际应用场景的例子。本书的例子、练习和解决方案都可以从www.qtrac.eu/gobook.html这个网址获得。

本书的主要目的是传授Go语言本身,虽然我们使用了Go语言标准库中的许多包,但不会试图全都涉及。这并不是问题,因为本书向读者提供了足够的Go语言知识来使用任何标准库中的包或者是任何第三方Go语言的包,当然还能够创建自己的包。

为什么是Go

Go语言始于2007年,当时只是Google内部的一个项目,其最初设计者是Robert Griesemer、Unix泰斗Rob Pike和Ken Thompson。2009年11月10日,Go语言以一个自由的开源许可方式公开亮相。Go语言由其原始设计者加上Russ Cox、Andrew Gerrand、Ian Lance Taylor以及其他许多人在内的一个Google团队开发。Go语言采取一种开放的开发模式,吸引了许多来自世界各地的开发者为这门语言的发展贡献力量。其中有些开发者获得了非常好的声望,因此他们也获得了与Google员工一样的代码提交权限。此外,Go Dashboard这个网站(godashboard.appspot.com/project)也提供了许多第三方的Go语言包。

Go语言是近15年来出现的最令人兴奋的新主流语言。它是第一个直接面向21世纪计算机和开发者的语言。

Go语言被设计为可高效地伸缩以便构建非常大的应用,并可在普通计算机上用几秒钟即完成编译。快如闪电的编译速度可能在一定程度上是因为语言的语法很容易解析,但更主要是因为它的依赖管理。如果文件app.go依赖于文件pkg1.go,而pkg1.go又依赖于pkg2.go,在传统的编译型语言中,app.go需要依赖于pkg1.go和pkg2.go目标文件。但在Go语言中,一切pkg2.go导出的内容都被缓存在pkg1.go的目标文件中,所以pkg1.go的目标文件足够独立构建 app.go。对于只有三个源文件的程序来说,这看不出什么优劣,但对于有着大量依赖关系的大型应用程序来说,这样做可以获得巨大的编译速度提升。

由于Go语言程序的构建是如此之快,因此它也适用一些本来应该使用脚本语言的场景(见“Go语言版Shebang脚本”,参见1.2节)。此外,Go语言可用于构建基于Google App Engine的Web应用程序。

Go语言使用了一种非常干净且易于理解的语法,避免了像老的语言如C++(发布于1983年)或Java(发布于1995年)一样的复杂和冗长。Go语言是一种强静态类型的语言,这在有些程序员看来是构建大型应用程序的必备特性。然而,使用 Go语言进行编程并不需要像使用别的静态语言那样打太多的字,这要归功于 Go语言简短的“声明并初始化”的变量声明语法(由于编译器会推断类型,因此并不需要显式地写明),以及它对鸭子类型强大而便捷的支持。

像C和C++这样的语言,当涉及内存管理时需要程序员非常谨慎地面对,特别是对于并发程序,要跟踪它们的内存分配简直犹如噩梦,而这些本来可以交给计算机去做。近年来,C++在这方面用各种“智能”指针进行了很大的改善,但在线程库方面还一直在追赶Java。通过使用垃圾收集器,Java减轻了程序员管理内存的负担。虽然C++语言现在有一个标准的线程库,但是C语言还只能使用第三方线程库。然而,在C、C++或Java中编写并发程序仍然需要相当地谨慎,以确保在恰当的时间正确地锁定和解锁资源。

Go编译器和运行时系统会处理这些繁琐的跟踪问题。对于内存管理而言,Go语言提供了一个垃圾收集器,因此无需使用智能指针或者手动释放内存。Go语言的并发机制基于计算机科学家C.A.R.Hoare提出的CSP(Communicating Sequential Processes)模型构建,这意味着许多并发的Go语言程序不需要加任何锁[1]。此外,Go语言引入goroutine ——一种非常轻量级的进程,可以一次性大量创建,并可跨处理器和处理器核心自动进行负载平衡,以提供比老的基于线程的语言更细粒度的并发。事实上,因为Go语言的并发支持使用起来如此简单和自然,移植单线程程序到Go时经常会发现转为并发模型的机会大增,从而可以更充分地利用计算机资源。

Go语言是一门务实的语言,与语言的纯净度相比,它更关注语言效率以及为程序员带来的便捷性。例如,Go语言的内置类型和用户自定义的类型是不一样的,因为前者可以高度优化,后者却不能。Go语言也提供了两个基本的内置集合类型:切片(slice,它的实际用途是为了提供变长功能的数组)和映射(map,也叫键值字典或散列表)。这些集合类型非常高效,并且在大多数情况下都能非常好地满足需求。当然,Go语言也支持指针(它是一个完全编译型的语言,因此在性能方面没有虚拟机挡路),所以它可以轻松创建复杂的自定义类型,如平衡二叉树。

虽然C语言仅支持过程式编程,而Java则强制要求程序员按照面向对象的方式来编程,但Go语言允许程序员使用最合适的编程范式来解决问题。Go语言可以被用做一个纯粹的过程式编程语言,但对面向对象编程也支持得很好。不过,我们也会在后文看到,Go语言面向对象编程的方式与C++、Java或Python非常不同,它更容易使用且在形式上更加灵活。

就像C语言一样,Go语言也不支持泛型(用C++的话来说就是模板)。然而,Go语言所提供的别的功能特性消除了对泛型支持的依赖。Go并不使用预处理器或者包含文件(这也是为什么它编译得如此之快的另一个原因),因此也无需像 C和C++那样复制函数签名。同时,因为没有使用预处理器,程序的语义就无法在Go语言程序员背后悄悄变化,但这种情况在C和C++下使用#define时一不小心就会发生。

可以说,C++、Objective-C和Java都试图成为更好的C语言(后者是间接地成为了更好的C++语言)。尽管Go语言干净而轻盈的语法容易让人联想到Python,Go语言的切片和映射也非常类似于Python的列表和字典,但Go语言也可以被认为试图成为一个更好的C。然而,与任何其他语言相比,Go语言从语言本质上都更接近于C语言,并可以被认为保留了C语言的所有精华的同时试图消除C语言中的缺陷,同时加入了许多强大而有用的独有特性。

Go语言最初被构思为一门可充分利用分布式系统以及多核联网计算机优势且适用于开发大型项目的编译速度很快的系统级语言。现在,Go语言的触角已经远远超出了原定的范畴,它正被用做一个具有高度生产力的通用编程语言。使用 Go语言开发和维护系统都让人感觉是一种享受。

本书的结构

第1章开始讲解如何建立和运行Go程序。这一章通过5个简短的示例简要介绍了Go语言的语法与特性,以及一些标准库。每个例子都介绍了一些不同的特性。这一章主要是为了让读者尝试一下Go语言,以此让读者感受一下学习Go语言需要学习的大致内容是什么。(这一章章还讲解了如何获取和安装Go语言环境。)

第2章至第7章更深入地讲解了Go语言的方方面面。其中有三章专门讲解了Go语言的内置数据类型:第2章涵盖了标识符、布尔值和数值类型,第3章涵盖了字符串,第4章涵盖了Go语言内置的集合类型。

第5章描述并讲解了Go语言的语句和控制结构,还解释了如何创建和使用自定义的函数,最后展示了如何使用Go语言创建一个过程式的非并发程序。

第6章展示了如何在Go语言中进行面向对象编程。本章的内容包括可用于聚合和嵌入(委托)其他类型的结构体,可作为一个抽象类型的接口,以及如何在某些情况下产生类似继承的效果。由于 Go语言中进行面向对象编程的方式可能与大多数读者的经验不同,这一章会给出几个完整的例子并详细讲解,以确保读者完全理解Go语言的面向对象编程方式。

第7掌讲解了Go语言的并发特性,与面向对象编程一章相比,这一章给出了更多实例,以确保读者对这些新的Go语言特性有透彻的了解。

第8章展示了如何读取和写入自定义的二进制文件、Go二进制(gob)文件、文本、JSON以及XML文件。(读取和写入文本文件的知识在第1章和后续几章中都有所涉及,因为这些知识可以更易于提供一些有价值的示例和练习。)

本书的最后一章是第9章。这一章先展示了如何导入和使用标准库包、自定义包以及第三方软件包。它还展示如何对自定义的包进行文档的自动提取、单元测试和性能基准测试。这一章的最后一节对Go编译器(gc)提供的工具集以及Go语言的标准库做了简要的概述。

Go语言虽然小巧,但它同时也是一门功能丰富和强大表达能力(在语法结构、概念和编程习惯方面)的语言。本书的例子都符合良好的Go语言编程范式[2]。当然,这种做法也意味着有些概念出现时不会被当场解释。但我们希望读者相信,所有的概念都会在本书中进行解释(当然,没有当场解释的内容都会以交叉引用的形式给出相应讲解的位置)。

Go是一门迷人的语言,使用起来感觉非常好。学习Go语法和编程习惯并不会很难,但它的确引入了一些新颖的、对许多读者来说可能不那么熟悉的概念。这本书试图给读者概念上的突破,尤其是在面向对象的Go语言编程和并发Go语言编程方面。如果只阅读那些定义良好却非常简要的文档,读者可能需要花费数周甚至数月的时间才能真正理解相关的知识。[1].指不需要用户主动加锁,而不是指从内部实现来说没有锁。——译者注[2].这里有一个例外:前面几章中,即使通道只被当做单向通道使用,我们也总是将通道声明为双向的。从第7章开始,通道只被声明为只有某一特殊的方向,这样,这里所说的Go语言风格用法也就讲得通了。第1章5个例子

本章总共有5个比较小的示例程序。这些示例程序概览了Go语言的一些关键特性和核心包(在其他语言里也叫模块或者库,在Go语言里叫做包(package),这些官方提供的包统称为Go语言标准库),从而让读者对学习Go语言编程有一个初步的认识。如果有些语法或者专业术语没法立即理解,不用担心,本章所有提到的知识点在后面的章节中都有详细的描述。

要使用Go语言写出Go味道的程序需要一定的时间和实践。如果你想将C、C++、Java、Python以及其他语言实现的程序移植到Go语言,花些时间学习Go语言特别是面向对象和并发编程的知识将会让你事半功倍。而如果你想使用 Go语言来从头创建新的应用,那就更要好好掌握 Go语言提供的功能了,所以说前期投入足够的学习时间非常重要,前期付出的越多,后期节省的时间也将越多。1.1 开始

为了尽可能获得最佳的运行性能,Go语言被设计成一门静态编译型的语言,而不是动态解释型的。Go语言的编译速度非常块,明显要快过其他同类的语言,比如C和C++。

Go语言的官方编译器被称为gc,包括编译工具5g、6g和8g,链接工具5l、6l和8l,以及文档查看工具godoc(在Windows下分别是5g.exe、6l.exe等)。这些古怪的命名习惯源自于Plan 9操作系统,例如用数字来表示处理器的架构(5代表ARM,6代表包括Intel 64位处理器在内的AMD64架构,而8则代表Intel 386)。幸好,我们不必担心如何挑选这些工具,因为Go语言提供了名字为go的高级构建工具,会帮我们处理编译和链接的事情。

Go语言官方文档Go语言的官方网站是golang.org,包含了最新的Go语言文档。其中Packages链接对 Go 标准库里的包做了详细的介绍,还提供了所有包的源码,在文档不足的情况下是非常有用的。Commands 页面介绍了 Go语言的命令行程序,包括 Go 编译器和构建工具等。Specification链接主要非正式、全面地描述了Go语言的语法规格。最后,Effective Go链接包含了大量Go语言的最佳实践。Go语言官网还特地为读者准备了一个沙盒,你可以在这个沙盒中在线编写、编译以及运行Go小程序(有一些功能限制)。这个沙盒对于初学者而言非常有用,可以用来熟悉Go语法的某些特殊之处,甚至可以用来学习fmt包中复杂的文本格式化功能或者regexp包中的正则表达式引擎等。官网的搜索功能只搜索官方文档。如果需要更多其他的Go语言资源,你可以访问go-lang.cat-v.org/go-search。读者也可以在本地直接查看Go语言官方文档。要在本地查看,读者需要运行godoc工具,运行时需要提供一个参数以使godoc运行为Web服务器。下面演示了如何在一个Unix终端(xterm、gnome-terminal、onsole、Terminal.app或者类似的程序)中运行$ godoc -http=:8000或者在Windows的终端中(也就是命令提示符或MS-DOS的命令窗口):C:\>godoc -http=:8000其中端口号可任意指定,只要不跟已经运行的服务器端口号冲突就行。假设 godoc 命令的执行路径已经包含在你的PATH环境变量中。运行 godoc 后,你只需用浏览器打开 http://localhost:8000 即可在本地查看 Go语言官方文档。你会发现本地的文档看起来跟golang.org的首页非常相似。Packages链接会显示Go语言的官方标准库和所有安装在GOROOT下的第三方包的文档。如果GOPATH变量已经定义(指向某些本地程序和包的路径),Packages 链接旁边会出现另一个链接。你可以通过这个链接访问相应的文档(环境变量GOROOT和GOPATH将在本章后面小节和第9章中讨论)。读者也可以在终端中使用godoc命令来查看整个包或者包中某个特定功能的文档。例如,在终端中执行godoc image NewRGBA命令将会输出关于函数image.NewRGBA()的文档。执行godoc image/png命令会输出关于整个image/png包的文档。

本书中的所有示例(可以从www.qtrac.eu/gobook.html获得)已经在Linux、Mac OS X和Windows平台上用Go 1中的gc编译器测试通过。Go语言的开发团队会让所有后续的Go 1.x版本都向后兼容Go 1,因此本书所述文字及示例都适用于整个1.x系列的Go。(如果发生不兼容的情况,我们也会及时更新书中的示例以与最新的Go语言发布版兼容。因此,随着时间的推移,网站上的示例程序可能跟本书中所展示的代码不完全相同。)

要下载和安装Go,请访问golang.org/doc/install.html,那里有安装指南和下载链接。在撰写本书时,Go 1已经发布了适用于FreeBSD 7+、Linux 2.6+、Mac OS X(Snow Leopard和Lion)以及Windows 2000+平台的源代码和二进制版本,并且同时支持这些平台的Intel 32位和AMD 64位处理器架构。另外Go 1还在Linux平台上支持ARM架构。预编译的Go安装包已经包含在Ubuntu Linux的发行版中,而在你阅读本书时可能更多的其他Linux发行版也包含Go安装包。如果只为了学习Go语言编程,从Go安装包安装要比从头编译和安装Go环境简单得多。

用gc构建的程序使用一种特定的调用约定。这意味着用gc构建的程序只能链接到使用相同调用约定的外部包,除非出现合适的桥接工具。Go语言支持在程序中以 cgo 工具(golang.org/cmd/cgo)的形式调用外部的C语言代码。而且目前至少在Linux和BSD系统中已经可以通过SWIG工具 (www.swig.org)在Go程序中调用C和C++语言的代码。

除了gc之外还有一个名为gccgo的Go编译器。这是一个针对Go语言的gcc(GNU编译工具集)前端工具。4.6以上版本的gcc都包含这个工具。像gc一样,gccgo也已经在部分Linux发行版中预装。编译和安装gccgo的指南请查看这个网址:golang.org/doc/gccgo_install.html。1.2 编辑、编译和运行

Go程序使用UTF-8编码[1]的纯Unicode文本编写。大部分现代编辑器都能够自动处理编码,并且某些最流行的编辑器还支持Go语言的语法高亮和自动缩进。如果你用的编辑器不支持Go语言,可以在Go语言官网的搜索框中输入编辑器的名字,看看是否有合适的插件可用。为了编辑方便,所有的Go语言关键字和操作符都使用ASCII编码字符,但是Go语言中的标识符可以是任一Unicode编码字符后跟若干Unicode字符或数字,这样Go语言开发者可以在代码中自由地使用他们的母语。

Go语言版Shebang脚本因为Go的编译速度非常快,Go程序可以作为类Unix系统上的shebang #! 脚本使用。我们需要安装一个合适的工具来实现脚本效果。在撰写本书的时候已经有两个能提供所需功能的工具:gonow(github.com/kison/gonow)和gorun(wiki.ubuntu.com/gorun)在安装完gonow或者gorun后,我们就可以通过简单的两个步骤将任意Go程序当做shebang脚本使用。首先,将#!/usr/bin/env gonow或者#!/usr/bin/env gorun添加到包含main()函数(在main包里)的.go文件开始处。然后,将文件设置成可执行(如用chmod +x命令)。这些文件只能够用gonow或者 gorun来编译,而不能用普通的编译方式来编译,因为文件中的#!在Go语言中是非法的。当gonow或者gorun首次执行一个.go文件时,它会编译该文件(当然,非常快),然后运行。在随后的使用过程中,只有当这个.go文件自上次编译后又被修改过后才会被再次编译这使得用Go语言来快速而方便地创建各种实用工具成为可能,比如创建系统管理任务。

为了感受一下如何编辑、编译和运行Go程序,我将从经典的“Hello World”程序开始(虽然我们会将其设计得稍微复杂些)。我们首先讨论编译与运行,然后在下一节中详细解读文件hello/hello.go中的源代码,因为它包含了一些Go语言的基本思想和特性。

我们可以从www.qtrac.eu/gobook.html得到本书中的所有源码,源代码包解压后将是一个goeg文件夹。所以如果我们在$HOME文件夹下解压缩,源文件hello.go的路径将会是$HOME/goeg/src/hello/hello.go。如无特别说明,我们在提到程序的源文件路径时将默认忽略$HOME/goeg/src 部分,比如在这个例子里 hello 程序的源文件路径被描述为hello/hello.go(当然,Windows用户必须将“/”替换成“\”,同时使用它们自己解压的路径,如C:\goeg或者%HOME-PATH%\goeg等)。

如果你直接从预编译Go安装包安装,或从源码编译并以root或Administrator的身份安装,那么你的系统中应该至少有一个环境变量GOROOT,它包含了Go安装目录的路径,同时你系统中的环境变量PATH现在应该已经包含$GOROOT/bin或%GOROOT%\bin。要查看Go是否安装正确,在终端(xterm、gnome-terminal、konsole、Terminal.app或者类似的工具)里键入以下命令即可:

$ go version

或者在Windows系统的MS-DOS命令提示符窗口里键入:

C:\>go version

如果返回的是“command not found”或者“‘go’is not recognized...”这样的错误信息,意味着Go不在环境变量PATH中。如果你用的是类Unix系统(包括Mac OS X),有一个很简单的解决办法,就是将该环境变量加入.bashrc(或者其他shell程序的类似文件)中。例如,作者的.bashrc文件包含这几行:

export GOROOT=$HOME/opt/go

export PATH=$PATH:$GOROOT/bin

通常情况下,你必须调整这些值来匹配你自己的系统(当然这只有在 go version 命令返回失败时才需要这样做)。

如果你用的是Windows系统,可以写一个批处理文件来设置Go语言的环境变量,每次打开命令提示符窗口执行Go命令时先运行这个批处理文件即可。不过最好还是在控制面板里设置Go语言的环境变量,一劳永逸。步骤如下,依次点击“开始菜单”(那个Windows图标)、“控制面板”、“系统和安全”、“系统”、“高级系统设置”,在系统属性对话框中点击“环境变量”按钮,然后点击“新建...”按钮,在其中加入一个以GOROOT命名的变量以及一个适当的值,如C:\Go。在相同的对话框中,编辑PATH环境变量,并在尾部加入文字;C:\Go\bin——文字开头的分号至关重要!在以上两者中,用你系统上实际安装的Go 路径来替代 C:\Go,如果你实际安装的Go 路径不是C:\Go的话。(再次声明,只有在go version命令返回失败时才需要这样做。)

现在我们假设Go在你机器上安装正确,并且Go bin目录包含PATH中所有的Go构建工具。(为了让新设置生效,可能有必要重新打开一个终端或命令行窗口。)

构建Go程序,有两步是必须的:编译和链接。[2]所有这两步都由go构建工具处理。go构建工具不仅可以构建本地程序和本地包,并且可以抓取、构建和安装第三方程序和第三方包。

让 go的构建工具能够构建本地程序和本地包需满足三个条件。首先,Go的bin 目录($GOROOT/bin或者 %GOROOT%\bin)必须在环境变量中。其次,必须有一个包含src目录的目录树,其中包含了本地程序和本地包的源代码。例如,本书的示例代码被解压到goeg/src/hello和goeg/src/bigdigits等目录。最后,src目录的上一级目录必须在环境变量GOPATH中。例如,为了使用go的构建工具构建本书的hello示例程序,我们必须这样做:$ export GOPATH=$HOME/goeg$ cd $GOPATH/src/hello$ go build

相应地,在Windows上也可以这样做:C:\>set GOPATH=C:\goegC:\>cd %gopath%\src\helloC:\goeg\src\hello>go build

以上两种情况都假设PATH环境变量中已经包含$GOROOT/bin或者%GOROOT%\bin。在go构建工具构建好了程序后,我们就可以尝试运行它。可执行文件的默认文件名跟它所位于的目录名称一致(例如,在类Unix系统中是hello,在Windows系统中是hello.exe),一旦构建完成,我们就可以运行这个程序了。$./helloHello World!

或者$./hello Go Programmers!Hello Go Programmers!

在Windows上也类似:C:\goeg\src\hello>hello Windows Go Programmers!Hello Windows Go Programmers!

我们用加粗代码字体的形式显示需要你在终端输入的文字,并以罗马字体的形式显示终端的输出。我们也假设命令提示符是$,但其实是什么都没关系(如Windows下的C:\>)。

有一点可以注意到的是,我们无需编译或者显式链接任何其他的包(即使我们将看到hello.go使用了3个标准库中的包)。这是为什么Go程序构建得如此快的原因。

如果我们有好几个 Go 程序,如果它们的可执行程序都可以保存在同一个目录下,由于我们可以一次性将这个目录加入到PATH中,这将会非常的方便。幸运的是,go构建工具可以用以下方式来支持这样的特性:$ export GOPATH=$HOME/goeg$ cd $GOPATH/src/hello$ go install

同样地,我们可以在Windows上这样做:C:\>set GOPATH=C:\goegC:\>cd %gopath%\src\helloC:\goeg\src\hello>go install

go install 命令跟 go build 所做的工作是一样的,唯一不同的是,它将可执行文件放入一个标准路径中($GOPATH/bin或者 %GOPATH%\bin)。这意味着,只需在PATH中加上一个统一路径($GOPATH/bin 或者 %GOPATH%\bin),我们所安装的所有 Go 程序都会包含在PATH中从而可以在任一路径下直接运行。

除了本书中的示例程序之外,我们可能会想在自己的一个目录下开发自己的Go程序和包。要达到这个目的,我们可以将 GOPATH 环境变量设置成两个或者多个以冒号分隔的路径(在Windows中是以分号分隔)。例如,export GOPATH=$HOME/app/go:$HOME/goeg或者SET GOPATH=C:\app\go;C:\goeg。[3]在这个情况下我们必须将所有的程序和包的源代码都放入$HOME/app/go/src或者C:\app\go\src中。因此,如果我们开发了一个叫myapp的程序,它的.go源文件将位于$HOME/app/go/src/myapp或者C:\app\go\src\myapp。如果我们使用go install在一个GOPATH路径下构建程序,而且GOPATH环境变量包含了两个或者更多个路径,那么可执行文件将被放入相对应源代码目录的bin文件夹中。

通常,每次构建Go程序时export或者设置GOPATH环境变量可能很费劲,因此最好是永久性地设置好这个环境变量。前面我们已经提到过,类Unix系统可修改.bashrc文件(或类似的文件)以设置GOPATH环境变量(参见本书示例中的gopath.sh文件),Windows上可通过编写一个批处理文件(参见本书示例中的gopath.bat文件)或添加GOPATH到系统的环境变量:依次点击“开始菜单”(那个Windows图标)、“控制面板”、“系统和安全”、“系统”、“高级系统设置”,在系统属性对话框中点击“环境变量”按钮,然后点击“新建...”按钮,在其中加入一个以GOPATH命名的变量以及一个适当的值,如C:\goeg或C:\app\go;C:\goeg。

虽然Go语言的推荐构建工具是go命令行工具,我们完全可以使用make或者其他现代构建工具,或者使用别的针对Go语言的构建工具,或者给流行集成开发环境如Eclipse和Visual Studio安装合适的插件来进行Go工程的构建。1.3 Hello Who?

现在我们已经知道怎么编译一个 hello 程序,让我们看看它的代码。不要担心细节,本章所提及的一切(以及更多的内容)在后面的章节中都有详细描述。下面是完整的hello程序(在文件hello/hello.go中):

// hello.go

package main

import (①

"fmt"

"os"

"strings"

)

func main() {

who := "World!" ②

if len(os.Args) > 1 { /* os.Args[0]是"hello"或者"hello.exe" */ ③

who = strings.Join(os.Args[1:], " ") ④

}

fmt.Println("Hello", who) ⑤

}

Go语言使用 C++风格的注释://表示单行注释,到行尾结束,/…/ 表示多行注释。Go语言中的惯例是使用单行注释,而多行注释则往往用于在开发过程中注释掉若干行代码。[4]

所有的Go语言代码都只能放置于一个包中,每一个Go程序都必须包含一个main包以及一个 main()函数。main()函数作为整个程序的入口,在程序运行时最先被执行。实际上,Go语言中的包还可能包含init()函数,它先于main()函数被执行,我们将在1.7节了解到,关于init函数的完全介绍在5.6.2节。需要注意的是,包名和函数名之间不会发生命名冲突情况。

Go语言针对的处理单元是包而非文件,这意味着我们可以将包拆分成任意数量的文件。在Go编译器看来,如果所有这些文件的包声明都是一样的,那么它们就同样属于一个包,这跟把所有内容放在一个单一的文件里是一样的。通常,我们也可以根据应用程序的功能将其拆分成尽可能多的包,以保持一切模块化,我们将在第9章看到相关内容。

代码中的import语句(标注为①的地方)导入了3个标准库中的包。fmt包提供来格式化文本和读入格式文本的函数(参见 3.5 节),os 包提供了跨平台的操作系统层面变量及函数,而strings包则提供了处理字符串的函数(参见3.6.1节)。

Go语言的基本类型支持常用的操作符(如+操作符可用于数字加法运算和字符串连接运算),同时Go语言的标准库也提供了拥有各种功能的包来对这些操作进行补充,如这里引入的strings包。你也可以基于这些基本类型创建自己的类型或者为这些类型添加自定义方法(我们将在1.5节提及,并在第6章详细阐述)。

读者可能也已经注意到程序中没有分号,那些 import 语句也不用逗号分隔,if 语句的条件也不用圆括号括起来。在Go语言中,包含函数体以及控制结构体(例如if语句和for循环语句)在内的代码块均使用花括号作为边界符。使用代码缩进仅仅是为了提高代码可读性。从技术层面讲,Go语言的语句是以分号分隔的,但这些是由编译器自动添加的,我们不用手动输入,除非我们需要在同一行中写入多个语句。没有分号及只需要少量的逗号和圆括号,使得Go语言的程序更容易阅读,并且可以大幅降低编写代码时的键盘敲击次数。

Go语言的函数和方法以关键字func定义。但main包里的main()函数比较特别,它既没有参数,也没有返回值。当main.main()运行完毕,程序会自动终止并向操作系统返回0。通常我们可以随时选择退出程序,并返回一个自己选择的返回值,这点我们随后将详细讲解(参见1.4节)。

main()函数中的第一行(标注②)使用了 := 操作符,在Go语言中叫做快速变量声明。这条语句同时声明并初始化了一个变量,也就是说我们不必声明一个具体类型的变量,因为Go语言可以从其初始化值中推导出其类型。所以这里我们相当于声明了一个string类型的变量who,而且由于go是强类型的语言,也就只能将string类型的值赋值给who。

就像大多数语言使用if语句检测一个条件是否成立一样,在这个例子里if语句用来判断命令行中是否输入了一个字符串,如果条件成立就执行相应大括号中的代码块。我们将在本章末尾(参见1.6节)及后面的章节(参见5.2.1节)中看到一些更加复杂的if语句。

代码中的os.Args变量是一个string类型的切片(标注③)。数组、切片和其他容器类型将在第4章中详细阐述(参见4.2节)。现在我们只需要知道可以使用语言内置的len()函数来获得切片的长度即可,而切片的元素则可以通过[]索引操作来获得,其语法是一个 Python 语法子集。具体而言,slice[n]返回切片的第n个元素(从0开始计数),而slice[n:]则返回另一个包含从第n个元素到最后一个元素的切片。在数据集合那一章节,我们将会看到Go语言在这方面的详细语法。对于os.Args,这个切片总是至少包含一个string(程序本身的名字),其在切片中的位置索引为0(Go语言中的所有索引都是从0开始的)。

只要用户输入一个或多个命令行参数,if 语句的条件就成立了,我们将从命令行输入的所有参数连接成一个字符串并赋值给 who 变量(标注④)。在这里我们使用赋值操作符(=),因为如果我们使用快速声明操作符(:=)的话,只能得到另一个生命周期仅限于当前 if代码块的新局部变量who。strings.Join()函数的输入参数为以一个string类型的切片和一个分隔符(可以是一个空字符,如"")作为输入,返回一个由分隔符将切片中的所有字符串连接在一起的新字符串。在这个示例里我们用空格作为连接符来连接所有输入的字符串参数。

最后,在最后一个语句(标注⑤)中,我们打印Hello和一个空格,以及who变量中的字符串,并添加一个换行符。fmt 包提供了许多不同的打印函数变体,比如像 fmt.Println()会整洁地打印任何输入的内容,而像 fmt.Printf() 则使用占位符来提供良好的格式化输出控制能力。打印函数将在第3章(参见3.5节)详细阐述。

本节的hello 程序展示了很多超出这类程序一般所做事情之外的语言特性。接下来的示例也会这样做,在保持程序尽量简短的情况下尽量覆盖更多的高级特性。这样做的主要目的是,通过熟悉简单的语言基础,让读者在构建、运行和体验简单的Go程序的同时体验一下Go语言的强大与独特。当然,本章提及的所有内容都将在后面章节中更详细地阐述。1.4 大数字——二维切片

示例程序bigdigits(源文件是bigdigits/bigdigits.go)从命令行接收一个数字(作为一个字符串输入),然后用大数字的格式将这个数字输出到命令行窗口。回溯到20世纪,在一些多个用户共用一台高速行式打印机的地方,通常都会习惯性地为每个用户的打印任务添加一个封面页以显示该用户的一些标识信息,比如他们的用户名和打印的文件名等。那时候采取的就是类似于这个例子中演示的大数字技术。

我们将分3部分了解这个示例程序:首先介绍import部分,然后是静态数据,再之后是程序处理过程。为了让大家对整个过程有个大致的印象,我们先来看看程序的运行结果,如下:$./bigdigits 290175493222  9999  000  1 77777 55555  4  9999  3332  2 9  9  0  0  11   7 5    44  9  9  3  32  9  9 0  0  1   7  5   4 4  9  9    32  9999 0  0  1  7  555  4  4  9999   332     9 0  0  1  7    5 444444   9    32     9  0  0  1  7   5  5   4    9  3  322222   9  000  111 7   555   4    9  333

从这个例子可以看出,每个数字都由一个字符串类型的切片来表示,所有的数字可以用一个二维的字符串类型切片来表示。在查看数据之前,我们先来了解如何声明和初始化一维的字符串类型以及数字类型的切片。

longWeekend := []string{"Friday", "Saturday", "Sunday", "Monday"}

var lowPrimes = []int{2, 3, 5, 7, 11, 13, 17, 19}

切片的表达方式为[]Type,如果我们希望同时完成初始化的话,可以在后面直接跟一个花括号,括号内是一个对应类型的元素列表,并在元素之间用逗号分隔。本来对于这两个切片我们可以用同样的变量声明语法,但我们刻意地对 LowPrimes 切片的声明采用了相对较长的声明方式。采取这个方式的原因我们很快会给出说明。因为一个切片的类型本身可以是另一个切片,所以我们可以很容易地创建多维的集合(例如元素类型为切片的切片等)。

bigdigits程序只需要引入四个包:

import (

"fmt"

"log"

"os"

"path/filepath"

)

fmt包提供了格式化文本和读取格式化文本的相关函数(参见3.5节)。log包提供了日志功能。os 包提供的是平台无关的操作系统级别变量和函数,包括用于保存命令行参数的类型为[]string的os.Args变量(即字符串类型的切片)。而path包中的filepath子包则提供了一系列可跨平台的对文件名和路径操作的函数。需要注意的是,对于位于其他包内的子包,在我们的代码中用到时只需要指定其包名称的最后一部分即可(对于此例而言就是filepath)。

对于bigdigits程序而言,我们需要二维数据(字符串类型的二维切片)。下面我们示范一下如何创建这样的数据,通过将数字0排列好以展示数字对应的字符串如何对应到输出里的行,不过省略了数字3到8的对应字符串。

var bigDigits = [][]string{

{" 000 ",

" 0  0 ",

"0   0",

"0   0",

"0   0",

" 0  0 ",

" 000 "},

{" 1 ", "11 ", " 1 ", " 1 ", " 1 ", " 1 ", "111"},

{" 222 ", "2  2", "  2 ", "  2 ", " 2 ",  "2 ", "22222"},

//...3至8...

{" 9999", "9  9", "9  9", " 9999", "  9", "  9", "  9"},

}

虽然在函数和方法之外声明的变量不能使用 := 操作符,但我们可以通过使用关键字var和赋值运算符 =的长声明方式来达到同样的效果,例如本例中我们为 bigDigits 变量所做的。其实之前我们在声明 lowPrimes 变量时已经使用过了。不过我们仍然不需要指定bigDigits的数据类型,因为Go语言能够从赋值动作中推导出相应的类型信息。

我们把计数工作丢给了Go编译器,因此不需要明确指定切片的维度。Go语言的众多便利之一就是支持像大括号这样的复合文面量语法,因此我们不必在一个地方声明这个变量,又在别的地方将相应的值赋值给它,当然,这么做也是可以的。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载