diff --git a/ebook/01.md b/ebook/01.md
new file mode 100644
index 000000000..a0a494d7f
--- /dev/null
+++ b/ebook/01.md
@@ -0,0 +1,904 @@
+# 1 GO环境配置
+
+欢迎来到Go的世界,让我们开始探索吧!
+
+Go是一种新的语言,一种并发的、带垃圾回收的、快速编译的语言。它具有以下特点:
+
+- 它可以在一台计算机上用几秒钟的时间编译一个大型的Go程序。
+- Go为软件构造提供了一种模型,它使依赖分析更加容易,且避免了大部分C风格include文件与库的开头。
+- Go是静态类型的语言,它的类型系统没有层级。因此用户不需要在定义类型之间的关系上花费时间,这样感觉起来比典型的面向对象语言更轻量级。
+- Go完全是垃圾回收型的语言,并为并发执行与通信提供了基本的支持。
+- 按照其设计,Go打算为多核机器上系统软件的构造提供一种方法。
+
+Go是一种编译型语言,它结合了解释型语言的游刃有余,动态类型语言的开发效率,以及静态类型的安全性。它也打算成为现代的,支持网络与多核计算的语言。要满足这些目标,需要解决一些语言上的问题:一个富有表达能力但轻量级的类型系统,并发与垃圾回收机制,严格的依赖规范等等。这些无法通过库或工具解决好,因此Go也就应运而生了。
+
+在本章中,我们将讲述Go的安装方法,以及如何配置项目信息。
+
+## 目录
+
+
+
+
+
+# 1.1 Go 安装
+
+## Go的三种安装方式
+Go有多种安装方式,你可以选择自己喜欢的。这里我们介绍三种最常见的安装方式:
+
+- Go源码安装:这是一种标准的软件安装方式。对于经常使用Unix类系统的用户,尤其对于开发者来说,从源码安装是最方便而熟悉的。
+- Go标准包安装:Go提供了方便的安装包,支持Windows、Linux、Mac等系统。这种方式适合初学者,可根据自己的系统位数下载好相应的安装包,一路next就可以轻松安装了。
+- 第三方工具安装:目前有很多方便的第三方软件包工具,例如Ubuntu的apt-get、Mac的homebrew等。这种安装方式适合那些熟悉相应系统的用户。
+
+最后,如果你想在同一个系统中安装多个版本的Go,你可以参考第三方工具[GVM](https://github.com/moovweb/gvm),这是目前在这方面做得最好的工具,除非你知道怎么处理。
+
+## Go源码安装
+在Go的源代码中,有些部分是用Plan 9 C和AT&T汇编写的,因此假如你要想从源码安装,就必须安装C的编译工具。
+
+在Mac系统中,只要你安装了Xcode,就已经包含了相应的编译工具。
+
+在类Unix系统中,需要安装gcc等工具。例如Ubuntu系统可通过在终端中执行`sudo apt-get install gcc libc6-dev`来安装编译工具。
+
+在Windows系统中,你需要安装MinGW,然后通过MinGW安装gcc,并设置相应的环境变量。
+
+Go使用[Mercurial][hg]进行版本管理,首先你必须安装了Mercurial,然后才能下载。假设你已经安装好Mercurial,执行如下代码:
+
+假设已经位于Go的安装目录 `$GO_INSTALL_DIR`下
+
+ hg clone -u release https://code.google.com/p/go
+ cd go/src
+ ./all.bash
+
+运行all.bash后出现"ALL TESTS PASSED"字样时才算安装成功。
+
+上面是Unix风格的命令,Windows下的安装方式类似,只不过是运行all.bat,调用的编译器是MinGW的gcc。
+
+然后设置几个环境变量,
+
+ export GOROOT=$HOME/go
+ export GOBIN=$GOROOT/bin
+ export PATH=$PATH:$GOBIN
+
+看到如下图片即说明你已经安装成功
+
+
+
+图1.1 源码安装之后执行Go命令的图
+
+如果出现Go的Usage信息,那么说明Go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了Go的安装目录。
+
+
+## Go标准包安装
+
+Go提供了每个平台打好包的一键安装,这些包默认会安装到如下目录:/usr/local/go (Windows系统:c:\Go),当然你可以改变他们的安装位置,但是改变之后你必须在你的环境变量中设置如下信息:
+
+ export GOROOT=$HOME/go
+ export PATH=$PATH:$GOROOT/bin
+
+### 如何判断自己的操作系统是32位还是64位?
+
+我们接下来的Go安装需要判断操作系统的位数,所以这小节我们先确定自己的系统类型。
+
+Windows系统用户请按Win+R运行cmd,输入`systeminfo`后回车,稍等片刻,会出现一些系统信息。在“系统类型”一行中,若显示“x64-based PC”,即为64位系统;若显示“X86-based PC”,则为32位系统。
+
+Mac系统用户建议直接使用64位的,因为Go所支持的Mac OS X版本已经不支持纯32位处理器了。
+
+Linux系统用户可通过在Terminal中执行命令`arch`(即`uname -m`)来查看系统信息:
+
+64位系统显示
+
+ x86_64
+
+32位系统显示
+
+ i386
+
+### Mac 安装
+
+访问[下载地址][downlink],32位系统下载go1.0.3.darwin-386.pkg,64位系统下载go1.0.3.darwin-amd64.pkg,双击下载文件,一路默认安装点击下一步,这个时候go已经安装到你的系统中,默认已经在PATH中增加了相应的`~/go/bin`,这个时候打开终端,输入`go`
+
+看到类似上面源码安装成功的图片说明已经安装成功
+
+如果出现go的Usage信息,那么说明go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了go的安装目录。
+
+### Linux 安装
+
+访问[下载地址][downlink],32位系统下载go1.0.3.linux-386.tar.gz,64位系统下载go1.0.3.linux-amd64.tar.gz,
+
+假定你想要安装Go的目录为 `$GO_INSTALL_DIR`,后面替换为相应的目录路径。
+
+解压缩`tar.gz`包到安装目录下:`tar zxvf go1.0.3.linux-amd64.tar.gz -C $GO_INSTALL_DIR`。
+
+设置PATH,`export PATH=$PATH:$GO_INSTALL_DIR/go/bin`
+
+然后执行`go`
+
+
+
+图1.2 Linux系统下安装成功之后执行go显示的信息
+
+如果出现go的Usage信息,那么说明go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了go的安装目录。
+
+### Windows 安装
+
+访问[下载地址][downlink],32位系统下载go1.0.3.windows-386.msi,64位系统下载go1.0.3.windows-amd64.msi。双击打开下载的文件,一路按照默认点击下一步,这个时候go已经安装到你的系统中,默认安装之后已经在你的系统环境变量中加入了`c:/go/bin`,这个时候打开cmd,输入`go`
+
+看到类似上面mac安装成功的图片说明已经安装成功
+
+如果出现Go的Usage信息,那么说明Go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了Go的安装目录。
+
+## 第三方工具安装
+### GVM
+gvm是第三方开发的Go多版本管理工具,类似ruby里面的rvm工具。使用起来相当的方便,安装gvm使用如下命令:
+
+ bash < <(curl -s https://raw.github.com/moovweb/gvm/master/binscripts/gvm-installer)
+
+安装完成后我们就可以安装go了:
+
+ gvm install go1.1
+ gvm use go1.1
+
+也可以使用下面的命令,省去每次调用gvm use的麻烦:
+ gvm use go1.1 --default
+
+执行完上面的命令之后GOPATH、GOROOT等环境变量会自动设置好,这样就可以直接使用了。
+
+### apt-get
+Ubuntu是目前使用最多的Linux桌面系统,使用`apt-get`命令来管理软件包,我们可以通过下面的命令来安装Go,为了以后方便,应该把 `git` `mercurial` 也安装上:
+
+ sudo apt-get install python-software-properties
+ sudo add-apt-repository ppa:gophers/go
+ sudo apt-get update
+ sudo apt-get install golang-stable git-core mercurial
+
+### homebrew
+homebrew是Mac系统下面目前使用最多的管理软件的工具,目前已支持Go,可以通过命令直接安装Go,为了以后方便,应该把 `git` `mercurial` 也安装上:
+
+ brew update && brew upgrade
+ brew install go
+ brew install git
+ brew install mercurial
+
+
+
+[downlink]: http://code.google.com/p/go/downloads/list "Go安装包下载"
+[hg]: http://mercurial.selenic.com/downloads/ "Mercurial下载"
+
+
+
+# 1.2 GOPATH与工作空间
+
+## GOPATH设置
+ go 命令依赖一个重要的环境变量:$GOPATH1
+
+ *(注:这个不是Go安装目录。下面以笔者的工作目录为说明,请替换自己机器上的工作目录。)*
+
+ 在类似 Unix 环境大概这样设置:
+```sh
+ export GOPATH=/home/apple/mygo
+```
+ 为了方便,应该把新建以上文件夹,并且把以上一行加入到 `.bashrc` 或者 `.zshrc` 或者自己的 `sh` 的配置文件中。
+
+ Windows 设置如下,新建一个环境变量名称叫做GOPATH:
+```sh
+ GOPATH=c:\mygo
+```
+GOPATH允许多个目录,当有多个目录时,请注意分隔符,多个目录的时候Windows是分号,Linux系统是冒号,当有多个GOPATH时,默认会将go get的内容放在第一个目录下
+
+
+以上 $GOPATH 目录约定有三个子目录:
+
+- src 存放源代码(比如:.go .c .h .s等)
+- pkg 编译后生成的文件(比如:.a)
+- bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中,如果有多个gopath,那么使用`${GOPATH//://bin:}/bin`添加所有的bin目录)
+
+以后我所有的例子都是以mygo作为我的gopath目录
+
+## 应用目录结构
+建立包和目录:$GOPATH/src/mymath/sqrt.go(包名:"mymath")
+
+以后自己新建应用或者一个代码包都是在src目录下新建一个文件夹,文件夹名称一般是代码包名称,当然也允许多级目录,例如在src下面新建了目录$GOPATH/src/github.com/astaxie/beedb 那么这个包路径就是“github.com/astaxie/beedb”,包名称是最后一个目录beedb
+
+执行如下代码
+```sh
+ cd $GOPATH/src
+ mkdir mymath
+```
+新建文件sqrt.go,内容如下
+```go
+ // $GOPATH/src/mymath/sqrt.go源码如下:
+ package mymath
+
+ func Sqrt(x float64) float64 {
+ z := 0.0
+ for i := 0; i < 1000; i++ {
+ z -= (z*z - x) / (2 * x)
+ }
+ return z
+ }
+```
+这样我的应用包目录和代码已经新建完毕,注意:一般建议package的名称和目录名保持一致
+
+## 编译应用
+上面我们已经建立了自己的应用包,如何进行编译安装呢?有两种方式可以进行安装
+
+1、只要进入对应的应用包目录,然后执行`go install`,就可以安装了
+
+2、在任意的目录执行如下代码`go install mymath`
+
+安装完之后,我们可以进入如下目录
+```sh
+ cd $GOPATH/pkg/${GOOS}_${GOARCH}
+ //可以看到如下文件
+ mymath.a
+```
+这个.a文件是应用包,那么我们如何进行调用呢?
+
+接下来我们新建一个应用程序来调用
+
+新建应用包mathapp
+```sh
+ cd $GOPATH/src
+ mkdir mathapp
+ cd mathapp
+ vim main.go
+```
+// `$GOPATH/src/mathapp/main.go`源码:
+```go
+ package main
+
+ import (
+ "mymath"
+ "fmt"
+ )
+
+ func main() {
+ fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2))
+ }
+```
+如何编译程序呢?进入该应用目录,然后执行`go build`,那么在该目录下面会生成一个mathapp的可执行文件
+```sh
+ ./mathapp
+```
+输出如下内容
+```sh
+ Hello, world. Sqrt(2) = 1.414213562373095
+```
+如何安装该应用,进入该目录执行`go install`,那么在$GOPATH/bin/下增加了一个可执行文件mathapp,这样可以在命令行输入如下命令就可以执行
+
+ mathapp
+
+也是输出如下内容
+
+ Hello, world. Sqrt(2) = 1.414213562373095
+
+## 获取远程包
+ go语言有一个获取远程包的工具就是`go get`,目前go get支持多数开源社区(例如:github、googlecode、bitbucket、Launchpad)
+
+ go get github.com/astaxie/beedb
+
+>go get -u 参数可以自动更新包,而且当go get的时候会自动获取该包依赖的其他第三方包
+
+通过这个命令可以获取相应的源码,对应的开源平台采用不同的源码控制工具,例如github采用git、googlecode采用hg,所以要想获取这些源码,必须先安装相应的源码控制工具
+
+通过上面获取的代码在我们本地的源码相应的代码结构如下
+
+ $GOPATH
+ src
+ |--github.com
+ |-astaxie
+ |-beedb
+ pkg
+ |--相应平台
+ |-github.com
+ |--astaxie
+ |beedb.a
+
+go get本质上可以理解为首先第一步是通过源码工具clone代码到src下面,然后执行`go install`
+
+在代码中如何使用远程包,很简单的就是和使用本地包一样,只要在开头import相应的路径就可以
+
+ import "github.com/astaxie/beedb"
+
+## 程序的整体结构
+通过上面建立的我本地的mygo的目录结构如下所示
+
+ bin/
+ mathapp
+ pkg/
+ 平台名/ 如:darwin_amd64、linux_amd64
+ mymath.a
+ github.com/
+ astaxie/
+ beedb.a
+ src/
+ mathapp
+ main.go
+ mymath/
+ sqrt.go
+ github.com/
+ astaxie/
+ beedb/
+ beedb.go
+ util.go
+
+从上面的结构我们可以很清晰的看到,bin目录下面存的是编译之后可执行的文件,pkg下面存放的是函数包,src下面保存的是应用源代码
+
+ - - -
+[1] Windows系统中环境变量的形式为`%GOPATH%`,本书主要使用Unix形式,Windows用户请自行替换。
+
+
+
+# 1.3 Go 命令
+
+## Go 命令
+
+ Go语言自带有一套完整的命令操作工具,你可以通过在命令行中执行`go`来查看它们:
+
+ 
+
+图1.3 Go命令显示详细的信息
+
+ 这些命令对于我们平时编写的代码非常有用,接下来就让我们了解一些常用的命令。
+
+## go build
+
+ 这个命令主要用于测试编译。在包的编译过程中,若有必要,会同时编译与之相关联的包。
+
+ - 如果是普通包,就像我们在1.2节中编写的`mymath`包那样,当你执行`go build`之后,它不会产生任何文件。如果你需要在`$GOPATH/pkg`下生成相应的文件,那就得执行`go install`了。
+
+ - 如果是`main`包,当你执行`go build`之后,它就会在当前目录下生成一个可执行文件。如果你需要在`$GOPATH/bin`下生成相应的文件,需要执行`go install`,或者使用`go build -o 路径/a.exe`。
+
+ - 如果某个项目文件夹下有多个文件,而你只想编译某个文件,就可在`go build`之后加上文件名,例如`go build a.go`;`go build`命令默认会编译当前目录下的所有go文件。
+
+ - 你也可以指定编译输出的文件名。例如1.2节中的`mathapp`应用,我们可以指定`go build -o astaxie.exe`,默认情况是你的package名(非main包),或者是第一个源文件的文件名(main包)。
+
+ (注:实际上,package名在[Go语言规范](https://golang.org/ref/spec)中指代码中“package”后使用的名称,此名称可以与文件夹名不同。默认生成的可执行文件名是文件夹名。)
+
+ - go build会忽略目录下以“_”或“.”开头的go文件。
+
+ - 如果你的源代码针对不同的操作系统需要不同的处理,那么你可以根据不同的操作系统后缀来命名文件。例如有一个读取数组的程序,它对于不同的操作系统可能有如下几个源文件:
+
+ array_linux.go
+ array_darwin.go
+ array_windows.go
+ array_freebsd.go
+
+ `go build`的时候会选择性地编译以系统名结尾的文件(linux、darwin、windows、freebsd)。例如Linux系统下面编译只会选择array_linux.go文件,其它系统命名后缀文件全部忽略。
+
+## go clean
+
+ 这个命令是用来移除当前源码包里面编译生成的文件。这些文件包括
+
+ _obj/ 旧的object目录,由Makefiles遗留
+ _test/ 旧的test目录,由Makefiles遗留
+ _testmain.go 旧的gotest文件,由Makefiles遗留
+ test.out 旧的test记录,由Makefiles遗留
+ build.out 旧的test记录,由Makefiles遗留
+ *.[568ao] object文件,由Makefiles遗留
+
+ DIR(.exe) 由go build产生
+ DIR.test(.exe) 由go test -c产生
+ MAINFILE(.exe) 由go build MAINFILE.go产生
+
+ 我一般都是利用这个命令清除编译文件,然后github递交源码,在本机测试的时候这些编译文件都是和系统相关的,但是对于源码管理来说没必要
+
+## go fmt
+
+ 有过C/C++经验的读者会知道,一些人经常为代码采取K&R风格还是ANSI风格而争论不休。在go中,代码则有标准的风格。由于之前已经有的一些习惯或其它的原因我们常将代码写成ANSI风格或者其它更合适自己的格式,这将为人们在阅读别人的代码时添加不必要的负担,所以go强制了代码格式(比如左大括号必须放在行尾),不按照此格式的代码将不能编译通过,为了减少浪费在排版上的时间,go工具集中提供了一个`go fmt`命令 它可以帮你格式化你写好的代码文件,使你写代码的时候不需要关心格式,你只需要在写完之后执行`go fmt <文件名>.go`,你的代码就被修改成了标准格式,但是我平常很少用到这个命令,因为开发工具里面一般都带了保存时候自动格式化功能,这个功能其实在底层就是调用了`go fmt`。接下来的一节我将讲述两个工具,这两个工具都自带了保存文件时自动化`go fmt`功能。
+
+>使用go fmt命令,更多时候是用gofmt,而且需要参数-w,否则格式化结果不会写入文件。gofmt -w src,可以格式化整个项目。
+
+## go get
+
+ 这个命令是用来动态获取远程代码包的,目前支持的有BitBucket、GitHub、Google Code和Launchpad。这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行`go install`。下载源码包的go工具会自动根据不同的域名调用不同的源码工具,对应关系如下:
+
+ BitBucket (Mercurial Git)
+ GitHub (Git)
+ Google Code Project Hosting (Git, Mercurial, Subversion)
+ Launchpad (Bazaar)
+
+ 所以为了`go get` 能正常工作,你必须确保安装了合适的源码管理工具,并同时把这些命令加入你的PATH中。其实`go get`支持自定义域名的功能,具体参见`go help remote`。
+
+## go install
+
+ 这个命令在内部实际上分成了两步操作:第一步是生成结果文件(可执行文件或者.a包),第二步会把编译好的结果移到`$GOPATH/pkg`或者`$GOPATH/bin`。
+
+## go test
+
+ 执行这个命令,会自动读取源码目录下面名为`*_test.go`的文件,生成并运行测试用的可执行文件。输出的信息类似
+
+ ok archive/tar 0.011s
+ FAIL archive/zip 0.022s
+ ok compress/gzip 0.033s
+ ...
+
+ 默认的情况下,不需要任何的参数,它会自动把你源码包下面所有test文件测试完毕,当然你也可以带上参数,详情请参考`go help testflag`
+
+## go doc
+
+ (1.2rc1 以後沒有 go doc 指令, 只留下 godoc 指令)
+ 很多人说go不需要任何的第三方文档,例如chm手册之类的(其实我已经做了一个了,[chm手册](https://github.com/astaxie/godoc)),因为它内部就有一个很强大的文档工具。
+
+ 如何查看相应package的文档呢?
+ 例如builtin包,那么执行`go doc builtin`
+ 如果是http包,那么执行`go doc net/http`
+ 查看某一个包里面的函数,那么执行`godoc fmt Printf`
+ 也可以查看相应的代码,执行`godoc -src fmt Printf`
+
+ 通过命令在命令行执行 godoc -http=:端口号 比如`godoc -http=:8080`。然后在浏览器中打开`127.0.0.1:8080`,你将会看到一个golang.org的本地copy版本,通过它你可以查询pkg文档等其它内容。如果你设置了GOPATH,在pkg分类下,不但会列出标准包的文档,还会列出你本地`GOPATH`中所有项目的相关文档,这对于经常被墙的用户来说是一个不错的选择。
+
+## 其它命令
+
+ go还提供了其它很多的工具,例如下面的这些工具
+
+ go fix 用来修复以前老版本的代码到新版本,例如go1之前老版本的代码转化到go1
+ go version 查看go当前的版本
+ go env 查看当前go的环境变量
+ go list 列出当前全部安装的package
+ go run 编译并运行Go程序
+
+以上这些工具还有很多参数没有一一介绍,用户可以使用`go help 命令`获取更详细的帮助信息。
+
+
+
+# 1.4 Go开发工具
+
+本节我将介绍几个开发工具,它们都具有自动化提示,自动化fmt功能。因为它们都是跨平台的,所以安装步骤之类的都是通用的。
+
+## LiteIDE
+
+ LiteIDE是一款专门为Go语言开发的跨平台轻量级集成开发环境(IDE),由visualfc编写。
+
+ 
+
+图1.4 LiteIDE主界面
+
+**LiteIDE主要特点:**
+
+* 支持主流操作系统
+ * Windows
+ * Linux
+ * MacOS X
+* Go编译环境管理和切换
+ * 管理和切换多个Go编译环境
+ * 支持Go语言交叉编译
+* 与Go标准一致的项目管理方式
+ * 基于GOPATH的包浏览器
+ * 基于GOPATH的编译系统
+ * 基于GOPATH的Api文档检索
+* Go语言的编辑支持
+ * 类浏览器和大纲显示
+ * Gocode(代码自动完成工具)的完美支持
+ * Go语言文档查看和Api快速检索
+ * 代码表达式信息显示`F1`
+ * 源代码定义跳转支持`F2`
+ * Gdb断点和调试支持
+ * gofmt自动格式化支持
+* 其他特征
+ * 支持多国语言界面显示
+ * 完全插件体系结构
+ * 支持编辑器配色方案
+ * 基于Kate的语法显示支持
+ * 基于全文的单词自动完成
+ * 支持键盘快捷键绑定方案
+ * Markdown文档编辑支持
+ * 实时预览和同步显示
+ * 自定义CSS显示
+ * 可导出HTML和PDF文档
+ * 批量转换/合并为HTML/PDF文档
+
+**LiteIDE安装配置**
+
+* LiteIDE安装
+ * 下载地址
+ * 源码地址
+
+ 首先安装好Go语言环境,然后根据操作系统下载LiteIDE对应的压缩文件直接解压即可使用。
+
+* 安装Gocode
+
+ 启用Go语言的输入自动完成需要安装Gocode:
+
+ go get -u github.com/nsf/gocode
+
+* 编译环境设置
+
+ 根据自身系统要求切换和配置LiteIDE当前使用的环境变量。
+
+ 以Windows操作系统,64位Go语言为例,
+ 工具栏的环境配置中选择win64,点`编辑环境`,进入LiteIDE编辑win64.env文件
+
+ GOROOT=c:\go
+ GOBIN=
+ GOARCH=amd64
+ GOOS=windows
+ CGO_ENABLED=1
+
+ PATH=%GOBIN%;%GOROOT%\bin;%PATH%
+ 。。。
+
+ 将其中的`GOROOT=c:\go`修改为当前Go安装路径,存盘即可,如果有MinGW64,可以将`c:\MinGW64\bin`加入PATH中以便go调用gcc支持CGO编译。
+
+ 以Linux操作系统,64位Go语言为例,
+ 工具栏的环境配置中选择linux64,点`编辑环境`,进入LiteIDE编辑linux64.env文件
+
+ GOROOT=$HOME/go
+ GOBIN=
+ GOARCH=amd64
+ GOOS=linux
+ CGO_ENABLED=1
+
+ PATH=$GOBIN:$GOROOT/bin:$PATH
+ 。。。
+
+ 将其中的`GOROOT=$HOME/go`修改为当前Go安装路径,存盘即可。
+
+* GOPATH设置
+
+ Go语言的工具链使用GOPATH设置,是Go语言开发的项目路径列表,在命令行中输入(在LiteIDE中也可以`Ctrl+,`直接输入)`go help gopath`快速查看GOPATH文档。
+
+ 在LiteIDE中可以方便的查看和设置GOPATH。通过`菜单-查看-GOPATH`设置,可以查看系统中已存在的GOPATH列表,
+ 同时可根据需要添加项目目录到自定义GOPATH列表中。
+
+## Sublime Text
+
+ 这里将介绍Sublime Text 2(以下简称Sublime)+GoSublime+gocode+MarGo的组合,那么为什么选择这个组合呢?
+
+ - 自动化提示代码,如下图所示
+
+ 
+
+ 图1.5 sublime自动化提示界面
+
+ - 保存的时候自动格式化代码,让您编写的代码更加美观,符合Go的标准。
+ - 支持项目管理
+
+ 
+
+ 图1.6 sublime项目管理界面
+
+ - 支持语法高亮
+ - Sublime Text 2可免费使用,只是保存次数达到一定数量之后就会提示是否购买,点击取消继续用,和正式注册版本没有任何区别。
+
+
+接下来就开始讲如何安装,下载[Sublime](http://www.sublimetext.com/)
+
+ 根据自己相应的系统下载相应的版本,然后打开Sublime,对于不熟悉Sublime的同学可以先看一下这篇文章[Sublime Text 2 入门及技巧](http://lucifr.com/139225/sublime-text-2-tricks-and-tips/)
+
+ 1. 打开之后安装 Package Control:Ctrl+` 打开命令行,执行如下代码:
+
+ import urllib2,os; pf='Package Control.sublime-package'; ipp=sublime.installed_packages_path(); os.makedirs(ipp) if not os.path.exists(ipp) else None; urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())); open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()); print 'Please restart Sublime Text to finish installation'
+
+ 这个时候重启一下Sublime,可以发现在在菜单栏多了一个如下的栏目,说明Package Control已经安装成功了。
+
+ 
+
+ 图1.7 sublime包管理
+
+
+ 2. 安装完之后就可以安装Sublime的插件了。需安装GoSublime、SidebarEnhancements和Go Build,安装插件之后记得重启Sublime生效,Ctrl+Shift+p打开Package Controll 输入`pcip`(即“Package Control: Install Package”的缩写)。
+
+ 这个时候看左下角显示正在读取包数据,完成之后出现如下界面
+
+ 
+
+ 图1.8 sublime安装插件界面
+
+ 这个时候输入GoSublime,按确定就开始安装了。同理应用于SidebarEnhancements和Go Build。
+
+ 3. 验证是否安装成功,你可以打开Sublime,打开main.go,看看语法是不是高亮了,输入`import`是不是自动化提示了,`import "fmt"`之后,输入`fmt.`是不是自动化提示有函数了。
+
+ 如果已经出现这个提示,那说明你已经安装完成了,并且完成了自动提示。
+
+ 如果没有出现这样的提示,一般就是你的`$PATH`没有配置正确。你可以打开终端,输入gocode,是不是能够正确运行,如果不行就说明`$PATH`没有配置正确。
+ (针对XP)有时候在终端能运行成功,但sublime无提示或者编译解码错误,请安装sublime text3和convert utf8插件试一试
+
+ 4. MacOS下已经设置了$GOROOT, $GOPATH, $GOBIN,还是没有自动提示怎么办。
+
+ 请在sublime中使用command + 9, 然后输入env检查$PATH, GOROOT, $GOPATH, $GOBIN等变量, 如果没有请采用下面的方法。
+
+ 首先建立下面的连接, 然后从Terminal中直接启动sublime
+
+ ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime
+
+ 5. 项目支持,让sublime支持项目本身的pkg库提示,有两种基本的实现
+
+ 一种为设定 gosublime 插件的 `Setting - user` 配置
+
+ {
+ "env": { "GOPATH": "$HOME/golang:$GS_GOPATH" }
+ }
+
+ `$GS_GOPATH` 是 gosublime 的伪环境变量, 它自动寻找 `.go`文件所在的 `~/go/src` 来推测 `~/go/` 为项目位置, 从而自动适应 `GOPATH` 。
+
+ 另外一种为保存sublime 项目 , 修改 project_name.sublime-project 添加节点
+
+
+ "settings": {
+ "GoSublime": {
+ "env": {
+ "GOPATH": "$HOME/golang/pwd" // 此处修改为项目路径
+ }
+ }
+ },
+
+ "folders"{...
+
+
+
+## Vim
+Vim是从vi发展出来的一个文本编辑器, 代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。
+
+
+
+图1.9 VIM编辑器自动化提示Go界面
+
+ 1. 配置vim高亮显示
+
+ cp -r $GOROOT/misc/vim/* ~/.vim/
+
+ 2. 在~/.vimrc文件中增加语法高亮显示
+
+ filetype plugin indent on
+ syntax on
+
+ 3. 安装[Gocode](https://github.com/nsf/gocode/)
+
+ go get -u github.com/nsf/gocode
+
+ gocode默认安装到`$GOBIN`下面。
+
+ 4. 配置[Gocode](https://github.com/nsf/gocode/)
+
+ ~ cd $GOPATH/src/github.com/nsf/gocode/vim
+ ~ ./update.bash
+ ~ gocode set propose-builtins true
+ propose-builtins true
+ ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64"
+ lib-path "/home/border/gocode/pkg/linux_amd64"
+ ~ gocode set
+ propose-builtins true
+ lib-path "/home/border/gocode/pkg/linux_amd64"
+
+ >gocode set里面的两个参数的含意说明:
+ >
+ >propose-builtins:是否自动提示Go的内置函数、类型和常量,默认为false,不提示。
+ >
+ >lib-path:默认情况下,gocode只会搜索**$GOPATH/pkg/$GOOS_$GOARCH** 和 **$GOROOT/pkg/$GOOS_$GOARCH**目录下的包,当然这个设置就是可以设置我们额外的lib能访问的路径
+
+
+ 5. 恭喜你,安装完成,你现在可以使用`:e main.go`体验一下开发Go的乐趣。
+
+更多VIM 設定, 可參考[連結](http://monnand.me/p/vim-golang-environment/zhCN/)
+
+## Emacs
+Emacs传说中的神器,她不仅仅是一个编辑器,它是一个整合环境,或可称它为集成开发环境,这些功能如让使用者置身于全功能的操作系统中。
+
+ 
+
+图1.10 Emacs编辑Go主界面
+
+1. 配置Emacs高亮显示
+
+ cp $GOROOT/misc/emacs/* ~/.emacs.d/
+
+2. 安装[Gocode](https://github.com/nsf/gocode/)
+
+ go get -u github.com/nsf/gocode
+
+ gocode默认安装到`$GOBIN`里面下面。
+
+3. 配置[Gocode](https://github.com/nsf/gocode/)
+
+
+ ~ cd $GOPATH/src/github.com/nsf/gocode/emacs
+ ~ cp go-autocomplete.el ~/.emacs.d/
+ ~ gocode set propose-builtins true
+ propose-builtins true
+ ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" // 换为你自己的路径
+ lib-path "/home/border/gocode/pkg/linux_amd64"
+ ~ gocode set
+ propose-builtins true
+ lib-path "/home/border/gocode/pkg/linux_amd64"
+
+4. 需要安装 [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete)
+
+ 下载AutoComplete并解压
+
+ ~ make install DIR=$HOME/.emacs.d/auto-complete
+
+ 配置~/.emacs文件
+
+ ;;auto-complete
+ (require 'auto-complete-config)
+ (add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict")
+ (ac-config-default)
+ (local-set-key (kbd "M-/") 'semantic-complete-analyze-inline)
+ (local-set-key "." 'semantic-complete-self-insert)
+ (local-set-key ">" 'semantic-complete-self-insert)
+
+ 详细信息参考: http://www.emacswiki.org/emacs/AutoComplete
+
+5. 配置.emacs
+
+ ;; golang mode
+ (require 'go-mode-load)
+ (require 'go-autocomplete)
+ ;; speedbar
+ ;; (speedbar 1)
+ (speedbar-add-supported-extension ".go")
+ (add-hook
+ 'go-mode-hook
+ '(lambda ()
+ ;; gocode
+ (auto-complete-mode 1)
+ (setq ac-sources '(ac-source-go))
+ ;; Imenu & Speedbar
+ (setq imenu-generic-expression
+ '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
+ ("func" "^func *\\(.*\\) {" 1)))
+ (imenu-add-to-menubar "Index")
+ ;; Outline mode
+ (make-local-variable 'outline-regexp)
+ (setq outline-regexp "//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....")
+ (outline-minor-mode 1)
+ (local-set-key "\M-a" 'outline-previous-visible-heading)
+ (local-set-key "\M-e" 'outline-next-visible-heading)
+ ;; Menu bar
+ (require 'easymenu)
+ (defconst go-hooked-menu
+ '("Go tools"
+ ["Go run buffer" go t]
+ ["Go reformat buffer" go-fmt-buffer t]
+ ["Go check buffer" go-fix-buffer t]))
+ (easy-menu-define
+ go-added-menu
+ (current-local-map)
+ "Go tools"
+ go-hooked-menu)
+
+ ;; Other
+ (setq show-trailing-whitespace t)
+ ))
+ ;; helper function
+ (defun go ()
+ "run current buffer"
+ (interactive)
+ (compile (concat "go run " (buffer-file-name))))
+
+ ;; helper function
+ (defun go-fmt-buffer ()
+ "run gofmt on current buffer"
+ (interactive)
+ (if buffer-read-only
+ (progn
+ (ding)
+ (message "Buffer is read only"))
+ (let ((p (line-number-at-pos))
+ (filename (buffer-file-name))
+ (old-max-mini-window-height max-mini-window-height))
+ (show-all)
+ (if (get-buffer "*Go Reformat Errors*")
+ (progn
+ (delete-windows-on "*Go Reformat Errors*")
+ (kill-buffer "*Go Reformat Errors*")))
+ (setq max-mini-window-height 1)
+ (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" "*Go Reformat Output*" nil "*Go Reformat Errors*" t))
+ (progn
+ (erase-buffer)
+ (insert-buffer-substring "*Go Reformat Output*")
+ (goto-char (point-min))
+ (forward-line (1- p)))
+ (with-current-buffer "*Go Reformat Errors*"
+ (progn
+ (goto-char (point-min))
+ (while (re-search-forward "" nil t)
+ (replace-match filename))
+ (goto-char (point-min))
+ (compilation-mode))))
+ (setq max-mini-window-height old-max-mini-window-height)
+ (delete-windows-on "*Go Reformat Output*")
+ (kill-buffer "*Go Reformat Output*"))))
+ ;; helper function
+ (defun go-fix-buffer ()
+ "run gofix on current buffer"
+ (interactive)
+ (show-all)
+ (shell-command-on-region (point-min) (point-max) "go tool fix -diff"))
+
+6. 恭喜你,你现在可以体验在神器中开发Go的乐趣。默认speedbar是关闭的,如果打开需要把 ;; (speedbar 1) 前面的注释去掉,或者也可以通过 *M-x speedbar* 手动开启。
+
+## Eclipse
+Eclipse也是非常常用的开发利器,以下介绍如何使用Eclipse来编写Go程序。
+
+ 
+
+图1.11 Eclipse编辑Go的主界面
+
+1. 首先下载并安装好[Eclipse](http://www.eclipse.org/)
+
+2. 下载[goclipse](https://code.google.com/p/goclipse/)插件
+
+ http://code.google.com/p/goclipse/wiki/InstallationInstructions
+
+3. 下载gocode,用于go的代码补全提示
+
+ gocode的github地址:
+
+ https://github.com/nsf/gocode
+
+ 在windows下要安装git,通常用[msysgit](https://code.google.com/p/msysgit/)
+
+ 再在cmd下安装:
+
+ go get -u github.com/nsf/gocode
+
+ 也可以下载代码,直接用go build来编译,会生成gocode.exe
+
+4. 下载[MinGW](http://sourceforge.net/projects/mingw/files/MinGW/)并按要求装好
+
+5. 配置插件
+
+ Windows->Reference->Go
+
+ (1).配置Go的编译器
+
+ 
+
+ 图1.12 设置Go的一些基础信息
+
+
+ (2).配置Gocode(可选,代码补全),设置Gocode路径为之前生成的gocode.exe文件
+
+ 
+
+ 图1.13 设置gocode信息
+
+ (3).配置GDB(可选,做调试用),设置GDB路径为MingW安装目录下的gdb.exe文件
+
+ 
+
+ 图1.14 设置GDB信息
+
+6. 测试是否成功
+
+ 新建一个go工程,再建立一个hello.go。如下图:
+
+ 
+
+ 图1.15 新建项目编辑文件
+
+ 调试如下(要在console中用输入命令来调试):
+
+ 
+
+ 图1.16 调试Go程序
+
+## IntelliJ IDEA
+熟悉Java的读者应该对于idea不陌生,idea是通过一个插件来支持go语言的高亮语法,代码提示和重构实现。
+
+1. 先下载idea,idea支持多平台:win,mac,linux,如果有钱就买个正式版,如果不行就使用社区免费版,对于只是开发Go语言来说免费版足够用了
+
+ 
+
+2. 安装Go插件,点击菜单File中的Setting,找到Plugins,点击,Broswer repo按钮。国内的用户可能会报错,自己解决哈。
+
+ 
+
+3. 这时候会看见很多插件,搜索找到Golang,双击,download and install。等到golang那一行后面出现Downloaded标志后,点OK。
+
+ 
+
+ 然后点 Apply .这时候IDE会要求你重启。
+
+4. 重启完毕后,创建新项目会发现已经可以创建golang项目了:
+
+ 
+
+ 下一步,会要求你输入 go sdk的位置,一般都安装在C:\Go,linux和mac根据自己的安装目录设置,选中目录确定,就可以了。
+
+
+
+# 1.5 总结
+
+这一章中我们主要介绍了如何安装Go,Go可以通过三种方式安装:源码安装、标准包安装、第三方工具安装,安装之后我们需要配置我们的开发环境,然后介绍了如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后读者就可以创建项目,接着介绍了如何来进行项目编译、应用安装等问题,这些需要用到很多Go命令,所以接着就介绍了一些Go的常用命令工具,包括编译、安装、格式化、测试等命令,最后介绍了Go的开发工具,目前有很多Go的开发工具:LiteIDE、sublime、VIM、Emacs、Eclipse、Idea等工具,读者可以根据自己熟悉的工具进行配置,希望能够通过方便的工具快速的开发Go应用。
diff --git a/ebook/02.md b/ebook/02.md
new file mode 100644
index 000000000..ba718547d
--- /dev/null
+++ b/ebook/02.md
@@ -0,0 +1,2236 @@
+# 2 Go语言基础
+
+Go是一门类似C的编译型语言,但是它的编译速度非常快。这门语言的关键字总共也就二十五个,比英文字母还少一个,这对于我们的学习来说就简单了很多。先让我们看一眼这些关键字都长什么样:
+
+ break default func interface select
+ case defer go map struct
+ chan else goto package switch
+ const fallthrough if range type
+ continue for import return var
+
+在接下来的这一章中,我将带领你去学习这门语言的基础。通过每一小节的介绍,你将发现,Go的世界是那么地简洁,设计是如此地美妙,编写Go将会是一件愉快的事情。等回过头来,你就会发现这二十五个关键字是多么地亲切。
+
+## 目录
+
+
+
+
+# 2.1 你好,Go
+
+在开始编写应用之前,我们先从最基本的程序开始。就像你造房子之前不知道什么是地基一样,编写程序也不知道如何开始。因此,在本节中,我们要学习用最基本的语法让Go程序运行起来。
+
+## 程序
+
+这就像一个传统,在学习大部分语言之前,你先学会如何编写一个可以输出`hello world`的程序。
+
+准备好了吗?Let's Go!
+
+ package main
+
+ import "fmt"
+
+ func main() {
+ fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n")
+ }
+
+输出如下:
+
+ Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい
+
+## 详解
+首先我们要了解一个概念,Go程序是通过`package`来组织的
+
+`package `(在我们的例子中是`package main`)这一行告诉我们当前文件属于哪个包,而包名`main`则告诉我们它是一个可独立运行的包,它在编译后会产生可执行文件。除了`main`包之外,其它的包最后都会生成`*.a`文件(也就是包文件)并放置在`$GOPATH/pkg/$GOOS_$GOARCH`中(以Mac为例就是`$GOPATH/pkg/darwin_amd64`)。
+
+>每一个可独立运行的Go程序,必定包含一个`package main`,在这个`main`包中必定包含一个入口函数`main`,而这个函数既没有参数,也没有返回值。
+
+为了打印`Hello, world...`,我们调用了一个函数`Printf`,这个函数来自于`fmt`包,所以我们在第三行中导入了系统级别的`fmt`包:`import "fmt"`。
+
+包的概念和Python中的package类似,它们都有一些特别的好处:模块化(能够把你的程序分成多个模块)和可重用性(每个模块都能被其它应用程序反复使用)。我们在这里只是先了解一下包的概念,后面我们将会编写自己的包。
+
+在第五行中,我们通过关键字`func`定义了一个`main`函数,函数体被放在`{}`(大括号)中,就像我们平时写C、C++或Java时一样。
+
+大家可以看到`main`函数是没有任何的参数的,我们接下来就学习如何编写带参数的、返回0个或多个值的函数。
+
+第六行,我们调用了`fmt`包里面定义的函数`Printf`。大家可以看到,这个函数是通过`.`的方式调用的,这一点和Python十分相似。
+
+>前面提到过,包名和包所在的文件夹名可以是不同的,此处的``即为通过`package `声明的包名,而非文件夹名。
+
+最后大家可以看到我们输出的内容里面包含了很多非ASCII码字符。实际上,Go是天生支持UTF-8的,任何字符都可以直接输出,你甚至可以用UTF-8中的任何字符作为标识符。
+
+
+## 结论
+
+Go使用`package`(和Python的模块类似)来组织代码。`main.main()`函数(这个函数主要位于主包)是每一个独立的可运行程序的入口点。Go使用UTF-8字符串和标识符(因为UTF-8的发明者也就是Go的发明者),所以它天生就具有多语言的支持。
+
+
+
+# 2.2 Go基础
+
+这小节我们将要介绍如何定义变量、常量、Go内置类型以及Go程序设计中的一些技巧。
+
+## 定义变量
+
+Go语言里面定义变量有多种方式。
+
+使用`var`关键字是Go最基本的定义变量方式,与C语言不同的是Go把变量类型放在变量名后面:
+
+ //定义一个名称为“variableName”,类型为"type"的变量
+ var variableName type
+
+定义多个变量
+
+ //定义三个类型都是“type”的三个变量
+ var vname1, vname2, vname3 type
+
+定义变量并初始化值
+
+ //初始化“variableName”的变量为“value”值,类型是“type”
+ var variableName type = value
+
+同时初始化多个变量
+
+ /*
+ 定义三个类型都是"type"的三个变量,并且它们分别初始化相应的值
+ vname1为v1,vname2为v2,vname3为v3
+ */
+ var vname1, vname2, vname3 type= v1, v2, v3
+
+你是不是觉得上面这样的定义有点繁琐?没关系,因为Go语言的设计者也发现了,有一种写法可以让它变得简单一点。我们可以直接忽略类型声明,那么上面的代码变成这样了:
+
+ /*
+ 定义三个变量,它们分别初始化相应的值
+ vname1为v1,vname2为v2,vname3为v3
+ 然后Go会根据其相应值的类型来帮你初始化它们
+ */
+ var vname1, vname2, vname3 = v1, v2, v3
+
+你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化:
+
+ /*
+ 定义三个变量,它们分别初始化相应的值
+ vname1为v1,vname2为v2,vname3为v3
+ 编译器会根据初始化的值自动推导出相应的类型
+ */
+ vname1, vname2, vname3 := v1, v2, v3
+
+现在是不是看上去非常简洁了?`:=`这个符号直接取代了`var`和`type`,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用`var`方式来定义全局变量。
+
+`_`(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。在这个例子中,我们将值`35`赋予`b`,并同时丢弃`34`:
+
+ _, b := 34, 35
+
+Go对于已声明但未使用的变量会在编译阶段报错,比如下面的代码就会产生一个错误:声明了`i`但未使用。
+
+ package main
+
+ func main() {
+ var i int
+ }
+
+## 常量
+
+所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时则无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型。
+
+它的语法如下:
+
+ const constantName = value
+ //如果需要,也可以明确指定常量的类型:
+ const Pi float32 = 3.1415926
+
+下面是一些常量声明的例子:
+
+ const Pi = 3.1415926
+ const i = 10000
+ const MaxThread = 10
+ const prefix = "astaxie_"
+
+Go 常量和一般程序语言不同的是,可以指定相当多的小数位数(例如200位),
+若指定給float32自动缩短为32bit,指定给float64自动缩短为64bit,详情参考[链接](http://golang.org/ref/spec#Constants)
+
+## 内置基础类型
+
+### Boolean
+
+在Go中,布尔值的类型为`bool`,值是`true`或`false`,默认为`false`。
+
+ //示例代码
+ var isActive bool // 全局变量声明
+ var enabled, disabled = true, false // 忽略类型的声明
+ func test() {
+ var available bool // 一般声明
+ valid := false // 简短声明
+ available = true // 赋值操作
+ }
+
+
+### 数值类型
+
+整数类型有无符号和带符号两种。Go同时支持`int`和`uint`,这两种类型的长度相同,但具体长度取决于不同编译器的实现。~~当前的gcc和gccgo编译器在32位和64位平台上都使用32位来表示`int`和`uint`,但未来在64位平台上可能增加到64位~~。Go里面也有直接定义好位数的类型:`rune`, `int8`, `int16`, `int32`, `int64`和`byte`, `uint8`, `uint16`, `uint32`, `uint64`。其中`rune`是`int32`的别称,`byte`是`uint8`的别称。
+
+>需要注意的一点是,这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。
+>
+>如下的代码会产生错误
+>
+>> var a int8
+
+>> var b int32
+
+>> c:=a + b
+>
+>另外,尽管int的长度是32 bit, 但int 与 int32并不可以互用。
+
+浮点数的类型有`float32`和`float64`两种(没有`float`类型),默认是`float64`。
+
+这就是全部吗?No!Go还支持复数。它的默认类型是`complex128`(64位实数+64位虚数)。如果需要小一些的,也有`complex64`(32位实数+32位虚数)。复数的形式为`RE + IMi`,其中`RE`是实数部分,`IM`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子:
+
+ var c complex64 = 5+5i
+ //output: (5+5i)
+ fmt.Printf("Value is: %v", c)
+
+
+### 字符串
+
+我们在上一节中讲过,Go中的字符串都是采用`UTF-8`字符集编码。字符串是用一对双引号(`""`)或反引号(`` ` `` `` ` ``)括起来定义,它的类型是`string`。
+
+ //示例代码
+ var frenchHello string // 声明变量为字符串的一般方法
+ var emptyString string = "" // 声明了一个字符串变量,初始化为空字符串
+ func test() {
+ no, yes, maybe := "no", "yes", "maybe" // 简短声明,同时声明多个变量
+ japaneseHello := "Konichiwa" // 同上
+ frenchHello = "Bonjour" // 常规赋值
+ }
+
+在Go中字符串是不可变的,例如下面的代码编译时会报错:
+
+ var s string = "hello"
+ s[0] = 'c'
+
+
+但如果真的想要修改怎么办呢?下面的代码可以实现:
+
+ s := "hello"
+ c := []byte(s) // 将字符串 s 转换为 []byte 类型
+ c[0] = 'c'
+ s2 := string(c) // 再转换回 string 类型
+ fmt.Printf("%s\n", s2)
+
+
+Go中可以使用`+`操作符来连接两个字符串:
+
+ s := "hello,"
+ m := " world"
+ a := s + m
+ fmt.Printf("%s\n", a)
+
+修改字符串也可写为:
+
+ s := "hello"
+ s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作
+ fmt.Printf("%s\n", s)
+
+如果要声明一个多行的字符串怎么办?可以通过`` ` ``来声明:
+
+ m := `hello
+ world`
+
+`` ` `` 括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,它没有字符转义,换行也将原样输出。
+
+### 错误类型
+Go内置有一个`error`类型,专门用来处理错误信息,Go的`package`里面还专门有一个包`errors`来处理错误:
+
+ err := errors.New("emit macho dwarf: elf header corrupted")
+ if err != nil {
+ fmt.Print(err)
+ }
+
+### Go数据底层的存储
+
+下面这张图来源于[Russ Cox Blog](http://research.swtch.com/)中一篇介绍[Go数据结构](http://research.swtch.com/godata)的文章,大家可以看到这些基础类型底层都是分配了一块内存,然后存储了相应的值。
+
+
+
+图2.1 Go数据格式的存储
+
+## 一些技巧
+
+### 分组声明
+
+在Go语言中,同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。
+
+例如下面的代码:
+
+ import "fmt"
+ import "os"
+
+ const i = 100
+ const pi = 3.1415
+ const prefix = "Go_"
+
+ var i int
+ var pi float32
+ var prefix string
+
+可以分组写成如下形式:
+
+ import(
+ "fmt"
+ "os"
+ )
+
+ const(
+ i = 100
+ pi = 3.1415
+ prefix = "Go_"
+ )
+
+ var(
+ i int
+ pi float32
+ prefix string
+ )
+
+>除非被显式设置为其它值或`iota`,每个`const`分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是`iota`,则它也被设置为`iota`。
+
+### iota枚举
+
+Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采用,它默认开始值是0,每调用一次加1:
+
+ const(
+ x = iota // x == 0
+ y = iota // y == 1
+ z = iota // z == 2
+ w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"
+ )
+
+ const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0
+
+ const (
+ e, f, g = iota, iota, iota //e=0,f=0,g=0 iota在同一行值相同
+ )
+
+### Go程序设计的一些规则
+Go之所以会那么简洁,是因为它有一些默认的行为:
+- 大写字母开头的变量是可导出的,也就是其它包可以读取的,是公用变量;小写字母开头的就是不可导出的,是私有变量。
+- 大写字母开头的函数也是一样,相当于`class`中的带`public`关键词的公有函数;小写字母开头的就是有`private`关键词的私有函数。
+
+## array、slice、map
+
+### array
+`array`就是数组,它的定义方式如下:
+
+ var arr [n]type
+
+在`[n]type`中,`n`表示数组的长度,`type`表示存储元素的类型。对数组的操作和其它语言类似,都是通过`[]`来进行读取或赋值:
+
+ var arr [10]int // 声明了一个int类型的数组
+ arr[0] = 42 // 数组下标是从0开始的
+ arr[1] = 13 // 赋值操作
+ fmt.Printf("The first element is %d\n", arr[0]) // 获取数据,返回42
+ fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素,默认返回0
+
+由于长度也是数组类型的一部分,因此`[3]int`与`[4]int`是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的`slice`类型了。
+
+数组可以使用另一种`:=`来声明
+
+ a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
+
+ b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0
+
+ c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度
+
+也许你会说,我想数组里面的值还是数组,能实现吗?当然咯,Go支持嵌套数组,即多维数组。比如下面的代码就声明了一个二维数组:
+
+ // 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素
+ doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
+
+ // 上面的声明可以简化,直接忽略内部的类型
+ easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
+
+数组的分配如下所示:
+
+
+
+图2.2 多维数组的映射关系
+
+
+### slice
+
+在很多应用场景中,数组并不能满足我们的需求。在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫`slice`
+
+`slice`并不是真正意义上的动态数组,而是一个引用类型。`slice`总是指向一个底层`array`,`slice`的声明也可以像`array`一样,只是不需要长度。
+
+ // 和声明array一样,只是少了长度
+ var fslice []int
+
+接下来我们可以声明一个`slice`,并初始化数据,如下所示:
+
+ slice := []byte {'a', 'b', 'c', 'd'}
+
+`slice`可以从一个数组或一个已经存在的`slice`中再次声明。`slice`通过`array[i:j]`来获取,其中`i`是数组的开始位置,`j`是结束位置,但不包含`array[j]`,它的长度是`j-i`。
+
+ // 声明一个含有10个元素元素类型为byte的数组
+ var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
+
+ // 声明两个含有byte的slice
+ var a, b []byte
+
+ // a指向数组的第3个元素开始,并到第五个元素结束,
+ a = ar[2:5]
+ //现在a含有的元素: ar[2]、ar[3]和ar[4]
+
+ // b是数组ar的另一个slice
+ b = ar[3:5]
+ // b的元素是:ar[3]和ar[4]
+
+>注意`slice`和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明`slice`时,方括号内没有任何字符。
+
+它们的数据结构如下所示
+
+
+
+图2.3 slice和array的对应关系图
+
+slice有一些简便的操作
+
+ - `slice`的默认开始位置是0,`ar[:n]`等价于`ar[0:n]`
+ - `slice`的第二个序列默认是数组的长度,`ar[n:]`等价于`ar[n:len(ar)]`
+ - 如果从一个数组里面直接获取`slice`,可以这样`ar[:]`,因为默认第一个序列是0,第二个是数组的长度,即等价于`ar[0:len(ar)]`
+
+下面这个例子展示了更多关于`slice`的操作:
+
+ // 声明一个数组
+ var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
+ // 声明两个slice
+ var aSlice, bSlice []byte
+
+ // 演示一些简便操作
+ aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c
+ aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j
+ aSlice = array[:] // 等价于aSlice = array[0:10] 这样aSlice包含了全部的元素
+
+ // 从slice中获取slice
+ aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4,cap=7
+ bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f
+ bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f
+ bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h
+ bSlice = aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g
+
+`slice`是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值,例如上面的`aSlice`和`bSlice`,如果修改了`aSlice`中元素的值,那么`bSlice`相对应的值也会改变。
+
+从概念上面来说`slice`像一个结构体,这个结构体包含了三个元素:
+- 一个指针,指向数组中`slice`指定的开始位置
+- 长度,即`slice`的长度
+- 最大长度,也就是`slice`开始位置到数组的最后位置的长度
+
+ Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
+ Slice_a := Array_a[2:5]
+
+上面代码的真正存储结构如下图所示
+
+
+
+图2.4 slice对应数组的信息
+
+对于`slice`有几个有用的内置函数:
+
+- `len` 获取`slice`的长度
+- `cap` 获取`slice`的最大容量
+- `append` 向`slice`里面追加一个或者多个元素,然后返回一个和`slice`一样类型的`slice`
+- `copy` 函数`copy`从源`slice`的`src`中复制元素到目标`dst`,并且返回复制的元素的个数
+
+注:`append`函数会改变`slice`所引用的数组的内容,从而影响到引用同一数组的其它`slice`。
+但当`slice`中没有剩余空间(即`(cap-len) == 0`)时,此时将动态分配新的数组空间。返回的`slice`数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的`slice`则不受影响。
+
+### map
+
+`map`也就是Python中字典的概念,它的格式为`map[keyType]valueType`
+
+我们看下面的代码,`map`的读取和设置也类似`slice`一样,通过`key`来操作,只是`slice`的`index`只能是`int`类型,而`map`多了很多类型,可以是`int`,可以是`string`及所有完全定义了`==`与`!=`操作的类型。
+
+ // 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化
+ var numbers map[string] int
+ // 另一种map的声明方式
+ numbers := make(map[string]int)
+ numbers["one"] = 1 //赋值
+ numbers["ten"] = 10 //赋值
+ numbers["three"] = 3
+
+ fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据
+ // 打印出来如:第三个数字是: 3
+
+这个`map`就像我们平常看到的表格一样,左边列是`key`,右边列是值
+
+使用map过程中需要注意的几点:
+- `map`是无序的,每次打印出来的`map`都会不一样,它不能通过`index`获取,而必须通过`key`获取
+- `map`的长度是不固定的,也就是和`slice`一样,也是一种引用类型
+- 内置的`len`函数同样适用于`map`,返回`map`拥有的`key`的数量
+- `map`的值可以很方便的修改,通过`numbers["one"]=11`可以很容易的把key为`one`的字典值改为`11`
+- `map`和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
+
+`map`的初始化可以通过`key:val`的方式初始化值,同时`map`内置有判断是否存在`key`的方式
+
+通过`delete`删除`map`的元素:
+
+ // 初始化一个字典
+ rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
+ // map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true
+ csharpRating, ok := rating["C#"]
+ if ok {
+ fmt.Println("C# is in the map and its rating is ", csharpRating)
+ } else {
+ fmt.Println("We have no rating associated with C# in the map")
+ }
+
+ delete(rating, "C") // 删除key为C的元素
+
+
+上面说过了,`map`也是一种引用类型,如果两个`map`同时指向一个底层,那么一个改变,另一个也相应的改变:
+
+ m := make(map[string]string)
+ m["Hello"] = "Bonjour"
+ m1 := m
+ m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了
+
+
+### make、new操作
+
+`make`用于内建类型(`map`、`slice` 和`channel`)的内存分配。`new`用于各种类型的内存分配。
+
+内建函数`new`本质上说跟其它语言中的同名函数功能一样:`new(T)`分配了零值填充的`T`类型的内存空间,并且返回其地址,即一个`*T`类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型`T`的零值。有一点非常重要:
+
+>`new`返回指针。
+
+内建函数`make(T, args)`与`new(T)`有着不同的功能,make只能创建`slice`、`map`和`channel`,并且返回一个有初始值(非零)的`T`类型,而不是`*T`。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个`slice`,是一个包含指向数据(内部`array`)的指针、长度和容量的三项描述符;在这些项目被初始化之前,`slice`为`nil`。对于`slice`、`map`和`channel`来说,`make`初始化了内部的数据结构,填充适当的值。
+
+>`make`返回初始化后的(非零)值。
+
+下面这个图详细的解释了`new`和`make`之间的区别。
+
+
+
+图2.5 make和new对应底层的内存分配
+
+关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为0。
+此处罗列 部分类型 的 “零值”
+
+ int 0
+ int8 0
+ int32 0
+ int64 0
+ uint 0x0
+ rune 0 //rune的实际类型是 int32
+ byte 0x0 // byte的实际类型是 uint8
+ float32 0 //长度为 4 byte
+ float64 0 //长度为 8 byte
+ bool false
+ string ""
+
+
+
+ # 2.3 流程和函数
+这小节我们要介绍Go里面的流程控制以及函数操作
+## 流程控制
+流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑。流程控制包含分三大类:条件判断,循环控制和无条件跳转。
+### if
+`if`也许是各种编程语言中最常见的了,它的语法概括起来就是:如果满足条件就做某事,否则做另一件事。
+
+Go里面`if`条件判断语句中不需要括号,如下代码所示
+
+ if x > 10 {
+ fmt.Println("x is greater than 10")
+ } else {
+ fmt.Println("x is less than 10")
+ }
+
+Go的`if`还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示
+
+ // 计算获取值x,然后根据x返回的大小,判断是否大于10。
+ if x := computedValue(); x > 10 {
+ fmt.Println("x is greater than 10")
+ } else {
+ fmt.Println("x is less than 10")
+ }
+
+ //这个地方如果这样调用就编译出错了,因为x是条件里面的变量
+ fmt.Println(x)
+
+多个条件的时候如下所示:
+
+ if integer == 3 {
+ fmt.Println("The integer is equal to 3")
+ } else if integer < 3 {
+ fmt.Println("The integer is less than 3")
+ } else {
+ fmt.Println("The integer is greater than 3")
+ }
+
+### goto
+
+Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前函数内定义的标签。例如假设这样一个循环:
+
+ func myFunc() {
+ i := 0
+ Here: //这行的第一个词,以冒号结束作为标签
+ println(i)
+ i++
+ goto Here //跳转到Here去
+ }
+
+>标签名是大小写敏感的。
+
+### for
+Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下:
+
+ for expression1; expression2; expression3 {
+ //...
+ }
+
+`expression1`、`expression2`和`expression3`都是表达式,其中`expression1`和`expression3`是变量声明或者函数调用返回值之类的,`expression2`是用来条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。
+
+一个例子比上面讲那么多更有用,那么我们看看下面的例子吧:
+
+ package main
+ import "fmt"
+
+ func main(){
+ sum := 0;
+ for index:=0; index < 10 ; index++ {
+ sum += index
+ }
+ fmt.Println("sum is equal to ", sum)
+ }
+ // 输出:sum is equal to 45
+
+有些时候需要进行多个赋值操作,由于Go里面没有`,`操作符,那么可以使用平行赋值`i, j = i+1, j-1`
+
+
+有些时候如果我们忽略`expression1`和`expression3`:
+
+ sum := 1
+ for ; sum < 1000; {
+ sum += sum
+ }
+
+其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识?对,这就是`while`的功能。
+
+ sum := 1
+ for sum < 1000 {
+ sum += sum
+ }
+
+在循环里面有两个关键操作`break`和`continue` ,`break`操作是跳出当前循环,`continue`是跳过本次循环。当嵌套过深的时候,`break`可以配合标签使用,即跳转至标签所指定的位置,详细参考如下例子:
+
+ for index := 10; index>0; index-- {
+ if index == 5{
+ break // 或者continue
+ }
+ fmt.Println(index)
+ }
+ // break打印出来10、9、8、7、6
+ // continue打印出来10、9、8、7、6、4、3、2、1
+
+`break`和`continue`还可以跟着标号,用来跳到多重循环中的外层循环
+
+`for`配合`range`可以用于读取`slice`和`map`的数据:
+
+ for k,v:=range map {
+ fmt.Println("map's key:",k)
+ fmt.Println("map's val:",v)
+ }
+
+由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用`_`来丢弃不需要的返回值
+例如
+
+ for _, v := range map{
+ fmt.Println("map's val:", v)
+ }
+
+
+### switch
+有些时候你需要写很多的`if-else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候`switch`就能很好的解决这个问题。它的语法如下
+
+ switch sExpr {
+ case expr1:
+ some instructions
+ case expr2:
+ some other instructions
+ case expr3:
+ some other instructions
+ default:
+ other code
+ }
+
+`sExpr`和`expr1`、`expr2`、`expr3`的类型必须一致。Go的`switch`非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;而如果`switch`没有表达式,它会匹配`true`。
+
+ i := 10
+ switch i {
+ case 1:
+ fmt.Println("i is equal to 1")
+ case 2, 3, 4:
+ fmt.Println("i is equal to 2, 3 or 4")
+ case 10:
+ fmt.Println("i is equal to 10")
+ default:
+ fmt.Println("All I know is that i is an integer")
+ }
+
+在第5行中,我们把很多值聚合在了一个`case`里面,同时,Go里面`switch`默认相当于每个`case`最后带有`break`,匹配成功后不会自动向下执行其他case,而是跳出整个`switch`, 但是可以使用`fallthrough`强制执行后面的case代码。
+
+ integer := 6
+ switch integer {
+ case 4:
+ fmt.Println("The integer was <= 4")
+ fallthrough
+ case 5:
+ fmt.Println("The integer was <= 5")
+ fallthrough
+ case 6:
+ fmt.Println("The integer was <= 6")
+ fallthrough
+ case 7:
+ fmt.Println("The integer was <= 7")
+ fallthrough
+ case 8:
+ fmt.Println("The integer was <= 8")
+ fallthrough
+ default:
+ fmt.Println("default case")
+ }
+
+上面的程序将输出
+
+ The integer was <= 6
+ The integer was <= 7
+ The integer was <= 8
+ default case
+
+
+## 函数
+函数是Go里面的核心设计,它通过关键字`func`来声明,它的格式如下:
+
+ func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
+ //这里是处理逻辑代码
+ //返回多个值
+ return value1, value2
+ }
+
+上面的代码我们看出
+
+- 关键字`func`用来声明一个函数`funcName`
+- 函数可以有一个或者多个参数,每个参数后面带有类型,通过`,`分隔
+- 函数可以返回多个值
+- 上面返回值声明了两个变量`output1`和`output2`,如果你不想声明也可以,直接就两个类型
+- 如果只有一个返回值且不声明返回值变量,那么你可以省略 包括返回值 的括号
+- 如果没有返回值,那么就直接省略最后的返回信息
+- 如果有返回值, 那么必须在函数的外层添加return语句
+
+下面我们来看一个实际应用函数的例子(用来计算Max值)
+
+ package main
+ import "fmt"
+
+ // 返回a、b中最大值.
+ func max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+ }
+
+ func main() {
+ x := 3
+ y := 4
+ z := 5
+
+ max_xy := max(x, y) //调用函数max(x, y)
+ max_xz := max(x, z) //调用函数max(x, z)
+
+ fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
+ fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
+ fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在这直接调用它
+ }
+
+上面这个里面我们可以看到`max`函数有两个参数,它们的类型都是`int`,那么第一个变量的类型可以省略(即 a,b int,而非 a int, b int),默认为离它最近的类型,同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型,这个就是省略写法。
+
+### 多个返回值
+Go语言比C更先进的特性,其中一点就是函数能够返回多个值。
+
+我们直接上代码看例子
+
+ package main
+ import "fmt"
+
+ //返回 A+B 和 A*B
+ func SumAndProduct(A, B int) (int, int) {
+ return A+B, A*B
+ }
+
+ func main() {
+ x := 3
+ y := 4
+
+ xPLUSy, xTIMESy := SumAndProduct(x, y)
+
+ fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
+ fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
+ }
+
+上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写),官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。
+
+ func SumAndProduct(A, B int) (add int, Multiplied int) {
+ add = A+B
+ Multiplied = A*B
+ return
+ }
+
+### 变参
+Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:
+
+ func myfunc(arg ...int) {}
+`arg ...int`告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是`int`。在函数体中,变量`arg`是一个`int`的`slice`:
+
+ for _, n := range arg {
+ fmt.Printf("And the number is: %d\n", n)
+ }
+
+### 传值与传指针
+当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。
+
+为了验证我们上面的说法,我们来看一个例子
+
+ package main
+ import "fmt"
+
+ //简单的一个函数,实现了参数+1的操作
+ func add1(a int) int {
+ a = a+1 // 我们改变了a的值
+ return a //返回一个新值
+ }
+
+ func main() {
+ x := 3
+
+ fmt.Println("x = ", x) // 应该输出 "x = 3"
+
+ x1 := add1(x) //调用add1(x)
+
+ fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4"
+ fmt.Println("x = ", x) // 应该输出"x = 3"
+ }
+
+看到了吗?虽然我们调用了`add1`函数,并且在`add1`中执行`a = a+1`操作,但是上面例子中`x`变量的值没有发生变化
+
+理由很简单:因为当我们调用`add1`的时候,`add1`接收的参数其实是`x`的copy,而不是`x`本身。
+
+那你也许会问了,如果真的需要传这个`x`本身,该怎么办呢?
+
+这就牵扯到了所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有`add1`函数知道`x`变量所在的地址,才能修改`x`变量的值。所以我们需要将`x`所在地址`&x`传入函数,并将函数的参数的类型由`int`改为`*int`,即改为指针类型,才能在函数中修改`x`变量的值。此时参数仍然是按copy传递的,只是copy的是一个指针。请看下面的例子
+
+ package main
+ import "fmt"
+
+ //简单的一个函数,实现了参数+1的操作
+ func add1(a *int) int { // 请注意,
+ *a = *a+1 // 修改了a的值
+ return *a // 返回新值
+ }
+
+ func main() {
+ x := 3
+
+ fmt.Println("x = ", x) // 应该输出 "x = 3"
+
+ x1 := add1(&x) // 调用 add1(&x) 传x的地址
+
+ fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4"
+ fmt.Println("x = ", x) // 应该输出 "x = 4"
+ }
+
+这样,我们就达到了修改`x`的目的。那么到底传指针有什么好处呢?
+
+- 传指针使得多个函数能操作同一个对象。
+- 传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。
+- Go语言中`string`,`slice`,`map`这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变`slice`的长度,则仍需要取地址传递指针)
+
+### defer
+Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的:
+
+ func ReadWrite() bool {
+ file.Open("file")
+ // 做一些工作
+ if failureX {
+ file.Close()
+ return false
+ }
+
+ if failureY {
+ file.Close()
+ return false
+ }
+
+ file.Close()
+ return true
+ }
+
+我们看到上面有很多重复的代码,Go的`defer`有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在`defer`后指定的函数会在函数退出前调用。
+
+ func ReadWrite() bool {
+ file.Open("file")
+ defer file.Close()
+ if failureX {
+ return false
+ }
+ if failureY {
+ return false
+ }
+ return true
+ }
+
+如果有很多调用`defer`,那么`defer`是采用后进先出模式,所以如下代码会输出`4 3 2 1 0`
+
+ for i := 0; i < 5; i++ {
+ defer fmt.Printf("%d ", i)
+ }
+
+### 函数作为值、类型
+
+在Go中函数也是一种变量,我们可以通过`type`来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型
+
+ type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
+
+函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递,请看下面的例子
+
+ package main
+ import "fmt"
+
+ type testInt func(int) bool // 声明了一个函数类型
+
+ func isOdd(integer int) bool {
+ if integer%2 == 0 {
+ return false
+ }
+ return true
+ }
+
+ func isEven(integer int) bool {
+ if integer%2 == 0 {
+ return true
+ }
+ return false
+ }
+
+ // 声明的函数类型在这个地方当做了一个参数
+
+ func filter(slice []int, f testInt) []int {
+ var result []int
+ for _, value := range slice {
+ if f(value) {
+ result = append(result, value)
+ }
+ }
+ return result
+ }
+
+ func main(){
+ slice := []int {1, 2, 3, 4, 5, 7}
+ fmt.Println("slice = ", slice)
+ odd := filter(slice, isOdd) // 函数当做值来传递了
+ fmt.Println("Odd elements of slice are: ", odd)
+ even := filter(slice, isEven) // 函数当做值来传递了
+ fmt.Println("Even elements of slice are: ", even)
+ }
+
+函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到`testInt`这个类型是一个函数类型,然后两个`filter`函数的参数和返回值与`testInt`类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。
+
+### Panic和Recover
+
+Go没有像Java那样的异常机制,它不能抛出异常,而是使用了`panic`和`recover`机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有`panic`的东西。这是个强大的工具,请明智地使用它。那么,我们应该如何使用它呢?
+
+Panic
+>是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数`F`调用`panic`,函数F的执行被中断,但是`F`中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,`F`的行为就像调用了`panic`。这一过程继续向上,直到发生`panic`的`goroutine`中所有调用的函数返回,此时程序退出。恐慌可以直接调用`panic`产生。也可以由运行时错误产生,例如访问越界的数组。
+
+Recover
+>是一个内建的函数,可以让进入令人恐慌的流程中的`goroutine`恢复过来。`recover`仅在延迟函数中有效。在正常的执行过程中,调用`recover`会返回`nil`,并且没有其它任何效果。如果当前的`goroutine`陷入恐慌,调用`recover`可以捕获到`panic`的输入值,并且恢复正常的执行。
+
+下面这个函数演示了如何在过程中使用`panic`
+
+ var user = os.Getenv("USER")
+
+ func init() {
+ if user == "" {
+ panic("no value for $USER")
+ }
+ }
+
+下面这个函数检查作为其参数的函数在执行时是否会产生`panic`:
+
+ func throwsPanic(f func()) (b bool) {
+ defer func() {
+ if x := recover(); x != nil {
+ b = true
+ }
+ }()
+ f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
+ return
+ }
+
+### `main`函数和`init`函数
+
+Go里面有两个保留的函数:`init`函数(能够应用于所有的`package`)和`main`函数(只能应用于`package main`)。这两个函数在定义时不能有任何的参数和返回值。虽然一个`package`里面可以写任意多个`init`函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个`package`中每个文件只写一个`init`函数。
+
+Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方调用这两个函数。每个`package`中的`init`函数都是可选的,但`package main`就必须包含一个`main`函数。
+
+程序的初始化和执行都起始于`main`包。如果`main`包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到`fmt`包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行`init`函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对`main`包中的包级常量和变量进行初始化,然后执行`main`包中的`init`函数(如果存在的话),最后执行`main`函数。下图详细地解释了整个执行过程:
+
+
+
+图2.6 main函数引入包初始化流程图
+
+### import
+我们在写Go代码的时候经常用到import这个命令用来导入包文件,而我们经常看到的方式参考如下:
+
+ import(
+ "fmt"
+ )
+
+然后我们代码里面可以通过如下的方式调用
+
+ fmt.Println("hello world")
+
+上面这个fmt是Go语言的标准库,其实是去goroot下去加载该模块,当然Go的import还支持如下两种方式来加载自己写的模块:
+
+1. 相对路径
+
+ import “./model” //当前文件同一目录的model目录,但是不建议这种方式来import
+
+2. 绝对路径
+
+ import “shorturl/model” //加载gopath/src/shorturl/model模块
+
+
+上面展示了一些import常用的几种方式,但是还有一些特殊的import,让很多新手很费解,下面我们来一一讲解一下到底是怎么一回事
+
+
+1. 点操作
+
+ 我们有时候会看到如下的方式导入包
+
+ import(
+ . "fmt"
+ )
+
+ 这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的fmt.Println("hello world")可以省略的写成Println("hello world")
+
+2. 别名操作
+
+ 别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字
+
+ import(
+ f "fmt"
+ )
+
+ 别名操作的话调用包函数时前缀变成了我们的前缀,即f.Println("hello world")
+
+3. _操作
+
+ 这个操作经常是让很多人费解的一个操作符,请看下面这个import
+
+ import (
+ "database/sql"
+ _ "github.com/ziutek/mymysql/godrv"
+ )
+
+ _操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
+
+
+
+# 2.4 struct类型
+## struct
+Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。例如,我们可以创建一个自定义类型`person`代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型我们称之`struct`。如下代码所示:
+
+ type person struct {
+ name string
+ age int
+ }
+看到了吗?声明一个struct如此简单,上面的类型包含有两个字段
+- 一个string类型的字段name,用来保存用户名称这个属性
+- 一个int类型的字段age,用来保存用户年龄这个属性
+
+如何使用struct呢?请看下面的代码
+
+ type person struct {
+ name string
+ age int
+ }
+
+ var P person // P现在就是person类型的变量了
+
+ P.name = "Astaxie" // 赋值"Astaxie"给P的name属性.
+ P.age = 25 // 赋值"25"给变量P的age属性
+ fmt.Printf("The person's name is %s", P.name) // 访问P的name属性.
+除了上面这种P的声明使用之外,还有另外几种声明使用方式:
+
+- 1.按照顺序提供初始化值
+
+ P := person{"Tom", 25}
+
+- 2.通过`field:value`的方式初始化,这样可以任意顺序
+
+ P := person{age:24, name:"Tom"}
+
+- 3.当然也可以通过`new`函数分配一个指针,此处P的类型为*person
+
+ P := new(person)
+
+下面我们看一个完整的使用struct的例子
+
+ package main
+ import "fmt"
+
+ // 声明一个新的类型
+ type person struct {
+ name string
+ age int
+ }
+
+ // 比较两个人的年龄,返回年龄大的那个人,并且返回年龄差
+ // struct也是传值的
+ func Older(p1, p2 person) (person, int) {
+ if p1.age>p2.age { // 比较p1和p2这两个人的年龄
+ return p1, p1.age-p2.age
+ }
+ return p2, p2.age-p1.age
+ }
+
+ func main() {
+ var tom person
+
+ // 赋值初始化
+ tom.name, tom.age = "Tom", 18
+
+ // 两个字段都写清楚的初始化
+ bob := person{age:25, name:"Bob"}
+
+ // 按照struct定义顺序初始化值
+ paul := person{"Paul", 43}
+
+ tb_Older, tb_diff := Older(tom, bob)
+ tp_Older, tp_diff := Older(tom, paul)
+ bp_Older, bp_diff := Older(bob, paul)
+
+ fmt.Printf("Of %s and %s, %s is older by %d years\n",
+ tom.name, bob.name, tb_Older.name, tb_diff)
+
+ fmt.Printf("Of %s and %s, %s is older by %d years\n",
+ tom.name, paul.name, tp_Older.name, tp_diff)
+
+ fmt.Printf("Of %s and %s, %s is older by %d years\n",
+ bob.name, paul.name, bp_Older.name, bp_diff)
+ }
+
+### struct的匿名字段
+我们上面介绍了如何定义一个struct,定义的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
+
+当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。
+
+让我们来看一个例子,让上面说的这些更具体化
+
+ package main
+ import "fmt"
+
+ type Human struct {
+ name string
+ age int
+ weight int
+ }
+
+ type Student struct {
+ Human // 匿名字段,那么默认Student就包含了Human的所有字段
+ speciality string
+ }
+
+ func main() {
+ // 我们初始化一个学生
+ mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
+
+ // 我们访问相应的字段
+ fmt.Println("His name is ", mark.name)
+ fmt.Println("His age is ", mark.age)
+ fmt.Println("His weight is ", mark.weight)
+ fmt.Println("His speciality is ", mark.speciality)
+ // 修改对应的备注信息
+ mark.speciality = "AI"
+ fmt.Println("Mark changed his speciality")
+ fmt.Println("His speciality is ", mark.speciality)
+ // 修改他的年龄信息
+ fmt.Println("Mark become old")
+ mark.age = 46
+ fmt.Println("His age is", mark.age)
+ // 修改他的体重信息
+ fmt.Println("Mark is not an athlet anymore")
+ mark.weight += 60
+ fmt.Println("His weight is", mark.weight)
+ }
+
+图例如下:
+
+
+
+图2.7 Student和Human的方法继承
+
+我们看到Student访问属性age和name的时候,就像访问自己所有用的字段一样,对,匿名字段就是这样,能够实现字段的继承。是不是很酷啊?还有比这个更酷的呢,那就是student还能访问Human这个字段作为字段名。请看下面的代码,是不是更酷了。
+
+ mark.Human = Human{"Marcus", 55, 220}
+ mark.Human.age -= 1
+
+通过匿名访问和修改字段相当的有用,但是不仅仅是struct字段哦,所有的内置类型和自定义类型都是可以作为匿名字段的。请看下面的例子
+
+ package main
+ import "fmt"
+
+ type Skills []string
+
+ type Human struct {
+ name string
+ age int
+ weight int
+ }
+
+ type Student struct {
+ Human // 匿名字段,struct
+ Skills // 匿名字段,自定义的类型string slice
+ int // 内置类型作为匿名字段
+ speciality string
+ }
+
+ func main() {
+ // 初始化学生Jane
+ jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"}
+ // 现在我们来访问相应的字段
+ fmt.Println("Her name is ", jane.name)
+ fmt.Println("Her age is ", jane.age)
+ fmt.Println("Her weight is ", jane.weight)
+ fmt.Println("Her speciality is ", jane.speciality)
+ // 我们来修改他的skill技能字段
+ jane.Skills = []string{"anatomy"}
+ fmt.Println("Her skills are ", jane.Skills)
+ fmt.Println("She acquired two new ones ")
+ jane.Skills = append(jane.Skills, "physics", "golang")
+ fmt.Println("Her skills now are ", jane.Skills)
+ // 修改匿名内置类型字段
+ jane.int = 3
+ fmt.Println("Her preferred number is", jane.int)
+ }
+
+从上面例子我们看出来struct不仅仅能够将struct作为匿名字段、自定义类型、内置类型都可以作为匿名字段,而且可以在相应的字段上面进行函数操作(如例子中的append)。
+
+这里有一个问题:如果human里面有一个字段叫做phone,而student也有一个字段叫做phone,那么该怎么办呢?
+
+Go里面很简单的解决了这个问题,最外层的优先访问,也就是当你通过`student.phone`访问的时候,是访问student里面的字段,而不是human里面的字段。
+
+这样就允许我们去重载通过匿名字段继承的一些字段,当然如果我们想访问重载后对应匿名类型里面的字段,可以通过匿名字段名来访问。请看下面的例子
+
+ package main
+ import "fmt"
+
+ type Human struct {
+ name string
+ age int
+ phone string // Human类型拥有的字段
+ }
+
+ type Employee struct {
+ Human // 匿名字段Human
+ speciality string
+ phone string // 雇员的phone字段
+ }
+
+ func main() {
+ Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
+ fmt.Println("Bob's work phone is:", Bob.phone)
+ // 如果我们要访问Human的phone字段
+ fmt.Println("Bob's personal phone is:", Bob.Human.phone)
+ }
+
+
+
+# 2.5 面向对象
+前面两章我们介绍了函数和struct,那你是否想过函数当作struct的字段一样来处理呢?今天我们就讲解一下函数的另一种形态,带有接收者的函数,我们称为`method`
+
+## method
+现在假设有这么一个场景,你定义了一个struct叫做长方形,你现在想要计算他的面积,那么按照我们一般的思路应该会用下面的方式来实现
+
+ package main
+ import "fmt"
+
+ type Rectangle struct {
+ width, height float64
+ }
+
+ func area(r Rectangle) float64 {
+ return r.width*r.height
+ }
+
+ func main() {
+ r1 := Rectangle{12, 2}
+ r2 := Rectangle{9, 4}
+ fmt.Println("Area of r1 is: ", area(r1))
+ fmt.Println("Area of r2 is: ", area(r2))
+ }
+
+这段代码可以计算出来长方形的面积,但是area()不是作为Rectangle的方法实现的(类似面向对象里面的方法),而是将Rectangle的对象(如r1,r2)作为参数传入函数计算面积的。
+
+这样实现当然没有问题咯,但是当需要增加圆形、正方形、五边形甚至其它多边形的时候,你想计算他们的面积的时候怎么办啊?那就只能增加新的函数咯,但是函数名你就必须要跟着换了,变成`area_rectangle, area_circle, area_triangle...`
+
+像下图所表示的那样, 椭圆代表函数, 而这些函数并不从属于struct(或者以面向对象的术语来说,并不属于class),他们是单独存在于struct外围,而非在概念上属于某个struct的。
+
+
+
+图2.8 方法和struct的关系图
+
+很显然,这样的实现并不优雅,并且从概念上来说"面积"是"形状"的一个属性,它是属于这个特定的形状的,就像长方形的长和宽一样。
+
+基于上面的原因所以就有了`method`的概念,`method`是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在`func`后面增加了一个receiver(也就是method所依从的主体)。
+
+用上面提到的形状的例子来说,method `area()` 是依赖于某个形状(比如说Rectangle)来发生作用的。Rectangle.area()的发出者是Rectangle, area()是属于Rectangle的方法,而非一个外围函数。
+
+更具体地说,Rectangle存在字段length 和 width, 同时存在方法area(), 这些字段和方法都属于Rectangle。
+
+用Rob Pike的话来说就是:
+
+>"A method is a function with an implicit first argument, called a receiver."
+
+method的语法如下:
+
+ func (r ReceiverType) funcName(parameters) (results)
+
+下面我们用最开始的例子用method来实现:
+
+ package main
+ import (
+ "fmt"
+ "math"
+ )
+
+ type Rectangle struct {
+ width, height float64
+ }
+
+ type Circle struct {
+ radius float64
+ }
+
+ func (r Rectangle) area() float64 {
+ return r.width*r.height
+ }
+
+ func (c Circle) area() float64 {
+ return c.radius * c.radius * math.Pi
+ }
+
+
+ func main() {
+ r1 := Rectangle{12, 2}
+ r2 := Rectangle{9, 4}
+ c1 := Circle{10}
+ c2 := Circle{25}
+
+ fmt.Println("Area of r1 is: ", r1.area())
+ fmt.Println("Area of r2 is: ", r2.area())
+ fmt.Println("Area of c1 is: ", c1.area())
+ fmt.Println("Area of c2 is: ", c2.area())
+ }
+
+
+
+在使用method的时候重要注意几点
+
+- 虽然method的名字一模一样,但是如果接收者不一样,那么method就不一样
+- method里面可以访问接收者的字段
+- 调用method通过`.`访问,就像struct里面访问字段一样
+
+图示如下:
+
+
+
+图2.9 不同struct的method不同
+
+在上例,method area() 分别属于Rectangle和Circle, 于是他们的 Receiver 就变成了Rectangle 和 Circle, 或者说,这个area()方法 是由 Rectangle/Circle 发出的。
+
+>值得说明的一点是,图示中method用虚线标出,意思是此处方法的Receiver是以值传递,而非引用传递,是的,Receiver还可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。后文对此会有详细论述。
+
+那是不是method只能作用在struct上面呢?当然不是咯,他可以定义在任何你自定义的类型、内置类型、struct等各种类型上面。这里你是不是有点迷糊了,什么叫自定义类型,自定义类型不就是struct嘛,不是这样的哦,struct只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。
+
+ type typeName typeLiteral
+
+请看下面这个申明自定义类型的代码
+
+ type ages int
+
+ type money float32
+
+ type months map[string]int
+
+ m := months {
+ "January":31,
+ "February":28,
+ ...
+ "December":31,
+ }
+
+看到了吗?简单的很吧,这样你就可以在自己的代码里面定义有意义的类型了,实际上只是一个定义了一个别名,有点类似于c中的typedef,例如上面ages替代了int
+
+好了,让我们回到`method`
+
+你可以在任何的自定义类型中定义任意多的`method`,接下来让我们看一个复杂一点的例子
+
+ package main
+ import "fmt"
+
+ const(
+ WHITE = iota
+ BLACK
+ BLUE
+ RED
+ YELLOW
+ )
+
+ type Color byte
+
+ type Box struct {
+ width, height, depth float64
+ color Color
+ }
+
+ type BoxList []Box //a slice of boxes
+
+ func (b Box) Volume() float64 {
+ return b.width * b.height * b.depth
+ }
+
+ func (b *Box) SetColor(c Color) {
+ b.color = c
+ }
+
+ func (bl BoxList) BiggestsColor() Color {
+ v := 0.00
+ k := Color(WHITE)
+ for _, b := range bl {
+ if bv := b.Volume(); bv > v {
+ v = bv
+ k = b.color
+ }
+ }
+ return k
+ }
+
+ func (bl BoxList) PaintItBlack() {
+ for i, _ := range bl {
+ bl[i].SetColor(BLACK)
+ }
+ }
+
+ func (c Color) String() string {
+ strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
+ return strings[c]
+ }
+
+ func main() {
+ boxes := BoxList {
+ Box{4, 4, 4, RED},
+ Box{10, 10, 1, YELLOW},
+ Box{1, 1, 20, BLACK},
+ Box{10, 10, 1, BLUE},
+ Box{10, 30, 1, WHITE},
+ Box{20, 20, 20, YELLOW},
+ }
+
+ fmt.Printf("We have %d boxes in our set\n", len(boxes))
+ fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
+ fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
+ fmt.Println("The biggest one is", boxes.BiggestsColor().String())
+
+ fmt.Println("Let's paint them all black")
+ boxes.PaintItBlack()
+ fmt.Println("The color of the second one is", boxes[1].color.String())
+
+ fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String())
+ }
+
+上面的代码通过const定义了一些常量,然后定义了一些自定义类型
+
+- Color作为byte的别名
+- 定义了一个struct:Box,含有三个长宽高字段和一个颜色属性
+- 定义了一个slice:BoxList,含有Box
+
+然后以上面的自定义类型为接收者定义了一些method
+
+- Volume()定义了接收者为Box,返回Box的容量
+- SetColor(c Color),把Box的颜色改为c
+- BiggestsColor()定在在BoxList上面,返回list里面容量最大的颜色
+- PaintItBlack()把BoxList里面所有Box的颜色全部变成黑色
+- String()定义在Color上面,返回Color的具体颜色(字符串格式)
+
+上面的代码通过文字描述出来之后是不是很简单?我们一般解决问题都是通过问题的描述,去写相应的代码实现。
+
+### 指针作为receiver
+现在让我们回过头来看看SetColor这个method,它的receiver是一个指向Box的指针,是的,你可以使用*Box。想想为啥要使用指针而不是Box本身呢?
+
+我们定义SetColor的真正目的是想改变这个Box的颜色,如果不传Box的指针,那么SetColor接受的其实是Box的一个copy,也就是说method内对于颜色值的修改,其实只作用于Box的copy,而不是真正的Box。所以我们需要传入指针。
+
+这里可以把receiver当作method的第一个参数来看,然后结合前面函数讲解的传值和传引用就不难理解
+
+这里你也许会问了那SetColor函数里面应该这样定义`*b.Color=c`,而不是`b.Color=c`,因为我们需要读取到指针相应的值。
+
+你是对的,其实Go里面这两种方式都是正确的,当你用指针去访问相应的字段时(虽然指针没有任何的字段),Go知道你要通过指针去获取这个值,看到了吧,Go的设计是不是越来越吸引你了。
+
+也许细心的读者会问这样的问题,PaintItBlack里面调用SetColor的时候是不是应该写成`(&bl[i]).SetColor(BLACK)`,因为SetColor的receiver是*Box,而不是Box。
+
+你又说对的,这两种方式都可以,因为Go知道receiver是指针,他自动帮你转了。
+
+也就是说:
+>如果一个method的receiver是*T,你可以在一个T类型的实例变量V上面调用这个method,而不需要&V去调用这个method
+
+类似的
+>如果一个method的receiver是T,你可以在一个*T类型的变量P上面调用这个method,而不需要 *P去调用这个method
+
+所以,你不用担心你是调用的指针的method还是不是指针的method,Go知道你要做的一切,这对于有多年C/C++编程经验的同学来说,真是解决了一个很大的痛苦。
+
+### method继承
+前面一章我们学习了字段的继承,那么你也会发现Go的一个神奇之处,method也是可以继承的。如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。让我们来看下面这个例子
+
+ package main
+ import "fmt"
+
+ type Human struct {
+ name string
+ age int
+ phone string
+ }
+
+ type Student struct {
+ Human //匿名字段
+ school string
+ }
+
+ type Employee struct {
+ Human //匿名字段
+ company string
+ }
+
+ //在human上面定义了一个method
+ func (h *Human) SayHi() {
+ fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
+ }
+
+ func main() {
+ mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
+ sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
+
+ mark.SayHi()
+ sam.SayHi()
+ }
+
+### method重写
+上面的例子中,如果Employee想要实现自己的SayHi,怎么办?简单,和匿名字段冲突一样的道理,我们可以在Employee上面定义一个method,重写了匿名字段的方法。请看下面的例子
+
+ package main
+ import "fmt"
+
+ type Human struct {
+ name string
+ age int
+ phone string
+ }
+
+ type Student struct {
+ Human //匿名字段
+ school string
+ }
+
+ type Employee struct {
+ Human //匿名字段
+ company string
+ }
+
+ //Human定义method
+ func (h *Human) SayHi() {
+ fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
+ }
+
+ //Employee的method重写Human的method
+ func (e *Employee) SayHi() {
+ fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
+ e.company, e.phone) //Yes you can split into 2 lines here.
+ }
+
+ func main() {
+ mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
+ sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
+
+ mark.SayHi()
+ sam.SayHi()
+ }
+
+上面的代码设计的是如此的美妙,让人不自觉的为Go的设计惊叹!
+
+通过这些内容,我们可以设计出基本的面向对象的程序了,但是Go里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现(大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。
+
+
+
+# 2.6 interface
+
+## interface
+Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计所折服。
+### 什么是interface
+简单的说,interface是一组method的组合,我们通过interface来定义对象的一组行为。
+
+我们前面一章最后一个例子中Student和Employee都能SayHi,虽然他们的内部实现不一样,但是那不重要,重要的是他们都能`say hi`
+
+让我们来继续做更多的扩展,Student和Employee实现另一个方法`Sing`,然后Student实现方法BorrowMoney而Employee实现SpendSalary。
+
+这样Student实现了三个方法:SayHi、Sing、BorrowMoney;而Employee实现了SayHi、Sing、SpendSalary。
+
+上面这些方法的组合称为interface(被对象Student和Employee实现)。例如Student和Employee都实现了interface:SayHi和Sing,也就是这两个对象是该interface类型。而Employee没有实现这个interface:SayHi、Sing和BorrowMoney,因为Employee没有实现BorrowMoney这个方法。
+### interface类型
+interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子
+
+ type Human struct {
+ name string
+ age int
+ phone string
+ }
+
+ type Student struct {
+ Human //匿名字段Human
+ school string
+ loan float32
+ }
+
+ type Employee struct {
+ Human //匿名字段Human
+ company string
+ money float32
+ }
+
+ //Human对象实现Sayhi方法
+ func (h *Human) SayHi() {
+ fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
+ }
+
+ // Human对象实现Sing方法
+ func (h *Human) Sing(lyrics string) {
+ fmt.Println("La la, la la la, la la la la la...", lyrics)
+ }
+
+ //Human对象实现Guzzle方法
+ func (h *Human) Guzzle(beerStein string) {
+ fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
+ }
+
+ // Employee重载Human的Sayhi方法
+ func (e *Employee) SayHi() {
+ fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
+ e.company, e.phone) //此句可以分成多行
+ }
+
+ //Student实现BorrowMoney方法
+ func (s *Student) BorrowMoney(amount float32) {
+ s.loan += amount // (again and again and...)
+ }
+
+ //Employee实现SpendSalary方法
+ func (e *Employee) SpendSalary(amount float32) {
+ e.money -= amount // More vodka please!!! Get me through the day!
+ }
+
+ // 定义interface
+ type Men interface {
+ SayHi()
+ Sing(lyrics string)
+ Guzzle(beerStein string)
+ }
+
+ type YoungChap interface {
+ SayHi()
+ Sing(song string)
+ BorrowMoney(amount float32)
+ }
+
+ type ElderlyGent interface {
+ SayHi()
+ Sing(song string)
+ SpendSalary(amount float32)
+ }
+
+通过上面的代码我们可以知道,interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理,一个对象可以实现任意多个interface,例如上面的Student实现了Men和YoungChap两个interface。
+
+最后,任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface。
+
+### interface值
+那么interface里面到底能存什么值呢?如果我们定义了一个interface的变量,那么这个变量里面可以存实现这个interface的任意类型的对象。例如上面例子中,我们定义了一个Men interface类型的变量m,那么m里面可以存Human、Student或者Employee值。
+
+因为m能够持有这三种类型的对象,所以我们可以定义一个包含Men类型元素的slice,这个slice可以被赋予实现了Men接口的任意结构的对象,这个和我们传统意义上面的slice有所不同。
+
+让我们来看一下下面这个例子:
+
+ package main
+ import "fmt"
+
+ type Human struct {
+ name string
+ age int
+ phone string
+ }
+
+ type Student struct {
+ Human //匿名字段
+ school string
+ loan float32
+ }
+
+ type Employee struct {
+ Human //匿名字段
+ company string
+ money float32
+ }
+
+ //Human实现SayHi方法
+ func (h Human) SayHi() {
+ fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
+ }
+
+ //Human实现Sing方法
+ func (h Human) Sing(lyrics string) {
+ fmt.Println("La la la la...", lyrics)
+ }
+
+ //Employee重载Human的SayHi方法
+ func (e Employee) SayHi() {
+ fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
+ e.company, e.phone)
+ }
+
+ // Interface Men被Human,Student和Employee实现
+ // 因为这三个类型都实现了这两个方法
+ type Men interface {
+ SayHi()
+ Sing(lyrics string)
+ }
+
+ func main() {
+ mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
+ paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
+ sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
+ Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}
+
+ //定义Men类型的变量i
+ var i Men
+
+ //i能存储Student
+ i = mike
+ fmt.Println("This is Mike, a Student:")
+ i.SayHi()
+ i.Sing("November rain")
+
+ //i也能存储Employee
+ i = Tom
+ fmt.Println("This is Tom, an Employee:")
+ i.SayHi()
+ i.Sing("Born to be wild")
+
+ //定义了slice Men
+ fmt.Println("Let's use a slice of Men and see what happens")
+ x := make([]Men, 3)
+ //这三个都是不同类型的元素,但是他们实现了interface同一个接口
+ x[0], x[1], x[2] = paul, sam, mike
+
+ for _, value := range x{
+ value.SayHi()
+ }
+ }
+
+通过上面的代码,你会发现interface就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, Go通过interface实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。
+
+### 空interface
+空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。
+
+ // 定义a为空接口
+ var a interface{}
+ var i int = 5
+ s := "Hello world"
+ // a可以存储任意类型的数值
+ a = i
+ a = s
+
+一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊!
+### interface函数参数
+interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数。
+
+举个例子:fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:
+
+ type Stringer interface {
+ String() string
+ }
+也就是说,任何实现了String方法的类型都能作为参数被fmt.Println调用,让我们来试一试
+
+ package main
+ import (
+ "fmt"
+ "strconv"
+ )
+
+ type Human struct {
+ name string
+ age int
+ phone string
+ }
+
+ // 通过这个方法 Human 实现了 fmt.Stringer
+ func (h Human) String() string {
+ return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱"
+ }
+
+ func main() {
+ Bob := Human{"Bob", 39, "000-7777-XXX"}
+ fmt.Println("This Human is : ", Bob)
+ }
+现在我们再回顾一下前面的Box示例,你会发现Color结构也定义了一个method:String。其实这也是实现了fmt.Stringer这个interface,即如果需要某个类型能被fmt包以特殊的格式输出,你就必须实现Stringer这个接口。如果没有实现这个接口,fmt将以默认的方式输出。
+
+ //实现同样的功能
+ fmt.Println("The biggest one is", boxes.BiggestsColor().String())
+ fmt.Println("The biggest one is", boxes.BiggestsColor())
+
+注:实现了error接口的对象(即实现了Error() string的对象),使用fmt输出时,会调用Error()方法,因此不必再定义String()方法了。
+### interface变量存储的类型
+
+我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:
+
+- Comma-ok断言
+
+ Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。
+
+ 如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。
+
+ 让我们通过一个例子来更加深入的理解。
+
+ package main
+
+ import (
+ "fmt"
+ "strconv"
+ )
+
+ type Element interface{}
+ type List [] Element
+
+ type Person struct {
+ name string
+ age int
+ }
+
+ //定义了String方法,实现了fmt.Stringer
+ func (p Person) String() string {
+ return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
+ }
+
+ func main() {
+ list := make(List, 3)
+ list[0] = 1 // an int
+ list[1] = "Hello" // a string
+ list[2] = Person{"Dennis", 70}
+
+ for index, element := range list {
+ if value, ok := element.(int); ok {
+ fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
+ } else if value, ok := element.(string); ok {
+ fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
+ } else if value, ok := element.(Person); ok {
+ fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
+ } else {
+ fmt.Println("list[%d] is of a different type", index)
+ }
+ }
+ }
+
+ 是不是很简单啊,同时你是否注意到了多个if里面,还记得我前面介绍流程时讲过,if里面允许初始化变量。
+
+ 也许你注意到了,我们断言的类型越多,那么if else也就越多,所以才引出了下面要介绍的switch。
+- switch测试
+
+ 最好的讲解就是代码例子,现在让我们重写上面的这个实现
+
+ package main
+
+ import (
+ "fmt"
+ "strconv"
+ )
+
+ type Element interface{}
+ type List [] Element
+
+ type Person struct {
+ name string
+ age int
+ }
+
+ //打印
+ func (p Person) String() string {
+ return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
+ }
+
+ func main() {
+ list := make(List, 3)
+ list[0] = 1 //an int
+ list[1] = "Hello" //a string
+ list[2] = Person{"Dennis", 70}
+
+ for index, element := range list{
+ switch value := element.(type) {
+ case int:
+ fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
+ case string:
+ fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
+ case Person:
+ fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
+ default:
+ fmt.Println("list[%d] is of a different type", index)
+ }
+ }
+ }
+
+ 这里有一点需要强调的是:`element.(type)`语法不能在switch外的任何逻辑里面使用,如果你要在switch外面判断一个类型就使用`comma-ok`。
+
+### 嵌入interface
+Go里面真正吸引人的是它内置的逻辑语法,就像我们在学习Struct时学习的匿名字段,多么的优雅啊,那么相同的逻辑引入到interface里面,那不是更加完美了。如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。
+
+我们可以看到源码包container/heap里面有这样的一个定义
+
+ type Interface interface {
+ sort.Interface //嵌入字段sort.Interface
+ Push(x interface{}) //a Push method to push elements into the heap
+ Pop() interface{} //a Pop elements that pops elements from the heap
+ }
+
+我们看到sort.Interface其实就是嵌入字段,把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法:
+
+ type Interface interface {
+ // Len is the number of elements in the collection.
+ Len() int
+ // Less returns whether the element with index i should sort
+ // before the element with index j.
+ Less(i, j int) bool
+ // Swap swaps the elements with indexes i and j.
+ Swap(i, j int)
+ }
+
+另一个例子就是io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer两个interface:
+
+ // io.ReadWriter
+ type ReadWriter interface {
+ Reader
+ Writer
+ }
+
+### 反射
+Go语言实现了反射,所谓反射就是动态运行时的状态。我们一般用到的包是reflect包。如何运用reflect包,官方的这篇文章详细的讲解了reflect包的实现原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html)
+
+使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下:
+
+ t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
+ v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
+
+转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如
+
+ tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
+ name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
+
+获取反射值能返回相应的类型和数值
+
+ var x float64 = 3.4
+ v := reflect.ValueOf(x)
+ fmt.Println("type:", v.Type())
+ fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
+ fmt.Println("value:", v.Float())
+
+最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误
+
+ var x float64 = 3.4
+ v := reflect.ValueOf(x)
+ v.SetFloat(7.1)
+
+如果要修改相应的值,必须这样写
+
+ var x float64 = 3.4
+ p := reflect.ValueOf(&x)
+ v := p.Elem()
+ v.SetFloat(7.1)
+
+上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。
+
+
+
+# 2.7 并发
+
+有人把Go比作21世纪的C语言,第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持了并行。
+
+## goroutine
+
+goroutine是Go并行设计的核心。goroutine说到底其实就是线程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。
+
+goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`go`关键字实现了,其实就是一个普通的函数。
+
+ go hello(a, b, c)
+
+通过关键字go就启动了一个goroutine。我们来看一个例子
+
+ package main
+
+ import (
+ "fmt"
+ "runtime"
+ )
+
+ func say(s string) {
+ for i := 0; i < 5; i++ {
+ runtime.Gosched()
+ fmt.Println(s)
+ }
+ }
+
+ func main() {
+ go say("world") //开一个新的Goroutines执行
+ say("hello") //当前Goroutines执行
+ }
+
+ 输出:
+ hello
+ world
+ hello
+ world
+ hello
+ world
+ hello
+ world
+ hello
+
+我们可以看到go关键字很方便的就实现了并发编程。
+上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。
+
+> runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine。
+
+>默认情况下,调度器仅使用单线程,也就是说只实现了并发。想要发挥多核处理器的并行,需要在我们的程序中显式调用 runtime.GOMAXPROCS(n) 告诉调度器同时使用多个线程。GOMAXPROCS 设置了同时运行逻辑代码的系统线程的最大数量,并返回之前的设置。如果n < 1,不会改变当前设置。以后Go的新版本中调度得到改进后,这将被移除。这里有一篇Rob介绍的关于并发和并行的文章:http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide
+
+## channels
+goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel:
+
+ ci := make(chan int)
+ cs := make(chan string)
+ cf := make(chan interface{})
+
+channel通过操作符`<-`来接收和发送数据
+
+ ch <- v // 发送v到channel ch.
+ v := <-ch // 从ch中接收数据,并赋值给v
+
+我们把这些应用到我们的例子中来:
+
+ package main
+
+ import "fmt"
+
+ func sum(a []int, c chan int) {
+ total := 0
+ for _, v := range a {
+ total += v
+ }
+ c <- total // send total to c
+ }
+
+ func main() {
+ a := []int{7, 2, 8, -9, 4, 0}
+
+ c := make(chan int)
+ go sum(a[:len(a)/2], c)
+ go sum(a[len(a)/2:], c)
+ x, y := <-c, <-c // receive from c
+
+ fmt.Println(x, y, x + y)
+ }
+
+默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。
+
+## Buffered Channels
+上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。
+
+ ch := make(chan type, value)
+
+ value == 0 ! 无缓冲(阻塞)
+ value > 0 ! 缓冲(非阻塞,直到value 个元素)
+
+我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值
+
+
+ package main
+
+ import "fmt"
+
+ func main() {
+ c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
+ c <- 1
+ c <- 2
+ fmt.Println(<-c)
+ fmt.Println(<-c)
+ }
+
+## Range和Close
+上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子
+
+ package main
+
+ import (
+ "fmt"
+ )
+
+ func fibonacci(n int, c chan int) {
+ x, y := 1, 1
+ for i := 0; i < n; i++ {
+ c <- x
+ x, y = y, x + y
+ }
+ close(c)
+ }
+
+ func main() {
+ c := make(chan int, 10)
+ go fibonacci(cap(c), c)
+ for i := range c {
+ fmt.Println(i)
+ }
+ }
+
+`for i := range c`能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过关键字`close`函数关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法`v, ok := <-ch`测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。
+
+>记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic
+
+>另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的
+
+## Select
+我们上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们该如何操作呢,Go里面提供了一个关键字`select`,通过`select`可以监听channel上的数据流动。
+
+`select`默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。
+
+ package main
+
+ import "fmt"
+
+ func fibonacci(c, quit chan int) {
+ x, y := 1, 1
+ for {
+ select {
+ case c <- x:
+ x, y = y, x + y
+ case <-quit:
+ fmt.Println("quit")
+ return
+ }
+ }
+ }
+
+ func main() {
+ c := make(chan int)
+ quit := make(chan int)
+ go func() {
+ for i := 0; i < 10; i++ {
+ fmt.Println(<-c)
+ }
+ quit <- 0
+ }()
+ fibonacci(c, quit)
+ }
+
+在`select`里面还有default语法,`select`其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。
+
+ select {
+ case i := <-c:
+ // use i
+ default:
+ // 当c阻塞的时候执行这里
+ }
+
+## 超时
+有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现:
+
+ func main() {
+ c := make(chan int)
+ o := make(chan bool)
+ go func() {
+ for {
+ select {
+ case v := <- c:
+ println(v)
+ case <- time.After(5 * time.Second):
+ println("timeout")
+ o <- true
+ break
+ }
+ }
+ }()
+ <- o
+ }
+
+
+## runtime goroutine
+runtime包中有几个处理goroutine的函数:
+
+- Goexit
+
+ 退出当前执行的goroutine,但是defer函数还会继续调用
+
+- Gosched
+
+ 让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。
+
+- NumCPU
+
+ 返回 CPU 核数量
+
+- NumGoroutine
+
+ 返回正在执行和排队的任务总数
+
+- GOMAXPROCS
+
+ 用来设置可以并行计算的CPU核数的最大值,并返回之前的值。
+
+
+
+# 2.8 总结
+
+这一章我们主要介绍了Go语言的一些语法,通过语法我们可以发现Go是多么的简单,只有二十五个关键字。让我们再来回顾一下这些关键字都是用来干什么的。
+
+ break default func interface select
+ case defer go map struct
+ chan else goto package switch
+ const fallthrough if range type
+ continue for import return var
+
+- var和const参考2.2Go语言基础里面的变量和常量申明
+- package和import已经有过短暂的接触
+- func 用于定义函数和方法
+- return 用于从函数返回
+- defer 用于类似析构函数
+- go 用于并行
+- select 用于选择不同类型的通讯
+- interface 用于定义接口,参考2.6小节
+- struct 用于定义抽象数据类型,参考2.5小节
+- break、case、continue、for、fallthrough、else、if、switch、goto、default这些参考2.3流程介绍里面
+- chan用于channel通讯
+- type用于声明自定义类型
+- map用于声明map类型数据
+- range用于读取slice、map、channel数据
+
+上面这二十五个关键字记住了,那么Go你也已经差不多学会了。
\ No newline at end of file
diff --git a/ebook/03.md b/ebook/03.md
new file mode 100644
index 000000000..5e2647ad7
--- /dev/null
+++ b/ebook/03.md
@@ -0,0 +1,498 @@
+
+# 3 Web基础
+
+学习基于Web的编程可能正是你读本书的原因。事实上,如何通过Go来编写Web应用也是我编写这本书的初衷。前面已经介绍过,Go目前已经拥有了成熟的Http处理包,这使得编写能做任何事情的动态Web程序易如反掌。在接下来的各章中将要介绍的内容,都是属于Web编程的范畴。本章则集中讨论一些与Web相关的概念和Go如何运行Web程序的话题。
+
+## 目录
+
+
+
+
+# 3.1 Web工作方式
+
+我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容。在这个看似简单的用户行为背后,到底隐藏了些什么呢?
+
+对于普通的上网过程,系统其实是这样做的:浏览器本身是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回HTTP Response(响应)包;客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。
+
+
+
+图3.1 用户访问一个Web站点的过程
+
+ 一个Web服务器也被称为HTTP服务器,它通过HTTP协议与客户端通信。这个客户端通常指的是Web浏览器(其实手机端客户端内部也是浏览器实现的)。
+
+Web服务器的工作原理可以简单地归纳为:
+
+- 客户机通过TCP/IP协议建立到服务器的TCP连接
+- 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档
+- 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端
+- 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果
+
+一个简单的HTTP事务就是这样实现的,看起来很复杂,原理其实是挺简单的。需要注意的是客户机与服务器之间的通信是非持久连接的,也就是当服务器发送了应答后就与客户机断开连接,等待下一次请求。
+
+## URL和DNS解析
+我们浏览网页都是通过URL访问的,那么URL到底是怎么样的呢?
+
+URL(Uniform Resource Locator)是“统一资源定位符”的英文缩写,用于描述一个网络上的资源, 基本格式如下
+
+ scheme://host[:port#]/path/.../[?query-string][#anchor]
+ scheme 指定低层使用的协议(例如:http, https, ftp)
+ host HTTP服务器的IP地址或者域名
+ port# HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如 http://www.cnblogs.com:8080/
+ path 访问资源的路径
+ query-string 发送给http服务器的数据
+ anchor 锚
+
+ DNS(Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于TCP/IP网络,它从事将主机名或域名转换为实际IP地址的工作。DNS就是这样的一位“翻译官”,它的基本工作原理可用下图来表示。
+
+
+
+图3.2 DNS工作原理
+
+更详细的DNS解析的过程如下,这个过程有助于我们理解DNS的工作模式
+
+1. 在浏览器中输入www.qq.com域名,操作系统会先检查自己本地的hosts文件是否有这个网址映射关系,如果有,就先调用这个IP地址映射,完成域名解析。
+
+2. 如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存,是否有这个网址映射关系,如果有,直接返回,完成域名解析。
+
+3. 如果hosts与本地DNS解析器缓存都没有相应的网址映射关系,首先会找TCP/IP参数中设置的首选DNS服务器,在此我们叫它本地DNS服务器,此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。
+
+4. 如果要查询的域名,不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析,此解析不具有权威性。
+
+5. 如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置(是否设置转发器)进行查询,如果未用转发模式,本地DNS就把请求发至 “根DNS服务器”,“根DNS服务器”收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。本地DNS服务器收到IP信息后,将会联系负责.com域的这台服务器。这台负责.com域的服务器收到请求后,如果自己无法解析,它就会找一个管理.com域的下一级DNS服务器地址(qq.com)给本地DNS服务器。当本地DNS服务器收到这个地址后,就会找qq.com域服务器,重复上面的动作,进行查询,直至找到www.qq.com主机。
+
+6. 如果用的是转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。不管是本地DNS服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。
+
+
+
+图3.3 DNS解析的整个流程
+
+> 所谓 `递归查询过程` 就是 “查询的递交者” 更替, 而 `迭代查询过程` 则是 “查询的递交者”不变。
+>
+> 举个例子来说,你想知道某个一起上法律课的女孩的电话,并且你偷偷拍了她的照片,回到寝室告诉一个很仗义的哥们儿,这个哥们儿二话没说,拍着胸脯告诉你,甭急,我替你查(此处完成了一次递归查询,即,问询者的角色更替)。然后他拿着照片问了学院大四学长,学长告诉他,这姑娘是xx系的;然后这哥们儿马不停蹄又问了xx系的办公室主任助理同学,助理同学说是xx系yy班的,然后很仗义的哥们儿去xx系yy班的班长那里取到了该女孩儿电话。(此处完成若干次迭代查询,即,问询者角色不变,但反复更替问询对象)最后,他把号码交到了你手里。完成整个查询过程。
+
+通过上面的步骤,我们最后获取的是IP地址,也就是浏览器最后发起请求的时候是基于IP来和服务器做信息交互的。
+
+## HTTP协议详解
+
+HTTP协议是Web工作的核心,所以要了解清楚Web的工作方式就需要详细的了解清楚HTTP是怎么样工作的。
+
+HTTP是一种让Web服务器与浏览器(客户端)通过Internet发送与接收数据的协议,它建立在TCP协议之上,一般采用TCP的80端口。它是一个请求、响应协议--客户端发出一个请求,服务器响应这个请求。在HTTP中,客户端总是通过建立一个连接与发送一个HTTP请求来发起一个事务。服务器不能主动去与客户端联系,也不能给客户端发出一个回调连接。客户端与服务器端都可以提前中断一个连接。例如,当浏览器下载一个文件时,你可以通过点击“停止”键来中断文件的下载,关闭与服务器的HTTP连接。
+
+HTTP协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系,对HTTP服务器来说,它并不知道这两个请求是否来自同一个客户端。为了解决这个问题, Web程序引入了Cookie机制来维护连接的可持续状态。
+
+>HTTP协议是建立在TCP协议之上的,因此TCP攻击一样会影响HTTP的通讯,例如比较常见的一些攻击:SYN Flood是当前最流行的DoS(拒绝服务攻击)与DdoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式。
+
+### HTTP请求包(浏览器信息)
+
+我们先来看看Request包的结构, Request包分为3部分,第一部分叫Request line(请求行), 第二部分叫Request header(请求头),第三部分是body(主体)。header和body之间有个空行,请求包的例子所示:
+
+ GET /domains/example/ HTTP/1.1 //请求行: 请求方法 请求URI HTTP协议/协议版本
+ Host:www.iana.org //服务端的主机名
+ User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 //浏览器信息
+ Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 //客户端能接收的mine
+ Accept-Encoding:gzip,deflate,sdch //是否支持流压缩
+ Accept-Charset:UTF-8,*;q=0.5 //客户端字符编码集
+ //空行,用于分割请求头和消息体
+ //消息体,请求资源参数,例如POST传递的参数
+
+我们通过fiddler抓包可以看到如下请求信息
+
+
+
+图3.4 fiddler抓取的GET信息
+
+
+
+图3.5 fiddler抓取的POST信息
+
+**我们可以看到GET请求消息体为空,POST请求带有消息体**。
+
+HTTP协议定义了很多与服务器交互的请求方法,最基本的有4种,分别是GET,POST,PUT,DELETE. 一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE就对应着对这个资源的查,改,增,删4个操作。 我们最常见的就是GET和POST了。GET一般用于获取/查询资源信息,而POST一般用于更新资源信息.
+我们看看GET和POST的区别
+1. GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditPosts.aspx?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的Body中.
+2. GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
+3. GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码。
+
+### HTTP响应包(服务器信息)
+我们再来看看HTTP的response包,他的结构如下:
+
+ HTTP/1.1 200 OK //状态行
+ Server: nginx/1.0.8 //服务器使用的WEB软件名及版本
+ Date:Date: Tue, 30 Oct 2012 04:14:25 GMT //发送时间
+ Content-Type: text/html //服务器发送信息的类型
+ Transfer-Encoding: chunked //表示发送HTTP包是分段发的
+ Connection: keep-alive //保持连接状态
+ Content-Length: 90 //主体内容长度
+ //空行 用来分割消息头和主体
+ 网页优化方面有一项措施是减少HTTP请求次数,就是把尽量多的css和js资源合并在一起,目的是尽量减少网页请求静态资源的次数,提高网页加载速度,同时减缓服务器的压力。
+
+
+
+# 3.2 GO搭建一个web服务器
+
+前面小节已经介绍了Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的就搭建起来一个可以运行的web服务。同时使用这个包能很简单地对web的路由,静态文件,模版,cookie等数据进行设置和操作。
+
+## http包建立web服务器
+
+ package main
+
+ import (
+ "fmt"
+ "net/http"
+ "strings"
+ "log"
+ )
+
+ func sayhelloName(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm() //解析参数,默认是不会解析的
+ fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
+ fmt.Println("path", r.URL.Path)
+ fmt.Println("scheme", r.URL.Scheme)
+ fmt.Println(r.Form["url_long"])
+ for k, v := range r.Form {
+ fmt.Println("key:", k)
+ fmt.Println("val:", strings.Join(v, ""))
+ }
+ fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
+ }
+
+ func main() {
+ http.HandleFunc("/", sayhelloName) //设置访问的路由
+ err := http.ListenAndServe(":9090", nil) //设置监听的端口
+ if err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+ }
+
+上面这个代码,我们build之后,然后执行web.exe,这个时候其实已经在9090端口监听http链接请求了。
+
+在浏览器输入`http://localhost:9090`
+
+可以看到浏览器页面输出了`Hello astaxie!`
+
+可以换一个地址试试:`http://localhost:9090/?url_long=111&url_long=222`
+
+看看浏览器输出的是什么,服务器输出的是什么?
+
+在服务器端输出的信息如下:
+
+
+
+图3.8 用户访问Web之后服务器端打印的信息
+
+我们看到上面的代码,要编写一个web服务器很简单,只要调用http包的两个函数就可以了。
+
+>如果你以前是PHP程序员,那你也许就会问,我们的nginx、apache服务器不需要吗?Go就是不需要这些,因为他直接就监听tcp端口了,做了nginx做的事情,然后sayhelloName这个其实就是我们写的逻辑函数了,跟php里面的控制层(controller)函数类似。
+
+>如果你以前是python程序员,那么你一定听说过tornado,这个代码和他是不是很像,对,没错,go就是拥有类似python这样动态语言的特性,写web应用很方便。
+
+>如果你以前是ruby程序员,会发现和ROR的/script/server启动有点类似。
+
+我们看到Go通过简单的几行代码就已经运行起来一个web服务了,而且这个Web服务内部有支持高并发的特性,我将会在接下来的两个小节里面详细的讲解一下go是如何实现Web高并发的。
+
+
+
+# 3.3 Go如何使得Web工作
+前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单应用一个net/http包就方便的搭建起来了。那么Go在底层到底是怎么做的呢?万变不离其宗,Go的Web服务工作也离不开我们第一小节介绍的Web工作方式。
+
+## web工作方式的几个概念
+
+以下均是服务器端的几个概念
+
+Request:用户请求的信息,用来解析用户的请求信息,包括post、get、cookie、url等信息
+
+Response:服务器需要反馈给客户端的信息
+
+Conn:用户的每次请求链接
+
+Handler:处理请求和生成返回信息的处理逻辑
+
+## 分析http包运行机制
+
+如下图所示,是Go实现Web服务的工作模式的流程图
+
+
+
+图3.9 http包执行流程
+
+1. 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。
+
+2. Listen Socket接受客户端的请求, 得到Client Socket, 接下来通过Client Socket与客户端通信。
+
+3. 处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过Client Socket写给客户端。
+
+这整个的过程里面我们只要了解清楚下面三个问题,也就知道Go是如何让Web运行起来了
+
+- 如何监听端口?
+- 如何接收客户端请求?
+- 如何分配handler?
+
+前面小节的代码里面我们可以看到,Go是通过一个函数`ListenAndServe`来处理这些事情的,这个底层其实这样处理的:初始化一个server对象,然后调用了`net.Listen("tcp", addr)`,也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。
+
+下面代码来自Go的http包的源码,通过下面的代码我们可以看到整个的http处理过程:
+
+ func (srv *Server) Serve(l net.Listener) error {
+ defer l.Close()
+ var tempDelay time.Duration // how long to sleep on accept failure
+ for {
+ rw, e := l.Accept()
+ if e != nil {
+ if ne, ok := e.(net.Error); ok && ne.Temporary() {
+ if tempDelay == 0 {
+ tempDelay = 5 * time.Millisecond
+ } else {
+ tempDelay *= 2
+ }
+ if max := 1 * time.Second; tempDelay > max {
+ tempDelay = max
+ }
+ log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
+ time.Sleep(tempDelay)
+ continue
+ }
+ return e
+ }
+ tempDelay = 0
+ c, err := srv.newConn(rw)
+ if err != nil {
+ continue
+ }
+ go c.serve()
+ }
+ }
+
+监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。
+
+那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个作用就是注册了请求`/`的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。
+
+
+详细的整个流程如下图所示:
+
+
+
+图3.10 一个http连接处理流程
+
+至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经基本了解呢?
+
+
+
+# 3.4 Go的http包详解
+前面小节介绍了Go怎么样实现了Web工作模式的一个流程,这一小节,我们将详细地解剖一下http包,看它到底是怎样实现整个过程的。
+
+Go的http有两个核心功能:Conn、ServeMux
+
+## Conn的goroutine
+与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。
+
+Go在等待客户端请求里面是这样写的:
+
+ c, err := srv.newConn(rw)
+ if err != nil {
+ continue
+ }
+ go c.serve()
+
+这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中便可以读取到相应的header信息,这样保证了每个请求的独立性。
+
+## ServeMux的自定义
+我们前面小节讲述conn.server的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?
+
+它的结构如下:
+
+ type ServeMux struct {
+ mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
+ m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
+ hosts bool // 是否在任意的规则中带有host信息
+ }
+
+下面看一下muxEntry
+
+ type muxEntry struct {
+ explicit bool // 是否精确匹配
+ h Handler // 这个路由表达式对应哪个handler
+ pattern string //匹配字符串
+ }
+
+接着看一下Handler的定义
+
+ type Handler interface {
+ ServeHTTP(ResponseWriter, *Request) // 路由实现器
+ }
+
+Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有实现ServeHTTP这个接口,为什么能添加呢?原来在http包里面还定义了一个类型`HandlerFunc`,我们定义的函数`sayhelloName`就是这个HandlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServHTTP方法。
+
+ type HandlerFunc func(ResponseWriter, *Request)
+
+ // ServeHTTP calls f(w, r).
+ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
+ f(w, r)
+ }
+
+路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了`ServeHTTP`:
+
+ func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
+ if r.RequestURI == "*" {
+ w.Header().Set("Connection", "close")
+ w.WriteHeader(StatusBadRequest)
+ return
+ }
+ h, _ := mux.Handler(r)
+ h.ServeHTTP(w, r)
+ }
+
+如上所示路由器接收到请求之后,如果是`*`那么关闭链接,不然调用`mux.Handler(r)`返回对应设置路由的处理Handler,然后执行`h.ServeHTTP(w, r)`
+
+也就是调用对应路由的handler的ServerHTTP接口,那么mux.Handler(r)怎么处理的呢?
+
+ func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
+ if r.Method != "CONNECT" {
+ if p := cleanPath(r.URL.Path); p != r.URL.Path {
+ _, pattern = mux.handler(r.Host, p)
+ return RedirectHandler(p, StatusMovedPermanently), pattern
+ }
+ }
+ return mux.handler(r.Host, r.URL.Path)
+ }
+
+ func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
+ mux.mu.RLock()
+ defer mux.mu.RUnlock()
+
+ // Host-specific pattern takes precedence over generic ones
+ if mux.hosts {
+ h, pattern = mux.match(host + path)
+ }
+ if h == nil {
+ h, pattern = mux.match(path)
+ }
+ if h == nil {
+ h, pattern = NotFoundHandler(), ""
+ }
+ return
+ }
+
+原来他是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到之后返回存储的handler,调用这个handler的ServHTTP接口就可以执行到相应的函数了。
+
+通过上面这个介绍,我们了解了整个路由过程,Go其实支持外部实现的路由器 `ListenAndServe`的第二个参数就是用以配置外部路由器的,它是一个Handler接口,即外部路由器只要实现了Handler接口就可以,我们可以在自己实现的路由器的ServHTTP里面实现自定义路由功能。
+
+如下代码所示,我们自己实现了一个简易的路由器
+
+ package main
+
+ import (
+ "fmt"
+ "net/http"
+ )
+
+ type MyMux struct {
+ }
+
+ func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/" {
+ sayhelloName(w, r)
+ return
+ }
+ http.NotFound(w, r)
+ return
+ }
+
+ func sayhelloName(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Hello myroute!")
+ }
+
+ func main() {
+ mux := &MyMux{}
+ http.ListenAndServe(":9090", mux)
+ }
+
+## Go代码的执行流程
+
+通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。
+
+- 首先调用Http.HandleFunc
+
+ 按顺序做了几件事:
+
+ 1 调用了DefaultServerMux的HandleFunc
+
+ 2 调用了DefaultServerMux的Handle
+
+ 3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
+
+- 其次调用http.ListenAndServe(":9090", nil)
+
+ 按顺序做了几件事情:
+
+ 1 实例化Server
+
+ 2 调用Server的ListenAndServe()
+
+ 3 调用net.Listen("tcp", addr)监听端口
+
+ 4 启动一个for循环,在循环体中Accept请求
+
+ 5 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
+
+ 6 读取每个请求的内容w, err := c.readRequest()
+
+ 7 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux
+
+ 8 调用handler的ServeHttp
+
+ 9 在这个例子中,下面就进入到DefaultServerMux.ServeHttp
+
+ 10 根据request选择handler,并且进入到这个handler的ServeHTTP
+
+ mux.handler(r).ServeHTTP(w, r)
+
+ 11 选择handler:
+
+ A 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry)
+
+ B 如果有路由满足,调用这个路由handler的ServeHttp
+
+ C 如果没有路由满足,调用NotFoundHandler的ServeHttp
+
+
+
+# 3.5 小结
+ 这一章我们介绍了HTTP协议, DNS解析的过程, 如何用go实现一个简陋的web server。并深入到net/http包的源码中为大家揭开实现此server的秘密。
+
+ 希望通过这一章的学习,你能够对Go开发Web有了初步的了解,我们也看到相应的代码了,Go开发Web应用是很方便的,同时又是相当的灵活。
\ No newline at end of file
diff --git a/ebook/04.md b/ebook/04.md
new file mode 100644
index 000000000..b96f65f63
--- /dev/null
+++ b/ebook/04.md
@@ -0,0 +1,565 @@
+# 4 表单
+
+表单是我们平常编写Web应用常用的工具,通过表单我们可以方便的让客户端和服务器进行数据的交互。对于以前开发过Web的用户来说表单都非常熟悉,但是对于C/C++程序员来说,这可能是一个有些陌生的东西,那么什么是表单呢?
+
+表单是一个包含表单元素的区域。表单元素是允许用户在表单中(比如:文本域、下拉列表、单选框、复选框等等)输入信息的元素。表单使用表单标签(\
+
+Go里面对于form处理已经有很方便的方法了,在Request里面的有专门的form处理,可以很方便的整合到Web开发里面来,4.1小节里面将讲解Go如何处理表单的输入。由于不能信任任何用户的输入,所以我们需要对这些输入进行有效性验证,4.2小节将就如何进行一些普通的验证进行详细的演示。
+
+HTTP协议是一种无状态的协议,那么如何才能辨别是否是同一个用户呢?同时又如何保证一个表单不出现多次递交的情况呢?4.3和4.4小节里面将对cookie(cookie是存储在客户端的信息,能够每次通过header和服务器进行交互的数据)等进行详细讲解。
+
+表单还有一个很大的功能就是能够上传文件,那么Go是如何处理文件上传的呢?针对大文件上传我们如何有效的处理呢?4.5小节我们将一起学习Go处理文件上传的知识。
+
+## 目录
+
+
+
+
+# 4.1 处理表单的输入
+
+先来看一个表单递交的例子,我们有如下的表单内容,命名成文件login.gtpl(放入当前新建项目的目录里面)
+
+
+
+
+
+
+
+
+
+
+上面递交表单到服务器的`/login`,当用户输入信息点击登陆之后,会跳转到服务器的路由`login`里面,我们首先要判断这个是什么方式传递过来,POST还是GET呢?
+
+http包里面有一个很简单的方式就可以获取,我们在前面web的例子的基础上来看看怎么处理login页面的form数据
+
+
+ package main
+
+ import (
+ "fmt"
+ "html/template"
+ "log"
+ "net/http"
+ "strings"
+ )
+
+ func sayhelloName(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm() //解析url传递的参数,对于POST则解析响应包的主体(request body)
+ //注意:如果没有调用ParseForm方法,下面无法获取表单的数据
+ fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
+ fmt.Println("path", r.URL.Path)
+ fmt.Println("scheme", r.URL.Scheme)
+ fmt.Println(r.Form["url_long"])
+ for k, v := range r.Form {
+ fmt.Println("key:", k)
+ fmt.Println("val:", strings.Join(v, ""))
+ }
+ fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
+ }
+
+ func login(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("method:", r.Method) //获取请求的方法
+ if r.Method == "GET" {
+ t, _ := template.ParseFiles("login.gtpl")
+ t.Execute(w, nil)
+ } else {
+ //请求的是登陆数据,那么执行登陆的逻辑判断
+ fmt.Println("username:", r.Form["username"])
+ fmt.Println("password:", r.Form["password"])
+ }
+ }
+
+ func main() {
+ http.HandleFunc("/", sayhelloName) //设置访问的路由
+ http.HandleFunc("/login", login) //设置访问的路由
+ err := http.ListenAndServe(":9090", nil) //设置监听的端口
+ if err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+ }
+
+
+通过上面的代码我们可以看出获取请求方法是通过`r.Method`来完成的,这是个字符串类型的变量,返回GET, POST, PUT等method信息。
+
+login函数中我们根据`r.Method`来判断是显示登录界面还是处理登录逻辑。当GET方式请求时显示登录界面,其他方式请求时则处理登录逻辑,如查询数据库、验证登录信息等。
+
+当我们在浏览器里面打开`http://127.0.0.1:9090/login`的时候,出现如下界面
+
+
+
+图4.1 用户登录界面
+
+我们输入用户名和密码之后发现在服务器端是不会打印出来任何输出的,为什么呢?默认情况下,Handler里面是不会自动解析form的,必须显式的调用`r.ParseForm()`后,你才能对这个表单数据进行操作。我们修改一下代码,在`fmt.Println("username:", r.Form["username"])`之前加一行`r.ParseForm()`,重新编译,再次测试输入递交,现在是不是在服务器端有输出你的输入的用户名和密码了。
+
+`r.Form`里面包含了所有请求的参数,比如URL中query-string、POST的数据、PUT的数据,所有当你在URL的query-string字段和POST冲突时,会保存成一个slice,里面存储了多个值,Go官方文档中说在接下来的版本里面将会把POST、GET这些数据分离开来。
+
+现在我们修改一下login.gtpl里面form的action值`http://127.0.0.1:9090/login`修改为`http://127.0.0.1:9090/login?username=astaxie`,再次测试,服务器的输出username是不是一个slice。服务器端的输出如下:
+
+
+
+图4.2 服务器端打印接受到的信息
+
+`request.Form`是一个url.Values类型,里面存储的是对应的类似`key=value`的信息,下面展示了可以对form数据进行的一些操作:
+
+ v := url.Values{}
+ v.Set("name", "Ava")
+ v.Add("friend", "Jess")
+ v.Add("friend", "Sarah")
+ v.Add("friend", "Zoe")
+ // v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
+ fmt.Println(v.Get("name"))
+ fmt.Println(v.Get("friend"))
+ fmt.Println(v["friend"])
+
+>**Tips**:
+Request本身也提供了FormValue()函数来获取用户提交的参数。如r.Form["username"]也可写成r.FormValue("username")。调用r.FormValue时会自动调用r.ParseForm,所以不必提前调用。r.FormValue只会返回同名参数中的第一个,若参数不存在则返回空字符串。
+
+
+
+# 4.2 验证表单的输入
+
+开发Web的一个原则就是,不能信任用户输入的任何信息,所以验证和过滤用户的输入信息就变得非常重要,我们经常会在微博、新闻中听到某某网站被入侵了,存在什么漏洞,这些大多是因为网站对于用户输入的信息没有做严格的验证引起的,所以为了编写出安全可靠的Web程序,验证表单输入的意义重大。
+
+我们平常编写Web应用主要有两方面的数据验证,一个是在页面端的js验证(目前在这方面有很多的插件库,比如ValidationJS插件),一个是在服务器端的验证,我们这小节讲解的是如何在服务器端验证。
+
+## 必填字段
+你想要确保从一个表单元素中得到一个值,例如前面小节里面的用户名,我们如何处理呢?Go有一个内置函数`len`可以获取字符串的长度,这样我们就可以通过len来获取数据的长度,例如:
+
+ if len(r.Form["username"][0])==0{
+ //为空的处理
+ }
+
+`r.Form`对不同类型的表单元素的留空有不同的处理, 对于空文本框、空文本区域以及文件上传,元素的值为空值,而如果是未选中的复选框和单选按钮,则根本不会在r.Form中产生相应条目,如果我们用上面例子中的方式去获取数据时程序就会报错。所以我们需要通过`r.Form.Get()`来获取值,因为如果字段不存在,通过该方式获取的是空值。但是通过`r.Form.Get()`只能获取单个的值,如果是map的值,必须通过上面的方式来获取。
+
+## 数字
+你想要确保一个表单输入框中获取的只能是数字,例如,你想通过表单获取某个人的具体年龄是50岁还是10岁,而不是像“一把年纪了”或“年轻着呢”这种描述
+
+如果我们是判断正整数,那么我们先转化成int类型,然后进行处理
+
+ getint,err:=strconv.Atoi(r.Form.Get("age"))
+ if err!=nil{
+ //数字转化出错了,那么可能就不是数字
+ }
+
+ //接下来就可以判断这个数字的大小范围了
+ if getint >100 {
+ //太大了
+ }
+
+还有一种方式就是正则匹配的方式
+
+ if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
+ return false
+ }
+
+对于性能要求很高的用户来说,这是一个老生常谈的问题了,他们认为应该尽量避免使用正则表达式,因为使用正则表达式的速度会比较慢。但是在目前机器性能那么强劲的情况下,对于这种简单的正则表达式效率和类型转换函数是没有什么差别的。如果你对正则表达式很熟悉,而且你在其它语言中也在使用它,那么在Go里面使用正则表达式将是一个便利的方式。
+
+>Go实现的正则是[RE2](http://code.google.com/p/re2/wiki/Syntax),所有的字符都是UTF-8编码的。
+
+## 中文
+有时候我们想通过表单元素获取一个用户的中文名字,但是又为了保证获取的是正确的中文,我们需要进行验证,而不是用户随便的一些输入。对于中文我们目前有效的验证只有正则方式来验证,如下代码所示
+
+ if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", r.Form.Get("realname")); !m {
+ return false
+ }
+
+## 英文
+我们期望通过表单元素获取一个英文值,例如我们想知道一个用户的英文名,应该是astaxie,而不是asta谢。
+
+我们可以很简单的通过正则验证数据:
+
+ if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
+ return false
+ }
+
+
+## 电子邮件地址
+你想知道用户输入的一个Email地址是否正确,通过如下这个方式可以验证:
+
+ if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
+ fmt.Println("no")
+ }else{
+ fmt.Println("yes")
+ }
+
+
+## 手机号码
+你想要判断用户输入的手机号码是否正确,通过正则也可以验证:
+
+ if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
+ return false
+ }
+
+## 下拉菜单
+如果我们想要判断表单里面`