diff --git a/1.1.md b/1.1.md
index 11b7cd9d5393eed52719489a15bdd99f08b23271..95f8b8b18e7ca09bbefee77d32021cf8e7ba7485 100644
--- a/1.1.md
+++ b/1.1.md
@@ -130,8 +130,5 @@ homebrew是Mac系统下面目前使用最多的管理软件的工具,目前已
* 上一节: [Go环境配置](<1.md>)
* 下一节: [GOPATH 与工作空间](<1.2.md>)
-## LastModified
- * $Id$
-
[downlink]: http://code.google.com/p/go/downloads/list "Go安装包下载"
[hg]: http://mercurial.selenic.com/downloads/ "Mercurial下载"
diff --git a/1.2.md b/1.2.md
index 7dd28c80ccf870a6c08cc288ac9be9d2eca69781..1f5db4c77f5f42ac9180bd517b02fd63733c94c0 100644
--- a/1.2.md
+++ b/1.2.md
@@ -1,162 +1,159 @@
-# 1.2 GOPATH与工作空间
-
-## GOPATH设置
- go 命令依赖一个重要的环境变量:$GOPATH1
-
- *(注:这个不是Go安装目录。下面以笔者的工作目录为说明,请替换自己机器上的工作目录。)*
-
- 在类似 Unix 环境大概这样设置:
-
- export GOPATH=/home/apple/mygo
-
- Windows 设置如下,新建一个环境变量名称叫做GOPATH:
-
- GOPATH=c:\mygo
-
-GOPATH允许多个目录,当有多个目录时,请注意分隔符,多个GOPATH的时候Windows是分号,Linux系统是冒号,当有多个GOPATH时,默认会将go get的内容放在第一个目录下
-
-
-以上 $GOPATH 目录约定有三个子目录:
-
-- src 存放源代码(比如:.go .c .h .s等)
-- pkg 编译后生成的文件(比如:.a)
-- bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中)
-
-以后我所有的例子都是以mygo作为我的gopath目录
-
-## 应用目录结构
-建立包和目录:$GOPATH/src/mymath/sqrt.go(包名:"mymath")
-
-以后自己新建应用或者一个代码包都是在src目录下新建一个文件夹,文件夹名称代码包名称,当然也允许多级目录,例如在src下面新建了目录$GOPATH/src/github.com/astaxie/beedb 那么这个包名称就是“github.com/astaxie/beedb”
-
-执行如下代码
-
- cd $GOPATH/src
- mkdir mymath
-
-新建文件sqrt.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`
-
-安装完之后,我们可以进入如下目录
-
- cd $GOPATH/pkg/${GOOS}_${GOARCH}
- //可以看到如下文件
- mymath.a
-
-这个.a文件是应用包,相当于一个函数库一样,那么我们如何进行调用呢?
-
-接下来我们新建一个应用程序来调用
-
-新建应用包mathapp
-
- cd $GOPATH/src
- mkdir mathapp
- vim main.go
-
-// `$GOPATH/src/mathapp/main.go`源码:
-
- package main
-
- import (
- "mymath"
- "fmt"
- )
-
- func main() {
- fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2))
- }
-
-如何编译程序呢?进入该应用目录,然后执行`go build`,那么在该目录下面会生成一个mathapp的可执行文件
-
- ./mathapp
-
-输出如下内容
-
- 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
-
-通过这个命令可以获取相应的源码,对应的开源平台采用不同的源码控制工具,例如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用户请自行替换。
-## links
- * [目录]()
- * 上一节: [GO安装](<1.1.md>)
- * 下一节: [GO 命令](<1.3.md>)
-
-## LastModified
- * $Id$
+# 1.2 GOPATH与工作空间
+
+## GOPATH设置
+ go 命令依赖一个重要的环境变量:$GOPATH1
+
+ *(注:这个不是Go安装目录。下面以笔者的工作目录为说明,请替换自己机器上的工作目录。)*
+
+ 在类似 Unix 环境大概这样设置:
+
+ export GOPATH=/home/apple/mygo
+
+ Windows 设置如下,新建一个环境变量名称叫做GOPATH:
+
+ GOPATH=c:\mygo
+
+GOPATH允许多个目录,当有多个目录时,请注意分隔符,多个GOPATH的时候Windows是分号,Linux系统是冒号,当有多个GOPATH时,默认会将go get的内容放在第一个目录下
+
+
+以上 $GOPATH 目录约定有三个子目录:
+
+- src 存放源代码(比如:.go .c .h .s等)
+- pkg 编译后生成的文件(比如:.a)
+- bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中)
+
+以后我所有的例子都是以mygo作为我的gopath目录
+
+## 应用目录结构
+建立包和目录:$GOPATH/src/mymath/sqrt.go(包名:"mymath")
+
+以后自己新建应用或者一个代码包都是在src目录下新建一个文件夹,文件夹名称代码包名称,当然也允许多级目录,例如在src下面新建了目录$GOPATH/src/github.com/astaxie/beedb 那么这个包名称就是“github.com/astaxie/beedb”
+
+执行如下代码
+
+ cd $GOPATH/src
+ mkdir mymath
+
+新建文件sqrt.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`
+
+安装完之后,我们可以进入如下目录
+
+ cd $GOPATH/pkg/${GOOS}_${GOARCH}
+ //可以看到如下文件
+ mymath.a
+
+这个.a文件是应用包,相当于一个函数库一样,那么我们如何进行调用呢?
+
+接下来我们新建一个应用程序来调用
+
+新建应用包mathapp
+
+ cd $GOPATH/src
+ mkdir mathapp
+ vim main.go
+
+// `$GOPATH/src/mathapp/main.go`源码:
+
+ package main
+
+ import (
+ "mymath"
+ "fmt"
+ )
+
+ func main() {
+ fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2))
+ }
+
+如何编译程序呢?进入该应用目录,然后执行`go build`,那么在该目录下面会生成一个mathapp的可执行文件
+
+ ./mathapp
+
+输出如下内容
+
+ 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
+
+通过这个命令可以获取相应的源码,对应的开源平台采用不同的源码控制工具,例如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用户请自行替换。
+## links
+ * [目录]()
+ * 上一节: [GO安装](<1.1.md>)
+ * 下一节: [GO 命令](<1.3.md>)
diff --git a/1.3.md b/1.3.md
index e997a5d50aaf2d80da3ecfc4df4a055eddada6a2..cf51e55c8b45a0afe992993875e1b80099775ca8 100644
--- a/1.3.md
+++ b/1.3.md
@@ -1,111 +1,108 @@
-# 1.3 Go 命令
-
-## Go 命令
-
- Go语言自带有一套完整的命令操作工具,你可以通过在命令行中执行`go`来查看它们:
-
- ![](images/1.3.go.png?raw=true)
-
- 这些命令对于我们平时编写的代码非常有用,接下来就让我们了解一些常用的命令。
-
-## go build
-
- 这个命令主要用于测试编译。在包的编译过程中,若有必要,会同时编译与之相关联的包。
-
- - 如果是普通包,就像我们在1.2节中编写的`mymath`包那样,当你执行`go build`之后,它不会产生任何文件。如果你需要在`$GOPATH/pkg`下生成相应的文件,那就得执行`go install`了。
-
- - 如果是`main`包,当你执行`go build`之后,它就会在当前目录下生成一个可执行文件。如果你需要在`$GOPATH/bin`下生成相应的文件,同样需要执行`go install`。
-
- - 如果某个项目文件夹下有多个文件,而你只想编译某个文件,就可在`go build`之后加上文件名,例如`go build a.go`;`go build`命令默认会编译当前目录下的所有go文件。
-
- - 你也可以指定编译输出的文件名。例如1.2节中的`mathapp`应用,我们可以指定`go build -o astaxie.exe`,默认情况是你的package名,就是你的文件夹名称。
-
- (注:实际上,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 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
-
- 这个命令在内部实际上分成了两步操作:第一步是`go build`,第二步会把编译好的东西move到`$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
-
- 很多人说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程序
-
-## links
- * [目录]()
- * 上一节: [GOPATH与工作空间](<1.2.md>)
- * 下一节: [Go开发工具](<1.4.md>)
-
-## LastModified
- * $Id$
+# 1.3 Go 命令
+
+## Go 命令
+
+ Go语言自带有一套完整的命令操作工具,你可以通过在命令行中执行`go`来查看它们:
+
+ ![](images/1.3.go.png?raw=true)
+
+ 这些命令对于我们平时编写的代码非常有用,接下来就让我们了解一些常用的命令。
+
+## go build
+
+ 这个命令主要用于测试编译。在包的编译过程中,若有必要,会同时编译与之相关联的包。
+
+ - 如果是普通包,就像我们在1.2节中编写的`mymath`包那样,当你执行`go build`之后,它不会产生任何文件。如果你需要在`$GOPATH/pkg`下生成相应的文件,那就得执行`go install`了。
+
+ - 如果是`main`包,当你执行`go build`之后,它就会在当前目录下生成一个可执行文件。如果你需要在`$GOPATH/bin`下生成相应的文件,同样需要执行`go install`。
+
+ - 如果某个项目文件夹下有多个文件,而你只想编译某个文件,就可在`go build`之后加上文件名,例如`go build a.go`;`go build`命令默认会编译当前目录下的所有go文件。
+
+ - 你也可以指定编译输出的文件名。例如1.2节中的`mathapp`应用,我们可以指定`go build -o astaxie.exe`,默认情况是你的package名,就是你的文件夹名称。
+
+ (注:实际上,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 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
+
+ 这个命令在内部实际上分成了两步操作:第一步是`go build`,第二步会把编译好的东西move到`$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
+
+ 很多人说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程序
+
+## links
+ * [目录]()
+ * 上一节: [GOPATH与工作空间](<1.2.md>)
+ * 下一节: [Go开发工具](<1.4.md>)
diff --git a/1.4.md b/1.4.md
index cd91b8911444422417150e092c2cfdc55fc5adbd..a94521d4165e2c81da02f661ef4017507acf0f53 100644
--- a/1.4.md
+++ b/1.4.md
@@ -1,304 +1,301 @@
-# 1.4 Go开发工具
-
-本节我将介绍几个开发工具,它们都具有自动化提示,自动化fmt功能。因为它们都是跨平台的,所以安装步骤之类的都是通用的。
-
-## LiteIDE
-
- LiteIDE是一款专门为Go语言开发的集成开发环境(IDE),由visualfc编写。支持项目管理、集成构建、GDB调试、语法高亮、自动补全、大纲显示等功能。下载地址: [http://code.google.com/p/golangide/downloads/list](http://code.google.com/p/golangide/downloads/list),根据自己的系统下载相应的发行版本。Windows和Ubuntu系统可直接打开bin下面的liteide;Mac则需通过LaunchPad打开LiteIDE.app。
-
- ![](images/1.4.liteide.png?raw=true)
-
- LiteIDE配置需要按照前面几个小节配置了相应的go和`$GOPATH`(LiteIDE中也可以图形化配置LiteIDE专用的GOPATH)。LiteIDE当前的编译环境可以通过编译工具栏上的环境配置来切换,如切换32位和64位,针对自己的系统,可能需要修改相应的LiteEnv环境变量,如64位版本,LiteIDE => 查看 => 选项 => LiteEnv => Win64.env => GOROOT=c:\go-w64 为你的`$GOROOT`,不然会无法使用`build`命令。
-
- 配置好LiteIDE后,可以打开或拖动任何目录到LiteIDE中作为项目,LiteIDE的编译是针对当前编辑文档所属目录来执行相应的go命令,所以编译时要先打开相应的Go文件。LiteIDE仿IDEA界面,支持项目浏览、文件系统,Package浏览、Golang文档检索、类视图、大纲显示等多个工具窗口的切换。
-
- 代码补全需要安装gocode,目前LiteIDE的自动化提示只支持本文件中函数的提示,还没有做到整个项目中函数的提示。
-
-
-## Sublime Text
-
- 这里将介绍Sublime Text 2(以下简称Sublime)+GoSublime+gocode+MarGo的组合,那么为什么选择这个组合呢?
-
- - 自动化提示代码,如下图所示
- ![](images/1.4.sublime1.png?raw=true)
-
- - 保存的时候自动格式化代码,让您编写的代码更加美观,符合Go的标准。
- - 支持项目管理
- ![](images/1.4.sublime2.png?raw=true)
- - 支持语法高亮
- - 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已经安装成功了。
-
- ![](images/1.4.sublime3.png?raw=true)
-
- 2. 接下来安装gocode和MarGo。
- 打开终端运行如下代码(需要git)
-
- go get -u github.com/nsf/gocode
- go get -u github.com/DisposaBoy/MarGo
-
- 这个时候我们会发现在`$GOPATH/bin`下面多了两个可执行文件,gocode和MarGo,这两个文件会在GoSublime加载时自动启动。
-
- 3. 安装完之后就可以安装Sublime的插件了。需安装GoSublime、SidebarEnhancements和Go Build,安装插件之后记得重启Sublime生效,Ctrl+Shift+p打开Package Controll 输入`pcip`(即“Package Control: Install Package”的缩写)。
-
- 这个时候看左下角显示正在读取包数据,完成之后出现如下界面
-
- ![](images/1.4.sublime4.png?raw=true)
-
- 这个时候输入GoSublime,按确定就开始安装了。同理应用于SidebarEnhancements和Go Build。
-
- 4. 验证是否安装成功,你可以打开Sublime,打开main.go,看看语法是不是高亮了,输入`import`是不是自动化提示了,`import "fmt"`之后,输入`fmt.`是不是自动化提示有函数了。
-
- 如果已经出现这个提示,那说明你已经安装完成了,并且完成了自动提示。
-
- 如果没有出现这样的提示,一般就是你的`$PATH`没有配置正确。你可以打开终端,输入gocode,是不是能够正确运行,如果不行就说明`$PATH`没有配置正确。
-
-
-## Vim
-Vim是从vi发展出来的一个文本编辑器, 代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。
-
- ![](images/1.4.vim.png?raw=true)
-
- 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默认安装到`$GOPATH/bin`下面,需要把`$GOPATH/bin`路径设置到系统`$PATH`里面。
-
- 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的乐趣。
-
-
-## Emacs
-Emacs传说中的神器,她不仅仅是一个编辑器,它是一个整合环境,或可称它为集成开发环境,这些功能如让使用者置身于全功能的操作系统中。
-
- ![](images/1.4.emacs.png?raw=true)
-
- 1. 配置Emacs高亮显示
-
- cp $GOROOT/misc/emacs/* ~/.emacs.d/
-
- 2. 安装[Gocode](https://github.com/nsf/gocode/)
-
- go get -u github.com/nsf/gocode
-
- gocode默认安装到`$GOPATH/bin`里面下面,需要把`$GOPATH/bin`路径设置到系统`$PATH`里面。
-
- 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程序。
-
- ![](images/1.4.eclipse1.png?raw=true)
-
- 1.首先下载并安装好[Eclipse](http://www.eclipse.org/)
-
- 2.下载[goeclipse](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的编译器
-
- ![](images/1.4.eclipse2.png?raw=true)
-
- (2).配置Gocode(可选,代码补全),设置Gocode路径为之前生成的gocode.exe文件
-
- ![](images/1.4.eclipse3.png?raw=true)
-
- (3).配置GDB(可选,做调试用),设置GDB路径为MingW安装目录下的gdb.exe文件
-
- ![](images/1.4.eclipse4.png?raw=true)
-
- 6.测试是否成功
-
-新建一个go工程,再建立一个hello.go。如下图:
-
- ![](images/1.4.eclipse5.png?raw=true)
-
-调试如下(要在console中用输入命令来调试):
-
- ![](images/1.4.eclipse6.png?raw=true)
-
-
-## links
- * [目录]()
- * 上一节: [Go 命令](<1.3.md>)
- * 下一节: [总结](<1.5.md>)
-
-## LastModified
- * $Id$
+# 1.4 Go开发工具
+
+本节我将介绍几个开发工具,它们都具有自动化提示,自动化fmt功能。因为它们都是跨平台的,所以安装步骤之类的都是通用的。
+
+## LiteIDE
+
+ LiteIDE是一款专门为Go语言开发的集成开发环境(IDE),由visualfc编写。支持项目管理、集成构建、GDB调试、语法高亮、自动补全、大纲显示等功能。下载地址: [http://code.google.com/p/golangide/downloads/list](http://code.google.com/p/golangide/downloads/list),根据自己的系统下载相应的发行版本。Windows和Ubuntu系统可直接打开bin下面的liteide;Mac则需通过LaunchPad打开LiteIDE.app。
+
+ ![](images/1.4.liteide.png?raw=true)
+
+ LiteIDE配置需要按照前面几个小节配置了相应的go和`$GOPATH`(LiteIDE中也可以图形化配置LiteIDE专用的GOPATH)。LiteIDE当前的编译环境可以通过编译工具栏上的环境配置来切换,如切换32位和64位,针对自己的系统,可能需要修改相应的LiteEnv环境变量,如64位版本,LiteIDE => 查看 => 选项 => LiteEnv => Win64.env => GOROOT=c:\go-w64 为你的`$GOROOT`,不然会无法使用`build`命令。
+
+ 配置好LiteIDE后,可以打开或拖动任何目录到LiteIDE中作为项目,LiteIDE的编译是针对当前编辑文档所属目录来执行相应的go命令,所以编译时要先打开相应的Go文件。LiteIDE仿IDEA界面,支持项目浏览、文件系统,Package浏览、Golang文档检索、类视图、大纲显示等多个工具窗口的切换。
+
+ 代码补全需要安装gocode,目前LiteIDE的自动化提示只支持本文件中函数的提示,还没有做到整个项目中函数的提示。
+
+
+## Sublime Text
+
+ 这里将介绍Sublime Text 2(以下简称Sublime)+GoSublime+gocode+MarGo的组合,那么为什么选择这个组合呢?
+
+ - 自动化提示代码,如下图所示
+ ![](images/1.4.sublime1.png?raw=true)
+
+ - 保存的时候自动格式化代码,让您编写的代码更加美观,符合Go的标准。
+ - 支持项目管理
+ ![](images/1.4.sublime2.png?raw=true)
+ - 支持语法高亮
+ - 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已经安装成功了。
+
+ ![](images/1.4.sublime3.png?raw=true)
+
+ 2. 接下来安装gocode和MarGo。
+ 打开终端运行如下代码(需要git)
+
+ go get -u github.com/nsf/gocode
+ go get -u github.com/DisposaBoy/MarGo
+
+ 这个时候我们会发现在`$GOPATH/bin`下面多了两个可执行文件,gocode和MarGo,这两个文件会在GoSublime加载时自动启动。
+
+ 3. 安装完之后就可以安装Sublime的插件了。需安装GoSublime、SidebarEnhancements和Go Build,安装插件之后记得重启Sublime生效,Ctrl+Shift+p打开Package Controll 输入`pcip`(即“Package Control: Install Package”的缩写)。
+
+ 这个时候看左下角显示正在读取包数据,完成之后出现如下界面
+
+ ![](images/1.4.sublime4.png?raw=true)
+
+ 这个时候输入GoSublime,按确定就开始安装了。同理应用于SidebarEnhancements和Go Build。
+
+ 4. 验证是否安装成功,你可以打开Sublime,打开main.go,看看语法是不是高亮了,输入`import`是不是自动化提示了,`import "fmt"`之后,输入`fmt.`是不是自动化提示有函数了。
+
+ 如果已经出现这个提示,那说明你已经安装完成了,并且完成了自动提示。
+
+ 如果没有出现这样的提示,一般就是你的`$PATH`没有配置正确。你可以打开终端,输入gocode,是不是能够正确运行,如果不行就说明`$PATH`没有配置正确。
+
+
+## Vim
+Vim是从vi发展出来的一个文本编辑器, 代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。
+
+ ![](images/1.4.vim.png?raw=true)
+
+ 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默认安装到`$GOPATH/bin`下面,需要把`$GOPATH/bin`路径设置到系统`$PATH`里面。
+
+ 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的乐趣。
+
+
+## Emacs
+Emacs传说中的神器,她不仅仅是一个编辑器,它是一个整合环境,或可称它为集成开发环境,这些功能如让使用者置身于全功能的操作系统中。
+
+ ![](images/1.4.emacs.png?raw=true)
+
+ 1. 配置Emacs高亮显示
+
+ cp $GOROOT/misc/emacs/* ~/.emacs.d/
+
+ 2. 安装[Gocode](https://github.com/nsf/gocode/)
+
+ go get -u github.com/nsf/gocode
+
+ gocode默认安装到`$GOPATH/bin`里面下面,需要把`$GOPATH/bin`路径设置到系统`$PATH`里面。
+
+ 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程序。
+
+ ![](images/1.4.eclipse1.png?raw=true)
+
+ 1.首先下载并安装好[Eclipse](http://www.eclipse.org/)
+
+ 2.下载[goeclipse](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的编译器
+
+ ![](images/1.4.eclipse2.png?raw=true)
+
+ (2).配置Gocode(可选,代码补全),设置Gocode路径为之前生成的gocode.exe文件
+
+ ![](images/1.4.eclipse3.png?raw=true)
+
+ (3).配置GDB(可选,做调试用),设置GDB路径为MingW安装目录下的gdb.exe文件
+
+ ![](images/1.4.eclipse4.png?raw=true)
+
+ 6.测试是否成功
+
+新建一个go工程,再建立一个hello.go。如下图:
+
+ ![](images/1.4.eclipse5.png?raw=true)
+
+调试如下(要在console中用输入命令来调试):
+
+ ![](images/1.4.eclipse6.png?raw=true)
+
+
+## links
+ * [目录]()
+ * 上一节: [Go 命令](<1.3.md>)
+ * 下一节: [总结](<1.5.md>)
diff --git a/1.5.md b/1.5.md
index 8e0a998feeb7e2f779dd5c1e9d5193b6c79a8206..229a86ae81ba1b330ae7abbb4ab87e6f6306dcc6 100644
--- a/1.5.md
+++ b/1.5.md
@@ -1,11 +1,8 @@
-# 总结
-
-这一章中我们主要介绍了如何安装Go,以及如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后如何创建项目,项目创建之后如何编译、如何安装,接着介绍了一些Go的常用命令工具,最后介绍了Go的开发工具,希望能够通过有利的工具快速的开发Go应用。
-
-## links
- * [目录]()
- * 上一节: [Go开发工具](<1.4.md>)
- * 下一章: [go语言基础](<2.md>)
-
-## LastModified
- * $Id$
+# 总结
+
+这一章中我们主要介绍了如何安装Go,以及如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后如何创建项目,项目创建之后如何编译、如何安装,接着介绍了一些Go的常用命令工具,最后介绍了Go的开发工具,希望能够通过有利的工具快速的开发Go应用。
+
+## links
+ * [目录]()
+ * 上一节: [Go开发工具](<1.4.md>)
+ * 下一章: [go语言基础](<2.md>)
diff --git a/1.md b/1.md
index a3fb7537127a0fdda16ca3322daadeaef9d36a27..d13737f8d3ca13716c2fa7a18f063bfeb6f0fc1c 100644
--- a/1.md
+++ b/1.md
@@ -1,29 +1,26 @@
-# 1 GO环境配置
-
-## 目录
- * 1. [Go安装](1.1.md)
- * 2. [GOPATH与工作空间](1.2.md)
- * 3. [Go命令](1.3.md)
- * 4. [Go开发工具](1.4.md)
- * 5. [小结](1.5.md)
-
-欢迎来到Go的世界,让我们开始吧!
-
-Go是一种新的语言,一种并发的、带垃圾回收的、快速编译的语言。它具有以下特点:
-
-- 它可以在一台计算机上用几秒钟的时间编译一个大型的Go程序。
-- Go为软件构造提供了一种模型,它使依赖分析更加容易,且避免了大部分C风格include文件与库的开头。
-- Go是静态类型的语言,它的类型系统没有层级。因此用户不需要在定义类型之间的关系上花费时间,这样感觉起来比典型的面向对象语言更轻量级。
-- Go完全是垃圾回收型的语言,并为并发执行与通信提供了基本的支持。
-- 按照其设计,Go打算为多核机器上系统软件的构造提供一种方法。
-
-Go试图成为结合解释型编程的轻松、动态类型语言的高效以及静态类型语言的安全的编译型语言。它也打算成为现代的,支持网络与多核计算的语言。要满足这些目标,需要解决一些语言上的问题:一个富有表达能力但轻量级的类型系统,并发与垃圾回收机制,严格的依赖规范等等。这些无法通过库或工具解决好,因此Go也就应运而生了。
-
-在本章中,我们将讲述Go的安装方法,以及如何配置项目信息。
-
-## links
- * [目录]()
- * 下一节: [Go安装](<1.1.md>)
-
-## LastModified
- * $Id$
+# 1 GO环境配置
+
+## 目录
+ * 1. [Go安装](1.1.md)
+ * 2. [GOPATH与工作空间](1.2.md)
+ * 3. [Go命令](1.3.md)
+ * 4. [Go开发工具](1.4.md)
+ * 5. [小结](1.5.md)
+
+欢迎来到Go的世界,让我们开始吧!
+
+Go是一种新的语言,一种并发的、带垃圾回收的、快速编译的语言。它具有以下特点:
+
+- 它可以在一台计算机上用几秒钟的时间编译一个大型的Go程序。
+- Go为软件构造提供了一种模型,它使依赖分析更加容易,且避免了大部分C风格include文件与库的开头。
+- Go是静态类型的语言,它的类型系统没有层级。因此用户不需要在定义类型之间的关系上花费时间,这样感觉起来比典型的面向对象语言更轻量级。
+- Go完全是垃圾回收型的语言,并为并发执行与通信提供了基本的支持。
+- 按照其设计,Go打算为多核机器上系统软件的构造提供一种方法。
+
+Go试图成为结合解释型编程的轻松、动态类型语言的高效以及静态类型语言的安全的编译型语言。它也打算成为现代的,支持网络与多核计算的语言。要满足这些目标,需要解决一些语言上的问题:一个富有表达能力但轻量级的类型系统,并发与垃圾回收机制,严格的依赖规范等等。这些无法通过库或工具解决好,因此Go也就应运而生了。
+
+在本章中,我们将讲述Go的安装方法,以及如何配置项目信息。
+
+## links
+ * [目录]()
+ * 下一节: [Go安装](<1.1.md>)
diff --git a/10.1.md b/10.1.md
index 6adcaf9af4954aca8799329b52ffb42f7468c6ff..0ade1322f995cb21a2793902f3dbd96e168a63cb 100644
--- a/10.1.md
+++ b/10.1.md
@@ -1,87 +1,85 @@
-# 10.1 设置默认地区
-## 什么是Locale
-Locale是一组描述世界上某一特定区域文本格式和语言习惯的设置的集合。locale名通常由三个部分组成:第一部分,是一个强制性的,表示语言的缩写,例如"en"表示英文或"zh"表示中文。第二部分,跟在一个下划线之后,是一个可选的国家说明符,用于区分讲同一种语言的不同国家,例如"en_US"表示美国英语,而"en_UK"表示英国英语。最后一部分,跟在一个句点之后,是可选的字符集说明符,例如"zh_CN.gb2312"表示中国使用gb2312字符集。
-
-GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三部分,接下来我们都采用locale描述的前面两部分来作为i18n标准的locale名。
-
-
->在Linux和Solaris系统中可以通过`locale -a`命令列举所有支持的地区名,读者可以看到这些地区名的命名规范。对于BSD等系统,没有locale命令,但是地区信息存储在/usr/share/locale中。
-
-## 设置Locale
-有了上面对locale的定义,那么我们就需要根据用户的信息(访问信息、个人信息、访问域名等)来设置与之相关的locale,我们可以通过如下几种方式来设置用户的locale。
-
-### 通过域名设置Locale
-设置Locale的办法这一就是在应用运行的时候采用域名分级的方式,例如,我们采用www.asta.com当做我们的英文站(默认站),而把域名www.asta.cn当做中文站。这样通过在应用里面设置域名和相应的locale的对应关系,就可以设置好地区。这样处理有几点好处:
-
-- 通过URL就可以很明显的识别
-- 用户可以通过域名很直观的知道将访问那种语言的站点
-- 在Go程序中实现非常的简单方便,通过一个map就可以实现
-- 有利于搜索引擎抓取,能够提高站点的SEO
-
-我们可以通过下面的代码来实现域名的对应locale:
-
- if r.Host == "www.asta.com" {
- i18n.SetLocale("en")
- } else if r.Host == "www.asta.cn" {
- i18n.SetLocale("zh-CN")
- } else if r.Host == "www.asta.tw" {
- i18n.SetLocale("zh-TW")
- }
-
-当然除了整域名设置地区之外,我们还可以通过子域名来设置地区,例如"en.asta.com"表示英文站点,"cn.asta.com"表示中文站点。实现代码如下所示:
-
- prefix := strings.Split(r.Host,".")
-
- if prefix[0] == "en" {
- i18n.SetLocale("en")
- } else if prefix[0] == "cn" {
- i18n.SetLocale("zh-CN")
- } else if prefix[0] == "tw" {
- i18n.SetLocale("zh-TW")
- }
-
-通过域名设置Locale有如上所示的优点,但是我们一般开发Web应用的时候不会采用这种方式,因为首先域名成本比较高,开发一个Locale就需要一个域名,而且往往统一名称的域名不一定能申请的到,其次我们不愿意为每个站点去本地化一个配置,而更多的是采用url后面带参数的方式,请看下面的介绍。
-
-### 从域名参数设置Locale
+# 10.1 设置默认地区
+## 什么是Locale
+Locale是一组描述世界上某一特定区域文本格式和语言习惯的设置的集合。locale名通常由三个部分组成:第一部分,是一个强制性的,表示语言的缩写,例如"en"表示英文或"zh"表示中文。第二部分,跟在一个下划线之后,是一个可选的国家说明符,用于区分讲同一种语言的不同国家,例如"en_US"表示美国英语,而"en_UK"表示英国英语。最后一部分,跟在一个句点之后,是可选的字符集说明符,例如"zh_CN.gb2312"表示中国使用gb2312字符集。
+
+GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三部分,接下来我们都采用locale描述的前面两部分来作为i18n标准的locale名。
+
+
+>在Linux和Solaris系统中可以通过`locale -a`命令列举所有支持的地区名,读者可以看到这些地区名的命名规范。对于BSD等系统,没有locale命令,但是地区信息存储在/usr/share/locale中。
+
+## 设置Locale
+有了上面对locale的定义,那么我们就需要根据用户的信息(访问信息、个人信息、访问域名等)来设置与之相关的locale,我们可以通过如下几种方式来设置用户的locale。
+
+### 通过域名设置Locale
+设置Locale的办法这一就是在应用运行的时候采用域名分级的方式,例如,我们采用www.asta.com当做我们的英文站(默认站),而把域名www.asta.cn当做中文站。这样通过在应用里面设置域名和相应的locale的对应关系,就可以设置好地区。这样处理有几点好处:
+
+- 通过URL就可以很明显的识别
+- 用户可以通过域名很直观的知道将访问那种语言的站点
+- 在Go程序中实现非常的简单方便,通过一个map就可以实现
+- 有利于搜索引擎抓取,能够提高站点的SEO
+
+我们可以通过下面的代码来实现域名的对应locale:
+
+ if r.Host == "www.asta.com" {
+ i18n.SetLocale("en")
+ } else if r.Host == "www.asta.cn" {
+ i18n.SetLocale("zh-CN")
+ } else if r.Host == "www.asta.tw" {
+ i18n.SetLocale("zh-TW")
+ }
+
+当然除了整域名设置地区之外,我们还可以通过子域名来设置地区,例如"en.asta.com"表示英文站点,"cn.asta.com"表示中文站点。实现代码如下所示:
+
+ prefix := strings.Split(r.Host,".")
+
+ if prefix[0] == "en" {
+ i18n.SetLocale("en")
+ } else if prefix[0] == "cn" {
+ i18n.SetLocale("zh-CN")
+ } else if prefix[0] == "tw" {
+ i18n.SetLocale("zh-TW")
+ }
+
+通过域名设置Locale有如上所示的优点,但是我们一般开发Web应用的时候不会采用这种方式,因为首先域名成本比较高,开发一个Locale就需要一个域名,而且往往统一名称的域名不一定能申请的到,其次我们不愿意为每个站点去本地化一个配置,而更多的是采用url后面带参数的方式,请看下面的介绍。
+
+### 从域名参数设置Locale
目前最常用的设置Locale的方式是在URL里面带上参数,例如www.asta.com/hello?locale=zh或者www.asta.com/zh/hello。这样我们就可以设置地区:`i18n.SetLocale(params["locale"])`。
这种设置方式几乎拥有前面讲的通过域名设置Locale的所有优点,它采用RESTful的方式,以使得我们不需要增加额外的方法来处理。但是这种方式需要在每一个的link里面增加相应的参数locale,这也许有点复杂而且有时候甚至相当的繁琐。不过我们可以写一个通用的函数url,让所有的link地址都通过这个函数来生成,然后在这个函数里面增加`locale=params["locale"]`参数来缓解一下。
也许我们希望URL地址看上去更加的RESTful一点,例如:www.asta.com/en/books(英文站点)和www.asta.com/zh/books(中文站点),这种方式的URL更加有利于SEO,而且对于用户也比较友好,能够通过URL直观的知道访问的站点。那么这样的URL地址可以通过router来获取locale(参考REST小节里面介绍的router插件实现):
- mux.Get("/:locale/books", listbook)
-
+ mux.Get("/:locale/books", listbook)
+
### 从客户端设置地区
-在一些特殊的情况下,我们需要根据客户端的信息而不是通过URL来设置Locale,这些信息可能来自于客户端设置的喜好语言(浏览器中设置),用户的IP地址,用户在注册的时候填写的所在地信息等。这种方式比较适合Web为基础的应用。
-
+在一些特殊的情况下,我们需要根据客户端的信息而不是通过URL来设置Locale,这些信息可能来自于客户端设置的喜好语言(浏览器中设置),用户的IP地址,用户在注册的时候填写的所在地信息等。这种方式比较适合Web为基础的应用。
+
- Accept-Language
- 客户端请求的时候在HTTP头信息里面有`Accept-Language`,一般的客户端都会设置该信息,下面是Go语言实现的一个简单的根据`Accept-Language`实现设置地区的代码:
-
- AL := r.Header.Get("Accept-Language")
- if AL == "en" {
- i18n.SetLocale("en")
- } else if AL == "zh-CN" {
- i18n.SetLocale("zh-CN")
- } else if AL == "zh-TW" {
- i18n.SetLocale("zh-TW")
- }
- 当然在实际应用中,可能需要更加严格的判断来进行设置地区
+客户端请求的时候在HTTP头信息里面有`Accept-Language`,一般的客户端都会设置该信息,下面是Go语言实现的一个简单的根据`Accept-Language`实现设置地区的代码:
+
+ AL := r.Header.Get("Accept-Language")
+ if AL == "en" {
+ i18n.SetLocale("en")
+ } else if AL == "zh-CN" {
+ i18n.SetLocale("zh-CN")
+ } else if AL == "zh-TW" {
+ i18n.SetLocale("zh-TW")
+ }
+
+当然在实际应用中,可能需要更加严格的判断来进行设置地区
- IP地址
- 另一种根据客户端来设定地区就是用户访问的IP,我们根据相应的IP库,对应访问的IP到地区,目前全球比较常用的就是GeoIP Lite Country这个库。这种设置地区的机制非常简单,我们只需要根据IP数据库查询用户的IP然后返回国家地区,根据返回的结果设置对应的地区。
+ 另一种根据客户端来设定地区就是用户访问的IP,我们根据相应的IP库,对应访问的IP到地区,目前全球比较常用的就是GeoIP Lite Country这个库。这种设置地区的机制非常简单,我们只需要根据IP数据库查询用户的IP然后返回国家地区,根据返回的结果设置对应的地区。
- 用户profile
- 当然你也可以让用户根据你提供的下拉菜单或者别的什么方式的设置相应的locale,然后我们将用户输入的信息,保存到与它帐号相关的profile中,当用户再次登陆的时候把这个设置复写到locale设置中,这样就可以保证该用户每次访问都是基于自己先前设置的locale来获得页面。
+ 当然你也可以让用户根据你提供的下拉菜单或者别的什么方式的设置相应的locale,然后我们将用户输入的信息,保存到与它帐号相关的profile中,当用户再次登陆的时候把这个设置复写到locale设置中,这样就可以保证该用户每次访问都是基于自己先前设置的locale来获得页面。
## 总结
通过上面的介绍可知,设置Locale可以有很多种方式,我们应该根据需求的不同来选择不同的设置Locale的方法,以让用户能以它最熟悉的方式,获得我们提供的服务,提高应用的用户友好性。
-
-## links
- * [目录]()
- * 上一节: [国际化和本地化](<10.md>)
- * 下一节: [本地化资源](<10.2.md>)
-
-## LastModified
- * $Id$
+
+## links
+ * [目录]()
+ * 上一节: [国际化和本地化](<10.md>)
+ * 下一节: [本地化资源](<10.2.md>)
diff --git a/10.2.md b/10.2.md
index af6a67f8281c52ce642fb51d9f287cfa3bd90eb0..91375f54e1ce16f8fef5b8704aeb9b99870e086a 100644
--- a/10.2.md
+++ b/10.2.md
@@ -44,7 +44,7 @@
fmt.Printf(msg(lang, "how old"), 30)
上面的示例代码仅用以演示内部的实现方案,而实际数据是存储在JSON里面的,所以我们可以通过`json.Unmarshal`来为相应的map填充数据。
-
+
## 本地化日期和时间
因为时区的关系,同一时刻,在不同的地区,表示是不一样的,而且因为Locale的关系,时间格式也不尽相同,例如中文环境下可能显示:`2012年10月24日 星期三 23时11分13秒 CST`,而在英文环境下可能显示:`Wed Oct 24 23:11:13 CST 2012`。这里面我们需要解决两点:
@@ -55,7 +55,7 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为
en["time_zone"]="America/Chicago"
cn["time_zone"]="Asia/Shanghai"
-
+
loc,_:=time.LoadLocation(msg(lang,"time_zone"))
t:=time.Now()
t = t.In(loc)
@@ -65,9 +65,9 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为
en["date_format"]="%Y-%m-%d %H:%M:%S"
cn["date_format"]="%Y年%m月%d日 %H时%M分%S秒"
-
+
fmt.Println(date(msg(lang,"date_format"),t))
-
+
func date(fomate string,t time.Time) string{
year, month, day = t.Date()
hour, min, sec = t.Clock()
@@ -84,54 +84,51 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为
cn["money"] ="¥%d元"
fmt.Println(date(msg(lang,"date_format"),100))
-
+
func money_format(fomate string,money int64) string{
return fmt.Sprintf(fomate,money)
}
-
+
## 本地化视图和资源
我们可能会根据Locale的不同来展示视图,这些视图包含不同的图片、css、js等各种静态资源。那么应如何来处理这些信息呢?首先我们应按locale来组织文件信息,请看下面的文件目录安排:
views
- |--en //英文模板
- |--images //存储图片信息
- |--js //存储JS文件
- |--css //存储css文件
- index.tpl //用户首页
- login.tpl //登陆首页
- |--zh-CN //中文模板
- |--images
- |--js
- |--css
- index.tpl
- login.tpl
-
+ |--en //英文模板
+ |--images //存储图片信息
+ |--js //存储JS文件
+ |--css //存储css文件
+ index.tpl //用户首页
+ login.tpl //登陆首页
+ |--zh-CN //中文模板
+ |--images
+ |--js
+ |--css
+ index.tpl
+ login.tpl
+
有了这个目录结构后我们就可以在渲染的地方这样来实现代码:
-
+
s1, _ := template.ParseFiles("views"+lang+"index.tpl")
VV.Lang=lang
- s1.Execute(os.Stdout, VV)
-
+ s1.Execute(os.Stdout, VV)
+
而对于里面的index.tpl里面的资源设置如下:
- //js文件
-
- //css文件
-
- //图片文件
-
-
-采用这种方式来本地化视图以及资源时,我们就可以很容易的进行扩展了。
-
+// js文件
+
+// css文件
+
+// 图片文件
+
+
+采用这种方式来本地化视图以及资源时,我们就可以很容易的进行扩展了。
+
## 总结
本小节介绍了如何使用及存储本地资源,有时需要通过转换函数来实现,有时通过lang来设置,但是最终都是通过key-value的方式来存储Locale对应的数据,在需要时取出相应于Locale的信息后,如果是文本信息就直接输出,如果是时间日期或者货币,则需要先通过`fmt.Printf`或其他格式化函数来处理,而对于不同Locale的视图和资源则是最简单的,只要在路径里面增加lang就可以实现了。
-
-## links
- * [目录]()
- * 上一节: [设置默认地区](<10.1.md>)
- * 下一节: [国际化站点](<10.3.md>)
-
-## LastModified
- * $Id$
+
+## links
+ * [目录]()
+ * 上一节: [设置默认地区](<10.1.md>)
+ * 下一节: [国际化站点](<10.3.md>)
diff --git a/10.3.md b/10.3.md
index 416e80d45db0ac7bd8ac781f2c498caf9d935aaa..0d8c8a353698918619a3875a85a9d10ce737c858 100644
--- a/10.3.md
+++ b/10.3.md
@@ -2,30 +2,30 @@
前面小节介绍了如何处理本地化资源,即Locale一个相应的配置文件,那么如果处理多个的本地化资源呢?而对于一些我们经常用到的例如:简单的文本翻译、时间日期、数字等如果处理呢?本小节将一一解决这些问题。
## 管理多个本地包
在开发一个应用的时候,首先我们要决定是只支持一种语言,还是多种语言,如果要支持多种语言,我们则需要制定一个组织结构,以方便将来更多语言的添加。在此我们设计如下:Locale有关的文件放置在config/locales下,假设你要支持中文和英文,那么你需要在这个文件夹下放置en.json和zh.json。大概的内容如下所示:
-
+
# zh.json
{
- "zh": {
- "submit": "提交",
- "create": "创建"
- }
+ "zh": {
+ "submit": "提交",
+ "create": "创建"
+ }
}
#en.json
{
- "en": {
- "submit": "Submit",
- "create": "Create"
- }
+ "en": {
+ "submit": "Submit",
+ "create": "Create"
+ }
}
为了支持国际化,在此我们使用了一个国际化相关的包——go-i18n(https://github.com/astaxie/go-i18n),首先我们向go-i18n包注册config/locales这个目录,以加载所有的locale文件
- Tr:=i18n.NewLocale()
+ Tr:=i18n.NewLocale()
Tr.LoadPath("config/locales")
-
+
这个包使用起来很简单,你可以通过下面的方式进行测试:
fmt.Println(Tr.Translate("submit"))
@@ -41,27 +41,27 @@
//加载默认配置文件,这些文件都放在go-i18n/locales下面
//文件命名zh.json、en-json、en-US.json等,可以不断的扩展支持更多的语言
-
+
func (il *IL) loadDefaultTranslations(dirPath string) error {
dir, err := os.Open(dirPath)
if err != nil {
return err
}
defer dir.Close()
-
+
names, err := dir.Readdirnames(-1)
if err != nil {
return err
}
-
+
for _, name := range names {
fullPath := path.Join(dirPath, name)
-
+
fi, err := os.Stat(fullPath)
if err != nil {
return err
}
-
+
if fi.IsDir() {
if err := il.loadTranslations(fullPath); err != nil {
return err
@@ -72,13 +72,13 @@
return err
}
defer file.Close()
-
+
if err := il.loadTranslation(file, locale); err != nil {
return err
}
}
}
-
+
return nil
}
@@ -100,83 +100,81 @@
1. 文本信息
- 文本信息调用`Tr.Translate`来实现相应的信息转换,mapFunc的实现如下:
+文本信息调用`Tr.Translate`来实现相应的信息转换,mapFunc的实现如下:
- func I18nT(args ...interface{}) string {
- ok := false
- var s string
- if len(args) == 1 {
- s, ok = args[0].(string)
- }
- if !ok {
- s = fmt.Sprint(args...)
- }
- return Tr.Translate(s)
+ func I18nT(args ...interface{}) string {
+ ok := false
+ var s string
+ if len(args) == 1 {
+ s, ok = args[0].(string)
}
+ if !ok {
+ s = fmt.Sprint(args...)
+ }
+ return Tr.Translate(s)
+ }
- 注册函数如下:
+注册函数如下:
- t.Funcs(template.FuncMap{"T": I18nT})
+ t.Funcs(template.FuncMap{"T": I18nT})
- 模板中使用如下:
+模板中使用如下:
+
+ {{.V.Submit | T}}
- {{.V.Submit | T}}
-
2. 时间日期
- 时间日期调用`Tr.Time`函数来实现相应的时间转换,mapFunc的实现如下:
+时间日期调用`Tr.Time`函数来实现相应的时间转换,mapFunc的实现如下:
- func I18nTimeDate(args ...interface{}) string {
- ok := false
- var s string
- if len(args) == 1 {
- s, ok = args[0].(string)
- }
- if !ok {
- s = fmt.Sprint(args...)
- }
- return Tr.Time(s)
+ func I18nTimeDate(args ...interface{}) string {
+ ok := false
+ var s string
+ if len(args) == 1 {
+ s, ok = args[0].(string)
+ }
+ if !ok {
+ s = fmt.Sprint(args...)
}
+ return Tr.Time(s)
+ }
- 注册函数如下:
+注册函数如下:
- t.Funcs(template.FuncMap{"TD": I18nTimeDate})
+ t.Funcs(template.FuncMap{"TD": I18nTimeDate})
- 模板中使用如下:
+模板中使用如下:
+
+ {{.V.Now | TD}}
- {{.V.Now | TD}}
3. 货币信息
- 货币调用`Tr.Money`函数来实现相应的时间转换,mapFunc的实现如下:
+货币调用`Tr.Money`函数来实现相应的时间转换,mapFunc的实现如下:
- func I18nMoney(args ...interface{}) string {
- ok := false
- var s string
- if len(args) == 1 {
- s, ok = args[0].(string)
- }
- if !ok {
- s = fmt.Sprint(args...)
- }
- return Tr.Money(s)
+ func I18nMoney(args ...interface{}) string {
+ ok := false
+ var s string
+ if len(args) == 1 {
+ s, ok = args[0].(string)
}
+ if !ok {
+ s = fmt.Sprint(args...)
+ }
+ return Tr.Money(s)
+ }
+
+注册函数如下:
- 注册函数如下:
+ t.Funcs(template.FuncMap{"M": I18nMoney})
- t.Funcs(template.FuncMap{"M": I18nMoney})
+模板中使用如下:
- 模板中使用如下:
+ {{.V.Money | M}}
- {{.V.Money | M}}
+## 总结
+通过这小节我们知道了如何实现一个多语言包的Web应用,通过自定义语言包我们可以方便的实现多语言,而且通过配置文件能够非常方便的扩充多语言,默认情况下,go-i18n会自定加载一些公共的配置信息,例如时间、货币等,我们就可以非常方便的使用,同时为了支持在模板中使用这些函数,也实现了相应的模板函数,这样就允许我们在开发Web应用的时候直接在模板中通过pipeline的方式来操作多语言包。
-## 总结
-通过这小节我们知道了如何实现一个多语言包的Web应用,通过自定义语言包我们可以方便的实现多语言,而且通过配置文件能够非常方便的扩充多语言,默认情况下,go-i18n会自定加载一些公共的配置信息,例如时间、货币等,我们就可以非常方便的使用,同时为了支持在模板中使用这些函数,也实现了相应的模板函数,这样就允许我们在开发Web应用的时候直接在模板中通过pipeline的方式来操作多语言包。
-
-## links
- * [目录]()
- * 上一节: [本地化资源](<10.2.md>)
- * 下一节: [小结](<10.4.md>)
-
-## LastModified
- * $Id$
+## links
+ * [目录]()
+ * 上一节: [本地化资源](<10.2.md>)
+ * 下一节: [小结](<10.4.md>)
diff --git a/10.4.md b/10.4.md
index 2af42faca4302f936b732db1aeb232f638a2707f..2869ddc636ab03df50106675423fa53d3816cb64 100644
--- a/10.4.md
+++ b/10.4.md
@@ -1,9 +1,6 @@
# 10.4 小结
-通过这一章的介绍,读者应该对如何操作i18n有了深入的了解,我也根据这一章介绍的内容实现了一个开源的解决方案go-i18n:https://github.com/astaxie/go-i18n 通过这个开源库我们可以很方便的实现多语言版本的Web应用,使得我们的应用能够轻松的实现国际化。如果你发现这个开源库中的错误或者那些缺失的地方,请一起参与到这个开源项目中来,让我们的这个库争取成为Go的标准库。
-## links
- * [目录]()
- * 上一节: [国际化站点](<10.3.md>)
- * 下一节: [错误处理,故障排除和测试](<11.md>)
-
-## LastModified
- * $Id$
+通过这一章的介绍,读者应该对如何操作i18n有了深入的了解,我也根据这一章介绍的内容实现了一个开源的解决方案go-i18n:https://github.com/astaxie/go-i18n 通过这个开源库我们可以很方便的实现多语言版本的Web应用,使得我们的应用能够轻松的实现国际化。如果你发现这个开源库中的错误或者那些缺失的地方,请一起参与到这个开源项目中来,让我们的这个库争取成为Go的标准库。
+## links
+ * [目录]()
+ * 上一节: [国际化站点](<10.3.md>)
+ * 下一节: [错误处理,故障排除和测试](<11.md>)
diff --git a/10.md b/10.md
index 93ec1c88e921156346e6af8f655d4c1aadb1e05b..9c4c398e7ff883a587298a161c01be1961f298c0 100644
--- a/10.md
+++ b/10.md
@@ -1,30 +1,27 @@
-# 10 国际化和本地化
-为了适应经济的全球一体化,作为开发者,我们需要开发出支持多国语言、国际化的Web应用,即同样的页面在不同的语言环境下需要显示不同的效果,也就是说应用程序在运行时能够根据请求所来自的地域与语言的不同而显示不同的用户界面。这样,当需要在应用程序中添加对新的语言的支持时,无需修改应用程序的代码,只需要增加语言包即可实现。
-
-国际化与本地化(Internationalization and localization,通常用i18n和L10N表示),国际化是将针对某个地区设计的程序进行重构,以使它能够在更多地区使用,本地化是指在一个面向国际化的程序中增加对新地区的支持。
-
-目前,Go语言的标准包没有提供对i18n的支持,但有一些比较简单的第三方实现,这一章我们将实现一个go-i18n库,用来支持Go语言的i18n。
-
-所谓的国际化:就是根据特定的locale信息,提取与之相应的字符串或其它一些东西(比如时间和货币的格式)等等。这涉及到三个问题:
-
-1、如何确定locale。
-
-2、如何保存与locale相关的字符串或其它信息。
-
-3、如何根据locale提取字符串和其它相应的信息。
-
-在第一小节里,我们将介绍如何设置正确的locale以便让访问站点的用户能够获得与其语言相应的页面。第二小节将介绍如何处理或存储字符串、货币、时间日期等与locale相关的信息,第三小节将介绍如何实现国际化站点,即如何根据不同locale返回不同合适的内容。通过这三个小节的学习,我们将获得一个完整的i18n方案。
-
-## 目录
- * 1 [设置默认地区](10.1.md)
- * 2 [本地化资源](10.2.md)
- * 3 [国际化站点](10.3.md)
- * 4 [小结](10.4.md)
-
-## links
- * [目录]()
- * 上一章: [第九章总结](<9.7.md>)
- * 下一节: [设置默认地区](<10.1.md>)
-
-## LastModified
- * $Id$
+# 10 国际化和本地化
+为了适应经济的全球一体化,作为开发者,我们需要开发出支持多国语言、国际化的Web应用,即同样的页面在不同的语言环境下需要显示不同的效果,也就是说应用程序在运行时能够根据请求所来自的地域与语言的不同而显示不同的用户界面。这样,当需要在应用程序中添加对新的语言的支持时,无需修改应用程序的代码,只需要增加语言包即可实现。
+
+国际化与本地化(Internationalization and localization,通常用i18n和L10N表示),国际化是将针对某个地区设计的程序进行重构,以使它能够在更多地区使用,本地化是指在一个面向国际化的程序中增加对新地区的支持。
+
+目前,Go语言的标准包没有提供对i18n的支持,但有一些比较简单的第三方实现,这一章我们将实现一个go-i18n库,用来支持Go语言的i18n。
+
+所谓的国际化:就是根据特定的locale信息,提取与之相应的字符串或其它一些东西(比如时间和货币的格式)等等。这涉及到三个问题:
+
+1、如何确定locale。
+
+2、如何保存与locale相关的字符串或其它信息。
+
+3、如何根据locale提取字符串和其它相应的信息。
+
+在第一小节里,我们将介绍如何设置正确的locale以便让访问站点的用户能够获得与其语言相应的页面。第二小节将介绍如何处理或存储字符串、货币、时间日期等与locale相关的信息,第三小节将介绍如何实现国际化站点,即如何根据不同locale返回不同合适的内容。通过这三个小节的学习,我们将获得一个完整的i18n方案。
+
+## 目录
+ * 1 [设置默认地区](10.1.md)
+ * 2 [本地化资源](10.2.md)
+ * 3 [国际化站点](10.3.md)
+ * 4 [小结](10.4.md)
+
+## links
+ * [目录]()
+ * 上一章: [第九章总结](<9.7.md>)
+ * 下一节: [设置默认地区](<10.1.md>)
diff --git a/11.1.md b/11.1.md
index b71cbc5bce3865cb68f5642e6e9d1541d551958a..0a9e98b3759e6bd16d8dcefb6c49da1849df8617 100644
--- a/11.1.md
+++ b/11.1.md
@@ -6,9 +6,9 @@ Go语言设计的时候主要的特点是:简洁、明白,简洁是指语法
下面这个例子通过`os.Open`打开一个文件,如果出错那么会执行`log.Fatal`打印出来错误信息:
f, err := os.Open("filename.ext")
- if err != nil {
- log.Fatal(err)
- }
+ if err != nil {
+ log.Fatal(err)
+ }
其实这样的error返回在Go语言的很多内置包里面有很多,我们这个小节将详细的介绍这些error是怎么设计的,以及在我们设计的Web应用如何更好的处理error。
## Error类型
@@ -35,14 +35,14 @@ error是一个内置的类型变量,我们可以在/builtin/包下面找到相
return &errorString{text}
}
-下面这个例子演示了如何使用`errors.New`:
+下面这个例子演示了如何使用`errors.New`:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// implementation
- }
+ }
## 自定义Error
@@ -53,4 +53,4 @@ error是一个内置的类型变量,我们可以在/builtin/包下面找到相
## links
* [目录]()
* 上一节: [错误处理,调试和测试](<11.md>)
- * 下一节: [使用GDB调试](<11.2.md>)
\ No newline at end of file
+ * 下一节: [使用GDB调试](<11.2.md>)
diff --git a/11.md b/11.md
index 79f911c3723856ceb87d8f49b24a4d6d83970b54..66f0ac09a3829bab4585ccd1518b9f9ceccddf9c 100644
--- a/11.md
+++ b/11.md
@@ -9,13 +9,13 @@
长期以来,培养良好的调试、测试习惯一直是很多程序员逃避的事情,所以现在你不要再逃避了,就从你现在的项目开发,从学习Go Web开发开始养成良好的习惯。
-## 目录
- * 1 [错误处理](11.1.md)
- * 2 [使用GDB调试](11.2.md)
- * 3 [Go怎么写测试用例](11.3.md)
- * 4 [小结](11.4.md)
-
-## links
- * [目录]()
- * 上一章: [第十章总结](<10.4.md>)
+## 目录
+ * 1 [错误处理](11.1.md)
+ * 2 [使用GDB调试](11.2.md)
+ * 3 [Go怎么写测试用例](11.3.md)
+ * 4 [小结](11.4.md)
+
+## links
+ * [目录]()
+ * 上一章: [第十章总结](<10.4.md>)
* 下一节: [错误处理](<11.1.md>)
\ No newline at end of file
diff --git a/2.1.md b/2.1.md
index 6b079ce521d758d135c44b5527273ea09cf153df..a503142867df99875d79cab42ab84525c4c4d22c 100644
--- a/2.1.md
+++ b/2.1.md
@@ -1,55 +1,52 @@
-# 2.1 你好,Go
-
-在开始编写应用之前,我们先从最基本的程序开始。就像你造房子之前不知道什么是地基一样,编写程序也不知道如何开始。因此,在本节中,我们要学习用最基本的语法让Go程序运行起来。
-
-## 程序
-
-这就像一个传统,在学习大部分语言之前,你先学会如何编写一个可以输出`hello world`的程序。
-
-准备好了吗?Let's Go!
-
- package main
-
- import "fmt"
-
- func main() {
- fmt.Printf("Hello, world or καλημ ́ρα κóσμ or こんにちは世界\n")
- }
-
-输出如下:
-
- Hello, world 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中的module相同,它们都有一些特别的好处:模块化(能够把你的程序分成多个模块)和可重用性(每个模块都能被其它应用程序反复使用)。我们在这里只是先了解一下包的概念,后面我们将会编写自己的包。
-
-在第五行中,我们通过关键字`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的发明者),所以它天生就具有多语言的支持。
-
-## links
- * [目录]()
- * 上一节: [Go语言基础](<2.md>)
- * 下一节: [Go基础](<2.2.md>)
-
-## LastModified
- * $Id$
+# 2.1 你好,Go
+
+在开始编写应用之前,我们先从最基本的程序开始。就像你造房子之前不知道什么是地基一样,编写程序也不知道如何开始。因此,在本节中,我们要学习用最基本的语法让Go程序运行起来。
+
+## 程序
+
+这就像一个传统,在学习大部分语言之前,你先学会如何编写一个可以输出`hello world`的程序。
+
+准备好了吗?Let's Go!
+
+ package main
+
+ import "fmt"
+
+ func main() {
+ fmt.Printf("Hello, world or καλημ ́ρα κóσμ or こんにちは世界\n")
+ }
+
+输出如下:
+
+ Hello, world 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中的module相同,它们都有一些特别的好处:模块化(能够把你的程序分成多个模块)和可重用性(每个模块都能被其它应用程序反复使用)。我们在这里只是先了解一下包的概念,后面我们将会编写自己的包。
+
+在第五行中,我们通过关键字`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的发明者),所以它天生就具有多语言的支持。
+
+## links
+ * [目录]()
+ * 上一节: [Go语言基础](<2.md>)
+ * 下一节: [Go基础](<2.2.md>)
diff --git a/2.2.md b/2.2.md
index 69f092976890be751682eed135e6b0e3fa7eef3b..80bab5ad13d14a429f90b95fb834dea06bcb0512 100644
--- a/2.2.md
+++ b/2.2.md
@@ -1,453 +1,450 @@
-# 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_"
-
-
-## 内置基础类型
-
-### 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 := "Ohaiou" // 同上
- 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)的文章,大家可以看到这些基础类型底层都是分配了一块内存,然后存储了相应的值。
-
-![](images/2.2.basic.png?raw=true)
-
-## 一些技巧
-
-### 分组声明
-
-在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
-
-### 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}}
-
-数组的分配如下所示:
-
-![](images/2.2.array.png?raw=true)
-
-
-### 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`时,方括号内没有任何字符。
-
-它们的数据结构如下所示
-
-![](images/2.2.slice.png?raw=true)
-
-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:9] aSlice包含元素: f,g,h,i,j
- aSlice = array[:] // 等价于aSlice = array[0:9] 这样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]
-
-上面代码的真正存储结构如下图所示
-
-![](images/2.2.slice2.png?raw=true)
-
-对于`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`的`key`只能是`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`的初始化可以通过`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`之间的区别。
-
-![](images/2.2.makenew.png?raw=true)
-
-关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为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 ""
-
-
-
-
-## links
- * [目录]()
- * 上一章: [你好,Go](<2.1.md>)
- * 下一节: [流程和函数](<2.3.md>)
-
-## LastModified
- * $Id$
+# 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_"
+
+
+## 内置基础类型
+
+### 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 := "Ohaiou" // 同上
+ 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)的文章,大家可以看到这些基础类型底层都是分配了一块内存,然后存储了相应的值。
+
+![](images/2.2.basic.png?raw=true)
+
+## 一些技巧
+
+### 分组声明
+
+在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
+
+### 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}}
+
+数组的分配如下所示:
+
+![](images/2.2.array.png?raw=true)
+
+
+### 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`时,方括号内没有任何字符。
+
+它们的数据结构如下所示
+
+![](images/2.2.slice.png?raw=true)
+
+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:9] aSlice包含元素: f,g,h,i,j
+ aSlice = array[:] // 等价于aSlice = array[0:9] 这样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]
+
+上面代码的真正存储结构如下图所示
+
+![](images/2.2.slice2.png?raw=true)
+
+对于`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`的`key`只能是`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`的初始化可以通过`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`之间的区别。
+
+![](images/2.2.makenew.png?raw=true)
+
+关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为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 ""
+
+
+
+
+## links
+ * [目录]()
+ * 上一章: [你好,Go](<2.1.md>)
+ * 下一节: [流程和函数](<2.3.md>)
diff --git a/2.3.md b/2.3.md
index 60bef660bf8db27f8cddfa6ce3efcbc0550ced28..11dc24d7c68c9fde9e41b2f06a154f4e244aa2ac 100644
--- a/2.3.md
+++ b/2.3.md
@@ -1,465 +1,462 @@
-# 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里面有一个不错的设计,就是回调函数,有点类似面向对象语言里面的析构函数,当函数执行完之后再执行。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的:
-
- 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`函数。下图详细地解释了整个执行过程:
-
-![](images/2.3.init.png?raw=true)
-
-
-## links
- * [目录]()
- * 上一章: [Go基础](<2.2.md>)
- * 下一节: [struct类型](<2.4.md>)
-
-## LastModified
- * $Id$
+# 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里面有一个不错的设计,就是回调函数,有点类似面向对象语言里面的析构函数,当函数执行完之后再执行。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的:
+
+ 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`函数。下图详细地解释了整个执行过程:
+
+![](images/2.3.init.png?raw=true)
+
+
+## links
+ * [目录]()
+ * 上一章: [Go基础](<2.2.md>)
+ * 下一节: [struct类型](<2.4.md>)
diff --git a/2.4.md b/2.4.md
index be985cb0dd80ad6a971780fca4034e527dba494c..dce9ab1b5c1cc117f5e68c8e1903f8287c9d93a8 100644
--- a/2.4.md
+++ b/2.4.md
@@ -1,210 +1,207 @@
-# 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"}
-
-下面我们看一个完整的使用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)
- }
-
-图例如下:
-
-![](images/2.4.student_struct.png?raw=true)
-
-我们看到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)
- }
-
-
-## links
- * [目录]()
- * 上一章: [流程和函数](<2.3.md>)
- * 下一节: [面向对象](<2.5.md>)
-
-## LastModified
- * $Id$
+# 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"}
+
+下面我们看一个完整的使用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)
+ }
+
+图例如下:
+
+![](images/2.4.student_struct.png?raw=true)
+
+我们看到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)
+ }
+
+
+## links
+ * [目录]()
+ * 上一章: [流程和函数](<2.3.md>)
+ * 下一节: [面向对象](<2.5.md>)
diff --git a/2.5.md b/2.5.md
index c81cc4e9000fd236b225093ce696cd6066077dd2..00b1eafca62c3c627bdc04eabc028f5b1d509d9e 100644
--- a/2.5.md
+++ b/2.5.md
@@ -34,7 +34,7 @@
基于上面的原因所以就有了`method`的概念,`method`是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在`func`后面增加了一个receiver(也就是method所依从的主体)。
-用上面提到的形状的例子来说,method `area()` 是依赖于某个形状(比如说Rectangle)来发生作用的。Rectangle.area()的发出者是Rectangle, area()是属于Rectangle的方法,而非一个外围函数。
+用上面提到的形状的例子来说,method `area()` 是依赖于某个形状(比如说Rectangle)来发生作用的。Rectangle.area()的发出者是Rectangle, area()是属于Rectangle的方法,而非一个外围函数。
更具体地说,Rectangle存在字段length 和 width, 同时存在方法area(), 这些字段和方法都属于Rectangle。
@@ -311,7 +311,7 @@ method的语法如下:
mark.SayHi()
sam.SayHi()
}
-
+
上面的代码设计的是如此的美妙,让人不自觉的为Go的设计惊叹!
通过这些内容,我们可以设计出基本的面向对象的程序了,但是Go里面的面向对象是如此的简单,没有任何的私有、共有关键字,通过大小写来实现(大写开头的为共有,小写开头的为私有),方法也同样适用这个原则。
@@ -319,6 +319,3 @@ method的语法如下:
* [目录]()
* 上一章: [struct类型](<2.4.md>)
* 下一节: [interface](<2.6.md>)
-
-## LastModified
- * $Id$
diff --git a/2.6.md b/2.6.md
index 8c3c746569c532678c02c5da38488d74b77c1fe8..b284c0b67ce3383872248269a5681f95c5e9bd53 100644
--- a/2.6.md
+++ b/2.6.md
@@ -16,73 +16,73 @@ Go语言里面设计最精妙的应该算interface,它让面向对象,内容
interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子
type Human struct {
- name string
- age int
- phone string
+ name string
+ age int
+ phone string
}
-
+
type Student struct {
- Human //匿名字段Human
- school string
- loan float32
+ Human //匿名字段Human
+ school string
+ loan float32
}
-
+
type Employee struct {
- Human //匿名字段Human
- company string
- money float32
+ 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)
+ 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)
+ 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)
+ 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) //Yes you can split into 2 lines here.
+ 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.
}
-
+
//Student实现BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
- s.loan += amount // (again and again and...)
+ 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!
+ e.money -= amount // More vodka please!!! Get me through the day!
}
-
+
// 定义interface
type Men interface {
- SayHi()
- Sing(lyrics string)
- Guzzle(beerStein string)
+ SayHi()
+ Sing(lyrics string)
+ Guzzle(beerStein string)
}
-
+
type YoungChap interface {
- SayHi()
- Sing(song string)
- BorrowMoney(amount float32)
+ SayHi()
+ Sing(song string)
+ BorrowMoney(amount float32)
}
-
+
type ElderlyGent interface {
- SayHi()
- Sing(song string)
- SpendSalary(amount float32)
+ SayHi()
+ Sing(song string)
+ SpendSalary(amount float32)
}
-
+
通过上面的代码我们可以知道,interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理,一个对象可以实现任意多个interface,例如上面的Student实现了Men和YonggChap两个interface。
最后,任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface。
@@ -96,78 +96,78 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
package main
import "fmt"
-
+
type Human struct {
- name string
- age int
- phone string
+ name string
+ age int
+ phone string
}
-
+
type Student struct {
- Human //匿名字段
- school string
- loan float32
+ Human //匿名字段
+ school string
+ loan float32
}
-
+
type Employee struct {
- Human //匿名字段
- company string
- money float32
+ 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)
+ 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)
+ 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) //Yes you can split into 2 lines here.
+ 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.
}
-
+
// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
- SayHi()
- Sing(lyrics string)
+ 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)
- //T这三个都是不同类型的元素,但是他们实现了interface同一个接口
- x[0], x[1], x[2] = paul, sam, mike
-
- for _, value := range x{
- value.SayHi()
- }
+ 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)
+ //T这三个都是不同类型的元素,但是他们实现了interface同一个接口
+ x[0], x[1], x[2] = paul, sam, mike
+
+ for _, value := range x{
+ value.SayHi()
+ }
}
通过上面的代码,你会发现interface就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, go 通过interface实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。
@@ -182,41 +182,41 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
// a可以存储任意类型的数值
a = i
a = s
-
-一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊!
+
+一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊!
### interface函数参数
interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数。
举个例子:我们已经知道fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:
type Stringer interface {
- String() string
+ String() string
}
任何实现了String方法的类型都能作为参数去调用fmt.Println,让我们来试一试
package main
import (
- "fmt"
- "strconv"
+ "fmt"
+ "strconv"
)
-
+
type Human struct {
name string
- age int
- phone string
+ age int
+ phone string
}
-
+
// 通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {
- return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱"
+ 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)
+ 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())
@@ -229,98 +229,98 @@ 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)"
- }
+ package main
- 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)
- }
- }
+ 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)
+ }
}
-
- 是不是很简单啊,同时你是否注意到了多个ifs里面,还记得我前面介绍流程里面讲过,if里面允许初始化变量。
-
- 也许你注意到了,我们断言的类型越多,那么ifelse也就越多,所以才引出了下面要介绍的switch。
+ }
+
+是不是很简单啊,同时你是否注意到了多个ifs里面,还记得我前面介绍流程里面讲过,if里面允许初始化变量。
+
+也许你注意到了,我们断言的类型越多,那么ifelse也就越多,所以才引出了下面要介绍的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)"
- }
+ package main
+
+ import (
+ "fmt"
+ "strconv"
+ )
- 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)
- }
- }
+ 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`。
+ }
+
+ 这里有一点需要强调的是:`element.(type)`语法不能在switch外的任何逻辑里面使用,如果你要在switch外面判断一个类型就使用`comma-ok`。
### 嵌入interface
Go里面真正吸引人的是他内置的逻辑语法,就像我们在学习Struct时学习的匿名字段,多么的优雅啊,那么相同的逻辑引入到interface里面,那不是更加完美了。如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。
@@ -328,21 +328,21 @@ Go里面真正吸引人的是他内置的逻辑语法,就像我们在学习Str
我们可以看到源码包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
+ 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)
+ // 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。
@@ -350,9 +350,9 @@ Go里面真正吸引人的是他内置的逻辑语法,就像我们在学习Str
// io.ReadWriter
type ReadWriter interface {
Reader
- Writer
- }
-
+ Writer
+ }
+
### 反射
Go语言实现了反射,所谓反射就是动态运行时的状态。我们一般用到的包是reflect包。如何运用reflect包,官方的这篇文章详细的讲解了reflect包的实现原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html)
@@ -365,34 +365,31 @@ Go语言实现了反射,所谓反射就是动态运行时的状态。我们一
tag := t.Elem().Field(0).Tag //获取定义在strcut里面的标签
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())
-
+ 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)
-
+ v := reflect.ValueOf(x)
+ v.SetFloat(7.1)
+
如果要修改相应的值,必须这样写
var x float64 = 3.4
- p := reflect.ValueOf(&x)
- v := p.Elem()
- v.SetFloat(7.1)
-
+ p := reflect.ValueOf(&x)
+ v := p.Elem()
+ v.SetFloat(7.1)
+
使用反射需要自己在编程中不断的深入去了解,我这边只能大概的介绍一些。
## links
* [目录]()
* 上一章: [面向对象](<2.5.md>)
* 下一节: [并发](<2.7.md>)
-
-## LastModified
- * $Id$
diff --git a/2.7.md b/2.7.md
index 088edbdfa372930c1f5869ad6890776f23262274..ea296df1ea3079f483da6c9dcdc7b770a4f3e8f5 100644
--- a/2.7.md
+++ b/2.7.md
@@ -115,19 +115,19 @@ channel通过操作符`<-`来接收和发送数据
)
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)
+ 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)
+ fmt.Println(i)
}
}
@@ -149,13 +149,13 @@ channel通过操作符`<-`来接收和发送数据
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
- select {
- case c <- x:
- x, y = y, x + y
- case <-quit:
+ select {
+ case c <- x:
+ x, y = y, x + y
+ case <-quit:
fmt.Println("quit")
- return
- }
+ return
+ }
}
}
@@ -187,6 +187,3 @@ channel通过操作符`<-`来接收和发送数据
* [目录]()
* 上一章: [interface](<2.6.md>)
* 下一节: [总结](<2.8.md>)
-
-## LastModified
- * $Id$
diff --git a/2.8.md b/2.8.md
index 695689f9ea047803a0bd33c000af015d1de4e628..770b8b8658d16d9477a7b9cd0cda129fa9450c4f 100644
--- a/2.8.md
+++ b/2.8.md
@@ -1,34 +1,31 @@
-# 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你也已经差不多学会了。
-
-## links
- * [目录]()
- * 上一节: [并发](<2.7.md>)
- * 下一章: [Web基础](<3.md>)
-
-## LastModified
- * $Id$
+# 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你也已经差不多学会了。
+
+## links
+ * [目录]()
+ * 上一节: [并发](<2.7.md>)
+ * 下一章: [Web基础](<3.md>)
diff --git a/2.md b/2.md
index c6e28b1eb40657594bee12c91947b3248b07ac62..8ba57fd87684713849ae08fe5bfc11ae1be5be0e 100644
--- a/2.md
+++ b/2.md
@@ -1,30 +1,27 @@
-# 2 Go语言基础
-
-## 目录
- * 1. [你好,Go](2.1.md)
- * 2. [Go基础](2.2.md)
- * 3. [流程和函数](2.3.md)
- * 4. [struct类型](2.4.md)
- * 5. [面向对象](2.5.md)
- * 6. [interface](2.6.md)
- * 7. [并发](2.7.md)
- * 8. [小结](2.8.md)
-
-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将会是一件愉快的事情。等回过头来,你就会发现这二十五个关键字是多么地亲切。
-
-
-## links
- * [目录]()
- * 上一章: [第一章总结](<1.5.md>)
- * 下一节: [你好,Go](<2.1.md>)
-
-## LastModified
- * $Id$
+# 2 Go语言基础
+
+## 目录
+ * 1. [你好,Go](2.1.md)
+ * 2. [Go基础](2.2.md)
+ * 3. [流程和函数](2.3.md)
+ * 4. [struct类型](2.4.md)
+ * 5. [面向对象](2.5.md)
+ * 6. [interface](2.6.md)
+ * 7. [并发](2.7.md)
+ * 8. [小结](2.8.md)
+
+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将会是一件愉快的事情。等回过头来,你就会发现这二十五个关键字是多么地亲切。
+
+
+## links
+ * [目录]()
+ * 上一章: [第一章总结](<1.5.md>)
+ * 下一节: [你好,Go](<2.1.md>)
diff --git a/3.1.md b/3.1.md
index 8998288bbfd925d6081c99905476a3069ff827ab..767504c97a1fbbf0dc2f3f92536e752df1100abf 100644
--- a/3.1.md
+++ b/3.1.md
@@ -1,149 +1,146 @@
-# 3.1 Web工作方式
-
-我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容。在这个看似简单的用户行为背后,到底隐藏了些什么呢?
-
-对于普通的上网过程,系统其实是这样做的:浏览器本身是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回HTTP Response(响应)包;客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。
-
-![](images/3.1.web2.png?raw=true)
-
- 一个Web服务器也被称为HTTP服务器,它通过HTTP协议与客户端通信。这个客户端通常指的是Web浏览器(其实手机端客户端内部也是浏览器实现的)。
-
-Web服务器的工作原理可以简单地归纳为:
-
-- 客户机通过TCP/IP协议建立到服务器的TCP连接
-- 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档
-- 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端
-- 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果
-
-一个简单的HTTP事务就是这样实现的,看起来很复杂,原理其实是挺简单的。需要注意的是客户机与服务器之间的通信是非持久连接的,也就是当服务器发送了应答后就与客户机断开连接,等待下一次请求。
-
-## URL和DNS解析
-我们浏览网页都是通过URL访问的,那么URL到底是怎么样的呢?
-
-URL(Uniform Resource Locator)是“统一资源定位符”的英文缩写,用于描述一个网络上的资源, 基本格式如下
-
- schema://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就是这样的一位“翻译官”,它的基本工作原理可用下图来表示。
-
-![](images/3.1.dns_hierachy.png?raw=true)
-
-更详细的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服务器再返回给客户机。
-
-![](images/3.1.dns_inquery.png?raw=true)
-
-> 所谓 `递归查询过程` 就是 “查询的递交者” 更替, 而 `迭代查询过程` 则是 “查询的递交者”不变。
->
-> 举个例子来说,你想知道某个一起上法律课的女孩的电话,并且你偷偷拍了她的照片,回到寝室告诉一个很仗义的哥们儿,这个哥们儿二话没说,拍着胸脯告诉你,甭急,我替你查(此处完成了一次递归查询,即,问询者的角色更替)。然后他拿着照片问了学院大四学长,学长告诉他,这姑娘是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抓包可以看到如下请求信息
-
-![](images/3.1.http.png?raw=true)
-
-![](images/3.1.httpPOST.png?raw=true)
-
-**我们可以看到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资源合并在一起,目的是尽量减少网页请求静态资源的次数,提高网页加载速度,同时减缓服务器的压力。
-
-## links
- * [目录]()
- * 上一节: [Web基础](<3.md>)
- * 下一节: [GO搭建一个web服务器](<3.2.md>)
-
-## LastModified
- * $Id$
+# 3.1 Web工作方式
+
+我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容。在这个看似简单的用户行为背后,到底隐藏了些什么呢?
+
+对于普通的上网过程,系统其实是这样做的:浏览器本身是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回HTTP Response(响应)包;客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。
+
+![](images/3.1.web2.png?raw=true)
+
+ 一个Web服务器也被称为HTTP服务器,它通过HTTP协议与客户端通信。这个客户端通常指的是Web浏览器(其实手机端客户端内部也是浏览器实现的)。
+
+Web服务器的工作原理可以简单地归纳为:
+
+- 客户机通过TCP/IP协议建立到服务器的TCP连接
+- 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档
+- 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端
+- 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果
+
+一个简单的HTTP事务就是这样实现的,看起来很复杂,原理其实是挺简单的。需要注意的是客户机与服务器之间的通信是非持久连接的,也就是当服务器发送了应答后就与客户机断开连接,等待下一次请求。
+
+## URL和DNS解析
+我们浏览网页都是通过URL访问的,那么URL到底是怎么样的呢?
+
+URL(Uniform Resource Locator)是“统一资源定位符”的英文缩写,用于描述一个网络上的资源, 基本格式如下
+
+ schema://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就是这样的一位“翻译官”,它的基本工作原理可用下图来表示。
+
+![](images/3.1.dns_hierachy.png?raw=true)
+
+更详细的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服务器再返回给客户机。
+
+![](images/3.1.dns_inquery.png?raw=true)
+
+> 所谓 `递归查询过程` 就是 “查询的递交者” 更替, 而 `迭代查询过程` 则是 “查询的递交者”不变。
+>
+> 举个例子来说,你想知道某个一起上法律课的女孩的电话,并且你偷偷拍了她的照片,回到寝室告诉一个很仗义的哥们儿,这个哥们儿二话没说,拍着胸脯告诉你,甭急,我替你查(此处完成了一次递归查询,即,问询者的角色更替)。然后他拿着照片问了学院大四学长,学长告诉他,这姑娘是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抓包可以看到如下请求信息
+
+![](images/3.1.http.png?raw=true)
+
+![](images/3.1.httpPOST.png?raw=true)
+
+**我们可以看到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资源合并在一起,目的是尽量减少网页请求静态资源的次数,提高网页加载速度,同时减缓服务器的压力。
+
+## links
+ * [目录]()
+ * 上一节: [Web基础](<3.md>)
+ * 下一节: [GO搭建一个web服务器](<3.2.md>)
diff --git a/3.2.md b/3.2.md
index 9f0f6ea329ea98e8ca3502c627498b7ff17fc6d0..dba698642f92080aabaf11db452b6336ba6e7f61 100644
--- a/3.2.md
+++ b/3.2.md
@@ -62,6 +62,3 @@
* [目录]()
* 上一节: [Web工作方式](<3.1.md>)
* 下一节: [Go如何使得web工作](<3.3.md>)
-
-## LastModified
- * $Id$
diff --git a/3.3.md b/3.3.md
index 1cb06a15ec38407a23eea456f60a2a2f820a3e28..4e78daf0f20f77ebd528bc150138f8d8d58be0a5 100644
--- a/3.3.md
+++ b/3.3.md
@@ -48,6 +48,3 @@ Handler:处理请求和生成返回信息的处理逻辑
* [目录]()
* 上一节: [GO搭建一个简单的web服务](<3.2.md>)
* 下一节: [Go的http包详解](<3.4.md>)
-
-## LastModified
- * $Id$
diff --git a/3.4.md b/3.4.md
index 24fa7da88d577953ca7b89b46b213cc997d8718e..6c11e96262545d3ede64e453cada57fa964ee607 100644
--- a/3.4.md
+++ b/3.4.md
@@ -1,160 +1,157 @@
-# 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的时候可以读取到相应的header信息,这样保证了每个请求的独立性。
-
-## ServeMux的自定义
-我们前面小节讲述conn.server的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?
-
-它的结构如下:
-
- type ServeMux struct {
- mu sync.RWMutex //锁,由于请求设计到并发处理,因此这里需要一个锁机制
- m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
- }
-
-下面看一下muxEntry
-
- type muxEntry struct {
- explicit bool // 是否精确匹配
- h Handler // 这个路由表达式对应哪个handler
- }
-
-下面看一下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)
- }
-
-路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?
-
-路由器接收到请求之后调用`mux.handler(r).ServeHTTP(w, r)`
-
-也就是调用对应路由的handler的ServerHTTP接口,那么mux.handler(r)怎么处理的呢?
-
- func (mux *ServeMux) handler(r *Request) Handler {
- mux.mu.RLock()
- defer mux.mu.RUnlock()
-
- // Host-specific pattern takes precedence over generic ones
- h := mux.match(r.Host + r.URL.Path)
- if h == nil {
- h = mux.match(r.URL.Path)
- }
- if h == nil {
- h = NotFoundHandler()
- }
- return h
- }
-
-原来他是根据用户请求的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
-
-## links
- * [目录]()
- * 上一节: [Go如何使得web工作](<3.3.md>)
- * 下一节: [小结](<3.5.md>)
-
-## LastModified
- * $Id$
+# 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的时候可以读取到相应的header信息,这样保证了每个请求的独立性。
+
+## ServeMux的自定义
+我们前面小节讲述conn.server的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?
+
+它的结构如下:
+
+ type ServeMux struct {
+ mu sync.RWMutex //锁,由于请求设计到并发处理,因此这里需要一个锁机制
+ m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
+ }
+
+下面看一下muxEntry
+
+ type muxEntry struct {
+ explicit bool // 是否精确匹配
+ h Handler // 这个路由表达式对应哪个handler
+ }
+
+下面看一下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)
+ }
+
+路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?
+
+路由器接收到请求之后调用`mux.handler(r).ServeHTTP(w, r)`
+
+也就是调用对应路由的handler的ServerHTTP接口,那么mux.handler(r)怎么处理的呢?
+
+ func (mux *ServeMux) handler(r *Request) Handler {
+ mux.mu.RLock()
+ defer mux.mu.RUnlock()
+
+ // Host-specific pattern takes precedence over generic ones
+ h := mux.match(r.Host + r.URL.Path)
+ if h == nil {
+ h = mux.match(r.URL.Path)
+ }
+ if h == nil {
+ h = NotFoundHandler()
+ }
+ return h
+ }
+
+原来他是根据用户请求的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
+
+## links
+ * [目录]()
+ * 上一节: [Go如何使得web工作](<3.3.md>)
+ * 下一节: [小结](<3.5.md>)
diff --git a/3.5.md b/3.5.md
index ab0e5360210c9401b50f032059b5bef429727838..b4488e3d51b82f127e4c654c5c7c76fc2d1d624e 100644
--- a/3.5.md
+++ b/3.5.md
@@ -1,12 +1,9 @@
-# 3.5小结
-这一章我们介绍了HTTP协议, DNS解析的过程, 如何用go实现一个简陋的web server。并深入到net/http包的源码中为大家揭开如何实现此server的秘密。
-
-我希望通过这一章的学习,你能够对Go开发Web有了初步的了解,我们也看到相应的代码了,Go开发Web应用是很方便的,同时又是相当的灵活。
-
-## links
- * [目录]()
- * 上一节: [Go的http包详解](<3.4.md>)
- * 下一章: [表单](<4.md>)
-
-## LastModified
- * $Id$
\ No newline at end of file
+# 3.5小结
+这一章我们介绍了HTTP协议, DNS解析的过程, 如何用go实现一个简陋的web server。并深入到net/http包的源码中为大家揭开如何实现此server的秘密。
+
+我希望通过这一章的学习,你能够对Go开发Web有了初步的了解,我们也看到相应的代码了,Go开发Web应用是很方便的,同时又是相当的灵活。
+
+## links
+ * [目录]()
+ * 上一节: [Go的http包详解](<3.4.md>)
+ * 下一章: [表单](<4.md>)
diff --git a/3.md b/3.md
index e519512de7fc6509a63e8c3606d3f71579cf8cd3..573e3c46375bb894873c6fc2a53c63ab16681179 100644
--- a/3.md
+++ b/3.md
@@ -1,18 +1,15 @@
-# 3 Web基础
-
-## 目录
- * 1. [web工作方式](3.1.md)
- * 2. [GO搭建一个简单的web服务](3.2.md)
- * 3. [Go如何使得web工作](3.3.md)
- * 4. [Go的http包详解](3.4.md)
- * 5. [小结](3.5.md)
-
-学习基于Web的编程可能正是你读本书的原因。事实上,如何通过Go来编写Web应用也是我编写这本书的初衷。前面已经介绍过,Go目前已经拥有了成熟的Http处理包,这使得编写能做任何事情的动态Web程序易如反掌。在接下来的各章中将要介绍的内容,都是属于Web编程的范畴。本章则集中讨论一些与Web相关的概念和Go如何运行Web程序的话题。
-
-## links
- * [目录]()
- * 上一章: [第二章总结](<2.8.md>)
- * 下一节: [web工作方式](<3.1.md>)
-
-## LastModified
- * $Id$
\ No newline at end of file
+# 3 Web基础
+
+## 目录
+ * 1. [web工作方式](3.1.md)
+ * 2. [GO搭建一个简单的web服务](3.2.md)
+ * 3. [Go如何使得web工作](3.3.md)
+ * 4. [Go的http包详解](3.4.md)
+ * 5. [小结](3.5.md)
+
+学习基于Web的编程可能正是你读本书的原因。事实上,如何通过Go来编写Web应用也是我编写这本书的初衷。前面已经介绍过,Go目前已经拥有了成熟的Http处理包,这使得编写能做任何事情的动态Web程序易如反掌。在接下来的各章中将要介绍的内容,都是属于Web编程的范畴。本章则集中讨论一些与Web相关的概念和Go如何运行Web程序的话题。
+
+## links
+ * [目录]()
+ * 上一章: [第二章总结](<2.8.md>)
+ * 下一节: [web工作方式](<3.1.md>)
diff --git a/4.1.md b/4.1.md
index 07216d097a519a678de29f841d30e123aa8a0afd..ebabce88a631a924aed5acec619062fd69e1c486 100644
--- a/4.1.md
+++ b/4.1.md
@@ -8,9 +8,9 @@