新書推薦:
《
真需求
》
售價:HK$
110.9
《
阿勒泰的春天
》
售價:HK$
50.4
《
如见你
》
售價:HK$
51.3
《
人格阴影 全新修订版,更正旧版多处问题。国际分析心理学协会(IAAP)主席力作
》
售價:HK$
67.0
《
560种野菜野果鉴别与食用手册
》
售價:HK$
67.1
《
中国官僚政治研究(一部洞悉中国政治制度演变的经典之作)
》
售價:HK$
62.7
《
锂电储能产品设计及案例详解
》
售價:HK$
110.9
《
首辅养成手册(全三册)(张晚意、任敏主演古装剧《锦绣安宁》原著小说)
》
售價:HK$
121.0
|
編輯推薦: |
《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年的CC++开发经验。在接触Go语言后即可被其大道至简、少即是多的设计哲学所倾倒。七牛云存储是国内第一个吃螃蟹的团队,核心服务完全采用Go语言实现。
吕桂华
七牛云存储联合创始人,曾在金山软件、盛大游戏等公司担任架构师和部门经理等职务,在企业级系统和大型网游平台领域有较多涉猎。拥有十余年的CC++大型项目开发经验,也曾在Java和.NET平台上探索多年。同样被Go语言的魅力所吸引而不可自拔,希望能为推广这门优秀的语言尽自己的绵薄之力。
|
目錄:
|
目 录
第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 nethttp包简介 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 基于SSLTLS的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年(估计大部分读者那时候都还没出世),肯?汤普逊(Ken
Thompson)和丹尼斯?里奇(Dennis
Ritchie)在贝尔实验室的计算科学研究中心里开发出了Unix这个大名鼎鼎的操作系统,还因为开发Unix而衍生出了一门同样赫赫有名的编程语言——C语言。对于很大一部分人而言,Unix就是操作系统的鼻祖,C语言也是计算机课程中最广泛使用的编程语言。Unix和C语言在过去的几十年以来已经造就了无数的成功商业故事,比如曾在90年代如日中天的太阳微系统(Sun
MicroSystems),现在正如日中天的苹果的Mac OS
X操作系统其实也可以认为是Unix的一个变种(FreeBSD)。
虽然已经取得了如此巨大的成就,贝尔实验室的这几个人并没有因此而沉浸在光环中止步不前,他们从20世纪80年代又开始了一个名为Plan
9的操作系统研究项目,目的就是解决Unix中的一些问题,发展出一个Unix的后续替代系统。在之后的几十年中,该研究项目又演变出了另一个叫Inferno的项目分支,以及一个名为Limbo的编程语言。
Limbo是用于开发运行在小型计算机上的分布式应用的编程语言,它支持模块化编程,编译期和运行时的强类型检查,进程内基于具有类型的通信通道,原子性垃圾收集和简单的抽象数据类型。它被设计为:即便是在没有硬件内存保护的小型设备上,也能安全运行。
Limbo语言被认为是Go语言的前身,不仅仅因为是同一批人设计的语言,而是Go语言确实从Limbo语言中继承了众多优秀的特性。
贝尔实验室后来经历了多次的动荡,包括肯?汤普逊在内的Plan
9项目原班人马加入了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(Google
AppEngine)很早就开始支持Go语言了。按目前的发展态势,在Google内部,Go语言有逐渐取代Java和Python主流地位的趋势。在Google的更多产品中,我们将看到Go语言的踪影,比如Google最核心的搜索和广告业务。
在本书的序中,我们已经清晰诠释了为什么在语言泛滥的时代Google还要设计和推出一门新的编程语言。按照已经发布的Go语言的特性,我们有足够的理由相信Google推出此门新编程语言绝不仅仅是简单的跑马圈地运动,而是为了解决切实的问题。
下面我们再来看看Go语言的主要作者。
肯?汤普逊(Ken
Thompson,http:en.wikipedia.orgwikiKen_Thompson):设计了B语言和C语言,创建了Unix和Plan
9操作系统,1983年图灵奖得主,Go语言的共同作者。
罗布?派克(Rob
Pike,http:en.wikipedia.orgwikiRob_Pike):Unix小组的成员,参与Plan
9和Inferno操作系统,参与 Limbo和Go语言的研发,《Unix编程环境》作者之一。
罗伯特?格里泽默(Robert
Griesemer):曾协助制作Java的HotSpot编译器和Chrome浏览器的JavaScript引擎V8。
拉斯? 考克斯(Russ Cox,http:swtch.com~rsc):参与Plan
9操作系统的开发,Google Code Search项目负责人。
伊安?泰勒(Ian Lance
Taylor):GCC社区的活跃人物,gold连接器和GCC过程间优化LTO的主要设计者,Zembu公司的创始人。
布拉德?菲茨帕特里克(Brad
Fitzpatrick,http:en.wikipedia.orgwikiBrad_Fitzpatrick):LiveJournal的创始人,著名开源项目memcached的作者。
虽然我们这里只列出了一部分,大家已经可以看出这个语言开发团队空前强大,这让我们在为Go语言的优秀特性而兴奋之外,还非常看好这门语言的发展前景。
1.2 语言特性
Go语言作为一门全新的静态类型开发语言,与当前的开发语言相比具备众多令人兴奋不已的新特性。本书从第2章开始,我们将对Go语言的各个方面进行详细解析,让读者能够尽量轻松地掌握这门简洁、有趣却又超级强大的新语言。
这里先给读者罗列一下Go语言最主要的特性:
自动垃圾回收
更丰富的内置类型
函数多返回值
错误处理
匿名函数和闭包
类型和接口
并发编程
反射
语言交互性
1.2.1 自动垃圾回收
我们可以先看下不支持垃圾回收的语言的资源管理方式,以下为一小段C语言代码:
void foo
{
char* p = new char[128];
... 对p指向的内存块进行赋值
func1p; 使用内存指针
delete[] p;
}
各种非预期的原因,比如由于开发者的疏忽导致最后的delete语句没有被调用,都会引发经典而恼人的内存泄露问题。假如该函数被调用得非常频繁,那么我们观察该进程执行时,会发现该进程所占用的内存会一直疯长,直至占用所有系统内存并导致程序崩溃,而如果泄露的是系统资源的话,那么后果还会更加严重,最终很有可能导致系统崩溃。
手动管理内存的另外一个问题就是由于指针的到处传递而无法确定何时可以释放该指针所指向的内存块。假如代码中某个位置释放了内存,而另一些地方还在使用指向这块内存的指针,那么这些指针就变成了所谓的“野指针”(wild
pointer)或者“悬空指针”(dangling pointer),对这些指针进行的任何读写操作都会导致不可预料的后果。
由于其杰出的效率,C和C++语言在非常长的时间内都作为服务端系统的主要开发语言,比如Apache、Nginx和MySQL等著名的服务器端软件就是用C和C++开发的。然而,内存和资源管理一直是一个让人非常抓狂的难题。服务器的崩溃十有八九就是因为不正确的内存和资源管理导致,更讨厌的是这种内存和资源管理问题即使被发现了,也很难定位到具体的错误地点,导致无数程序员通宵达旦地调试程序。
这个问题在多年里被不同人用不同的方式来试图解决,并诞生了一些非常著名的内存检查工具,比如Rational
Purify、Compuware BoundsChecker和英特尔的Parallel
Inspector等。从设计方法的角度也衍生了类似于内存引用计数之类的方法(通常被称为“智能指针”),后续在Windows平台上标准化的COM出现的一个重要原因就是为了解决内存管理的难题。但是事实证明,这些工具和方法虽然能够在一定程度上辅助开发者,但并没法让开发者避免通宵调试这样又苦又累的工作。
到目前为止,内存泄露的最佳解决方案是在语言级别引入自动垃圾回收算法(Garbage
Collection,简称GC)。所谓垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。当然,因为需要尽量最小化垃圾回收的性能损耗,以及降低对正常程序执行过程的影响,现实中的垃圾回收算法要比这个复杂得多,比如为对象增加年龄属性等,但基本原理都是如此。
自动垃圾回收在CC++社区一直作为一柄双刃剑看待,虽然到C++0x(后命名为C++11)正式发布时,这个呼声颇高的特性总算是被加入了,但按C++之父的说法,由于C++本身过于强大,导致在C++中支持垃圾收集变成了一个困难的工作。假如C++支持垃圾收集,以下的代码片段在运行时就会是一个严峻的考验:
int* p = new int;
p += 10; 对指针进行了偏移,因此那块内存不再被引用
…… 这里可能会发生针对这块int内存的垃圾收集 ……
p -= 10; 咦,居然又偏移到原来的位置
*p = 10; 如果有垃圾收集,这里就无法保证可以正常运行了
微软的C++CLI算是用一种偏门的方式让C++程序员们有机会品尝一下垃圾回收功能的鲜美味道。在CC++之后出现的新语言,比如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_namefirst_name, middle_name, last_name, nick_name;
Go语言革命性地在静态开发语言阵营中率先提供了多返回值功能。这个特性让开发者可以从原来用各种比较别扭的方式返回多个值的痛苦中解脱出来,既不用再区分参数列表中哪几个用于输入,哪几个用于输出,也不用再只为了返回多个值而专门定义一个数据结构。
在Go语言中,上述的例子可以修改为以下的样子:
func getNamefirstName, middleName, lastName, nickName
string{
return "May", "M", "Chen", "Babe"
}
因为返回值都已经有名字,因此各个返回值也可以用如下方式来在不同的位置进行赋值,从而提供了极大的灵活性:
func getNamefirstName, middleName, lastName, nickName
string{
firstName = "May"
middleName = "M"
lastName = "Chen"
nickName = "Babe"
return
}
并不是每一个返回值都必须赋值,没有被明确赋值的返回值将保持默认的空值。而函数的调用相比CC++语言要简化很多:
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 := funcx, 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 = newBird
fly.Fly
}
可以看出,虽然Bird类型实现的时候,没有声明与接口IFly的关系,但接口和类型可以直接转换,甚至接口的定义都不用在类型定义之前,这种比较松散的对应关系可以大幅降低因为接口调整而导致的大量代码调整工作。
1.2.7 并发编程
Go语言引入了goroutine概念,它使得并发编程变得非常简单。通过使用goroutine而不是裸用操作系统的并发机制,以及使用消息传递来共享内存而不是使用共享内存来通信,Go语言让并发编程变得更加轻盈和安全。
通过在函数调用前使用关键字go,我们即可让该函数以goroutine方式执行。goroutine是一种比线程更加轻盈、更省资源的协程。Go语言通过系统的线程来多路派遣这些函数的执行,使得每个用go关键字执行的函数可以运行成为一个单位协程。当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。而且调度的开销非常小,一颗CPU调度的规模不下于每秒百万次,这使得我们能够创建大量的goroutine,从而可以很轻松地编写高并发程序,达到我们想要的目的。
Go语言实现了CSP(通信顺序进程,Communicating Sequential
Process)模型来作为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 sumvalues [] 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 := makechan int, 2
go sumvalues[:lenvalues2],
resultChan
go sumvalues[lenvalues2:],
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语言标准库的encodingjson、encodingxml、encodinggob、encodingbinary等包就大量依赖于反射功能来实现。
这里先举一个小例子,可以利用反射功能列出某个类型中所有成员变量的值,如代码清单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.ValueOfsparrow.Elem
typeOfT := s.Type
for i := 0; i s.NumField; i++ {
f := s.Fieldi
fmt.Printf"%d: %s %s
= %v\n", i, typeOfT.Fieldi.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.putscstr
C.freeunsafe.Pointercstr
}
我们将在第9章中详细介绍Cgo的用法。
1.3 第一个Go程序
自Kernighan和Ritchie合著的《C程序设计语言》(The C Programming
Language)出版以来,几乎所有的编程书都以一个Hello
world小例子作为开始。我们也不免俗(或者说尊重传统),下面我们从一个简单Go语言版本的Hello
world来初窥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 Computevalue1 int, value2 float64result float64, err
error {
函数体
}
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程序的基本结构,接下来我们来准备编译这段小程序的环境。
在Go
1发布之前,开发者要想使用Go,只能自行下载代码并进行编译,而现在可以直接下载对应的安装包进行安装,安装包的下载地址为http:code.google.compgodownloadslist。
在*nix环境中,Go默认会被安装到usrlocalgo目录中。安装包在安装完成后会自动添加执行文件目录到系统路径中。
安装完成后,请重新启动命令行程序,然后运行以下命令以验证Go是否已经正确安装:
$ go version
go version go1
如果该命令能够正常运行并输出相应的信息,说明Go编译环境已经正确安装完毕。如果提示找不到go命令,可以通过手动添加usrlocalgobin到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并没有随着Go
1的发布推出官方的Go集成开发环境(IDE),因此开发者需要自行考虑和选择合适的开发工具。目前比较流行的开发工具如下:
文本编辑工具gedit(Linux)Notepad++(Windows)Fraise(Mac OS X);
安装了GoClipse插件的Eclipse,集成性做得很好;
VimEmacs,万能开发工具;
LiteIDE,一款专为Go语言开发的集成开发环境。
由于Go代码的轻巧和模块化特征,其实一般的文本编辑工具就可以胜任Go开发工作。本书的所有代码均使用Linux上的gedit工具完成。
Go社区提供了各种文本编辑器的语法高亮设置方法,这在本书最后一章也有所介绍。
|
|