重磅推荐
【产品特色】


【编辑推荐】
《Go语言编程》首先概览了Go语言的诞生和发展历程,从面向过程编程特性入手介绍Go语言的基础用法,让有一定C语言基础的读者可以非常迅速地入门并开始上手用Go语言来解决实际问题,之后介绍了Go语言简洁却又无比强大的面向对象编程特性和并发编程能力,至此读者已经可以理解为什么Go语言是为互联网时代而生的语言。
从实用性角度出发,本书还介绍了Go语言标准库和配套工具的用法,包括安全编程、网络编程、工程管理工具等。
对于希望对Go语言有更深入了解的读者,我们也特别组织了一系列进阶话题,包括语言交互性、链接符号、goroutine机理和接口机制等。
【内容简介】
《Go语言编程》首先引领读者快速浏览Go 语言的全貌,迅速消除读者对这门语言的陌生感,然后循序渐进地介绍了Go 语言的面向程和面向对象的编程语法,其中穿插了一些与其他主流语言的比较以让读者理解Go 语言的设计动机,接着探讨了Go 语言重要的并行编程方法,之后介绍了网络编程、工程管理、安全编程、开发工具等非语法相关但非常重要的内容,*后为一列关于Go 语言的文章,可以帮助读者更深入了解这门全新的语言。  《Go语言编程》适合所有层次的开发者阅读。
【作者简介】
许式伟七牛云存储CEO,曾任盛大创新院资深研究员、金山软件技术总监、WPS Office 2005首席架构师。开源爱好者,发布过包括WINX、TPL等十余个C 开源项目,拥有超过15年的C/C 开发经验。在接触Go语言后即可被其大道至简、少即是多的设计哲学所倾倒。七牛云存储是国内*个吃螃蟹的团队,核心服务完全采用Go语言实现。吕桂华七牛云存储联合创始人,曾在金山软件、盛大游戏等公司担任架构师和部门经理等职务,在企业级系统和大型网游平台领域有较多涉猎。拥有十余年的C/C 大型项目开发经验,也曾在Java和.NET平台上探索多年。同样被Go语言的魅力所吸引而不可自拔,希望能为推广这门优秀的语言尽自己的绵薄之力。
【媒体评论】
Go语言具有简洁有力的语言表达能力、强大的系统开发能力、极高的运行效率、卓越的并发和并行能力、优秀的工程管理支持,以及美好的编程体验。我们曾经在其他语言中花费大量气力才能获得的一些能力,在Go中可以轻松得到。许式伟在Go语言出现后很快就把它用于大型项目,带领七牛团队积累了大量Go编程经验。这本书除了完整介绍Go语言特性以外,还深入剖析了语言实现机制。作为服务器软件开发者和编程语言爱好者,我强烈推荐此书。——李杰,盛大文学首席架构师我一直认为Go语言是一门愉快的语言,代码简洁,开发高效,无论是slice还是reader、writer,处处都让人舒坦,但是很多coder认为Go只是惊鸿一瞥,无法探其究竟。而《Go语言编程》正是这样一份猛料,能够带领越来越多的人了解Go,学习Go,用Go来实现自己的梦想。——何晓杰,国内知名Android研究者,安居客移动事业部高级开发经理就个人学习Go语言的体会来说,在众多编程语言中,它*属于无法让人一见钟情的那类,然而当放下偏见与傲慢,真心地去了解和体会它的时候,Go语言就如同一坛古酒、一饼老茶,总是能在某些地方触动开发者的心弦。《Go语言编程》这本书应当说是作者多年编程经验的沉淀和反思。通过Go语言构建的“七牛云存储平台”项目,对这些沉淀和反思进行了实践和验证,*终形成文字总结。Go语言作为一个工程化的编程语言,正是需要这样以工程化思想为依托的图书来向世人展示其优雅之处。本书一方面通过展示和分析大量Go语言代码,阐明了Go语言基本的使用方式,另一方面通过和C语言代码进行比较,进一步剖析了语言的内在设计思想,乃至底层实现原理,让各个层次的读者都能从书中汲取到大量的知识,使人读后必有所得。简单来说:好书,值得读!——邢星,Go语言社区积极推动者,39健康网技术部副总监
【目录】
目 录

第1章 初识Go语言 1
1.1 语言简史 1
1.2 语言特性 2
1.2.1 自动垃圾回收 3
1.2.2 更丰富的内置类型 4
1.2.3 函数多返回值 5
1.2.4 错误处理 6
1.2.5 匿名函数和闭包 6
1.2.6 类型和接口 7
1.2.7 并发编程 8
1.2.8 反射 9
1.2.9 语言交互性 10
1.3 *个Go程序 11
1.3.1 代码解读 11
1.3.2 编译环境准备 12
1.3.3 编译程序 12
1.4 开发工具选择 13
1.5 工程管理 13
1.6 问题追踪和调试 18
1.6.1 打印日志 18
1.6.2 GDB调试 18
1.7 如何寻求帮助 18
1.7.1 邮件列表 19
1.7.2 网站资源 19
1.8 小结 19

第2章 顺序编程 20
2.1 变量 20
2.1.1 变量声明 20
2.1.2 变量初始化 21
2.1.3 变量赋值 21
2.1.4 匿名变量 22
2.2 常量 22
2.2.1 字面常量 22
2.2.2 常量定义 23
2.2.3 预定义常量 23
2.2.4 枚举 24
2.3 类型 24
2.3.1 布尔类型 25
2.3.2 整型 25
2.3.3 浮点型 27
2.3.4 复数类型 28
2.3.5 字符串 28
2.3.6 字符类型 30
2.3.7 数组 31
2.3.8 数组切片 32
2.3.9 map 36
2.4 流程控制 38
2.4.1 条件语句 38
2.4.2 选择语句 39
2.4.3 循环语句 40
2.4.4 跳转语句 41
2.5 函数 41
2.5.1 函数定义 42
2.5.2 函数调用 42
2.5.3 不定参数 43
2.5.4 多返回值 45
2.5.5 匿名函数与闭包 45
2.6 错误处理 47
2.6.1 error接口 47
2.6.2 defer 48
2.6.3 panic()和recover() 49
2.7 完整示例 50
2.7.1 程序结构 51
2.7.2 主程序 51
2.7.3 算法实现 54
2.7.4 主程序 57
2.7.5 构建与执行 59
2.8 小结 61

第3章 面向对象编程 62
3.1 类型系统 62
3.1.1 为类型添加方法 63
3.1.2 值语义和引用语义 66
3.1.3 结构体 67
3.2 初始化 68
3.3 匿名组合 68
3.4 可见性 71
3.5 接口 71
3.5.1 其他语言的接口 71
3.5.2 非侵入式接口 73
3.5.3 接口赋值 74
3.5.4 接口查询 76
3.5.5 类型查询 78
3.5.6 接口组合 78
3.5.7 Any类型 79
3.6 完整示例 79
3.6.1 音乐库 80
3.6.2 音乐播放 82
3.6.3 主程序 84
3.6.4 构建运行 86
3.6.5 遗留问题 86
3.7 小结 87
第4章 并发编程 88
4.1 并发基础 88
4.2 协程 90
4.3 goroutine 90
4.4 并发通信 91
4.5 channel 94
4.5.1 基本语法 95
4.5.2 select 95
4.5.3 缓冲机制 96
4.5.4 超时机制 97
4.5.5 channel的传递 98
4.5.6 单向channel 98
4.5.7 关闭channel 99
4.6 多核并行化 100
4.7 出让时间片 101
4.8 同步 101
4.8.1 同步锁 101
4.8.2 全局*性操作 102
4.9 完整示例 103
4.9.1 简单IPC框架 105
4.9.2 中央服务器 108
4.9.3 主程序 113
4.9.4 运行程序 116
4.10 小结 117

第5章 网络编程 118
5.1 Socket编程 118
5.1.1 Dial()函数 118
5.1.2 ICMP示例程序 119
5.1.3 TCP示例程序 121
5.1.4 更丰富的网络通信 122
5.2 HTTP编程 124
5.2.1 HTTP客户端 124
5.2.2 HTTP服务端 130
5.3 RPC编程 132
5.3.1 Go语言中的RPC支持与处理 132
5.3.2 Gob简介 134
5.3.3 设计优雅的RPC接口 134
5.4 JSON处理 135
5.4.1 编码为JSON格式 136
5.4.2 解码JSON数据 137
5.4.3 解码未知结构的JSON数据 138
5.4.4 JSON的流式读写 140
5.5 网站开发 140
5.5.1 *简单的网站程序 141
5.5.2 net/http包简介 141
5.5.3 开发一个简单的相册网站 142
5.6 小结 157

第6章 安全编程 158
6.1 数据加密 158
6.2 数字签名 158
6.3 数字证书 159
6.4 PKI体系 159
6.5 Go语言的哈希函数 159
6.6 加密通信 160
6.6.1 加密通信流程 161
6.6.2 支持HTTPS的Web服务器 162
6.6.3 支持HTTPS的文件服务器 165
6.6.4 基于SSL/TLS的ECHO程序 166
6.7 小结 169

第7章 工程管理 170
7.1 Go命令行工具 170
7.2 代码风格 172
7.2.1 强制性编码规范 172
7.2.2 非强制性编码风格建议 173
7.3 远程import支持 175
7.4 工程组织 175
7.4.1 GOPATH 176
7.4.2 目录结构 176
7.5 文档管理 177
7.6 工程构建 180
7.7 跨平台开发 180
7.7.1 交叉编译 181
7.7.2 Android支持 182
7.8 单元测试 183
7.9 打包分发 184
7.10 小结 184

第8章 开发工具 186
8.1 选择开发工具 186
8.2 gedit 187
8.2.1 语法高亮 187
8.2.2 编译环境 187
8.3 Vim 188
8.4 Eclipse 189
8.5 Notepad  192
8.5.1 语法高亮 192
8.5.2 编译环境 192
8.6 LiteIDE 193
8.7 小结 195

第9章 进阶话题 196
9.1 反射 196
9.1.1 基本概念 196
9.1.2 基本用法 197
9.1.3 对结构的反射操作 199
9.2 语言交互性 199
9.2.1 类型映射 200
9.2.2 字符串映射 201
9.2.3 C程序 201
9.2.4 函数调用 202
9.2.5 编译Cgo 203
9.3 链接符号 203
9.4 goroutine机理 204
9.4.1 协程 204
9.4.2 协程的C语言实现 205
9.4.3 协程库概述 205
9.4.4 任务 208
9.4.5 任务调度 210
9.4.6 上下文切换 211
9.4.7 通信机制 215
9.5 接口机理 216
9.5.1 类型赋值给接口 217
9.5.2 接口查询 223
9.5.3 接口赋值 224

附录A 225
【免费在线读】

  初识Go语言
  本章将简要介绍Go语言的发展历史和关键的语言特性,并引领读者对Go语言的主要特性进行一次快速全面的浏览,让读者对Go语言的总体情况有一个清晰的印象,并能够快速上手,用Go语言编写和运行自己的*个小程序。
  1.1 语言简史
  提起Go语言的出身,我们就必须将我们饱含敬意的眼光投向持续推出惊世骇俗成果的贝尔实验室。贝尔实验室已经走出了多位诺贝尔奖获得者,一些对于现在科技至关重要的研究成果,比如晶体管、通信技术、数码相机的感光元件CCD和光电池等都源自贝尔实验室。该实验室在科技界的地位可想而之,是一个毫无争议的科研圣地。
  这里我们重点介绍一下贝尔实验室中一个叫计算科学研究中心的部门对于操作系统和编程语言的贡献。回溯至1969年(估计大部分读者那时候都还没出世),肯?汤普逊(KenThompson)和丹尼斯?里奇(DennisRitchie)在贝尔实验室的计算科学研究中心里开发出了Unix这个大名鼎鼎的操作系统,还因为开发Unix而衍生出了一门同样赫赫有名的编程语言——C语言。对于很大一部分人而言,Unix就是操作系统的鼻祖,C语言也是计算机课程中*广泛使用的编程语言。Unix和C语言在过去的几十年以来已经造就了无数的成功商业故事,比如曾在90年代如日中天的太阳微系统(SunMicroSystems),现在正如日中天的苹果的Mac OSX操作系统其实也可以认为是Unix的一个变种(FreeBSD)。
  虽然已经取得了如此巨大的成就,贝尔实验室的这几个人并没有因此而沉浸在光环中止步不前,他们从20世纪80年代又开始了一个名为Plan9的操作系统研究项目,目的就是解决Unix中的一些问题,发展出一个Unix的后续替代系统。在之后的几十年中,该研究项目又演变出了另一个叫Inferno的项目分支,以及一个名为Limbo的编程语言。
  Limbo是用于开发运行在小型计算机上的分布式应用的编程语言,它支持模块化编程,编译期和运行时的强类型检查,进程内基于具有类型的通信通道,原子性垃圾收集和简单的抽象数据类型。它被设计为:即便是在没有硬件内存保护的小型设备上,也能安全运行。
  Limbo语言被认为是Go语言的前身,不仅仅因为是同一批人设计的语言,而是Go语言确实从Limbo语言中继承了众多优秀的特性。
  贝尔实验室后来经历了多次的动荡,包括肯?汤普逊在内的Plan9项目原班人马加入了Google。在Google,他们创造了Go语言。早在2007年9月,Go语言还是这帮大牛的20%自由时间的实验项目。幸运的是,到了2008年5月,Google发现了Go语言的巨大潜力,从而开始全力支持这个项目,让这批人可以全身心投入Go语言的设计和开发工作中。Go语言的*个版本在2009年11月正式对外发布,并在此后的两年内快速迭代,发展迅猛。*个正式版本的Go语言于2012年3月28日正式发布,让Go语言迎来了*个引人瞩目的里程碑。
  基于Google对开源的一贯拥抱态度,Go语言也自然而然地选择了开源方式发布,并使用BSD授权协议。任何人可以查看Go语言的所有源代码,并可以为Go语言发展而奉献自己的力量。
  Google作为Go语言的主推者,并没有简简单单地把语言推给开源社区了事,它不仅组建了一个独立的小组全职开发Go语言,还在自家的服务中逐步增加对Go语言的支持,比如对于Google有战略意义的云计算平台GAE(GoogleAppEngine)很早就开始支持Go语言了。按目前的发展态势,在Google内部,Go语言有逐渐取代Java和Python主流地位的趋势。在Google的更多产品中,我们将看到Go语言的踪影,比如Google*核心的搜索和广告业务。
  在本书的序中,我们已经清晰诠释了为什么在语言泛滥的时代Google还要设计和推出一门新的编程语言。按照已经发布的Go语言的特性,我们有足够的理由相信Google推出此门新编程语言绝不仅仅是简单的跑马圈地运动,而是为了解决切实的问题。
  下面我们再来看看Go语言的主要作者。
  肯?汤普逊(KenThompson,http://en.wikipedia.org/wiki/Ken_Thompson):设计了B语言和C语言,创建了Unix和Plan9操作系统,1983年图灵奖得主,Go语言的共同作者。
  罗布?派克(RobPike,http://en.wikipedia.org/wiki/Rob_Pike):Unix小组的成员,参与Plan9和Inferno操作系统,参与 Limbo和Go语言的研发,《Unix编程环境》作者之一。
  罗伯特?格里泽默(RobertGriesemer):曾协助制作Java的HotSpot编译器和Chrome浏览器的JavaScript引擎V8。
  拉斯? 考克斯(Russ Cox,http://swtch.com/~rsc/):参与Plan9操作系统的开发,Google Code Search项目负责人。
  伊安?泰勒(Ian LanceTaylor):GCC社区的活跃人物,gold连接器和GCC过程间优化LTO的主要设计者,Zembu公司的创始人。
  布拉德?菲茨帕特里克(BradFitzpatrick,http://en.wikipedia.org/wiki/Brad_Fitzpatrick):LiveJournal的创始人,著名开源项目memcached的作者。
  虽然我们这里只列出了一部分,大家已经可以看出这个语言开发团队空前强大,这让我们在为Go语言的优秀特性而兴奋之外,还非常看好这门语言的发展前景。
  1.2 语言特性
  Go语言作为一门全新的静态类型开发语言,与当前的开发语言相比具备众多令人兴奋不已的新特性。本书从第2章开始,我们将对Go语言的各个方面进行详细解析,让读者能够尽量轻松地掌握这门简洁、有趣却又超级强大的新语言。
  这里先给读者罗列一下Go语言*主要的特性:
  自动垃圾回收
  更丰富的内置类型
  函数多返回值
  错误处理
  匿名函数和闭包
  类型和接口
  并发编程
  反射
  语言交互性
  1.2.1 自动垃圾回收
  我们可以先看下不支持垃圾回收的语言的资源管理方式,以下为一小段C语言代码:
  void foo()
  {
   char* p = new char[128];
   ... // 对p指向的内存块进行赋值
   func1(p); // 使用内存指针
  
   delete[] p;
  }
  各种非预期的原因,比如由于开发者的疏忽导致*后的delete语句没有被调用,都会引发经典而恼人的内存泄露问题。假如该函数被调用得非常频繁,那么我们观察该进程执行时,会发现该进程所占用的内存会一直疯长,直至占用所有系统内存并导致程序崩溃,而如果泄露的是系统资源的话,那么后果还会更加严重,*终很有可能导致系统崩溃。
  手动管理内存的另外一个问题就是由于指针的到处传递而无法确定何时可以释放该指针所指向的内存块。假如代码中某个位置释放了内存,而另一些地方还在使用指向这块内存的指针,那么这些指针就变成了所谓的“野指针”(wildpointer)或者“悬空指针”(dangling pointer),对这些指针进行的任何读写操作都会导致不可预料的后果。
  由于其杰出的效率,C和C 语言在非常长的时间内都作为服务端系统的主要开发语言,比如Apache、Nginx和MySQL等著名的服务器端软件就是用C和C 开发的。然而,内存和资源管理一直是一个让人非常抓狂的难题。服务器的崩溃十有八九就是因为不正确的内存和资源管理导致,更讨厌的是这种内存和资源管理问题即使被发现了,也很难定位到具体的错误地点,导致无数程序员通宵达旦地调试程序。
  这个问题在多年里被不同人用不同的方式来试图解决,并诞生了一些非常著名的内存检查工具,比如RationalPurify、Compuware BoundsChecker和英特尔的ParallelInspector等。从设计方法的角度也衍生了类似于内存引用计数之类的方法(通常被称为“智能指针”),后续在Windows平台上标准化的COM出现的一个重要原因就是为了解决内存管理的难题。但是事实证明,这些工具和方法虽然能够在一定程度上辅助开发者,但并没法让开发者避免通宵调试这样又苦又累的工作。
  到目前为止,内存泄露的*解决方案是在语言级别引入自动垃圾回收算法(GarbageCollection,简称GC)。所谓垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。当然,因为需要尽量*小化垃圾回收的性能损耗,以及降低对正常程序执行过程的影响,现实中的垃圾回收算法要比这个复杂得多,比如为对象增加年龄属性等,但基本原理都是如此。
  自动垃圾回收在C/C 社区一直作为一柄双刃剑看待,虽然到C 0x(后命名为C 11)正式发布时,这个呼声颇高的特性总算是被加入了,但按C 之父的说法,由于C 本身过于强大,导致在C 中支持垃圾收集变成了一个困难的工作。假如C 支持垃圾收集,以下的代码片段在运行时就会是一个严峻的考验:
  int* p = new int;
  p = 10; // 对指针进行了偏移,因此那块内存不再被引用
  // …… 这里可能会发生针对这块int内存的垃圾收集 ……
  p -= 10; // 咦,居然又偏移到原来的位置
  *p = 10; // 如果有垃圾收集,这里就无法保证可以正常运行了
  微软的C /CLI算是用一种偏门的方式让C 程序员们有机会品尝一下垃圾回收功能的鲜美味道。在C/C 之后出现的新语言,比如Java和C#等,基本上都已经自带自动垃圾回收功能。
  Go语言作为一门新生的开发语言,当然不能忽略内存管理这个问题。又因为Go语言没有C 这么“强大”的指针计算功能,因此可以很自然地包含垃圾回收功能。因为垃圾回收功能的支持,开发者无需担心所指向的对象失效的问题,因此Go语言中不需要delete关键字,也不需要free()方法来明确释放内存。例如,对于以上的这个C语言例子,如果使用Go语言实现,我们就完全不用考虑何时需要释放之前分配的内存的问题,系统会自动帮我们判断,并在合适的时候(比如CPU相对空闲的时候)进行自动垃圾收集工作。
  1.2.2 更丰富的内置类型
  除了几乎所有语言都支持的简单内置类型(比如整型和浮点型等)外,Go语言也内置了一些比较新的语言中内置的高级类型,比如C#和Java中的数组和字符串。除此之外,Go语言还内置了一个对于其他静态类型语言通常用库方式支持的字典类型(map)。Go语言设计者对为什么内置map这个问题的回答也颇为简单:既然绝大多数开发者都需要用到这个类型,为什么还非要每个人都写一行import语句来包含一个库?这也是一个典型的实战派观点,与很多其他语言的学院派气息迥然不同。
  另外有一个新增的数据类型:数组切片(Slice)。我们可以认为数组切片是一种可动态增长的数组。这几种数据结构基本上覆盖了绝大部分的应用场景。数组切片的功能与C 标准库中的vector非常类似。Go语言在语言层面对数组切片的支持,相比C 开发者有效地消除了反复写以下几行代码的工作量:
  #include
  #include
  #include
  
  using namespace std;
  因为是语言内置特性,开发者根本不用费事去添加依赖的包,既可以少一些输入工作量,也可以让代码看起来尽量简洁。
  1.2.3 函数多返回值
  目前的主流语言中除Python外基本都不支持函数的多返回值功能,不是没有这类需求,可能是语言设计者没有想好该如何提供这个功能,或者认为这个功能会影响语言的美感。
  比如我们如果要定义一个函数用于返回个人名字信息,而名字信息因为包含多个部分——姓氏、名字、中间名和别名,在不支持多返回值的语言中我们有以下两种做法:要么专门定义一个结构体用于返回,比如:
  struct name
  {
   char first_name[20];
   char middle_name[20];
   char last_name[20];
   char nick_name[48];
  };
  
  // 函数原型
  extern name get_name();
  
  // 函数调用
  name n = get_name();
  或者以传出参数的方式返回多个结果:
  // 函数原型
  extern void get_name(
   /*out*/char* first_name,
   /*out*/char* middle_name,
   /*out*/char* last_name,
   /*out*/char* nick_name);
  
  // 先分配内存
  char first_name[20];
  char middle_name[20];
  char last_name[20];
  char nick_name[48];
  
  // 函数调用
  get_name(first_name, middle_name, last_name, nick_name);
  Go语言革命性地在静态开发语言阵营中率先提供了多返回值功能。这个特性让开发者可以从原来用各种比较别扭的方式返回多个值的痛苦中解脱出来,既不用再区分参数列表中哪几个用于输入,哪几个用于输出,也不用再只为了返回多个值而专门定义一个数据结构。
  在Go语言中,上述的例子可以修改为以下的样子:
  func getName()(firstName, middleName, lastName, nickNamestring){
   return "May", "M", "Chen", "Babe"
  }
  因为返回值都已经有名字,因此各个返回值也可以用如下方式来在不同的位置进行赋值,从而提供了极大的灵活性:
  func getName()(firstName, middleName, lastName, nickNamestring){
   firstName = "May"
   middleName = "M"
   lastName = "Chen"
   nickName = "Babe"
   return
  }
  并不是每一个返回值都必须赋值,没有被明确赋值的返回值将保持默认的空值。而函数的调用相比C/C 语言要简化很多:
  fn, mn, ln, nn := getName()
  如果开发者只对该函数其中的某几个返回值感兴趣的话,也可以直接用下划线作为占位符来忽略其他不关心的返回值。下面的调用表示调用者只希望接收lastName的值,这样可以避免声明完全没用的变量:
  _, _, lastName, _ := getName()
  我们会在第2章中详细讲解多重返回值的用法。
  1.2.4 错误处理
  Go语言引入了3个关键字用于标准的错误处理流程,这3个关键字分别为defer、panic和recover。本书的“序”已经用示例展示了defer关键字的强大之处,在第2章中我们还会详细描述Go语言错误处理机制的独特之处。整体上而言与C 和Java等语言中的异常捕获机制相比,Go语言的错误处理机制可以大量减少代码量,让开发者也无需仅仅为了程序安全性而添加大量一层套一层的try-catch语句。这对于代码的阅读者和维护者来说也是一件很好的事情,因为可以避免在层层的代码嵌套中定位业务代码。2.6节将介绍Go语言中的错误处理机制。
  1.2.5 匿名函数和闭包
  在Go语言中,所有的函数也是值类型,可以作为参数传递。Go语言支持常规的匿名函数和闭包,比如下列代码就定义了一个名为f的匿名函数,开发者可以随意对该匿名函数变量进行传递和调用:
  f := func(x, y int) int {
   return x y
  }
  1.2.6 类型和接口
  Go语言的类型定义非常接近于C语言中的结构(struct),甚至直接沿用了struct关键字。相比而言,Go语言并没有直接沿袭C 和Java的传统去设计一个超级复杂的类型系统,不支持继承和重载,而只是支持了*基本的类型组合功能。
  巧妙的是,虽然看起来支持的功能过于简洁,细用起来你却会发现,C 和Java使用那些复杂的类型系统实现的功能在Go语言中并不会出现无法表现的情况,这反而让人反思其他语言中引入这些复杂概念的必要性。我们在第3章中将详细描述Go语言的类型系统。
  Go语言也不是简单的对面向对象开发语言做减法,它还引入了一个无比强大的“非侵入式”接口的概念,让开发者从以往对C 和Java开发中的接口管理问题中解脱出来。在C 中,我们通常会这样来确定接口和类型的关系:
  // 抽象接口
  interface IFly
  {
   virtual void Fly()=0;
  };
  
  // 实现类
  class Bird : public IFly
  {
  public:
   Bird()
  {}
   virtual ~Bird()
  {}
  public:
   void Fly()
   {
   // 以鸟的方式飞行
   }
   };
  
   void main()
   {
   IFly* pFly = new Bird();
   pFly->Fly();
   delete pFly;
   }
  显然,在实现一个接口之前必须先定义该接口,并且将类型和接口紧密绑定,即接口的修改会影响到所有实现了该接口的类型,而Go语言的接口体系则避免了这类问题:
  type Bird struct {
   ...
  }
  
  func (b *Bird) Fly() {
   // 以鸟的方式飞行
  }
  我们在实现Bird类型时完全没有任何IFly的信息。我们可以在另外一个地方定义这个IFly接口:
  type IFly interface {
   Fly()
  }
  这两者目前看起来完全没有关系,现在看看我们如何使用它们:
  func main() {
   var fly IFly = new(Bird)
   fly.Fly()
  }
  可以看出,虽然Bird类型实现的时候,没有声明与接口IFly的关系,但接口和类型可以直接转换,甚至接口的定义都不用在类型定义之前,这种比较松散的对应关系可以大幅降低因为接口调整而导致的大量代码调整工作。
  1.2.7 并发编程
  Go语言引入了goroutine概念,它使得并发编程变得非常简单。通过使用goroutine而不是裸用操作系统的并发机制,以及使用消息传递来共享内存而不是使用共享内存来通信,Go语言让并发编程变得更加轻盈和安全。
  通过在函数调用前使用关键字go,我们即可让该函数以goroutine方式执行。goroutine是一种比线程更加轻盈、更省资源的协程。Go语言通过系统的线程来多路派遣这些函数的执行,使得每个用go关键字执行的函数可以运行成为一个单位协程。当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。而且调度的开销非常小,一颗CPU调度的规模不下于每秒百万次,这使得我们能够创建大量的goroutine,从而可以很轻松地编写高并发程序,达到我们想要的目的。
  Go语言实现了CSP(通信顺序进程,Communicating SequentialProcess)模型来作为goroutine间的推荐通信方式。在CSP模型中,一个并发系统由若干并行运行的顺序进程组成,每个进程不能对其他进程的变量赋值。进程之间只能通过一对通信原语实现协作。Go语言用channel(通道)这个概念来轻巧地实现了CSP模型。channel的使用方式比较接近Unix系统中的管道(pipe)概念,可以方便地进行跨goroutine的通信。
  另外,由于一个进程内创建的所有goroutine运行在同一个内存地址空间中,因此如果不同的goroutine不得不去访问共享的内存变量,访问前应该先获取相应的读写锁。Go语言标准库中的sync包提供了完备的读写锁功能。
  下面我们用一个简单的例子来演示goroutine和channel的使用方式。这是一个并行计算的例子,由两个goroutine进行并行的累加计算,待这两个计算过程都完成后打印计算结果,具体如代码清单1-1所示。
  
  代码清单1-1 paracalc.go
  package main
  
  import "fmt"
  
  func sum(values [] int, resultChan chan int) {
   sum := 0
   for _, value := range values {
   sum = value
   }
   resultChan <- sum // 将计算结果发送到channel中
  }
  
  func main() {
   values := [] int{1, 2, 3, 4, 5, 6, 7, 8, 9,10}
  
   resultChan := make(chan int, 2)
   go sum(values[:len(values)/2],resultChan)
   go sum(values[len(values)/2:],resultChan)
   sum1, sum2 := <-resultChan,<-resultChan // 接收结果
  
   fmt.Println("Result:", sum1, sum2, sum1 sum2)
  }
  1.2.8 反射
  反射(reflection)是在Java语言出现后迅速流行起来的一种概念。通过反射,你可以获取对象类型的详细信息,并可动态操作对象。反射是把双刃剑,功能强大但代码可读性并不理想。若非必要,我们并不推荐使用反射。
  Go语言的反射实现了反射的大部分功能,但没有像Java语言那样内置类型工厂,故而无法做到像Java那样通过类型字符串创建对象实例。在Java中,你可以读取配置并根据类型名称创建对应的类型,这是一种常见的编程手法,但在Go语言中这并不被推荐。
  反射*常见的使用场景是做对象的序列化(serialization,有时候也叫Marshal &Unmarshal)。例如,Go语言标准库的encoding/json、encoding/xml、encoding/gob、encoding/binary等包就大量依赖于反射功能来实现。
  这里先举一个小例子,可以利用反射功能列出某个类型中所有成员变量的值,如代码清单1-2所示。
  代码清单1-2 reflect.go
  package main
  
  import (
   "fmt"
   "reflect"
  )
  
  type Bird struct {
   Name string
   LifeExpectance int
  }
  
  func (b *Bird) Fly() {
   fmt.Println("I am flying...")
  }
  
  func main() {
   sparrow := &Bird{"Sparrow", 3}
   s := reflect.ValueOf(sparrow).Elem()
   typeOfT := s.Type()
   for i := 0; i < s.NumField(); i {
   f := s.Field(i)
   fmt.Printf("%d: %s %s= %v\n", i, typeOfT.Field(i).Name, f.Type(),
   f.Interface())
   }
  }
  该程序的输出结果为:
  0: Name string = Sparrow
  1: LifeExpectance int = 3
  我们会在第9章中简要介绍反射的基本使用方法和注意事项。
  1.2.9 语言交互性
  由于Go语言与C语言之间的天生联系,Go语言的设计者们自然不会忽略如何重用现有C模块的这个问题,这个功能直接被命名为Cgo。Cgo既是语言特性,同时也是一个工具的名称。
  在Go代码中,可以按Cgo的特定语法混合编写C语言代码,然后Cgo工具可以将这些混合的C代码提取并生成对于C功能的调用包装代码。开发者基本上可以完全忽略这个Go语言和C语言的边界是如何跨越的。
  与Java中的JNI不同,Cgo的用法非常简单,比如代码清单1-3就可以实现在Go中调用C语言标准库的puts函数。
  代码清单1-3 cprint.go
  package main
  
  /*
  #include
  */
  import "C"
  import "unsafe"
  
  func main() {
   cstr := C.CString("Hello, world")
   C.puts(cstr)
   C.free(unsafe.Pointer(cstr))
  }
  我们将在第9章中详细介绍Cgo的用法。
  1.3 *个Go程序
  自Kernighan和Ritchie合著的《C程序设计语言》(The C ProgrammingLanguage)出版以来,几乎所有的编程书都以一个Helloworld小例子作为开始。我们也不免俗(或者说尊重传统),下面我们从一个简单Go语言版本的Helloworld来初窥Go这门新语言的模样,如代码清单1-4所示。
  代码清单1-4 hello.go
  package main
  
  import "fmt"// 我们需要使用fmt包中的Println()函数
  
  func main() {
   fmt.Println("Hello, world. 你好,世界!")
  }
  1.3.1 代码解读
  每个Go源代码文件的开头都是一个package声明,表示该Go代码所属的包。包是Go语言里*基本的分发单位,也是工程管理中依赖关系的体现。要生成Go可执行程序,必须建立一个名字为main的包,并且在该包中包含一个叫main()的函数(该函数是Go可执行程序的执行起点)。
  Go语言的main()函数不能带参数,也不能定义返回值。命令行传入的参数在os.Args变量中保存。如果需要支持命令行开关,可使用flag包。在本书后面我们将解释如何使用flag包来做命令行参数规范的定义,以及获取和解析命令行参数。
  在包声明之后,是一系列的import语句,用于导入该程序所依赖的包。由于本示例程序用到了Println()函数,所以需要导入该函数所属的fmt包。
  有一点需要注意,不得包含在源代码文件中没有用到的包,否则Go编译器会报编译错误。这与下面提到的强制左花括号{的放置位置以及之后会提到的函数名的大小写规则,均体现了Go语言在语言层面解决软件工程问题的设计哲学。
  所有Go函数(包括在对象编程中会提到的类型成员函数)以关键字func开头。一个常规的函数定义包含以下部分:
  func 函数名(参数列表)(返回值列表) {
   // 函数体
  }
  对应的一个实例如下:
  func Compute(value1 int, value2 float64)(result float64, errerror) {
   // 函数体
  }
  Go支持多个返回值。以上的示例函数Compute()返回了两个值,一个叫result,另一个是err。并不是所有返回值都必须赋值。在函数返回时没有被明确赋值的返回值都会被设置为默认值,比如result会被设为0.0,err会被设为nil。
  Go程序的代码注释与C 保持一致,即同时支持以下两种用法:
  /*
  块注释
  */
  // 行注释
  相信熟悉C和C 的读者也发现了另外一点,即在这段Go示例代码里没有出现分号。Go程序并不要求开发者在每个语句后面加上分号表示语句结束,这是与C和C 的一个明显不同之处。
  有些读者可能会自然地把左花括号{另起一行放置,这样做的结果是Go编译器报告编译错误,这点需要特别注意:
  syntax error: unexpected semicolon or newline before {
  1.3.2 编译环境准备
  前面我们给大家大概介绍了*个Go程序的基本结构,接下来我们来准备编译这段小程序的环境。
  在Go1发布之前,开发者要想使用Go,只能自行下载代码并进行编译,而现在可以直接下载对应的安装包进行安装,安装包的下载地址为http://code.google.com/p/go/downloads/list。
  在*nix环境中,Go默认会被安装到/usr/local/go目录中。安装包在安装完成后会自动添加执行文件目录到系统路径中。
  安装完成后,请重新启动命令行程序,然后运行以下命令以验证Go是否已经正确安装:
  $ go version
  go version go1
  如果该命令能够正常运行并输出相应的信息,说明Go编译环境已经正确安装完毕。如果提示找不到go命令,可以通过手动添加/usr/local/go/bin到PATH环境变量来解决。
  1.3.3 编译程序
  假设之前介绍的Hello,world代码被保存为了hello.go,并位于~/goyard目录下,那么可以用以下命令行编译并直接运行该程序:
  $ cd ~/goyard
  $ go run hello.go # 直接运行
  Hello, world. 你好,世界!
  使用这个命令,会将编译、链接和运行3个步骤合并为一步,运行完后在当前目录下也看不到任何中间文件和*终的可执行文件。如果要只生成编译结果而不自动运行,我们也可以使用Go命令行工具的build命令:
  $ cd ~/goyard
  $ go build hello.go
  $ ./hello
  Hello, world. 你好,世界!
  可以看出,Go命令行工具是一个非常强大的源代码管理工具。我们将在第4章中详细讲解Go命令行工具所包含的更多更强大的功能。
  从根本上说,Go命令行工具只是一个源代码管理工具,或者说是一个前端。真正的Go编译器和链接器被Go命令行工具隐藏在后面,我们可以直接使用它们:
  $ 6g helloworld.go
  $ 6l helloworld.6
  $ ./6.out
  Hello, world. 你好,世界!
  6g和6l是64位版本的Go编译器和链接器,对应的32位版本工具为8g和8l。Go还有另外一个GCC版本的编译器,名为gccgo,但不在本书的讨论范围内。
  1.4 开发工具选择
  Google并没有随着Go1的发布推出官方的Go集成开发环境(IDE),因此开发者需要自行考虑和选择合适的开发工具。目前比较流行的开发工具如下:
  文本编辑工具gedit(Linux)/Notepad (Windows)/Fraise(Mac OS X);
  安装了GoClipse插件的Eclipse,集成性做得很好;
  Vim/Emacs,*开发工具;
  LiteIDE,一款专为Go语言开发的集成开发环境。
  由于Go代码的轻巧和模块化特征,其实一般的文本编辑工具就可以胜任Go开发工作。本书的所有代码均使用Linux上的gedit工具完成。
  Go社区提供了各种文本编辑器的语法高亮设置方法,这在本书*后一章也有所介绍。


返回顶部