Merge pull request #855 from vCaesar/u17-pr
Update go install version and Remove zh/x.md spaces
This commit is contained in:
34
zh/01.1.md
34
zh/01.1.md
@@ -79,7 +79,7 @@ Linux系统用户可通过在Terminal中执行命令`arch`(即`uname -m`)来查
|
||||
|
||||
### Mac 安装
|
||||
|
||||
访问[下载地址][downlink],32位系统下载go1.4.2.darwin-386-osx10.8.pkg(更新的版本已无32位下载),64位系统下载go1.8.1.darwin-amd64.pkg,双击下载文件,一路默认安装点击下一步,这个时候go已经安装到你的系统中,默认已经在PATH中增加了相应的`~/go/bin`,这个时候打开终端,输入`go`
|
||||
访问[下载地址][downlink],32位系统下载go1.4.2.darwin-386-osx10.8.pkg(更新的版本已无32位下载),64位系统下载go1.8.3.darwin-amd64.pkg,双击下载文件,一路默认安装点击下一步,这个时候go已经安装到你的系统中,默认已经在PATH中增加了相应的`~/go/bin`,这个时候打开终端,输入`go`
|
||||
|
||||
看到类似上面源码安装成功的图片说明已经安装成功
|
||||
|
||||
@@ -87,11 +87,11 @@ Linux系统用户可通过在Terminal中执行命令`arch`(即`uname -m`)来查
|
||||
|
||||
### Linux 安装
|
||||
|
||||
访问[下载地址][downlink],32位系统下载go1.8.1.linux-386.tar.gz,64位系统下载go1.8.1.linux-amd64.tar.gz,
|
||||
访问[下载地址][downlink],32位系统下载go1.8.3.linux-386.tar.gz,64位系统下载go1.8.3.linux-amd64.tar.gz,
|
||||
|
||||
假定你想要安装Go的目录为 `$GO_INSTALL_DIR`,后面替换为相应的目录路径。
|
||||
|
||||
解压缩`tar.gz`包到安装目录下:`tar zxvf go1.8.1.linux-amd64.tar.gz -C $GO_INSTALL_DIR`。
|
||||
解压缩`tar.gz`包到安装目录下:`tar zxvf go1.8.3.linux-amd64.tar.gz -C $GO_INSTALL_DIR`。
|
||||
|
||||
设置PATH,`export PATH=$PATH:$GO_INSTALL_DIR/go/bin`
|
||||
|
||||
@@ -120,16 +120,16 @@ Linux系统用户可通过在Terminal中执行命令`arch`(即`uname -m`)来查
|
||||
gvm是第三方开发的Go多版本管理工具,类似ruby里面的rvm工具。使用起来相当的方便,安装gvm使用如下命令:
|
||||
```sh
|
||||
|
||||
bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
|
||||
bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
|
||||
```
|
||||
安装完成后我们就可以安装go了:
|
||||
```sh
|
||||
|
||||
gvm install go1.8.1
|
||||
gvm use go1.8.1
|
||||
gvm install go1.8.3
|
||||
gvm use go1.8.3
|
||||
```
|
||||
也可以使用下面的命令,省去每次调用gvm use的麻烦:
|
||||
gvm use go1.8.1 --default
|
||||
gvm use go1.8.3 --default
|
||||
|
||||
执行完上面的命令之后GOPATH、GOROOT等环境变量会自动设置好,这样就可以直接使用了。
|
||||
|
||||
@@ -137,16 +137,16 @@ gvm是第三方开发的Go多版本管理工具,类似ruby里面的rvm工具
|
||||
Ubuntu是目前使用最多的Linux桌面系统,使用`apt-get`命令来管理软件包,我们可以通过下面的命令来安装Go,为了以后方便,应该把 `git` `mercurial` 也安装上:
|
||||
```sh
|
||||
|
||||
sudo apt-get install python-software-properties
|
||||
sudo add-apt-repository ppa:gophers/go
|
||||
sudo apt-get update
|
||||
sudo apt-get install golang-stable git-core mercurial
|
||||
sudo apt-get install python-software-properties
|
||||
sudo add-apt-repository ppa:gophers/go
|
||||
sudo apt-get update
|
||||
sudo apt-get install golang-stable git-core mercurial
|
||||
```
|
||||
### wget
|
||||
```sh
|
||||
|
||||
wget https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz
|
||||
sudo tar -xzf go1.8.1.linux-amd64.tar.gz -C /usr/local
|
||||
wget https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
|
||||
sudo tar -xzf go1.8.3.linux-amd64.tar.gz -C /usr/local
|
||||
```
|
||||
|
||||
配置环境变量:
|
||||
@@ -186,10 +186,10 @@ homebrew是Mac系统下面目前使用最多的管理软件的工具,目前已
|
||||
|
||||
```sh
|
||||
|
||||
brew update && brew upgrade
|
||||
brew install go
|
||||
brew install git
|
||||
brew install mercurial //可选安装
|
||||
brew update && brew upgrade
|
||||
brew install go
|
||||
brew install git
|
||||
brew install mercurial //可选安装
|
||||
```
|
||||
|
||||
## links
|
||||
|
||||
@@ -18,7 +18,7 @@ export GOPATH=/home/apple/mygo
|
||||
|
||||
Windows 设置如下,新建一个环境变量名称叫做GOPATH:
|
||||
```sh
|
||||
GOPATH=c:\mygo
|
||||
GOPATH=c:\mygo
|
||||
```
|
||||
GOPATH允许多个目录,当有多个目录时,请注意分隔符,多个目录的时候Windows是分号,Linux系统是冒号,当有多个GOPATH时,默认会将go get的内容放在第一个目录下。
|
||||
|
||||
@@ -89,12 +89,12 @@ vim main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"mymath"
|
||||
"fmt"
|
||||
"mymath"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2))
|
||||
fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2))
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
56
zh/01.4.md
56
zh/01.4.md
@@ -121,12 +121,12 @@
|
||||
适用于 Sublime Text 3:
|
||||
|
||||
```Go
|
||||
import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read())
|
||||
import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read())
|
||||
```
|
||||
适用于 Sublime Text 2:
|
||||
|
||||
```Go
|
||||
import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;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')
|
||||
import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;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已经安装成功了。
|
||||
@@ -157,10 +157,10 @@
|
||||
|
||||
先在sublime安装gotests插件,再运行:
|
||||
|
||||
```Go
|
||||
go get -u -v github.com/cweill/gotests/...
|
||||
|
||||
```
|
||||
```Go
|
||||
go get -u -v github.com/cweill/gotests/...
|
||||
```
|
||||
|
||||
3. 验证是否安装成功,你可以打开Sublime,打开main.go,看看语法是不是高亮了,输入`import`是不是自动化提示了,`import "fmt"`之后,输入`fmt.`是不是自动化提示有函数了。
|
||||
|
||||
@@ -220,33 +220,31 @@ vscode代码设置可用于Go扩展。这些都可以在用户的喜好来设置
|
||||
|
||||
接着安装依赖包支持(网络不稳定,请直接到Github[Golang](https://github.com/golang)下载再移动到相关目录):
|
||||
```Go
|
||||
go get -u -v github.com/nsf/gocode
|
||||
go get -u -v github.com/rogpeppe/godef
|
||||
go get -u -v github.com/zmb3/gogetdoc
|
||||
go get -u -v github.com/golang/lint/golint
|
||||
go get -u -v github.com/lukehoban/go-outline
|
||||
go get -u -v sourcegraph.com/sqs/goreturns
|
||||
go get -u -v golang.org/x/tools/cmd/gorename
|
||||
go get -u -v github.com/tpng/gopkgs
|
||||
go get -u -v github.com/newhook/go-symbols
|
||||
go get -u -v golang.org/x/tools/cmd/guru
|
||||
go get -u -v github.com/cweill/gotests/...
|
||||
go get -u -v github.com/nsf/gocode
|
||||
go get -u -v github.com/rogpeppe/godef
|
||||
go get -u -v github.com/zmb3/gogetdoc
|
||||
go get -u -v github.com/golang/lint/golint
|
||||
go get -u -v github.com/lukehoban/go-outline
|
||||
go get -u -v sourcegraph.com/sqs/goreturns
|
||||
go get -u -v golang.org/x/tools/cmd/gorename
|
||||
go get -u -v github.com/tpng/gopkgs
|
||||
go get -u -v github.com/newhook/go-symbols
|
||||
go get -u -v golang.org/x/tools/cmd/guru
|
||||
go get -u -v github.com/cweill/gotests/...
|
||||
```
|
||||
|
||||
vscode还有一项很强大的功能就是断点调试,结合[delve](https://github.com/derekparker/delve)可以很好的进行Go代码调试
|
||||
|
||||
```Go
|
||||
|
||||
go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv
|
||||
|
||||
brew install go-delve/delve/delve(mac可选)
|
||||
go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv
|
||||
|
||||
brew install go-delve/delve/delve(mac可选)
|
||||
|
||||
```
|
||||
如果有问题再来一遍:
|
||||
```Go
|
||||
|
||||
go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv
|
||||
|
||||
go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv
|
||||
```
|
||||
注意:修改"dlv-cert"证书, 选择"显示简介"->"信任"->"代码签名" 修改为: 始终信任
|
||||
|
||||
@@ -285,13 +283,13 @@ Atom是Github基于Electron和web技术构建的开源编辑器, 是一款很漂
|
||||
|
||||
它需要依赖下面的go语言工具:
|
||||
```Go
|
||||
1.autocomplete-go :gocode的代码自动提示
|
||||
2.gofmt :使用goftm,goimports,goturns
|
||||
3.builder-go:go-install 和go-test,验证代码,给出建议
|
||||
4.gometalinet-linter:goline,vet,gotype的检查
|
||||
5.navigator-godef:godef
|
||||
6.tester-goo :go test
|
||||
7.gorename :rename
|
||||
1.autocomplete-go :gocode的代码自动提示
|
||||
2.gofmt :使用goftm,goimports,goturns
|
||||
3.builder-go:go-install 和go-test,验证代码,给出建议
|
||||
4.gometalinet-linter:goline,vet,gotype的检查
|
||||
5.navigator-godef:godef
|
||||
6.tester-goo :go test
|
||||
7.gorename :rename
|
||||
|
||||
```
|
||||
在Atom中的 Preference 中可以找到install菜单,输入 go-plus,然后点击安装(install)
|
||||
|
||||
10
zh/02.1.md
10
zh/02.1.md
@@ -9,13 +9,13 @@
|
||||
准备好了吗?Let's Go!
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n")
|
||||
}
|
||||
func main() {
|
||||
fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n")
|
||||
}
|
||||
```
|
||||
输出如下:
|
||||
|
||||
|
||||
390
zh/02.2.md
390
zh/02.2.md
@@ -9,49 +9,49 @@ Go语言里面定义变量有多种方式。
|
||||
使用`var`关键字是Go最基本的定义变量方式,与C语言不同的是Go把变量类型放在变量名后面:
|
||||
```Go
|
||||
|
||||
//定义一个名称为“variableName”,类型为"type"的变量
|
||||
var variableName type
|
||||
//定义一个名称为“variableName”,类型为"type"的变量
|
||||
var variableName type
|
||||
```
|
||||
定义多个变量
|
||||
```Go
|
||||
|
||||
//定义三个类型都是“type”的变量
|
||||
var vname1, vname2, vname3 type
|
||||
//定义三个类型都是“type”的变量
|
||||
var vname1, vname2, vname3 type
|
||||
```
|
||||
定义变量并初始化值
|
||||
```Go
|
||||
|
||||
//初始化“variableName”的变量为“value”值,类型是“type”
|
||||
var variableName type = value
|
||||
//初始化“variableName”的变量为“value”值,类型是“type”
|
||||
var variableName type = value
|
||||
```
|
||||
同时初始化多个变量
|
||||
```Go
|
||||
|
||||
/*
|
||||
定义三个类型都是"type"的变量,并且分别初始化为相应的值
|
||||
vname1为v1,vname2为v2,vname3为v3
|
||||
*/
|
||||
var vname1, vname2, vname3 type= v1, v2, v3
|
||||
/*
|
||||
定义三个类型都是"type"的变量,并且分别初始化为相应的值
|
||||
vname1为v1,vname2为v2,vname3为v3
|
||||
*/
|
||||
var vname1, vname2, vname3 type= v1, v2, v3
|
||||
```
|
||||
你是不是觉得上面这样的定义有点繁琐?没关系,因为Go语言的设计者也发现了,有一种写法可以让它变得简单一点。我们可以直接忽略类型声明,那么上面的代码变成这样了:
|
||||
```Go
|
||||
|
||||
/*
|
||||
定义三个变量,它们分别初始化为相应的值
|
||||
vname1为v1,vname2为v2,vname3为v3
|
||||
然后Go会根据其相应值的类型来帮你初始化它们
|
||||
*/
|
||||
var vname1, vname2, vname3 = v1, v2, v3
|
||||
/*
|
||||
定义三个变量,它们分别初始化为相应的值
|
||||
vname1为v1,vname2为v2,vname3为v3
|
||||
然后Go会根据其相应值的类型来帮你初始化它们
|
||||
*/
|
||||
var vname1, vname2, vname3 = v1, v2, v3
|
||||
```
|
||||
你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化:
|
||||
```Go
|
||||
|
||||
/*
|
||||
定义三个变量,它们分别初始化为相应的值
|
||||
vname1为v1,vname2为v2,vname3为v3
|
||||
编译器会根据初始化的值自动推导出相应的类型
|
||||
*/
|
||||
vname1, vname2, vname3 := v1, v2, v3
|
||||
/*
|
||||
定义三个变量,它们分别初始化为相应的值
|
||||
vname1为v1,vname2为v2,vname3为v3
|
||||
编译器会根据初始化的值自动推导出相应的类型
|
||||
*/
|
||||
vname1, vname2, vname3 := v1, v2, v3
|
||||
```
|
||||
现在是不是看上去非常简洁了?`:=`这个符号直接取代了`var`和`type`,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用`var`方式来定义全局变量。
|
||||
|
||||
@@ -62,11 +62,11 @@ Go语言里面定义变量有多种方式。
|
||||
Go对于已声明但未使用的变量会在编译阶段报错,比如下面的代码就会产生一个错误:声明了`i`但未使用。
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
func main() {
|
||||
var i int
|
||||
}
|
||||
func main() {
|
||||
var i int
|
||||
}
|
||||
```
|
||||
## 常量
|
||||
|
||||
@@ -75,17 +75,17 @@ Go对于已声明但未使用的变量会在编译阶段报错,比如下面的
|
||||
它的语法如下:
|
||||
```Go
|
||||
|
||||
const constantName = value
|
||||
//如果需要,也可以明确指定常量的类型:
|
||||
const Pi float32 = 3.1415926
|
||||
const constantName = value
|
||||
//如果需要,也可以明确指定常量的类型:
|
||||
const Pi float32 = 3.1415926
|
||||
```
|
||||
下面是一些常量声明的例子:
|
||||
```Go
|
||||
|
||||
const Pi = 3.1415926
|
||||
const i = 10000
|
||||
const MaxThread = 10
|
||||
const prefix = "astaxie_"
|
||||
const Pi = 3.1415926
|
||||
const i = 10000
|
||||
const MaxThread = 10
|
||||
const prefix = "astaxie_"
|
||||
```
|
||||
Go 常量和一般程序语言不同的是,可以指定相当多的小数位数(例如200位),
|
||||
若指定給float32自动缩短为32bit,指定给float64自动缩短为64bit,详情参考[链接](http://golang.org/ref/spec#Constants)
|
||||
@@ -97,14 +97,14 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位
|
||||
在Go中,布尔值的类型为`bool`,值是`true`或`false`,默认为`false`。
|
||||
```Go
|
||||
|
||||
//示例代码
|
||||
var isActive bool // 全局变量声明
|
||||
var enabled, disabled = true, false // 忽略类型的声明
|
||||
func test() {
|
||||
var available bool // 一般声明
|
||||
valid := false // 简短声明
|
||||
available = true // 赋值操作
|
||||
}
|
||||
//示例代码
|
||||
var isActive bool // 全局变量声明
|
||||
var enabled, disabled = true, false // 忽略类型的声明
|
||||
func test() {
|
||||
var available bool // 一般声明
|
||||
valid := false // 简短声明
|
||||
available = true // 赋值操作
|
||||
}
|
||||
```
|
||||
|
||||
### 数值类型
|
||||
@@ -128,9 +128,9 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位
|
||||
这就是全部吗?No!Go还支持复数。它的默认类型是`complex128`(64位实数+64位虚数)。如果需要小一些的,也有`complex64`(32位实数+32位虚数)。复数的形式为`RE + IMi`,其中`RE`是实数部分,`IM`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子:
|
||||
```Go
|
||||
|
||||
var c complex64 = 5+5i
|
||||
//output: (5+5i)
|
||||
fmt.Printf("Value is: %v", c)
|
||||
var c complex64 = 5+5i
|
||||
//output: (5+5i)
|
||||
fmt.Printf("Value is: %v", c)
|
||||
```
|
||||
|
||||
### 字符串
|
||||
@@ -138,46 +138,46 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位
|
||||
我们在上一节中讲过,Go中的字符串都是采用`UTF-8`字符集编码。字符串是用一对双引号(`""`)或反引号(`` ` `` `` ` ``)括起来定义,它的类型是`string`。
|
||||
```Go
|
||||
|
||||
//示例代码
|
||||
var frenchHello string // 声明变量为字符串的一般方法
|
||||
var emptyString string = "" // 声明了一个字符串变量,初始化为空字符串
|
||||
func test() {
|
||||
no, yes, maybe := "no", "yes", "maybe" // 简短声明,同时声明多个变量
|
||||
japaneseHello := "Konichiwa" // 同上
|
||||
frenchHello = "Bonjour" // 常规赋值
|
||||
}
|
||||
//示例代码
|
||||
var frenchHello string // 声明变量为字符串的一般方法
|
||||
var emptyString string = "" // 声明了一个字符串变量,初始化为空字符串
|
||||
func test() {
|
||||
no, yes, maybe := "no", "yes", "maybe" // 简短声明,同时声明多个变量
|
||||
japaneseHello := "Konichiwa" // 同上
|
||||
frenchHello = "Bonjour" // 常规赋值
|
||||
}
|
||||
```
|
||||
在Go中字符串是不可变的,例如下面的代码编译时会报错:cannot assign to s[0]
|
||||
```Go
|
||||
|
||||
var s string = "hello"
|
||||
s[0] = 'c'
|
||||
var s string = "hello"
|
||||
s[0] = 'c'
|
||||
|
||||
```
|
||||
但如果真的想要修改怎么办呢?下面的代码可以实现:
|
||||
```Go
|
||||
|
||||
s := "hello"
|
||||
c := []byte(s) // 将字符串 s 转换为 []byte 类型
|
||||
c[0] = 'c'
|
||||
s2 := string(c) // 再转换回 string 类型
|
||||
fmt.Printf("%s\n", s2)
|
||||
s := "hello"
|
||||
c := []byte(s) // 将字符串 s 转换为 []byte 类型
|
||||
c[0] = 'c'
|
||||
s2 := string(c) // 再转换回 string 类型
|
||||
fmt.Printf("%s\n", s2)
|
||||
```
|
||||
|
||||
Go中可以使用`+`操作符来连接两个字符串:
|
||||
```Go
|
||||
|
||||
s := "hello,"
|
||||
m := " world"
|
||||
a := s + m
|
||||
fmt.Printf("%s\n", a)
|
||||
s := "hello,"
|
||||
m := " world"
|
||||
a := s + m
|
||||
fmt.Printf("%s\n", a)
|
||||
```
|
||||
修改字符串也可写为:
|
||||
```Go
|
||||
|
||||
s := "hello"
|
||||
s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作
|
||||
fmt.Printf("%s\n", s)
|
||||
s := "hello"
|
||||
s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作
|
||||
fmt.Printf("%s\n", s)
|
||||
```
|
||||
如果要声明一个多行的字符串怎么办?可以通过`` ` ``来声明:
|
||||
|
||||
@@ -193,10 +193,10 @@ Go中可以使用`+`操作符来连接两个字符串:
|
||||
Go内置有一个`error`类型,专门用来处理错误信息,Go的`package`里面还专门有一个包`errors`来处理错误:
|
||||
```Go
|
||||
|
||||
err := errors.New("emit macho dwarf: elf header corrupted")
|
||||
if err != nil {
|
||||
fmt.Print(err)
|
||||
}
|
||||
err := errors.New("emit macho dwarf: elf header corrupted")
|
||||
if err != nil {
|
||||
fmt.Print(err)
|
||||
}
|
||||
```
|
||||
### Go数据底层的存储
|
||||
|
||||
@@ -215,72 +215,72 @@ Go内置有一个`error`类型,专门用来处理错误信息,Go的`package`
|
||||
例如下面的代码:
|
||||
```Go
|
||||
|
||||
import "fmt"
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "os"
|
||||
|
||||
const i = 100
|
||||
const pi = 3.1415
|
||||
const prefix = "Go_"
|
||||
const i = 100
|
||||
const pi = 3.1415
|
||||
const prefix = "Go_"
|
||||
|
||||
var i int
|
||||
var pi float32
|
||||
var prefix string
|
||||
var i int
|
||||
var pi float32
|
||||
var prefix string
|
||||
```
|
||||
可以分组写成如下形式:
|
||||
```Go
|
||||
|
||||
import(
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
import(
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const(
|
||||
i = 100
|
||||
pi = 3.1415
|
||||
prefix = "Go_"
|
||||
)
|
||||
const(
|
||||
i = 100
|
||||
pi = 3.1415
|
||||
prefix = "Go_"
|
||||
)
|
||||
|
||||
var(
|
||||
i int
|
||||
pi float32
|
||||
prefix string
|
||||
)
|
||||
var(
|
||||
i int
|
||||
pi float32
|
||||
prefix string
|
||||
)
|
||||
```
|
||||
### iota枚举
|
||||
|
||||
Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采用,它默认开始值是0,const中每增加一行加1:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
x = iota // x == 0
|
||||
y = iota // y == 1
|
||||
z = iota // z == 2
|
||||
w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"
|
||||
)
|
||||
const (
|
||||
x = iota // x == 0
|
||||
y = iota // y == 1
|
||||
z = iota // z == 2
|
||||
w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"
|
||||
)
|
||||
|
||||
const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0
|
||||
const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0
|
||||
|
||||
const (
|
||||
h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同
|
||||
)
|
||||
const (
|
||||
h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同
|
||||
)
|
||||
|
||||
const (
|
||||
a = iota //a=0
|
||||
b = "B"
|
||||
c = iota //c=2
|
||||
d, e, f = iota, iota, iota //d=3,e=3,f=3
|
||||
g = iota //g = 4
|
||||
)
|
||||
const (
|
||||
a = iota //a=0
|
||||
b = "B"
|
||||
c = iota //c=2
|
||||
d, e, f = iota, iota, iota //d=3,e=3,f=3
|
||||
g = iota //g = 4
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
|
||||
}
|
||||
func main() {
|
||||
fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
|
||||
}
|
||||
```
|
||||
>除非被显式设置为其它值或`iota`,每个`const`分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是`iota`,则它也被设置为`iota`。
|
||||
|
||||
@@ -295,36 +295,36 @@ Go之所以会那么简洁,是因为它有一些默认的行为:
|
||||
`array`就是数组,它的定义方式如下:
|
||||
```Go
|
||||
|
||||
var arr [n]type
|
||||
var arr [n]type
|
||||
```
|
||||
在`[n]type`中,`n`表示数组的长度,`type`表示存储元素的类型。对数组的操作和其它语言类似,都是通过`[]`来进行读取或赋值:
|
||||
```Go
|
||||
|
||||
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
|
||||
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`类型了。
|
||||
|
||||
数组可以使用另一种`:=`来声明
|
||||
```Go
|
||||
|
||||
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
|
||||
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
|
||||
|
||||
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0
|
||||
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0
|
||||
|
||||
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度
|
||||
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度
|
||||
```
|
||||
也许你会说,我想数组里面的值还是数组,能实现吗?当然咯,Go支持嵌套数组,即多维数组。比如下面的代码就声明了一个二维数组:
|
||||
```Go
|
||||
|
||||
// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素
|
||||
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
|
||||
// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有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}}
|
||||
// 上面的声明可以简化,直接忽略内部的类型
|
||||
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
|
||||
```
|
||||
数组的分配如下所示:
|
||||
|
||||
@@ -340,30 +340,30 @@ Go之所以会那么简洁,是因为它有一些默认的行为:
|
||||
`slice`并不是真正意义上的动态数组,而是一个引用类型。`slice`总是指向一个底层`array`,`slice`的声明也可以像`array`一样,只是不需要长度。
|
||||
```Go
|
||||
|
||||
// 和声明array一样,只是少了长度
|
||||
var fslice []int
|
||||
// 和声明array一样,只是少了长度
|
||||
var fslice []int
|
||||
```
|
||||
接下来我们可以声明一个`slice`,并初始化数据,如下所示:
|
||||
```Go
|
||||
|
||||
slice := []byte {'a', 'b', 'c', 'd'}
|
||||
slice := []byte {'a', 'b', 'c', 'd'}
|
||||
```
|
||||
`slice`可以从一个数组或一个已经存在的`slice`中再次声明。`slice`通过`array[i:j]`来获取,其中`i`是数组的开始位置,`j`是结束位置,但不包含`array[j]`,它的长度是`j-i`。
|
||||
```Go
|
||||
|
||||
// 声明一个含有10个元素元素类型为byte的数组
|
||||
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
// 声明一个含有10个元素元素类型为byte的数组
|
||||
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
|
||||
// 声明两个含有byte的slice
|
||||
var a, b []byte
|
||||
// 声明两个含有byte的slice
|
||||
var a, b []byte
|
||||
|
||||
// a指向数组的第3个元素开始,并到第五个元素结束,
|
||||
a = ar[2:5]
|
||||
//现在a含有的元素: ar[2]、ar[3]和ar[4]
|
||||
// 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]
|
||||
// b是数组ar的另一个slice
|
||||
b = ar[3:5]
|
||||
// b的元素是:ar[3]和ar[4]
|
||||
```
|
||||
>注意`slice`和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明`slice`时,方括号内没有任何字符。
|
||||
|
||||
@@ -382,22 +382,22 @@ slice有一些简便的操作
|
||||
下面这个例子展示了更多关于`slice`的操作:
|
||||
```Go
|
||||
|
||||
// 声明一个数组
|
||||
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
// 声明两个slice
|
||||
var aSlice, bSlice []byte
|
||||
// 声明一个数组
|
||||
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
// 声明两个slice
|
||||
var aSlice, bSlice []byte
|
||||
|
||||
// 演示一些简便操作
|
||||
aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c
|
||||
aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j
|
||||
aSlice = array[:] // 等价于aSlice = array[0:10] 这样aSlice包含了全部的元素
|
||||
// 演示一些简便操作
|
||||
aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c
|
||||
aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j
|
||||
aSlice = array[:] // 等价于aSlice = array[0:10] 这样aSlice包含了全部的元素
|
||||
|
||||
// 从slice中获取slice
|
||||
aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4,cap=7
|
||||
bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f
|
||||
bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f
|
||||
bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h
|
||||
bSlice = aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g
|
||||
// 从slice中获取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`相对应的值也会改变。
|
||||
|
||||
@@ -407,8 +407,8 @@ slice有一些简便的操作
|
||||
- 最大长度,也就是`slice`开始位置到数组的最后位置的长度
|
||||
```Go
|
||||
|
||||
Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
Slice_a := Array_a[2:5]
|
||||
Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
Slice_a := Array_a[2:5]
|
||||
```
|
||||
上面代码的真正存储结构如下图所示
|
||||
|
||||
@@ -429,8 +429,8 @@ slice有一些简便的操作
|
||||
从Go1.2开始slice支持了三个参数的slice,之前我们一直采用这种方式在slice或者array基础上来获取一个slice
|
||||
```Go
|
||||
|
||||
var array [10]int
|
||||
slice := array[2:4]
|
||||
var array [10]int
|
||||
slice := array[2:4]
|
||||
```
|
||||
这个例子里面slice的容量是8,新版本里面可以指定这个容量
|
||||
|
||||
@@ -447,16 +447,16 @@ slice有一些简便的操作
|
||||
我们看下面的代码,`map`的读取和设置也类似`slice`一样,通过`key`来操作,只是`slice`的`index`只能是`int`类型,而`map`多了很多类型,可以是`int`,可以是`string`及所有完全定义了`==`与`!=`操作的类型。
|
||||
```Go
|
||||
|
||||
// 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化
|
||||
var numbers map[string]int
|
||||
// 另一种map的声明方式
|
||||
numbers := make(map[string]int)
|
||||
numbers["one"] = 1 //赋值
|
||||
numbers["ten"] = 10 //赋值
|
||||
numbers["three"] = 3
|
||||
// 声明一个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
|
||||
fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据
|
||||
// 打印出来如:第三个数字是: 3
|
||||
```
|
||||
|
||||
这个`map`就像我们平常看到的表格一样,左边列是`key`,右边列是值
|
||||
@@ -474,26 +474,26 @@ slice有一些简便的操作
|
||||
|
||||
```Go
|
||||
|
||||
// 初始化一个字典
|
||||
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")
|
||||
}
|
||||
// 初始化一个字典
|
||||
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的元素
|
||||
delete(rating, "C") // 删除key为C的元素
|
||||
|
||||
```
|
||||
上面说过了,`map`也是一种引用类型,如果两个`map`同时指向一个底层,那么一个改变,另一个也相应的改变:
|
||||
```Go
|
||||
|
||||
m := make(map[string]string)
|
||||
m["Hello"] = "Bonjour"
|
||||
m1 := m
|
||||
m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了
|
||||
m := make(map[string]string)
|
||||
m["Hello"] = "Bonjour"
|
||||
m1 := m
|
||||
m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了
|
||||
|
||||
```
|
||||
### make、new操作
|
||||
@@ -520,17 +520,17 @@ slice有一些简便的操作
|
||||
此处罗列 部分类型 的 “零值”
|
||||
```Go
|
||||
|
||||
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 ""
|
||||
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
|
||||
|
||||
516
zh/02.3.md
516
zh/02.3.md
@@ -8,48 +8,48 @@
|
||||
Go里面`if`条件判断语句中不需要括号,如下代码所示
|
||||
```Go
|
||||
|
||||
if x > 10 {
|
||||
fmt.Println("x is greater than 10")
|
||||
} else {
|
||||
fmt.Println("x is less than 10")
|
||||
}
|
||||
if x > 10 {
|
||||
fmt.Println("x is greater than 10")
|
||||
} else {
|
||||
fmt.Println("x is less than 10")
|
||||
}
|
||||
```
|
||||
Go的`if`还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示
|
||||
```Go
|
||||
|
||||
// 计算获取值x,然后根据x返回的大小,判断是否大于10。
|
||||
if x := computedValue(); x > 10 {
|
||||
fmt.Println("x is greater than 10")
|
||||
} else {
|
||||
fmt.Println("x is less than 10")
|
||||
}
|
||||
// 计算获取值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)
|
||||
//这个地方如果这样调用就编译出错了,因为x是条件里面的变量
|
||||
fmt.Println(x)
|
||||
```
|
||||
多个条件的时候如下所示:
|
||||
```Go
|
||||
|
||||
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")
|
||||
}
|
||||
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`跳转到必须在当前函数内定义的标签。例如假设这样一个循环:
|
||||
```Go
|
||||
|
||||
func myFunc() {
|
||||
i := 0
|
||||
Here: //这行的第一个词,以冒号结束作为标签
|
||||
println(i)
|
||||
i++
|
||||
goto Here //跳转到Here去
|
||||
}
|
||||
func myFunc() {
|
||||
i := 0
|
||||
Here: //这行的第一个词,以冒号结束作为标签
|
||||
println(i)
|
||||
i++
|
||||
goto Here //跳转到Here去
|
||||
}
|
||||
```
|
||||
>标签名是大小写敏感的。
|
||||
|
||||
@@ -57,27 +57,27 @@ Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前
|
||||
Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下:
|
||||
```Go
|
||||
|
||||
for expression1; expression2; expression3 {
|
||||
//...
|
||||
}
|
||||
for expression1; expression2; expression3 {
|
||||
//...
|
||||
}
|
||||
```
|
||||
`expression1`、`expression2`和`expression3`都是表达式,其中`expression1`和`expression3`是变量声明或者函数调用返回值之类的,`expression2`是用来条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。
|
||||
|
||||
一个例子比上面讲那么多更有用,那么我们看看下面的例子吧:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
func main(){
|
||||
sum := 0;
|
||||
for index:=0; index < 10 ; index++ {
|
||||
sum += index
|
||||
}
|
||||
fmt.Println("sum is equal to ", sum)
|
||||
func main(){
|
||||
sum := 0;
|
||||
for index:=0; index < 10 ; index++ {
|
||||
sum += index
|
||||
}
|
||||
// 输出:sum is equal to 45
|
||||
fmt.Println("sum is equal to ", sum)
|
||||
}
|
||||
// 输出:sum is equal to 45
|
||||
```
|
||||
有些时候需要进行多个赋值操作,由于Go里面没有`,`操作符,那么可以使用平行赋值`i, j = i+1, j-1`
|
||||
|
||||
@@ -85,122 +85,122 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
|
||||
有些时候如果我们忽略`expression1`和`expression3`:
|
||||
```Go
|
||||
|
||||
sum := 1
|
||||
for ; sum < 1000; {
|
||||
sum += sum
|
||||
}
|
||||
sum := 1
|
||||
for ; sum < 1000; {
|
||||
sum += sum
|
||||
}
|
||||
```
|
||||
其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识?对,这就是`while`的功能。
|
||||
```Go
|
||||
|
||||
sum := 1
|
||||
for sum < 1000 {
|
||||
sum += sum
|
||||
}
|
||||
sum := 1
|
||||
for sum < 1000 {
|
||||
sum += sum
|
||||
}
|
||||
```
|
||||
在循环里面有两个关键操作`break`和`continue` ,`break`操作是跳出当前循环,`continue`是跳过本次循环。当嵌套过深的时候,`break`可以配合标签使用,即跳转至标签所指定的位置,详细参考如下例子:
|
||||
```Go
|
||||
|
||||
for index := 10; index>0; index-- {
|
||||
if index == 5{
|
||||
break // 或者continue
|
||||
}
|
||||
fmt.Println(index)
|
||||
for index := 10; index>0; index-- {
|
||||
if index == 5{
|
||||
break // 或者continue
|
||||
}
|
||||
// break打印出来10、9、8、7、6
|
||||
// continue打印出来10、9、8、7、6、4、3、2、1
|
||||
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`的数据:
|
||||
```Go
|
||||
|
||||
for k,v:=range map {
|
||||
fmt.Println("map's key:",k)
|
||||
fmt.Println("map's val:",v)
|
||||
}
|
||||
for k,v:=range map {
|
||||
fmt.Println("map's key:",k)
|
||||
fmt.Println("map's val:",v)
|
||||
}
|
||||
```
|
||||
由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用`_`来丢弃不需要的返回值
|
||||
例如
|
||||
```Go
|
||||
|
||||
for _, v := range map{
|
||||
fmt.Println("map's val:", v)
|
||||
}
|
||||
for _, v := range map{
|
||||
fmt.Println("map's val:", v)
|
||||
}
|
||||
|
||||
```
|
||||
### switch
|
||||
有些时候你需要写很多的`if-else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候`switch`就能很好的解决这个问题。它的语法如下
|
||||
```Go
|
||||
|
||||
switch sExpr {
|
||||
case expr1:
|
||||
some instructions
|
||||
case expr2:
|
||||
some other instructions
|
||||
case expr3:
|
||||
some other instructions
|
||||
default:
|
||||
other code
|
||||
}
|
||||
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`。
|
||||
```Go
|
||||
|
||||
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")
|
||||
}
|
||||
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代码。
|
||||
```Go
|
||||
|
||||
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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
```
|
||||
上面的程序将输出
|
||||
```Go
|
||||
|
||||
The integer was <= 6
|
||||
The integer was <= 7
|
||||
The integer was <= 8
|
||||
default case
|
||||
The integer was <= 6
|
||||
The integer was <= 7
|
||||
The integer was <= 8
|
||||
default case
|
||||
|
||||
```
|
||||
## 函数
|
||||
函数是Go里面的核心设计,它通过关键字`func`来声明,它的格式如下:
|
||||
```Go
|
||||
|
||||
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
|
||||
//这里是处理逻辑代码
|
||||
//返回多个值
|
||||
return value1, value2
|
||||
}
|
||||
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
|
||||
//这里是处理逻辑代码
|
||||
//返回多个值
|
||||
return value1, value2
|
||||
}
|
||||
```
|
||||
上面的代码我们看出
|
||||
|
||||
@@ -215,30 +215,30 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
|
||||
下面我们来看一个实际应用函数的例子(用来计算Max值)
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
// 返回a、b中最大值.
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
// 返回a、b中最大值.
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
y := 4
|
||||
z := 5
|
||||
func main() {
|
||||
x := 3
|
||||
y := 4
|
||||
z := 5
|
||||
|
||||
max_xy := max(x, y) //调用函数max(x, y)
|
||||
max_xz := max(x, z) //调用函数max(x, z)
|
||||
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)) // 也可在这直接调用它
|
||||
}
|
||||
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个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型,这个就是省略写法。
|
||||
|
||||
@@ -248,46 +248,46 @@ Go语言比C更先进的特性,其中一点就是函数能够返回多个值
|
||||
我们直接上代码看例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
//返回 A+B 和 A*B
|
||||
func SumAndProduct(A, B int) (int, int) {
|
||||
return A+B, A*B
|
||||
}
|
||||
//返回 A+B 和 A*B
|
||||
func SumAndProduct(A, B int) (int, int) {
|
||||
return A+B, A*B
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
y := 4
|
||||
func main() {
|
||||
x := 3
|
||||
y := 4
|
||||
|
||||
xPLUSy, xTIMESy := SumAndProduct(x, y)
|
||||
xPLUSy, xTIMESy := SumAndProduct(x, y)
|
||||
|
||||
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
|
||||
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
|
||||
}
|
||||
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
|
||||
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
|
||||
}
|
||||
```
|
||||
上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写),官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。
|
||||
```Go
|
||||
|
||||
func SumAndProduct(A, B int) (add int, Multiplied int) {
|
||||
add = A+B
|
||||
Multiplied = A*B
|
||||
return
|
||||
}
|
||||
func SumAndProduct(A, B int) (add int, Multiplied int) {
|
||||
add = A+B
|
||||
Multiplied = A*B
|
||||
return
|
||||
}
|
||||
```
|
||||
### 变参
|
||||
Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:
|
||||
```Go
|
||||
|
||||
func myfunc(arg ...int) {}
|
||||
func myfunc(arg ...int) {}
|
||||
```
|
||||
`arg ...int`告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是`int`。在函数体中,变量`arg`是一个`int`的`slice`:
|
||||
```Go
|
||||
|
||||
for _, n := range arg {
|
||||
fmt.Printf("And the number is: %d\n", n)
|
||||
}
|
||||
for _, n := range arg {
|
||||
fmt.Printf("And the number is: %d\n", n)
|
||||
}
|
||||
```
|
||||
### 传值与传指针
|
||||
当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。
|
||||
@@ -295,26 +295,26 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。
|
||||
为了验证我们上面的说法,我们来看一个例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
//简单的一个函数,实现了参数+1的操作
|
||||
func add1(a int) int {
|
||||
a = a+1 // 我们改变了a的值
|
||||
return a //返回一个新值
|
||||
}
|
||||
//简单的一个函数,实现了参数+1的操作
|
||||
func add1(a int) int {
|
||||
a = a+1 // 我们改变了a的值
|
||||
return a //返回一个新值
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
func main() {
|
||||
x := 3
|
||||
|
||||
fmt.Println("x = ", x) // 应该输出 "x = 3"
|
||||
fmt.Println("x = ", x) // 应该输出 "x = 3"
|
||||
|
||||
x1 := add1(x) //调用add1(x)
|
||||
x1 := add1(x) //调用add1(x)
|
||||
|
||||
fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4"
|
||||
fmt.Println("x = ", x) // 应该输出"x = 3"
|
||||
}
|
||||
fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4"
|
||||
fmt.Println("x = ", x) // 应该输出"x = 3"
|
||||
}
|
||||
```
|
||||
看到了吗?虽然我们调用了`add1`函数,并且在`add1`中执行`a = a+1`操作,但是上面例子中`x`变量的值没有发生变化
|
||||
|
||||
@@ -325,26 +325,26 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。
|
||||
这就牵扯到了所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有`add1`函数知道`x`变量所在的地址,才能修改`x`变量的值。所以我们需要将`x`所在地址`&x`传入函数,并将函数的参数的类型由`int`改为`*int`,即改为指针类型,才能在函数中修改`x`变量的值。此时参数仍然是按copy传递的,只是copy的是一个指针。请看下面的例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
//简单的一个函数,实现了参数+1的操作
|
||||
func add1(a *int) int { // 请注意,
|
||||
*a = *a+1 // 修改了a的值
|
||||
return *a // 返回新值
|
||||
}
|
||||
//简单的一个函数,实现了参数+1的操作
|
||||
func add1(a *int) int { // 请注意,
|
||||
*a = *a+1 // 修改了a的值
|
||||
return *a // 返回新值
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
func main() {
|
||||
x := 3
|
||||
|
||||
fmt.Println("x = ", x) // 应该输出 "x = 3"
|
||||
fmt.Println("x = ", x) // 应该输出 "x = 3"
|
||||
|
||||
x1 := add1(&x) // 调用 add1(&x) 传x的地址
|
||||
x1 := add1(&x) // 调用 add1(&x) 传x的地址
|
||||
|
||||
fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4"
|
||||
fmt.Println("x = ", x) // 应该输出 "x = 4"
|
||||
}
|
||||
fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4"
|
||||
fmt.Println("x = ", x) // 应该输出 "x = 4"
|
||||
}
|
||||
```
|
||||
这样,我们就达到了修改`x`的目的。那么到底传指针有什么好处呢?
|
||||
|
||||
@@ -356,44 +356,44 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。
|
||||
Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的:
|
||||
```Go
|
||||
|
||||
func ReadWrite() bool {
|
||||
file.Open("file")
|
||||
// 做一些工作
|
||||
if failureX {
|
||||
file.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
if failureY {
|
||||
file.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
func ReadWrite() bool {
|
||||
file.Open("file")
|
||||
// 做一些工作
|
||||
if failureX {
|
||||
file.Close()
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
if failureY {
|
||||
file.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
file.Close()
|
||||
return true
|
||||
}
|
||||
```
|
||||
我们看到上面有很多重复的代码,Go的`defer`有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在`defer`后指定的函数会在函数退出前调用。
|
||||
```Go
|
||||
|
||||
func ReadWrite() bool {
|
||||
file.Open("file")
|
||||
defer file.Close()
|
||||
if failureX {
|
||||
return false
|
||||
}
|
||||
if failureY {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
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`
|
||||
```Go
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
defer fmt.Printf("%d ", i)
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
defer fmt.Printf("%d ", i)
|
||||
}
|
||||
```
|
||||
### 函数作为值、类型
|
||||
|
||||
@@ -404,46 +404,46 @@ Go语言中有种不错的设计,即延迟(defer)语句,你可以在函
|
||||
函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递,请看下面的例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
package main
|
||||
|
||||
type testInt func(int) bool // 声明了一个函数类型
|
||||
import "fmt"
|
||||
|
||||
func isOdd(integer int) bool {
|
||||
if integer%2 == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
type testInt func(int) bool // 声明了一个函数类型
|
||||
|
||||
func isEven(integer int) bool {
|
||||
if integer%2 == 0 {
|
||||
return true
|
||||
}
|
||||
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)
|
||||
}
|
||||
// 声明的函数类型在这个地方当做了一个参数
|
||||
|
||||
func filter(slice []int, f testInt) []int {
|
||||
var result []int
|
||||
for _, value := range slice {
|
||||
if f(value) {
|
||||
result = append(result, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
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)
|
||||
}
|
||||
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`类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。
|
||||
|
||||
@@ -460,26 +460,26 @@ Recover
|
||||
下面这个函数演示了如何在过程中使用`panic`
|
||||
```Go
|
||||
|
||||
var user = os.Getenv("USER")
|
||||
var user = os.Getenv("USER")
|
||||
|
||||
func init() {
|
||||
if user == "" {
|
||||
panic("no value for $USER")
|
||||
}
|
||||
func init() {
|
||||
if user == "" {
|
||||
panic("no value for $USER")
|
||||
}
|
||||
}
|
||||
```
|
||||
下面这个函数检查作为其参数的函数在执行时是否会产生`panic`:
|
||||
```Go
|
||||
|
||||
func throwsPanic(f func()) (b bool) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
b = true
|
||||
}
|
||||
}()
|
||||
f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
|
||||
return
|
||||
}
|
||||
func throwsPanic(f func()) (b bool) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
b = true
|
||||
}
|
||||
}()
|
||||
f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
|
||||
return
|
||||
}
|
||||
```
|
||||
### `main`函数和`init`函数
|
||||
|
||||
@@ -497,14 +497,14 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方
|
||||
我们在写Go代码的时候经常用到import这个命令用来导入包文件,而我们经常看到的方式参考如下:
|
||||
```Go
|
||||
|
||||
import(
|
||||
"fmt"
|
||||
)
|
||||
import(
|
||||
"fmt"
|
||||
)
|
||||
```
|
||||
然后我们代码里面可以通过如下的方式调用
|
||||
```Go
|
||||
|
||||
fmt.Println("hello world")
|
||||
fmt.Println("hello world")
|
||||
```
|
||||
上面这个fmt是Go语言的标准库,其实是去`GOROOT`环境变量指定目录下去加载该模块,当然Go的import还支持如下两种方式来加载自己写的模块:
|
||||
|
||||
@@ -545,10 +545,10 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方
|
||||
这个操作经常是让很多人费解的一个操作符,请看下面这个import
|
||||
```Go
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/ziutek/mymysql/godrv"
|
||||
)
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/ziutek/mymysql/godrv"
|
||||
)
|
||||
```
|
||||
_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
|
||||
|
||||
|
||||
260
zh/02.4.md
260
zh/02.4.md
@@ -3,10 +3,10 @@
|
||||
Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。例如,我们可以创建一个自定义类型`person`代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型我们称之`struct`。如下代码所示:
|
||||
```Go
|
||||
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
```
|
||||
看到了吗?声明一个struct如此简单,上面的类型包含有两个字段
|
||||
- 一个string类型的字段name,用来保存用户名称这个属性
|
||||
@@ -15,16 +15,16 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型
|
||||
如何使用struct呢?请看下面的代码
|
||||
```Go
|
||||
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
var P person // P现在就是person类型的变量了
|
||||
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.name = "Astaxie" // 赋值"Astaxie"给P的name属性.
|
||||
P.age = 25 // 赋值"25"给变量P的age属性
|
||||
fmt.Printf("The person's name is %s", P.name) // 访问P的name属性.
|
||||
```
|
||||
除了上面这种P的声明使用之外,还有另外几种声明使用方式:
|
||||
|
||||
@@ -43,50 +43,50 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型
|
||||
下面我们看一个完整的使用struct的例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
// 声明一个新的类型
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
// 声明一个新的类型
|
||||
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
|
||||
}
|
||||
|
||||
// 比较两个人的年龄,返回年龄大的那个人,并且返回年龄差
|
||||
// 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
|
||||
|
||||
func main() {
|
||||
var tom person
|
||||
// 赋值初始化
|
||||
tom.name, tom.age = "Tom", 18
|
||||
|
||||
// 赋值初始化
|
||||
tom.name, tom.age = "Tom", 18
|
||||
// 两个字段都写清楚的初始化
|
||||
bob := person{age:25, name:"Bob"}
|
||||
|
||||
// 两个字段都写清楚的初始化
|
||||
bob := person{age:25, name:"Bob"}
|
||||
// 按照struct定义顺序初始化值
|
||||
paul := person{"Paul", 43}
|
||||
|
||||
// 按照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)
|
||||
|
||||
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, 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",
|
||||
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)
|
||||
}
|
||||
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支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
|
||||
@@ -96,43 +96,43 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型
|
||||
让我们来看一个例子,让上面说的这些更具体化
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
weight int
|
||||
}
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
weight int
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human // 匿名字段,那么默认Student就包含了Human的所有字段
|
||||
speciality string
|
||||
}
|
||||
type Student struct {
|
||||
Human // 匿名字段,那么默认Student就包含了Human的所有字段
|
||||
speciality string
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 我们初始化一个学生
|
||||
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
|
||||
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)
|
||||
}
|
||||
// 我们访问相应的字段
|
||||
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)
|
||||
}
|
||||
```
|
||||
图例如下:
|
||||
|
||||
@@ -143,49 +143,49 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型
|
||||
我们看到Student访问属性age和name的时候,就像访问自己所有用的字段一样,对,匿名字段就是这样,能够实现字段的继承。是不是很酷啊?还有比这个更酷的呢,那就是student还能访问Human这个字段作为字段名。请看下面的代码,是不是更酷了。
|
||||
```Go
|
||||
|
||||
mark.Human = Human{"Marcus", 55, 220}
|
||||
mark.Human.age -= 1
|
||||
mark.Human = Human{"Marcus", 55, 220}
|
||||
mark.Human.age -= 1
|
||||
```
|
||||
通过匿名访问和修改字段相当的有用,但是不仅仅是struct字段哦,所有的内置类型和自定义类型都是可以作为匿名字段的。请看下面的例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
type Skills []string
|
||||
type Skills []string
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
weight int
|
||||
}
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
weight int
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human // 匿名字段,struct
|
||||
Skills // 匿名字段,自定义的类型string slice
|
||||
int // 内置类型作为匿名字段
|
||||
speciality string
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)。
|
||||
|
||||
@@ -196,28 +196,28 @@ Go里面很简单的解决了这个问题,最外层的优先访问,也就是
|
||||
这样就允许我们去重载通过匿名字段继承的一些字段,当然如果我们想访问重载后对应匿名类型里面的字段,可以通过匿名字段名来访问。请看下面的例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
package main
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string // Human类型拥有的字段
|
||||
}
|
||||
import "fmt"
|
||||
|
||||
type Employee struct {
|
||||
Human // 匿名字段Human
|
||||
speciality string
|
||||
phone string // 雇员的phone字段
|
||||
}
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string // Human类型拥有的字段
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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
|
||||
|
||||
330
zh/02.5.md
330
zh/02.5.md
@@ -5,24 +5,24 @@
|
||||
现在假设有这么一个场景,你定义了一个struct叫做长方形,你现在想要计算他的面积,那么按照我们一般的思路应该会用下面的方式来实现
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
|
||||
func area(r Rectangle) float64 {
|
||||
return r.width*r.height
|
||||
}
|
||||
func area(r Rectangle) float64 {
|
||||
return r.width*r.height
|
||||
}
|
||||
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
fmt.Println("Area of r1 is: ", area(r1))
|
||||
fmt.Println("Area of r2 is: ", area(r2))
|
||||
}
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
fmt.Println("Area of r1 is: ", area(r1))
|
||||
fmt.Println("Area of r2 is: ", area(r2))
|
||||
}
|
||||
```
|
||||
这段代码可以计算出来长方形的面积,但是area()不是作为Rectangle的方法实现的(类似面向对象里面的方法),而是将Rectangle的对象(如r1,r2)作为参数传入函数计算面积的。
|
||||
|
||||
@@ -53,41 +53,41 @@ method的语法如下:
|
||||
下面我们用最开始的例子用method来实现:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
|
||||
type Circle struct {
|
||||
radius float64
|
||||
}
|
||||
type Circle struct {
|
||||
radius float64
|
||||
}
|
||||
|
||||
func (r Rectangle) area() float64 {
|
||||
return r.width*r.height
|
||||
}
|
||||
func (r Rectangle) area() float64 {
|
||||
return r.width*r.height
|
||||
}
|
||||
|
||||
func (c Circle) area() float64 {
|
||||
return c.radius * c.radius * math.Pi
|
||||
}
|
||||
func (c Circle) area() float64 {
|
||||
return c.radius * c.radius * math.Pi
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
c1 := Circle{10}
|
||||
c2 := Circle{25}
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
c1 := Circle{10}
|
||||
c2 := Circle{25}
|
||||
|
||||
fmt.Println("Area of r1 is: ", r1.area())
|
||||
fmt.Println("Area of r2 is: ", r2.area())
|
||||
fmt.Println("Area of c1 is: ", c1.area())
|
||||
fmt.Println("Area of c2 is: ", c2.area())
|
||||
}
|
||||
fmt.Println("Area of r1 is: ", r1.area())
|
||||
fmt.Println("Area of r2 is: ", r2.area())
|
||||
fmt.Println("Area of c1 is: ", c1.area())
|
||||
fmt.Println("Area of c2 is: ", c2.area())
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -110,23 +110,23 @@ method的语法如下:
|
||||
那是不是method只能作用在struct上面呢?当然不是咯,他可以定义在任何你自定义的类型、内置类型、struct等各种类型上面。这里你是不是有点迷糊了,什么叫自定义类型,自定义类型不就是struct嘛,不是这样的哦,struct只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。
|
||||
```Go
|
||||
|
||||
type typeName typeLiteral
|
||||
type typeName typeLiteral
|
||||
```
|
||||
请看下面这个申明自定义类型的代码
|
||||
```Go
|
||||
|
||||
type ages int
|
||||
type ages int
|
||||
|
||||
type money float32
|
||||
type money float32
|
||||
|
||||
type months map[string]int
|
||||
type months map[string]int
|
||||
|
||||
m := months {
|
||||
"January":31,
|
||||
"February":28,
|
||||
...
|
||||
"December":31,
|
||||
}
|
||||
m := months {
|
||||
"January":31,
|
||||
"February":28,
|
||||
...
|
||||
"December":31,
|
||||
}
|
||||
```
|
||||
看到了吗?简单的很吧,这样你就可以在自己的代码里面定义有意义的类型了,实际上只是一个定义了一个别名,有点类似于c中的typedef,例如上面ages替代了int
|
||||
|
||||
@@ -135,79 +135,79 @@ method的语法如下:
|
||||
你可以在任何的自定义类型中定义任意多的`method`,接下来让我们看一个复杂一点的例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
package main
|
||||
|
||||
const(
|
||||
WHITE = iota
|
||||
BLACK
|
||||
BLUE
|
||||
RED
|
||||
YELLOW
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
type Color byte
|
||||
const(
|
||||
WHITE = iota
|
||||
BLACK
|
||||
BLUE
|
||||
RED
|
||||
YELLOW
|
||||
)
|
||||
|
||||
type Box struct {
|
||||
width, height, depth float64
|
||||
color Color
|
||||
}
|
||||
type Color byte
|
||||
|
||||
type BoxList []Box //a slice of boxes
|
||||
type Box struct {
|
||||
width, height, depth float64
|
||||
color Color
|
||||
}
|
||||
|
||||
func (b Box) Volume() float64 {
|
||||
return b.width * b.height * b.depth
|
||||
}
|
||||
type BoxList []Box //a slice of boxes
|
||||
|
||||
func (b *Box) SetColor(c Color) {
|
||||
b.color = c
|
||||
}
|
||||
func (b Box) Volume() float64 {
|
||||
return b.width * b.height * b.depth
|
||||
}
|
||||
|
||||
func (bl BoxList) BiggestColor() Color {
|
||||
v := 0.00
|
||||
k := Color(WHITE)
|
||||
for _, b := range bl {
|
||||
if bv := b.Volume(); bv > v {
|
||||
v = bv
|
||||
k = b.color
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
||||
func (b *Box) SetColor(c Color) {
|
||||
b.color = c
|
||||
}
|
||||
|
||||
func (bl BoxList) PaintItBlack() {
|
||||
for i, _ := range bl {
|
||||
bl[i].SetColor(BLACK)
|
||||
func (bl BoxList) BiggestColor() Color {
|
||||
v := 0.00
|
||||
k := Color(WHITE)
|
||||
for _, b := range bl {
|
||||
if bv := b.Volume(); bv > v {
|
||||
v = bv
|
||||
k = b.color
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func (c Color) String() string {
|
||||
strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
|
||||
return strings[c]
|
||||
func (bl BoxList) PaintItBlack() {
|
||||
for i, _ := range bl {
|
||||
bl[i].SetColor(BLACK)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Color) String() string {
|
||||
strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
|
||||
return strings[c]
|
||||
}
|
||||
|
||||
func main() {
|
||||
boxes := BoxList {
|
||||
Box{4, 4, 4, RED},
|
||||
Box{10, 10, 1, YELLOW},
|
||||
Box{1, 1, 20, BLACK},
|
||||
Box{10, 10, 1, BLUE},
|
||||
Box{10, 30, 1, WHITE},
|
||||
Box{20, 20, 20, YELLOW},
|
||||
}
|
||||
|
||||
func main() {
|
||||
boxes := BoxList {
|
||||
Box{4, 4, 4, RED},
|
||||
Box{10, 10, 1, YELLOW},
|
||||
Box{1, 1, 20, BLACK},
|
||||
Box{10, 10, 1, BLUE},
|
||||
Box{10, 30, 1, WHITE},
|
||||
Box{20, 20, 20, YELLOW},
|
||||
}
|
||||
fmt.Printf("We have %d boxes in our set\n", len(boxes))
|
||||
fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
|
||||
fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
|
||||
fmt.Println("The biggest one is", boxes.BiggestColor().String())
|
||||
|
||||
fmt.Printf("We have %d boxes in our set\n", len(boxes))
|
||||
fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
|
||||
fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
|
||||
fmt.Println("The biggest one is", boxes.BiggestColor().String())
|
||||
fmt.Println("Let's paint them all black")
|
||||
boxes.PaintItBlack()
|
||||
fmt.Println("The color of the second one is", boxes[1].color.String())
|
||||
|
||||
fmt.Println("Let's paint them all black")
|
||||
boxes.PaintItBlack()
|
||||
fmt.Println("The color of the second one is", boxes[1].color.String())
|
||||
|
||||
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String())
|
||||
}
|
||||
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String())
|
||||
}
|
||||
```
|
||||
上面的代码通过const定义了一些常量,然后定义了一些自定义类型
|
||||
|
||||
@@ -252,81 +252,81 @@ method的语法如下:
|
||||
前面一章我们学习了字段的继承,那么你也会发现Go的一个神奇之处,method也是可以继承的。如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。让我们来看下面这个例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human //匿名字段
|
||||
school string
|
||||
}
|
||||
type Student struct {
|
||||
Human //匿名字段
|
||||
school string
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human //匿名字段
|
||||
company string
|
||||
}
|
||||
type Employee struct {
|
||||
Human //匿名字段
|
||||
company string
|
||||
}
|
||||
|
||||
//在human上面定义了一个method
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
//在human上面定义了一个method
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
```
|
||||
### method重写
|
||||
上面的例子中,如果Employee想要实现自己的SayHi,怎么办?简单,和匿名字段冲突一样的道理,我们可以在Employee上面定义一个method,重写了匿名字段的方法。请看下面的例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
package main
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
import "fmt"
|
||||
|
||||
type Student struct {
|
||||
Human //匿名字段
|
||||
school string
|
||||
}
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human //匿名字段
|
||||
company string
|
||||
}
|
||||
type Student struct {
|
||||
Human //匿名字段
|
||||
school string
|
||||
}
|
||||
|
||||
//Human定义method
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
type Employee struct {
|
||||
Human //匿名字段
|
||||
company string
|
||||
}
|
||||
|
||||
//Employee的method重写Human的method
|
||||
func (e *Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //Yes you can split into 2 lines here.
|
||||
}
|
||||
//Human定义method
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
//Employee的method重写Human的method
|
||||
func (e *Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //Yes you can split into 2 lines here.
|
||||
}
|
||||
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
```
|
||||
上面的代码设计的是如此的美妙,让人不自觉的为Go的设计惊叹!
|
||||
|
||||
|
||||
510
zh/02.6.md
510
zh/02.6.md
@@ -16,73 +16,73 @@ Go语言里面设计最精妙的应该算interface,它让面向对象,内容
|
||||
interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子
|
||||
```Go
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human //匿名字段Human
|
||||
school string
|
||||
loan float32
|
||||
}
|
||||
type Student struct {
|
||||
Human //匿名字段Human
|
||||
school string
|
||||
loan float32
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human //匿名字段Human
|
||||
company string
|
||||
money float32
|
||||
}
|
||||
type Employee struct {
|
||||
Human //匿名字段Human
|
||||
company string
|
||||
money float32
|
||||
}
|
||||
|
||||
//Human对象实现Sayhi方法
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
//Human对象实现Sayhi方法
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
// Human对象实现Sing方法
|
||||
func (h *Human) Sing(lyrics string) {
|
||||
fmt.Println("La la, la la la, la la la la la...", lyrics)
|
||||
}
|
||||
// Human对象实现Sing方法
|
||||
func (h *Human) Sing(lyrics string) {
|
||||
fmt.Println("La la, la la la, la la la la la...", lyrics)
|
||||
}
|
||||
|
||||
//Human对象实现Guzzle方法
|
||||
func (h *Human) Guzzle(beerStein string) {
|
||||
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
|
||||
}
|
||||
//Human对象实现Guzzle方法
|
||||
func (h *Human) Guzzle(beerStein string) {
|
||||
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
|
||||
}
|
||||
|
||||
// Employee重载Human的Sayhi方法
|
||||
func (e *Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //此句可以分成多行
|
||||
}
|
||||
// Employee重载Human的Sayhi方法
|
||||
func (e *Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //此句可以分成多行
|
||||
}
|
||||
|
||||
//Student实现BorrowMoney方法
|
||||
func (s *Student) BorrowMoney(amount float32) {
|
||||
s.loan += amount // (again and again and...)
|
||||
}
|
||||
//Student实现BorrowMoney方法
|
||||
func (s *Student) BorrowMoney(amount float32) {
|
||||
s.loan += amount // (again and again and...)
|
||||
}
|
||||
|
||||
//Employee实现SpendSalary方法
|
||||
func (e *Employee) SpendSalary(amount float32) {
|
||||
e.money -= amount // More vodka please!!! Get me through the day!
|
||||
}
|
||||
//Employee实现SpendSalary方法
|
||||
func (e *Employee) SpendSalary(amount float32) {
|
||||
e.money -= amount // More vodka please!!! Get me through the day!
|
||||
}
|
||||
|
||||
// 定义interface
|
||||
type Men interface {
|
||||
SayHi()
|
||||
Sing(lyrics string)
|
||||
Guzzle(beerStein string)
|
||||
}
|
||||
// 定义interface
|
||||
type Men interface {
|
||||
SayHi()
|
||||
Sing(lyrics string)
|
||||
Guzzle(beerStein string)
|
||||
}
|
||||
|
||||
type YoungChap interface {
|
||||
SayHi()
|
||||
Sing(song string)
|
||||
BorrowMoney(amount float32)
|
||||
}
|
||||
type YoungChap interface {
|
||||
SayHi()
|
||||
Sing(song string)
|
||||
BorrowMoney(amount float32)
|
||||
}
|
||||
|
||||
type ElderlyGent interface {
|
||||
SayHi()
|
||||
Sing(song string)
|
||||
SpendSalary(amount float32)
|
||||
}
|
||||
type ElderlyGent interface {
|
||||
SayHi()
|
||||
Sing(song string)
|
||||
SpendSalary(amount float32)
|
||||
}
|
||||
```
|
||||
通过上面的代码我们可以知道,interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理,一个对象可以实现任意多个interface,例如上面的Student实现了Men和YoungChap两个interface。
|
||||
|
||||
@@ -96,82 +96,82 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
|
||||
让我们来看一下下面这个例子:
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
package main
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human //匿名字段
|
||||
school string
|
||||
loan float32
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human //匿名字段
|
||||
company string
|
||||
money float32
|
||||
}
|
||||
|
||||
//Human实现SayHi方法
|
||||
func (h Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
//Human实现Sing方法
|
||||
func (h Human) Sing(lyrics string) {
|
||||
fmt.Println("La la la la...", lyrics)
|
||||
}
|
||||
|
||||
//Employee重载Human的SayHi方法
|
||||
func (e Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone)
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human //匿名字段
|
||||
school string
|
||||
loan float32
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human //匿名字段
|
||||
company string
|
||||
money float32
|
||||
}
|
||||
|
||||
//Human实现SayHi方法
|
||||
func (h Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
//Human实现Sing方法
|
||||
func (h Human) Sing(lyrics string) {
|
||||
fmt.Println("La la la la...", lyrics)
|
||||
}
|
||||
|
||||
//Employee重载Human的SayHi方法
|
||||
func (e Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone)
|
||||
}
|
||||
|
||||
// Interface Men被Human,Student和Employee实现
|
||||
// 因为这三个类型都实现了这两个方法
|
||||
type Men interface {
|
||||
SayHi()
|
||||
Sing(lyrics string)
|
||||
}
|
||||
|
||||
func main() {
|
||||
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
|
||||
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
|
||||
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
|
||||
tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
|
||||
|
||||
//定义Men类型的变量i
|
||||
var i Men
|
||||
|
||||
//i能存储Student
|
||||
i = mike
|
||||
fmt.Println("This is Mike, a Student:")
|
||||
i.SayHi()
|
||||
i.Sing("November rain")
|
||||
|
||||
//i也能存储Employee
|
||||
i = tom
|
||||
fmt.Println("This is tom, an Employee:")
|
||||
i.SayHi()
|
||||
i.Sing("Born to be wild")
|
||||
|
||||
//定义了slice Men
|
||||
fmt.Println("Let's use a slice of Men and see what happens")
|
||||
x := make([]Men, 3)
|
||||
//这三个都是不同类型的元素,但是他们实现了interface同一个接口
|
||||
x[0], x[1], x[2] = paul, sam, mike
|
||||
|
||||
for _, value := range x{
|
||||
value.SayHi()
|
||||
}
|
||||
// Interface Men被Human,Student和Employee实现
|
||||
// 因为这三个类型都实现了这两个方法
|
||||
type Men interface {
|
||||
SayHi()
|
||||
Sing(lyrics string)
|
||||
}
|
||||
|
||||
func main() {
|
||||
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
|
||||
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
|
||||
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
|
||||
tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
|
||||
|
||||
//定义Men类型的变量i
|
||||
var i Men
|
||||
|
||||
//i能存储Student
|
||||
i = mike
|
||||
fmt.Println("This is Mike, a Student:")
|
||||
i.SayHi()
|
||||
i.Sing("November rain")
|
||||
|
||||
//i也能存储Employee
|
||||
i = tom
|
||||
fmt.Println("This is tom, an Employee:")
|
||||
i.SayHi()
|
||||
i.Sing("Born to be wild")
|
||||
|
||||
//定义了slice Men
|
||||
fmt.Println("Let's use a slice of Men and see what happens")
|
||||
x := make([]Men, 3)
|
||||
//这三个都是不同类型的元素,但是他们实现了interface同一个接口
|
||||
x[0], x[1], x[2] = paul, sam, mike
|
||||
|
||||
for _, value := range x{
|
||||
value.SayHi()
|
||||
}
|
||||
}
|
||||
```
|
||||
通过上面的代码,你会发现interface就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, Go通过interface实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。
|
||||
|
||||
@@ -179,13 +179,13 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
|
||||
空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。
|
||||
```Go
|
||||
|
||||
// 定义a为空接口
|
||||
var a interface{}
|
||||
var i int = 5
|
||||
s := "Hello world"
|
||||
// a可以存储任意类型的数值
|
||||
a = i
|
||||
a = s
|
||||
// 定义a为空接口
|
||||
var a interface{}
|
||||
var i int = 5
|
||||
s := "Hello world"
|
||||
// a可以存储任意类型的数值
|
||||
a = i
|
||||
a = s
|
||||
```
|
||||
一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊!
|
||||
### interface函数参数
|
||||
@@ -194,41 +194,41 @@ interface的变量可以持有任意实现该interface类型的对象,这给
|
||||
举个例子:fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:
|
||||
```Go
|
||||
|
||||
type Stringer interface {
|
||||
String() string
|
||||
}
|
||||
type Stringer interface {
|
||||
String() string
|
||||
}
|
||||
```
|
||||
也就是说,任何实现了String方法的类型都能作为参数被fmt.Println调用,让我们来试一试
|
||||
```Go
|
||||
|
||||
package main
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
package main
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
// 通过这个方法 Human 实现了 fmt.Stringer
|
||||
func (h Human) String() string {
|
||||
return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱"
|
||||
}
|
||||
// 通过这个方法 Human 实现了 fmt.Stringer
|
||||
func (h Human) String() string {
|
||||
return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱"
|
||||
}
|
||||
|
||||
func main() {
|
||||
Bob := Human{"Bob", 39, "000-7777-XXX"}
|
||||
fmt.Println("This Human is : ", Bob)
|
||||
}
|
||||
func main() {
|
||||
Bob := Human{"Bob", 39, "000-7777-XXX"}
|
||||
fmt.Println("This Human is : ", Bob)
|
||||
}
|
||||
```
|
||||
现在我们再回顾一下前面的Box示例,你会发现Color结构也定义了一个method:String。其实这也是实现了fmt.Stringer这个interface,即如果需要某个类型能被fmt包以特殊的格式输出,你就必须实现Stringer这个接口。如果没有实现这个接口,fmt将以默认的方式输出。
|
||||
```Go
|
||||
|
||||
//实现同样的功能
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor())
|
||||
//实现同样的功能
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor())
|
||||
```
|
||||
注:实现了error接口的对象(即实现了Error() string的对象),使用fmt输出时,会调用Error()方法,因此不必再定义String()方法了。
|
||||
### interface变量存储的类型
|
||||
@@ -244,44 +244,44 @@ interface的变量可以持有任意实现该interface类型的对象,这给
|
||||
让我们通过一个例子来更加深入的理解。
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Element interface{}
|
||||
type List [] Element
|
||||
type Element interface{}
|
||||
type List [] Element
|
||||
|
||||
type Person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
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)"
|
||||
}
|
||||
//定义了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}
|
||||
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.Printf("list[%d] is of a different type\n", index)
|
||||
}
|
||||
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.Printf("list[%d] is of a different type\n", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
是不是很简单啊,同时你是否注意到了多个if里面,还记得我前面介绍流程时讲过,if里面允许初始化变量。
|
||||
|
||||
@@ -291,45 +291,45 @@ interface的变量可以持有任意实现该interface类型的对象,这给
|
||||
最好的讲解就是代码例子,现在让我们重写上面的这个实现
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Element interface{}
|
||||
type List [] Element
|
||||
type Element interface{}
|
||||
type List [] Element
|
||||
|
||||
type Person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
type Person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
//打印
|
||||
func (p Person) String() string {
|
||||
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
|
||||
}
|
||||
//打印
|
||||
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}
|
||||
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)
|
||||
}
|
||||
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`。
|
||||
|
||||
@@ -339,33 +339,33 @@ Go里面真正吸引人的是它内置的逻辑语法,就像我们在学习Str
|
||||
我们可以看到源码包container/heap里面有这样的一个定义
|
||||
```Go
|
||||
|
||||
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
|
||||
}
|
||||
type Interface interface {
|
||||
sort.Interface //嵌入字段sort.Interface
|
||||
Push(x interface{}) //a Push method to push elements into the heap
|
||||
Pop() interface{} //a Pop elements that pops elements from the heap
|
||||
}
|
||||
```
|
||||
我们看到sort.Interface其实就是嵌入字段,把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法:
|
||||
```Go
|
||||
|
||||
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)
|
||||
}
|
||||
type Interface interface {
|
||||
// Len is the number of elements in the collection.
|
||||
Len() int
|
||||
// Less returns whether the element with index i should sort
|
||||
// before the element with index j.
|
||||
Less(i, j int) bool
|
||||
// Swap swaps the elements with indexes i and j.
|
||||
Swap(i, j int)
|
||||
}
|
||||
```
|
||||
另一个例子就是io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer两个interface:
|
||||
```Go
|
||||
|
||||
// io.ReadWriter
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
// io.ReadWriter
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
```
|
||||
### 反射
|
||||
Go语言实现了反射,所谓反射就是能检查程序在运行时的状态。我们一般用到的包是reflect包。如何运用reflect包,官方的这篇文章详细的讲解了reflect包的实现原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html)
|
||||
@@ -373,38 +373,38 @@ Go语言实现了反射,所谓反射就是能检查程序在运行时的状态
|
||||
使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下:
|
||||
```Go
|
||||
|
||||
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
|
||||
v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
|
||||
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
|
||||
v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
|
||||
```
|
||||
转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如
|
||||
```Go
|
||||
|
||||
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
|
||||
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
|
||||
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
|
||||
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
|
||||
```
|
||||
获取反射值能返回相应的类型和数值
|
||||
```Go
|
||||
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
fmt.Println("type:", v.Type())
|
||||
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
|
||||
fmt.Println("value:", v.Float())
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
fmt.Println("type:", v.Type())
|
||||
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
|
||||
fmt.Println("value:", v.Float())
|
||||
```
|
||||
最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误
|
||||
```Go
|
||||
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
v.SetFloat(7.1)
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
v.SetFloat(7.1)
|
||||
```
|
||||
如果要修改相应的值,必须这样写
|
||||
```Go
|
||||
|
||||
var x float64 = 3.4
|
||||
p := reflect.ValueOf(&x)
|
||||
v := p.Elem()
|
||||
v.SetFloat(7.1)
|
||||
var x float64 = 3.4
|
||||
p := reflect.ValueOf(&x)
|
||||
v := p.Elem()
|
||||
v.SetFloat(7.1)
|
||||
```
|
||||
上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。
|
||||
|
||||
|
||||
236
zh/02.7.md
236
zh/02.7.md
@@ -9,40 +9,40 @@ goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但
|
||||
goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`go`关键字实现了,其实就是一个普通的函数。
|
||||
```Go
|
||||
|
||||
go hello(a, b, c)
|
||||
go hello(a, b, c)
|
||||
```
|
||||
通过关键字go就启动了一个goroutine。我们来看一个例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func say(s string) {
|
||||
for i := 0; i < 5; i++ {
|
||||
runtime.Gosched()
|
||||
fmt.Println(s)
|
||||
}
|
||||
func say(s string) {
|
||||
for i := 0; i < 5; i++ {
|
||||
runtime.Gosched()
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go say("world") //开一个新的Goroutines执行
|
||||
say("hello") //当前Goroutines执行
|
||||
}
|
||||
func main() {
|
||||
go say("world") //开一个新的Goroutines执行
|
||||
say("hello") //当前Goroutines执行
|
||||
}
|
||||
|
||||
// 以上程序执行后将输出:
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// 以上程序执行后将输出:
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
```
|
||||
我们可以看到go关键字很方便的就实现了并发编程。
|
||||
上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。
|
||||
@@ -57,41 +57,41 @@ goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`g
|
||||
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel:
|
||||
```Go
|
||||
|
||||
ci := make(chan int)
|
||||
cs := make(chan string)
|
||||
cf := make(chan interface{})
|
||||
ci := make(chan int)
|
||||
cs := make(chan string)
|
||||
cf := make(chan interface{})
|
||||
```
|
||||
channel通过操作符`<-`来接收和发送数据
|
||||
```Go
|
||||
|
||||
ch <- v // 发送v到channel ch.
|
||||
v := <-ch // 从ch中接收数据,并赋值给v
|
||||
ch <- v // 发送v到channel ch.
|
||||
v := <-ch // 从ch中接收数据,并赋值给v
|
||||
```
|
||||
我们把这些应用到我们的例子中来:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
func sum(a []int, c chan int) {
|
||||
total := 0
|
||||
for _, v := range a {
|
||||
total += v
|
||||
}
|
||||
c <- total // send total to c
|
||||
func sum(a []int, c chan int) {
|
||||
total := 0
|
||||
for _, v := range a {
|
||||
total += v
|
||||
}
|
||||
c <- total // send total to c
|
||||
}
|
||||
|
||||
func main() {
|
||||
a := []int{7, 2, 8, -9, 4, 0}
|
||||
func main() {
|
||||
a := []int{7, 2, 8, -9, 4, 0}
|
||||
|
||||
c := make(chan int)
|
||||
go sum(a[:len(a)/2], c)
|
||||
go sum(a[len(a)/2:], c)
|
||||
x, y := <-c, <-c // receive from c
|
||||
c := make(chan int)
|
||||
go sum(a[:len(a)/2], c)
|
||||
go sum(a[len(a)/2:], c)
|
||||
x, y := <-c, <-c // receive from c
|
||||
|
||||
fmt.Println(x, y, x + y)
|
||||
}
|
||||
fmt.Println(x, y, x + y)
|
||||
}
|
||||
```
|
||||
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。
|
||||
|
||||
@@ -99,24 +99,24 @@ channel通过操作符`<-`来接收和发送数据
|
||||
上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。
|
||||
```Go
|
||||
|
||||
ch := make(chan type, value)
|
||||
ch := make(chan type, value)
|
||||
```
|
||||
当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。
|
||||
|
||||
我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
|
||||
c <- 1
|
||||
c <- 2
|
||||
fmt.Println(<-c)
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
func main() {
|
||||
c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
|
||||
c <- 1
|
||||
c <- 2
|
||||
fmt.Println(<-c)
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
//修改为1报如下的错误:
|
||||
//fatal error: all goroutines are asleep - deadlock!
|
||||
```
|
||||
@@ -124,28 +124,28 @@ channel通过操作符`<-`来接收和发送数据
|
||||
上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func fibonacci(n int, c chan int) {
|
||||
x, y := 1, 1
|
||||
for i := 0; i < n; i++ {
|
||||
c <- x
|
||||
x, y = y, x + y
|
||||
}
|
||||
close(c)
|
||||
func fibonacci(n int, c chan int) {
|
||||
x, y := 1, 1
|
||||
for i := 0; i < n; i++ {
|
||||
c <- x
|
||||
x, y = y, x + y
|
||||
}
|
||||
close(c)
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 10)
|
||||
go fibonacci(cap(c), c)
|
||||
for i := range c {
|
||||
fmt.Println(i)
|
||||
}
|
||||
func main() {
|
||||
c := make(chan int, 10)
|
||||
go fibonacci(cap(c), c)
|
||||
for i := range c {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
```
|
||||
`for i := range c`能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过内置函数`close`关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法`v, ok := <-ch`测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。
|
||||
|
||||
@@ -159,66 +159,66 @@ channel通过操作符`<-`来接收和发送数据
|
||||
`select`默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
func fibonacci(c, quit chan int) {
|
||||
x, y := 1, 1
|
||||
for {
|
||||
select {
|
||||
case c <- x:
|
||||
x, y = y, x + y
|
||||
case <-quit:
|
||||
fmt.Println("quit")
|
||||
return
|
||||
}
|
||||
func fibonacci(c, quit chan int) {
|
||||
x, y := 1, 1
|
||||
for {
|
||||
select {
|
||||
case c <- x:
|
||||
x, y = y, x + y
|
||||
case <-quit:
|
||||
fmt.Println("quit")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
quit := make(chan int)
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
quit <- 0
|
||||
}()
|
||||
fibonacci(c, quit)
|
||||
}
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
quit := make(chan int)
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
quit <- 0
|
||||
}()
|
||||
fibonacci(c, quit)
|
||||
}
|
||||
```
|
||||
在`select`里面还有default语法,`select`其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。
|
||||
```Go
|
||||
|
||||
select {
|
||||
case i := <-c:
|
||||
// use i
|
||||
default:
|
||||
// 当c阻塞的时候执行这里
|
||||
}
|
||||
select {
|
||||
case i := <-c:
|
||||
// use i
|
||||
default:
|
||||
// 当c阻塞的时候执行这里
|
||||
}
|
||||
```
|
||||
## 超时
|
||||
有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现:
|
||||
```Go
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
o := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case v := <- c:
|
||||
println(v)
|
||||
case <- time.After(5 * time.Second):
|
||||
println("timeout")
|
||||
o <- true
|
||||
break
|
||||
}
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
o := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case v := <- c:
|
||||
println(v)
|
||||
case <- time.After(5 * time.Second):
|
||||
println("timeout")
|
||||
o <- true
|
||||
break
|
||||
}
|
||||
}()
|
||||
<- o
|
||||
}
|
||||
}
|
||||
}()
|
||||
<- o
|
||||
}
|
||||
```
|
||||
|
||||
## runtime goroutine
|
||||
|
||||
10
zh/02.8.md
10
zh/02.8.md
@@ -3,11 +3,11 @@
|
||||
这一章我们主要介绍了Go语言的一些语法,通过语法我们可以发现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
|
||||
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已经有过短暂的接触
|
||||
|
||||
50
zh/03.2.md
50
zh/03.2.md
@@ -5,36 +5,36 @@
|
||||
## http包建立Web服务器
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"log"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"log"
|
||||
)
|
||||
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm() //解析参数,默认是不会解析的
|
||||
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
|
||||
fmt.Println("path", r.URL.Path)
|
||||
fmt.Println("scheme", r.URL.Scheme)
|
||||
fmt.Println(r.Form["url_long"])
|
||||
for k, v := range r.Form {
|
||||
fmt.Println("key:", k)
|
||||
fmt.Println("val:", strings.Join(v, ""))
|
||||
}
|
||||
fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm() //解析参数,默认是不会解析的
|
||||
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
|
||||
fmt.Println("path", r.URL.Path)
|
||||
fmt.Println("scheme", r.URL.Scheme)
|
||||
fmt.Println(r.Form["url_long"])
|
||||
for k, v := range r.Form {
|
||||
fmt.Println("key:", k)
|
||||
fmt.Println("val:", strings.Join(v, ""))
|
||||
}
|
||||
fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", sayhelloName) //设置访问的路由
|
||||
err := http.ListenAndServe(":9090", nil) //设置监听的端口
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
func main() {
|
||||
http.HandleFunc("/", sayhelloName) //设置访问的路由
|
||||
err := http.ListenAndServe(":9090", nil) //设置监听的端口
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
上面这个代码,我们build之后,然后执行web.exe,这个时候其实已经在9090端口监听http链接请求了。
|
||||
|
||||
|
||||
48
zh/03.3.md
48
zh/03.3.md
@@ -38,35 +38,35 @@ Handler:处理请求和生成返回信息的处理逻辑
|
||||
下面代码来自Go的http包的源码,通过下面的代码我们可以看到整个的http处理过程:
|
||||
```Go
|
||||
|
||||
func (srv *Server) Serve(l net.Listener) error {
|
||||
defer l.Close()
|
||||
var tempDelay time.Duration // how long to sleep on accept failure
|
||||
for {
|
||||
rw, e := l.Accept()
|
||||
if e != nil {
|
||||
if ne, ok := e.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
func (srv *Server) Serve(l net.Listener) error {
|
||||
defer l.Close()
|
||||
var tempDelay time.Duration // how long to sleep on accept failure
|
||||
for {
|
||||
rw, e := l.Accept()
|
||||
if e != nil {
|
||||
if ne, ok := e.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
return e
|
||||
}
|
||||
tempDelay = 0
|
||||
c, err := srv.newConn(rw)
|
||||
if err != nil {
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
go c.serve()
|
||||
return e
|
||||
}
|
||||
tempDelay = 0
|
||||
c, err := srv.newConn(rw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go c.serve()
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。
|
||||
|
||||
150
zh/03.4.md
150
zh/03.4.md
@@ -9,11 +9,11 @@ Go的http有两个核心功能:Conn、ServeMux
|
||||
Go在等待客户端请求里面是这样写的:
|
||||
```Go
|
||||
|
||||
c, err := srv.newConn(rw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go c.serve()
|
||||
c, err := srv.newConn(rw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go c.serve()
|
||||
|
||||
```
|
||||
这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中便可以读取到相应的header信息,这样保证了每个请求的独立性。
|
||||
@@ -24,85 +24,85 @@ Go在等待客户端请求里面是这样写的:
|
||||
它的结构如下:
|
||||
```Go
|
||||
|
||||
type ServeMux struct {
|
||||
mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
|
||||
m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
|
||||
hosts bool // 是否在任意的规则中带有host信息
|
||||
}
|
||||
type ServeMux struct {
|
||||
mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
|
||||
m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
|
||||
hosts bool // 是否在任意的规则中带有host信息
|
||||
}
|
||||
|
||||
```
|
||||
下面看一下muxEntry
|
||||
```Go
|
||||
|
||||
type muxEntry struct {
|
||||
explicit bool // 是否精确匹配
|
||||
h Handler // 这个路由表达式对应哪个handler
|
||||
pattern string //匹配字符串
|
||||
}
|
||||
type muxEntry struct {
|
||||
explicit bool // 是否精确匹配
|
||||
h Handler // 这个路由表达式对应哪个handler
|
||||
pattern string //匹配字符串
|
||||
}
|
||||
|
||||
```
|
||||
接着看一下Handler的定义
|
||||
```Go
|
||||
|
||||
type Handler interface {
|
||||
ServeHTTP(ResponseWriter, *Request) // 路由实现器
|
||||
}
|
||||
type Handler interface {
|
||||
ServeHTTP(ResponseWriter, *Request) // 路由实现器
|
||||
}
|
||||
|
||||
```
|
||||
Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有实现ServeHTTP这个接口,为什么能添加呢?原来在http包里面还定义了一个类型`HandlerFunc`,我们定义的函数`sayhelloName`就是这个HandlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。
|
||||
```Go
|
||||
|
||||
type HandlerFunc func(ResponseWriter, *Request)
|
||||
type HandlerFunc func(ResponseWriter, *Request)
|
||||
|
||||
// ServeHTTP calls f(w, r).
|
||||
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
f(w, r)
|
||||
}
|
||||
// ServeHTTP calls f(w, r).
|
||||
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
f(w, r)
|
||||
}
|
||||
```
|
||||
路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了`ServeHTTP`:
|
||||
```Go
|
||||
|
||||
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
if r.RequestURI == "*" {
|
||||
w.Header().Set("Connection", "close")
|
||||
w.WriteHeader(StatusBadRequest)
|
||||
return
|
||||
}
|
||||
h, _ := mux.Handler(r)
|
||||
h.ServeHTTP(w, r)
|
||||
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
if r.RequestURI == "*" {
|
||||
w.Header().Set("Connection", "close")
|
||||
w.WriteHeader(StatusBadRequest)
|
||||
return
|
||||
}
|
||||
h, _ := mux.Handler(r)
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
```
|
||||
如上所示路由器接收到请求之后,如果是`*`那么关闭链接,不然调用`mux.Handler(r)`返回对应设置路由的处理Handler,然后执行`h.ServeHTTP(w, r)`
|
||||
|
||||
也就是调用对应路由的handler的ServerHTTP接口,那么mux.Handler(r)怎么处理的呢?
|
||||
```Go
|
||||
|
||||
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
|
||||
if r.Method != "CONNECT" {
|
||||
if p := cleanPath(r.URL.Path); p != r.URL.Path {
|
||||
_, pattern = mux.handler(r.Host, p)
|
||||
return RedirectHandler(p, StatusMovedPermanently), pattern
|
||||
}
|
||||
}
|
||||
return mux.handler(r.Host, r.URL.Path)
|
||||
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
|
||||
if r.Method != "CONNECT" {
|
||||
if p := cleanPath(r.URL.Path); p != r.URL.Path {
|
||||
_, pattern = mux.handler(r.Host, p)
|
||||
return RedirectHandler(p, StatusMovedPermanently), pattern
|
||||
}
|
||||
}
|
||||
return mux.handler(r.Host, r.URL.Path)
|
||||
}
|
||||
|
||||
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
|
||||
mux.mu.RLock()
|
||||
defer mux.mu.RUnlock()
|
||||
|
||||
// Host-specific pattern takes precedence over generic ones
|
||||
if mux.hosts {
|
||||
h, pattern = mux.match(host + path)
|
||||
}
|
||||
|
||||
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
|
||||
mux.mu.RLock()
|
||||
defer mux.mu.RUnlock()
|
||||
|
||||
// Host-specific pattern takes precedence over generic ones
|
||||
if mux.hosts {
|
||||
h, pattern = mux.match(host + path)
|
||||
}
|
||||
if h == nil {
|
||||
h, pattern = mux.match(path)
|
||||
}
|
||||
if h == nil {
|
||||
h, pattern = NotFoundHandler(), ""
|
||||
}
|
||||
return
|
||||
if h == nil {
|
||||
h, pattern = mux.match(path)
|
||||
}
|
||||
if h == nil {
|
||||
h, pattern = NotFoundHandler(), ""
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
||||
原来他是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到之后返回存储的handler,调用这个handler的ServeHTTP接口就可以执行到相应的函数了。
|
||||
|
||||
@@ -111,33 +111,33 @@ Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有
|
||||
如下代码所示,我们自己实现了一个简易的路由器
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type MyMux struct {
|
||||
}
|
||||
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)
|
||||
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 sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello myroute!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
mux := &MyMux{}
|
||||
http.ListenAndServe(":9090", mux)
|
||||
}
|
||||
func main() {
|
||||
mux := &MyMux{}
|
||||
http.ListenAndServe(":9090", mux)
|
||||
}
|
||||
```
|
||||
## Go代码的执行流程
|
||||
|
||||
|
||||
118
zh/04.1.md
118
zh/04.1.md
@@ -3,68 +3,68 @@
|
||||
先来看一个表单递交的例子,我们有如下的表单内容,命名成文件login.gtpl(放入当前新建项目的目录里面)
|
||||
```html
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="post">
|
||||
用户名:<input type="text" name="username">
|
||||
密码:<input type="password" name="password">
|
||||
<input type="submit" value="登陆">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="post">
|
||||
用户名:<input type="text" name="username">
|
||||
密码:<input type="password" name="password">
|
||||
<input type="submit" value="登陆">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
上面递交表单到服务器的`/login`,当用户输入信息点击登陆之后,会跳转到服务器的路由`login`里面,我们首先要判断这个是什么方式传递过来,POST还是GET呢?
|
||||
|
||||
http包里面有一个很简单的方式就可以获取,我们在前面web的例子的基础上来看看怎么处理login页面的form数据
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm() //解析url传递的参数,对于POST则解析响应包的主体(request body)
|
||||
//注意:如果没有调用ParseForm方法,下面无法获取表单的数据
|
||||
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
|
||||
fmt.Println("path", r.URL.Path)
|
||||
fmt.Println("scheme", r.URL.Scheme)
|
||||
fmt.Println(r.Form["url_long"])
|
||||
for k, v := range r.Form {
|
||||
fmt.Println("key:", k)
|
||||
fmt.Println("val:", strings.Join(v, ""))
|
||||
}
|
||||
fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm() //解析url传递的参数,对于POST则解析响应包的主体(request body)
|
||||
//注意:如果没有调用ParseForm方法,下面无法获取表单的数据
|
||||
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
|
||||
fmt.Println("path", r.URL.Path)
|
||||
fmt.Println("scheme", r.URL.Scheme)
|
||||
fmt.Println(r.Form["url_long"])
|
||||
for k, v := range r.Form {
|
||||
fmt.Println("key:", k)
|
||||
fmt.Println("val:", strings.Join(v, ""))
|
||||
}
|
||||
fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
|
||||
}
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //获取请求的方法
|
||||
if r.Method == "GET" {
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
log.Println(t.Execute(w, nil))
|
||||
} else {
|
||||
//请求的是登陆数据,那么执行登陆的逻辑判断
|
||||
fmt.Println("username:", r.Form["username"])
|
||||
fmt.Println("password:", r.Form["password"])
|
||||
}
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //获取请求的方法
|
||||
if r.Method == "GET" {
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
log.Println(t.Execute(w, nil))
|
||||
} else {
|
||||
//请求的是登陆数据,那么执行登陆的逻辑判断
|
||||
fmt.Println("username:", r.Form["username"])
|
||||
fmt.Println("password:", r.Form["password"])
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", sayhelloName) //设置访问的路由
|
||||
http.HandleFunc("/login", login) //设置访问的路由
|
||||
err := http.ListenAndServe(":9090", nil) //设置监听的端口
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
func main() {
|
||||
http.HandleFunc("/", sayhelloName) //设置访问的路由
|
||||
http.HandleFunc("/login", login) //设置访问的路由
|
||||
err := http.ListenAndServe(":9090", nil) //设置监听的端口
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
通过上面的代码我们可以看出获取请求方法是通过`r.Method`来完成的,这是个字符串类型的变量,返回GET, POST, PUT等method信息。
|
||||
@@ -92,16 +92,16 @@ login函数中我们根据`r.Method`来判断是显示登录界面还是处理
|
||||
`request.Form`是一个url.Values类型,里面存储的是对应的类似`key=value`的信息,下面展示了可以对form数据进行的一些操作:
|
||||
```Go
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("name", "Ava")
|
||||
v.Add("friend", "Jess")
|
||||
v.Add("friend", "Sarah")
|
||||
v.Add("friend", "Zoe")
|
||||
// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
|
||||
fmt.Println(v.Get("name"))
|
||||
fmt.Println(v.Get("friend"))
|
||||
fmt.Println(v["friend"])
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("name", "Ava")
|
||||
v.Add("friend", "Jess")
|
||||
v.Add("friend", "Sarah")
|
||||
v.Add("friend", "Zoe")
|
||||
// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
|
||||
fmt.Println(v.Get("name"))
|
||||
fmt.Println(v.Get("friend"))
|
||||
fmt.Println(v["friend"])
|
||||
|
||||
```
|
||||
>**Tips**:
|
||||
>Request本身也提供了FormValue()函数来获取用户提交的参数。如r.Form["username"]也可写成r.FormValue("username")。调用r.FormValue时会自动调用r.ParseForm,所以不必提前调用。r.FormValue只会返回同名参数中的第一个,若参数不存在则返回空字符串。
|
||||
|
||||
140
zh/04.2.md
140
zh/04.2.md
@@ -8,9 +8,9 @@
|
||||
你想要确保从一个表单元素中得到一个值,例如前面小节里面的用户名,我们如何处理呢?Go有一个内置函数`len`可以获取字符串的长度,这样我们就可以通过len来获取数据的长度,例如:
|
||||
```Go
|
||||
|
||||
if len(r.Form["username"][0])==0{
|
||||
//为空的处理
|
||||
}
|
||||
if len(r.Form["username"][0])==0{
|
||||
//为空的处理
|
||||
}
|
||||
```
|
||||
`r.Form`对不同类型的表单元素的留空有不同的处理, 对于空文本框、空文本区域以及文件上传,元素的值为空值,而如果是未选中的复选框和单选按钮,则根本不会在r.Form中产生相应条目,如果我们用上面例子中的方式去获取数据时程序就会报错。所以我们需要通过`r.Form.Get()`来获取值,因为如果字段不存在,通过该方式获取的是空值。但是通过`r.Form.Get()`只能获取单个的值,如果是map的值,必须通过上面的方式来获取。
|
||||
|
||||
@@ -20,22 +20,22 @@
|
||||
如果我们是判断正整数,那么我们先转化成int类型,然后进行处理
|
||||
```Go
|
||||
|
||||
getint,err:=strconv.Atoi(r.Form.Get("age"))
|
||||
if err!=nil{
|
||||
//数字转化出错了,那么可能就不是数字
|
||||
}
|
||||
getint,err:=strconv.Atoi(r.Form.Get("age"))
|
||||
if err!=nil{
|
||||
//数字转化出错了,那么可能就不是数字
|
||||
}
|
||||
|
||||
//接下来就可以判断这个数字的大小范围了
|
||||
if getint >100 {
|
||||
//太大了
|
||||
}
|
||||
//接下来就可以判断这个数字的大小范围了
|
||||
if getint >100 {
|
||||
//太大了
|
||||
}
|
||||
```
|
||||
还有一种方式就是正则匹配的方式
|
||||
```Go
|
||||
|
||||
if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
|
||||
return false
|
||||
}
|
||||
if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
|
||||
return false
|
||||
}
|
||||
```
|
||||
对于性能要求很高的用户来说,这是一个老生常谈的问题了,他们认为应该尽量避免使用正则表达式,因为使用正则表达式的速度会比较慢。但是在目前机器性能那么强劲的情况下,对于这种简单的正则表达式效率和类型转换函数是没有什么差别的。如果你对正则表达式很熟悉,而且你在其它语言中也在使用它,那么在Go里面使用正则表达式将是一个便利的方式。
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
有时候我们想通过表单元素获取一个用户的中文名字,但是又为了保证获取的是正确的中文,我们需要进行验证,而不是用户随便的一些输入。对于中文我们目前有两种方式来验证,可以使用 `unicode` 包提供的 `func Is(rangeTab *RangeTable, r rune) bool` 来验证,也可以使用正则方式来验证,这里使用最简单的正则方式,如下代码所示
|
||||
```Go
|
||||
|
||||
if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m {
|
||||
return false
|
||||
}
|
||||
if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m {
|
||||
return false
|
||||
}
|
||||
```
|
||||
## 英文
|
||||
我们期望通过表单元素获取一个英文值,例如我们想知道一个用户的英文名,应该是astaxie,而不是asta谢。
|
||||
@@ -55,29 +55,29 @@
|
||||
我们可以很简单的通过正则验证数据:
|
||||
```Go
|
||||
|
||||
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
|
||||
return false
|
||||
}
|
||||
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
|
||||
return false
|
||||
}
|
||||
|
||||
```
|
||||
## 电子邮件地址
|
||||
你想知道用户输入的一个Email地址是否正确,通过如下这个方式可以验证:
|
||||
```Go
|
||||
|
||||
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
|
||||
fmt.Println("no")
|
||||
}else{
|
||||
fmt.Println("yes")
|
||||
}
|
||||
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
|
||||
fmt.Println("no")
|
||||
}else{
|
||||
fmt.Println("yes")
|
||||
}
|
||||
|
||||
```
|
||||
## 手机号码
|
||||
你想要判断用户输入的手机号码是否正确,通过正则也可以验证:
|
||||
```Go
|
||||
|
||||
if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
|
||||
return false
|
||||
}
|
||||
if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
|
||||
return false
|
||||
}
|
||||
```
|
||||
## 下拉菜单
|
||||
如果我们想要判断表单里面`<select>`元素生成的下拉菜单中是否有被选中的项目。有些时候黑客可能会伪造这个下拉菜单不存在的值发送给你,那么如何判断这个值是否是我们预设的值呢?
|
||||
@@ -85,63 +85,63 @@
|
||||
我们的select可能是这样的一些元素
|
||||
```html
|
||||
|
||||
<select name="fruit">
|
||||
<option value="apple">apple</option>
|
||||
<option value="pear">pear</option>
|
||||
<option value="banane">banane</option>
|
||||
</select>
|
||||
<select name="fruit">
|
||||
<option value="apple">apple</option>
|
||||
<option value="pear">pear</option>
|
||||
<option value="banane">banane</option>
|
||||
</select>
|
||||
```
|
||||
那么我们可以这样来验证
|
||||
```Go
|
||||
|
||||
slice:=[]string{"apple","pear","banane"}
|
||||
|
||||
v := r.Form.Get("fruit")
|
||||
for _, item range slice {
|
||||
if item == v {
|
||||
return true
|
||||
}
|
||||
slice:=[]string{"apple","pear","banane"}
|
||||
|
||||
v := r.Form.Get("fruit")
|
||||
for _, item range slice {
|
||||
if item == v {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
```
|
||||
## 单选按钮
|
||||
如果我们想要判断radio按钮是否有一个被选中了,我们页面的输出可能就是一个男、女性别的选择,但是也可能一个15岁大的无聊小孩,一手拿着http协议的书,另一只手通过telnet客户端向你的程序在发送请求呢,你设定的性别男值是1,女是2,他给你发送一个3,你的程序会出现异常吗?因此我们也需要像下拉菜单的判断方式类似,判断我们获取的值是我们预设的值,而不是额外的值。
|
||||
```html
|
||||
|
||||
<input type="radio" name="gender" value="1">男
|
||||
<input type="radio" name="gender" value="2">女
|
||||
<input type="radio" name="gender" value="1">男
|
||||
<input type="radio" name="gender" value="2">女
|
||||
```
|
||||
那我们也可以类似下拉菜单的做法一样
|
||||
```Go
|
||||
|
||||
slice:=[]int{1,2}
|
||||
slice:=[]int{1,2}
|
||||
|
||||
for _, v := range slice {
|
||||
if v == r.Form.Get("gender") {
|
||||
return true
|
||||
}
|
||||
for _, v := range slice {
|
||||
if v == r.Form.Get("gender") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
```
|
||||
## 复选框
|
||||
有一项选择兴趣的复选框,你想确定用户选中的和你提供给用户选择的是同一个类型的数据。
|
||||
```html
|
||||
|
||||
<input type="checkbox" name="interest" value="football">足球
|
||||
<input type="checkbox" name="interest" value="basketball">篮球
|
||||
<input type="checkbox" name="interest" value="tennis">网球
|
||||
<input type="checkbox" name="interest" value="football">足球
|
||||
<input type="checkbox" name="interest" value="basketball">篮球
|
||||
<input type="checkbox" name="interest" value="tennis">网球
|
||||
```
|
||||
对于复选框我们的验证和单选有点不一样,因为接收到的数据是一个slice
|
||||
```Go
|
||||
|
||||
slice:=[]string{"football","basketball","tennis"}
|
||||
a:=Slice_diff(r.Form["interest"],slice)
|
||||
if a == nil{
|
||||
return true
|
||||
}
|
||||
slice:=[]string{"football","basketball","tennis"}
|
||||
a:=Slice_diff(r.Form["interest"],slice)
|
||||
if a == nil{
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return false
|
||||
```
|
||||
上面这个函数`Slice_diff`包含在我开源的一个库里面(操作slice和map的库),[https://github.com/astaxie/beeku](https://github.com/astaxie/beeku)
|
||||
|
||||
@@ -152,8 +152,8 @@
|
||||
Go里面提供了一个time的处理包,我们可以把用户的输入年月日转化成相应的时间,然后进行逻辑判断
|
||||
```Go
|
||||
|
||||
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
fmt.Printf("Go launched at %s\n", t.Local())
|
||||
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
fmt.Printf("Go launched at %s\n", t.Local())
|
||||
```
|
||||
获取time之后我们就可以进行很多时间函数的操作。具体的判断就根据自己的需求调整。
|
||||
|
||||
@@ -161,16 +161,16 @@ Go里面提供了一个time的处理包,我们可以把用户的输入年月
|
||||
如果我们想验证表单输入的是否是身份证,通过正则也可以方便的验证,但是身份证有15位和18位,我们两个都需要验证
|
||||
```Go
|
||||
|
||||
//验证15位身份证,15位的是全部数字
|
||||
if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m {
|
||||
return false
|
||||
}
|
||||
//验证15位身份证,15位的是全部数字
|
||||
if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m {
|
||||
return false
|
||||
}
|
||||
|
||||
//验证18位身份证,18位前17位为数字,最后一位是校验位,可能为数字或字符X。
|
||||
if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {
|
||||
return false
|
||||
}
|
||||
|
||||
//验证18位身份证,18位前17位为数字,最后一位是校验位,可能为数字或字符X。
|
||||
if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {
|
||||
return false
|
||||
}
|
||||
|
||||
```
|
||||
上面列出了我们一些常用的服务器端的表单元素验证,希望通过这个引导入门,能够让你对Go的数据验证有所了解,特别是Go里面的正则处理。
|
||||
|
||||
|
||||
30
zh/04.3.md
30
zh/04.3.md
@@ -16,9 +16,9 @@
|
||||
我们看4.1小节的例子
|
||||
```Go
|
||||
|
||||
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端
|
||||
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
|
||||
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
|
||||
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端
|
||||
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
|
||||
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
|
||||
```
|
||||
如果我们输入的username是`<script>alert()</script>`,那么我们可以在浏览器上面看到输出如下所示:
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
Go的html/template包默认帮你过滤了html标签,但是有时候你只想要输出这个`<script>alert()</script>`看起来正常的信息,该怎么处理?请使用text/template。请看下面的例子:
|
||||
```Go
|
||||
|
||||
import "text/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
|
||||
import "text/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
|
||||
```
|
||||
输出
|
||||
|
||||
@@ -41,10 +41,10 @@ Go的html/template包默认帮你过滤了html标签,但是有时候你只想
|
||||
或者使用template.HTML类型
|
||||
```Go
|
||||
|
||||
import "html/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", template.HTML("<script>alert('you have been pwned')</script>"))
|
||||
import "html/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", template.HTML("<script>alert('you have been pwned')</script>"))
|
||||
```
|
||||
输出
|
||||
|
||||
@@ -55,10 +55,10 @@ Go的html/template包默认帮你过滤了html标签,但是有时候你只想
|
||||
转义的例子:
|
||||
```Go
|
||||
|
||||
import "html/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
|
||||
import "html/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
|
||||
```
|
||||
转义之后的输出:
|
||||
|
||||
|
||||
56
zh/04.4.md
56
zh/04.4.md
@@ -7,42 +7,42 @@
|
||||
我继续拿4.2小节的例子优化:
|
||||
```html
|
||||
|
||||
<input type="checkbox" name="interest" value="football">足球
|
||||
<input type="checkbox" name="interest" value="basketball">篮球
|
||||
<input type="checkbox" name="interest" value="tennis">网球
|
||||
用户名:<input type="text" name="username">
|
||||
密码:<input type="password" name="password">
|
||||
<input type="hidden" name="token" value="{{.}}">
|
||||
<input type="submit" value="登陆">
|
||||
<input type="checkbox" name="interest" value="football">足球
|
||||
<input type="checkbox" name="interest" value="basketball">篮球
|
||||
<input type="checkbox" name="interest" value="tennis">网球
|
||||
用户名:<input type="text" name="username">
|
||||
密码:<input type="password" name="password">
|
||||
<input type="hidden" name="token" value="{{.}}">
|
||||
<input type="submit" value="登陆">
|
||||
```
|
||||
我们在模版里面增加了一个隐藏字段`token`,这个值我们通过MD5(时间戳)来获取唯一值,然后我们把这个值存储到服务器端(session来控制,我们将在第六章讲解如何保存),以方便表单提交时比对判定。
|
||||
```Go
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //获取请求的方法
|
||||
if r.Method == "GET" {
|
||||
crutime := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //获取请求的方法
|
||||
if r.Method == "GET" {
|
||||
crutime := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
t.Execute(w, token)
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
t.Execute(w, token)
|
||||
} else {
|
||||
//请求的是登陆数据,那么执行登陆的逻辑判断
|
||||
r.ParseForm()
|
||||
token := r.Form.Get("token")
|
||||
if token != "" {
|
||||
//验证token的合法性
|
||||
} else {
|
||||
//请求的是登陆数据,那么执行登陆的逻辑判断
|
||||
r.ParseForm()
|
||||
token := r.Form.Get("token")
|
||||
if token != "" {
|
||||
//验证token的合法性
|
||||
} else {
|
||||
//不存在token报错
|
||||
}
|
||||
fmt.Println("username length:", len(r.Form["username"][0]))
|
||||
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端
|
||||
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
|
||||
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
|
||||
//不存在token报错
|
||||
}
|
||||
fmt.Println("username length:", len(r.Form["username"][0]))
|
||||
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端
|
||||
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
|
||||
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
|
||||
}
|
||||
}
|
||||
```
|
||||
上面的代码输出到页面的源码如下:
|
||||
|
||||
|
||||
202
zh/04.5.md
202
zh/04.5.md
@@ -4,60 +4,60 @@
|
||||
要使表单能够上传文件,首先第一步就是要添加form的`enctype`属性,`enctype`属性有如下三种情况:
|
||||
```
|
||||
|
||||
application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
|
||||
multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
|
||||
text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
|
||||
application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
|
||||
multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
|
||||
text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
|
||||
```
|
||||
所以,创建新的表单html文件, 命名为upload.gtpl, html代码应该类似于:
|
||||
```html
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>上传文件</title>
|
||||
</head>
|
||||
<body>
|
||||
<form enctype="multipart/form-data" action="/upload" method="post">
|
||||
<input type="file" name="uploadfile" />
|
||||
<input type="hidden" name="token" value="{{.}}"/>
|
||||
<input type="submit" value="upload" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
<html>
|
||||
<head>
|
||||
<title>上传文件</title>
|
||||
</head>
|
||||
<body>
|
||||
<form enctype="multipart/form-data" action="/upload" method="post">
|
||||
<input type="file" name="uploadfile" />
|
||||
<input type="hidden" name="token" value="{{.}}"/>
|
||||
<input type="submit" value="upload" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
在服务器端,我们增加一个handlerFunc:
|
||||
```Go
|
||||
|
||||
http.HandleFunc("/upload", upload)
|
||||
http.HandleFunc("/upload", upload)
|
||||
|
||||
// 处理/upload 逻辑
|
||||
func upload(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //获取请求的方法
|
||||
if r.Method == "GET" {
|
||||
crutime := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
// 处理/upload 逻辑
|
||||
func upload(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //获取请求的方法
|
||||
if r.Method == "GET" {
|
||||
crutime := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
t, _ := template.ParseFiles("upload.gtpl")
|
||||
t.Execute(w, token)
|
||||
} else {
|
||||
r.ParseMultipartForm(32 << 20)
|
||||
file, handler, err := r.FormFile("uploadfile")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
fmt.Fprintf(w, "%v", handler.Header)
|
||||
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) // 此处假设当前目录下已存在test目录
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, file)
|
||||
t, _ := template.ParseFiles("upload.gtpl")
|
||||
t.Execute(w, token)
|
||||
} else {
|
||||
r.ParseMultipartForm(32 << 20)
|
||||
file, handler, err := r.FormFile("uploadfile")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
fmt.Fprintf(w, "%v", handler.Header)
|
||||
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) // 此处假设当前目录下已存在test目录
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, file)
|
||||
}
|
||||
}
|
||||
```
|
||||
通过上面的代码可以看到,处理文件上传我们需要调用`r.ParseMultipartForm`,里面的参数表示`maxMemory`,调用`ParseMultipartForm`之后,上传的文件存储在`maxMemory`大小的内存里面,如果文件大小超过了`maxMemory`,那么剩下的部分将存储在系统的临时文件中。我们可以通过`r.FormFile`获取上面的文件句柄,然后实例中使用了`io.Copy`来存储文件。
|
||||
|
||||
@@ -72,11 +72,11 @@
|
||||
文件handler是multipart.FileHeader,里面存储了如下结构信息
|
||||
```Go
|
||||
|
||||
type FileHeader struct {
|
||||
Filename string
|
||||
Header textproto.MIMEHeader
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
type FileHeader struct {
|
||||
Filename string
|
||||
Header textproto.MIMEHeader
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
我们通过上面的实例代码打印出来上传文件的信息如下
|
||||
|
||||
@@ -89,66 +89,66 @@
|
||||
我们上面的例子演示了如何通过表单上传文件,然后在服务器端处理文件,其实Go支持模拟客户端表单功能支持文件上传,详细用法请看如下示例:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func postFile(filename string, targetUrl string) error {
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||
func postFile(filename string, targetUrl string) error {
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||
|
||||
//关键的一步操作
|
||||
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
|
||||
if err != nil {
|
||||
fmt.Println("error writing to buffer")
|
||||
return err
|
||||
}
|
||||
|
||||
//打开文件句柄操作
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("error opening file")
|
||||
return err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
//iocopy
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
bodyWriter.Close()
|
||||
|
||||
resp, err := http.Post(targetUrl, contentType, bodyBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resp_body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(resp.Status)
|
||||
fmt.Println(string(resp_body))
|
||||
return nil
|
||||
//关键的一步操作
|
||||
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
|
||||
if err != nil {
|
||||
fmt.Println("error writing to buffer")
|
||||
return err
|
||||
}
|
||||
|
||||
// sample usage
|
||||
func main() {
|
||||
target_url := "http://localhost:9090/upload"
|
||||
filename := "./astaxie.pdf"
|
||||
postFile(filename, target_url)
|
||||
//打开文件句柄操作
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("error opening file")
|
||||
return err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
//iocopy
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
bodyWriter.Close()
|
||||
|
||||
resp, err := http.Post(targetUrl, contentType, bodyBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resp_body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(resp.Status)
|
||||
fmt.Println(string(resp_body))
|
||||
return nil
|
||||
}
|
||||
|
||||
// sample usage
|
||||
func main() {
|
||||
target_url := "http://localhost:9090/upload"
|
||||
filename := "./astaxie.pdf"
|
||||
postFile(filename, target_url)
|
||||
}
|
||||
|
||||
```
|
||||
上面的例子详细展示了客户端如何向服务器上传一个文件的例子,客户端通过multipart.Write把文件的文本流写入一个缓存中,然后调用http的Post方法把缓存传到服务器。
|
||||
|
||||
140
zh/05.1.md
140
zh/05.1.md
@@ -7,25 +7,25 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动,而是为开发
|
||||
我们来看一下mymysql、sqlite3的驱动里面都是怎么调用的:
|
||||
```Go
|
||||
|
||||
//https://github.com/mattn/go-sqlite3驱动
|
||||
func init() {
|
||||
sql.Register("sqlite3", &SQLiteDriver{})
|
||||
}
|
||||
//https://github.com/mattn/go-sqlite3驱动
|
||||
func init() {
|
||||
sql.Register("sqlite3", &SQLiteDriver{})
|
||||
}
|
||||
|
||||
//https://github.com/mikespook/mymysql驱动
|
||||
// Driver automatically registered in database/sql
|
||||
var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}
|
||||
func init() {
|
||||
Register("SET NAMES utf8")
|
||||
sql.Register("mymysql", &d)
|
||||
}
|
||||
//https://github.com/mikespook/mymysql驱动
|
||||
// Driver automatically registered in database/sql
|
||||
var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}
|
||||
func init() {
|
||||
Register("SET NAMES utf8")
|
||||
sql.Register("mymysql", &d)
|
||||
}
|
||||
```
|
||||
我们看到第三方数据库驱动都是通过调用这个函数来注册自己的数据库驱动名称以及相应的driver实现。在database/sql内部通过一个map来存储用户定义的相应驱动。
|
||||
```Go
|
||||
|
||||
var drivers = make(map[string]driver.Driver)
|
||||
var drivers = make(map[string]driver.Driver)
|
||||
|
||||
drivers[name] = driver
|
||||
drivers[name] = driver
|
||||
```
|
||||
因此通过database/sql的注册函数可以同时注册多个数据库驱动,只要不重复。
|
||||
|
||||
@@ -44,17 +44,17 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动,而是为开发
|
||||
Driver是一个数据库驱动的接口,他定义了一个method: Open(name string),这个方法返回一个数据库的Conn接口。
|
||||
```Go
|
||||
|
||||
type Driver interface {
|
||||
Open(name string) (Conn, error)
|
||||
}
|
||||
type Driver interface {
|
||||
Open(name string) (Conn, error)
|
||||
}
|
||||
```
|
||||
返回的Conn只能用来进行一次goroutine的操作,也就是说不能把这个Conn应用于Go的多个goroutine里面。如下代码会出现错误
|
||||
```Go
|
||||
|
||||
...
|
||||
go goroutineA (Conn) //执行查询操作
|
||||
go goroutineB (Conn) //执行插入操作
|
||||
...
|
||||
...
|
||||
go goroutineA (Conn) //执行查询操作
|
||||
go goroutineB (Conn) //执行插入操作
|
||||
...
|
||||
```
|
||||
上面这样的代码可能会使Go不知道某个操作究竟是由哪个goroutine发起的,从而导致数据混乱,比如可能会把goroutineA里面执行的查询操作的结果返回给goroutineB从而使B错误地把此结果当成自己执行的插入数据。
|
||||
|
||||
@@ -64,11 +64,11 @@ Driver是一个数据库驱动的接口,他定义了一个method: Open(name
|
||||
Conn是一个数据库连接的接口定义,他定义了一系列方法,这个Conn只能应用在一个goroutine里面,不能使用在多个goroutine里面,详情请参考上面的说明。
|
||||
```Go
|
||||
|
||||
type Conn interface {
|
||||
Prepare(query string) (Stmt, error)
|
||||
Close() error
|
||||
Begin() (Tx, error)
|
||||
}
|
||||
type Conn interface {
|
||||
Prepare(query string) (Stmt, error)
|
||||
Close() error
|
||||
Begin() (Tx, error)
|
||||
}
|
||||
```
|
||||
Prepare函数返回与当前连接相关的执行Sql语句的准备状态,可以进行查询、删除等操作。
|
||||
|
||||
@@ -80,12 +80,12 @@ Begin函数返回一个代表事务处理的Tx,通过它你可以进行查询,
|
||||
Stmt是一种准备好的状态,和Conn相关联,而且只能应用于一个goroutine中,不能应用于多个goroutine。
|
||||
```Go
|
||||
|
||||
type Stmt interface {
|
||||
Close() error
|
||||
NumInput() int
|
||||
Exec(args []Value) (Result, error)
|
||||
Query(args []Value) (Rows, error)
|
||||
}
|
||||
type Stmt interface {
|
||||
Close() error
|
||||
NumInput() int
|
||||
Exec(args []Value) (Result, error)
|
||||
Query(args []Value) (Rows, error)
|
||||
}
|
||||
```
|
||||
Close函数关闭当前的链接状态,但是如果当前正在执行query,query还是有效返回rows数据。
|
||||
|
||||
@@ -100,10 +100,10 @@ Query函数执行Prepare准备好的sql,传入需要的参数执行select操
|
||||
事务处理一般就两个过程,递交或者回滚。数据库驱动里面也只需要实现这两个函数就可以
|
||||
```Go
|
||||
|
||||
type Tx interface {
|
||||
Commit() error
|
||||
Rollback() error
|
||||
}
|
||||
type Tx interface {
|
||||
Commit() error
|
||||
Rollback() error
|
||||
}
|
||||
```
|
||||
这两个函数一个用来递交一个事务,一个用来回滚事务。
|
||||
|
||||
@@ -111,9 +111,9 @@ Query函数执行Prepare准备好的sql,传入需要的参数执行select操
|
||||
这是一个Conn可选择实现的接口
|
||||
```Go
|
||||
|
||||
type Execer interface {
|
||||
Exec(query string, args []Value) (Result, error)
|
||||
}
|
||||
type Execer interface {
|
||||
Exec(query string, args []Value) (Result, error)
|
||||
}
|
||||
```
|
||||
如果这个接口没有定义,那么在调用DB.Exec,就会首先调用Prepare返回Stmt,然后执行Stmt的Exec,然后关闭Stmt。
|
||||
|
||||
@@ -121,10 +121,10 @@ Query函数执行Prepare准备好的sql,传入需要的参数执行select操
|
||||
这个是执行Update/Insert等操作返回的结果接口定义
|
||||
```Go
|
||||
|
||||
type Result interface {
|
||||
LastInsertId() (int64, error)
|
||||
RowsAffected() (int64, error)
|
||||
}
|
||||
type Result interface {
|
||||
LastInsertId() (int64, error)
|
||||
RowsAffected() (int64, error)
|
||||
}
|
||||
```
|
||||
LastInsertId函数返回由数据库执行插入操作得到的自增ID号。
|
||||
|
||||
@@ -134,11 +134,11 @@ RowsAffected函数返回query操作影响的数据条目数。
|
||||
Rows是执行查询返回的结果集接口定义
|
||||
```Go
|
||||
|
||||
type Rows interface {
|
||||
Columns() []string
|
||||
Close() error
|
||||
Next(dest []Value) error
|
||||
}
|
||||
type Rows interface {
|
||||
Columns() []string
|
||||
Close() error
|
||||
Next(dest []Value) error
|
||||
}
|
||||
```
|
||||
Columns函数返回查询数据库表的字段信息,这个返回的slice和sql查询的字段一一对应,而不是返回整个表的所有字段。
|
||||
|
||||
@@ -151,35 +151,35 @@ Next函数用来返回下一条数据,把数据赋值给dest。dest里面的
|
||||
RowsAffected其实就是一个int64的别名,但是他实现了Result接口,用来底层实现Result的表示方式
|
||||
```Go
|
||||
|
||||
type RowsAffected int64
|
||||
type RowsAffected int64
|
||||
|
||||
func (RowsAffected) LastInsertId() (int64, error)
|
||||
func (RowsAffected) LastInsertId() (int64, error)
|
||||
|
||||
func (v RowsAffected) RowsAffected() (int64, error)
|
||||
func (v RowsAffected) RowsAffected() (int64, error)
|
||||
```
|
||||
## driver.Value
|
||||
Value其实就是一个空接口,他可以容纳任何的数据
|
||||
```Go
|
||||
|
||||
type Value interface{}
|
||||
type Value interface{}
|
||||
```
|
||||
drive的Value是驱动必须能够操作的Value,Value要么是nil,要么是下面的任意一种
|
||||
```Go
|
||||
|
||||
int64
|
||||
float64
|
||||
bool
|
||||
[]byte
|
||||
string [*]除了Rows.Next返回的不能是string.
|
||||
time.Time
|
||||
int64
|
||||
float64
|
||||
bool
|
||||
[]byte
|
||||
string [*]除了Rows.Next返回的不能是string.
|
||||
time.Time
|
||||
```
|
||||
## driver.ValueConverter
|
||||
ValueConverter接口定义了如何把一个普通的值转化成driver.Value的接口
|
||||
```Go
|
||||
|
||||
type ValueConverter interface {
|
||||
ConvertValue(v interface{}) (Value, error)
|
||||
}
|
||||
type ValueConverter interface {
|
||||
ConvertValue(v interface{}) (Value, error)
|
||||
}
|
||||
```
|
||||
在开发的数据库驱动包里面实现这个接口的函数在很多地方会使用到,这个ValueConverter有很多好处:
|
||||
|
||||
@@ -191,9 +191,9 @@ ValueConverter接口定义了如何把一个普通的值转化成driver.Value的
|
||||
Valuer接口定义了返回一个driver.Value的方式
|
||||
```Go
|
||||
|
||||
type Valuer interface {
|
||||
Value() (Value, error)
|
||||
}
|
||||
type Valuer interface {
|
||||
Value() (Value, error)
|
||||
}
|
||||
```
|
||||
很多类型都实现了这个Value方法,用来自身与driver.Value的转化。
|
||||
|
||||
@@ -203,13 +203,13 @@ Valuer接口定义了返回一个driver.Value的方式
|
||||
database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法,用以简化数据库操作,同时内部还建议性地实现一个conn pool。
|
||||
```Go
|
||||
|
||||
type DB struct {
|
||||
driver driver.Driver
|
||||
dsn string
|
||||
mu sync.Mutex // protects freeConn and closed
|
||||
freeConn []driver.Conn
|
||||
closed bool
|
||||
}
|
||||
type DB struct {
|
||||
driver driver.Driver
|
||||
dsn string
|
||||
mu sync.Mutex // protects freeConn and closed
|
||||
freeConn []driver.Conn
|
||||
closed bool
|
||||
}
|
||||
```
|
||||
我们可以看到Open函数返回的是DB对象,里面有一个freeConn,它就是那个简易的连接池。它的实现相当简单或者说简陋,就是当执行Db.prepare的时候会`defer db.putConn(ci, err)`,也就是把这个连接放入连接池,每次调用conn的时候会先判断freeConn的长度是否大于0,大于0说明有可以复用的conn,直接拿出来用就是了,如果不大于0,则创建一个conn,然后再返回之。
|
||||
|
||||
|
||||
164
zh/05.2.md
164
zh/05.2.md
@@ -18,99 +18,99 @@ Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持data
|
||||
接下来的几个小节里面我们都将采用同一个数据库表结构:数据库test,用户表userinfo,关联用户信息表userdetail。
|
||||
```sql
|
||||
|
||||
CREATE TABLE `userinfo` (
|
||||
`uid` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`username` VARCHAR(64) NULL DEFAULT NULL,
|
||||
`departname` VARCHAR(64) NULL DEFAULT NULL,
|
||||
`created` DATE NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
);
|
||||
CREATE TABLE `userinfo` (
|
||||
`uid` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`username` VARCHAR(64) NULL DEFAULT NULL,
|
||||
`departname` VARCHAR(64) NULL DEFAULT NULL,
|
||||
`created` DATE NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
);
|
||||
|
||||
CREATE TABLE `userdetail` (
|
||||
`uid` INT(10) NOT NULL DEFAULT '0',
|
||||
`intro` TEXT NULL,
|
||||
`profile` TEXT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
)
|
||||
CREATE TABLE `userdetail` (
|
||||
`uid` INT(10) NOT NULL DEFAULT '0',
|
||||
`intro` TEXT NULL,
|
||||
`profile` TEXT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
)
|
||||
```
|
||||
如下示例将示范如何使用database/sql接口对数据库表进行增删改查操作
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
//"time"
|
||||
)
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
//"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8")
|
||||
func main() {
|
||||
db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8")
|
||||
checkErr(err)
|
||||
|
||||
//插入数据
|
||||
stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
|
||||
checkErr(err)
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(id)
|
||||
//更新数据
|
||||
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
//查询数据
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created string
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
|
||||
//插入数据
|
||||
stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
|
||||
checkErr(err)
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(id)
|
||||
//更新数据
|
||||
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
//查询数据
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created string
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println(uid)
|
||||
fmt.Println(username)
|
||||
fmt.Println(department)
|
||||
fmt.Println(created)
|
||||
}
|
||||
|
||||
//删除数据
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
db.Close()
|
||||
|
||||
fmt.Println(uid)
|
||||
fmt.Println(username)
|
||||
fmt.Println(department)
|
||||
fmt.Println(created)
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//删除数据
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
db.Close()
|
||||
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
通过上面的代码我们可以看出,Go操作Mysql数据库是很方便的。
|
||||
|
||||
160
zh/05.3.md
160
zh/05.3.md
@@ -15,97 +15,97 @@ Go支持sqlite的驱动也比较多,但是好多都是不支持database/sql接
|
||||
示例的数据库表结构如下所示,相应的建表SQL:
|
||||
```sql
|
||||
|
||||
CREATE TABLE `userinfo` (
|
||||
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`username` VARCHAR(64) NULL,
|
||||
`departname` VARCHAR(64) NULL,
|
||||
`created` DATE NULL
|
||||
);
|
||||
CREATE TABLE `userinfo` (
|
||||
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`username` VARCHAR(64) NULL,
|
||||
`departname` VARCHAR(64) NULL,
|
||||
`created` DATE NULL
|
||||
);
|
||||
|
||||
CREATE TABLE `userdeatail` (
|
||||
`uid` INT(10) NULL,
|
||||
`intro` TEXT NULL,
|
||||
`profile` TEXT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
);
|
||||
CREATE TABLE `userdeatail` (
|
||||
`uid` INT(10) NULL,
|
||||
`intro` TEXT NULL,
|
||||
`profile` TEXT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
);
|
||||
```
|
||||
看下面Go程序是如何操作数据库表数据:增删改查
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db, err := sql.Open("sqlite3", "./foo.db")
|
||||
func main() {
|
||||
db, err := sql.Open("sqlite3", "./foo.db")
|
||||
checkErr(err)
|
||||
|
||||
//插入数据
|
||||
stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
|
||||
checkErr(err)
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(id)
|
||||
//更新数据
|
||||
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
//查询数据
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created time.Time
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
|
||||
//插入数据
|
||||
stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
|
||||
checkErr(err)
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(id)
|
||||
//更新数据
|
||||
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
//查询数据
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created time.Time
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println(uid)
|
||||
fmt.Println(username)
|
||||
fmt.Println(department)
|
||||
fmt.Println(created)
|
||||
}
|
||||
|
||||
//删除数据
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
db.Close()
|
||||
|
||||
fmt.Println(uid)
|
||||
fmt.Println(username)
|
||||
fmt.Println(department)
|
||||
fmt.Println(created)
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//删除数据
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
db.Close()
|
||||
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我们可以看到上面的代码和MySQL例子里面的代码几乎是一模一样的,唯一改变的就是导入的驱动改变了,然后调用`sql.Open`是采用了SQLite的方式打开。
|
||||
|
||||
180
zh/05.4.md
180
zh/05.4.md
@@ -19,109 +19,109 @@ Go实现的支持PostgreSQL的驱动也很多,因为国外很多人在开发
|
||||
数据库建表语句:
|
||||
```sql
|
||||
|
||||
CREATE TABLE userinfo
|
||||
(
|
||||
uid serial NOT NULL,
|
||||
username character varying(100) NOT NULL,
|
||||
departname character varying(500) NOT NULL,
|
||||
Created date,
|
||||
CONSTRAINT userinfo_pkey PRIMARY KEY (uid)
|
||||
)
|
||||
WITH (OIDS=FALSE);
|
||||
CREATE TABLE userinfo
|
||||
(
|
||||
uid serial NOT NULL,
|
||||
username character varying(100) NOT NULL,
|
||||
departname character varying(500) NOT NULL,
|
||||
Created date,
|
||||
CONSTRAINT userinfo_pkey PRIMARY KEY (uid)
|
||||
)
|
||||
WITH (OIDS=FALSE);
|
||||
|
||||
CREATE TABLE userdeatail
|
||||
(
|
||||
uid integer,
|
||||
intro character varying(100),
|
||||
profile character varying(100)
|
||||
)
|
||||
WITH(OIDS=FALSE);
|
||||
CREATE TABLE userdeatail
|
||||
(
|
||||
uid integer,
|
||||
intro character varying(100),
|
||||
profile character varying(100)
|
||||
)
|
||||
WITH(OIDS=FALSE);
|
||||
|
||||
```
|
||||
看下面这个Go如何操作数据库表数据:增删改查
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db, err := sql.Open("postgres", "user=astaxie password=astaxie dbname=test sslmode=disable")
|
||||
func main() {
|
||||
db, err := sql.Open("postgres", "user=astaxie password=astaxie dbname=test sslmode=disable")
|
||||
checkErr(err)
|
||||
|
||||
//插入数据
|
||||
stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) RETURNING uid")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
|
||||
checkErr(err)
|
||||
|
||||
//pg不支持这个函数,因为他没有类似MySQL的自增ID
|
||||
// id, err := res.LastInsertId()
|
||||
// checkErr(err)
|
||||
// fmt.Println(id)
|
||||
|
||||
var lastInsertId int
|
||||
err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId)
|
||||
checkErr(err)
|
||||
fmt.Println("最后插入id =", lastInsertId)
|
||||
|
||||
|
||||
//更新数据
|
||||
stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", 1)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
//查询数据
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created string
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
|
||||
//插入数据
|
||||
stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) RETURNING uid")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
|
||||
checkErr(err)
|
||||
|
||||
//pg不支持这个函数,因为他没有类似MySQL的自增ID
|
||||
// id, err := res.LastInsertId()
|
||||
// checkErr(err)
|
||||
// fmt.Println(id)
|
||||
|
||||
var lastInsertId int
|
||||
err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId)
|
||||
checkErr(err)
|
||||
fmt.Println("最后插入id =", lastInsertId)
|
||||
|
||||
|
||||
//更新数据
|
||||
stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", 1)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
//查询数据
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created string
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println(uid)
|
||||
fmt.Println(username)
|
||||
fmt.Println(department)
|
||||
fmt.Println(created)
|
||||
}
|
||||
|
||||
//删除数据
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=$1")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(1)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
db.Close()
|
||||
|
||||
fmt.Println(uid)
|
||||
fmt.Println(username)
|
||||
fmt.Println(department)
|
||||
fmt.Println(created)
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//删除数据
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=$1")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(1)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
db.Close()
|
||||
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
从上面的代码我们可以看到,PostgreSQL是通过`$1`,`$2`这种方式来指定要传递的参数,而不是MySQL中的`?`,另外在sql.Open中的dsn信息的格式也与MySQL的驱动中的dsn格式不一样,所以在使用时请注意它们的差异。
|
||||
|
||||
244
zh/05.5.md
244
zh/05.5.md
@@ -34,21 +34,21 @@ beego orm支持go get方式安装,是完全按照Go Style的方式来实现的
|
||||
首先你需要import相应的数据库驱动包、database/sql标准接口包以及beego orm包,如下所示:
|
||||
```Go
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/astaxie/beego/orm"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/astaxie/beego/orm"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 设置默认数据库
|
||||
orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
||||
//注册定义的model
|
||||
func init() {
|
||||
// 设置默认数据库
|
||||
orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
||||
//注册定义的model
|
||||
orm.RegisterModel(new(User))
|
||||
|
||||
// 创建table
|
||||
orm.RunSyncdb("default", false, true)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
PostgreSQL 配置:
|
||||
@@ -96,9 +96,9 @@ beego orm:
|
||||
|
||||
```Go
|
||||
|
||||
func main() {
|
||||
func main() {
|
||||
orm := orm.NewOrm()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
简单示例:
|
||||
@@ -124,8 +124,8 @@ func init() {
|
||||
|
||||
// 注册定义的 model
|
||||
orm.RegisterModel(new(User))
|
||||
//RegisterModel 也可以同时注册多个 model
|
||||
//orm.RegisterModel(new(User), new(Profile), new(Post))
|
||||
//RegisterModel 也可以同时注册多个 model
|
||||
//orm.RegisterModel(new(User), new(Profile), new(Post))
|
||||
|
||||
// 创建 table
|
||||
orm.RunSyncdb("default", false, true)
|
||||
@@ -175,49 +175,49 @@ orm.SetMaxOpenConns("default", 30)
|
||||
目前beego orm支持打印调试,你可以通过如下的代码实现调试
|
||||
```Go
|
||||
|
||||
orm.Debug = true
|
||||
orm.Debug = true
|
||||
```
|
||||
|
||||
接下来我们的例子采用前面的数据库表User,现在我们建立相应的struct
|
||||
```Go
|
||||
|
||||
type Userinfo struct {
|
||||
Uid int `PK` //如果表的主键不是id,那么需要加上pk注释,显式的说这个字段是主键
|
||||
Username string
|
||||
Departname string
|
||||
Created time.Time
|
||||
}
|
||||
type Userinfo struct {
|
||||
Uid int `PK` //如果表的主键不是id,那么需要加上pk注释,显式的说这个字段是主键
|
||||
Username string
|
||||
Departname string
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Uid int `PK` //如果表的主键不是id,那么需要加上pk注释,显式的说这个字段是主键
|
||||
Name string
|
||||
Profile *Profile `orm:"rel(one)"` // OneToOne relation
|
||||
Post []*Post `orm:"reverse(many)"` // 设置一对多的反向关系
|
||||
}
|
||||
type User struct {
|
||||
Uid int `PK` //如果表的主键不是id,那么需要加上pk注释,显式的说这个字段是主键
|
||||
Name string
|
||||
Profile *Profile `orm:"rel(one)"` // OneToOne relation
|
||||
Post []*Post `orm:"reverse(many)"` // 设置一对多的反向关系
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
Id int
|
||||
Age int16
|
||||
User *User `orm:"reverse(one)"` // 设置一对一反向关系(可选)
|
||||
}
|
||||
type Profile struct {
|
||||
Id int
|
||||
Age int16
|
||||
User *User `orm:"reverse(one)"` // 设置一对一反向关系(可选)
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
Id int
|
||||
Title string
|
||||
User *User `orm:"rel(fk)"` //设置一对多关系
|
||||
Tags []*Tag `orm:"rel(m2m)"`
|
||||
}
|
||||
type Post struct {
|
||||
Id int
|
||||
Title string
|
||||
User *User `orm:"rel(fk)"` //设置一对多关系
|
||||
Tags []*Tag `orm:"rel(m2m)"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Id int
|
||||
Name string
|
||||
Posts []*Post `orm:"reverse(many)"`
|
||||
}
|
||||
type Tag struct {
|
||||
Id int
|
||||
Name string
|
||||
Posts []*Post `orm:"reverse(many)"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 需要在init中注册定义的model
|
||||
orm.RegisterModel(new(Userinfo),new(User), new(Profile), new(Tag))
|
||||
}
|
||||
func init() {
|
||||
// 需要在init中注册定义的model
|
||||
orm.RegisterModel(new(Userinfo),new(User), new(Profile), new(Tag))
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
@@ -227,15 +227,15 @@ orm.SetMaxOpenConns("default", 30)
|
||||
下面的代码演示了如何插入一条记录,可以看到我们操作的是struct对象,而不是原生的sql语句,最后通过调用Insert接口将数据保存到数据库。
|
||||
```Go
|
||||
|
||||
o := orm.NewOrm()
|
||||
var user User
|
||||
user.Name = "zxxx"
|
||||
user.Departname = "zxxx"
|
||||
o := orm.NewOrm()
|
||||
var user User
|
||||
user.Name = "zxxx"
|
||||
user.Departname = "zxxx"
|
||||
|
||||
id, err := o.Insert(&user)
|
||||
if err == nil {
|
||||
fmt.Println(id)
|
||||
}
|
||||
id, err := o.Insert(&user)
|
||||
if err == nil {
|
||||
fmt.Println(id)
|
||||
}
|
||||
```
|
||||
我们看到插入之后`user.Uid`就是插入成功之后的自增ID。
|
||||
|
||||
@@ -267,14 +267,14 @@ bulk 为 1 时,将会顺序插入 slice 中的数据
|
||||
继续上面的例子来演示更新操作,现在user的主键已经有值了,此时调用Insert接口,beego orm内部会自动调用update以进行数据的更新而非插入操作。
|
||||
```Go
|
||||
|
||||
o := orm.NewOrm()
|
||||
user := User{Uid: 1}
|
||||
if o.Read(&user) == nil {
|
||||
user.Name = "MyName"
|
||||
if num, err := o.Update(&user); err == nil {
|
||||
fmt.Println(num)
|
||||
}
|
||||
o := orm.NewOrm()
|
||||
user := User{Uid: 1}
|
||||
if o.Read(&user) == nil {
|
||||
user.Name = "MyName"
|
||||
if num, err := o.Update(&user); err == nil {
|
||||
fmt.Println(num)
|
||||
}
|
||||
}
|
||||
```
|
||||
Update 默认更新所有的字段,可以更新指定的字段:
|
||||
```Go
|
||||
@@ -293,44 +293,44 @@ beego orm的查询接口比较灵活,具体使用请看下面的例子
|
||||
例子1,根据主键获取数据:
|
||||
```Go
|
||||
|
||||
o := orm.NewOrm()
|
||||
var user User
|
||||
|
||||
user := User{Id: 1}
|
||||
o := orm.NewOrm()
|
||||
var user User
|
||||
|
||||
err = o.Read(&user)
|
||||
user := User{Id: 1}
|
||||
|
||||
if err == orm.ErrNoRows {
|
||||
fmt.Println("查询不到")
|
||||
} else if err == orm.ErrMissPK {
|
||||
fmt.Println("找不到主键")
|
||||
} else {
|
||||
fmt.Println(user.Id, user.Name)
|
||||
}
|
||||
err = o.Read(&user)
|
||||
|
||||
if err == orm.ErrNoRows {
|
||||
fmt.Println("查询不到")
|
||||
} else if err == orm.ErrMissPK {
|
||||
fmt.Println("找不到主键")
|
||||
} else {
|
||||
fmt.Println(user.Id, user.Name)
|
||||
}
|
||||
```
|
||||
|
||||
例子2:
|
||||
```Go
|
||||
|
||||
o := orm.NewOrm()
|
||||
var user User
|
||||
o := orm.NewOrm()
|
||||
var user User
|
||||
|
||||
qs := o.QueryTable(user) // 返回 QuerySeter
|
||||
qs.Filter("id", 1) // WHERE id = 1
|
||||
qs.Filter("profile__age", 18) // WHERE profile.age = 18
|
||||
qs := o.QueryTable(user) // 返回 QuerySeter
|
||||
qs.Filter("id", 1) // WHERE id = 1
|
||||
qs.Filter("profile__age", 18) // WHERE profile.age = 18
|
||||
```
|
||||
例子3,WHERE IN查询条件:
|
||||
```Go
|
||||
|
||||
qs.Filter("profile__age__in", 18, 20)
|
||||
// WHERE profile.age IN (18, 20)
|
||||
qs.Filter("profile__age__in", 18, 20)
|
||||
// WHERE profile.age IN (18, 20)
|
||||
|
||||
```
|
||||
例子4,更加复杂的条件:
|
||||
```Go
|
||||
|
||||
qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000)
|
||||
// WHERE profile.age IN (18, 20) AND NOT profile_id < 1000
|
||||
qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000)
|
||||
// WHERE profile.age IN (18, 20) AND NOT profile_id < 1000
|
||||
|
||||
```
|
||||
|
||||
@@ -339,15 +339,15 @@ beego orm的查询接口比较灵活,具体使用请看下面的例子
|
||||
例子1,根据条件age>17,获取20位置开始的10条数据的数据
|
||||
```Go
|
||||
|
||||
var allusers []User
|
||||
qs.Filter("profile__age__gt", 17)
|
||||
// WHERE profile.age > 17
|
||||
var allusers []User
|
||||
qs.Filter("profile__age__gt", 17)
|
||||
// WHERE profile.age > 17
|
||||
```
|
||||
例子2,limit默认从10开始,获取10条数据
|
||||
```Go
|
||||
|
||||
qs.Limit(10, 20)
|
||||
// LIMIT 10 OFFSET 20 注意跟SQL反过来的
|
||||
qs.Limit(10, 20)
|
||||
// LIMIT 10 OFFSET 20 注意跟SQL反过来的
|
||||
```
|
||||
|
||||
## 删除数据
|
||||
@@ -356,10 +356,10 @@ beedb提供了丰富的删除数据接口,请看下面的例子
|
||||
例子1,删除单条数据
|
||||
```Go
|
||||
|
||||
o := orm.NewOrm()
|
||||
if num, err := o.Delete(&User{Id: 1}); err == nil {
|
||||
fmt.Println(num)
|
||||
}
|
||||
o := orm.NewOrm()
|
||||
if num, err := o.Delete(&User{Id: 1}); err == nil {
|
||||
fmt.Println(num)
|
||||
}
|
||||
```
|
||||
Delete 操作会对反向关系进行操作,此例中 Post 拥有一个到 User 的外键。删除 User 的时候。如果 on_delete 设置为默认的级联操作,将删除对应的 Post
|
||||
|
||||
@@ -367,15 +367,15 @@ Delete 操作会对反向关系进行操作,此例中 Post 拥有一个到 Use
|
||||
有些应用却需要用到连接查询,所以现在beego orm提供了一个简陋的实现方案:
|
||||
```Go
|
||||
|
||||
type Post struct {
|
||||
Id int `orm:"auto"`
|
||||
Title string `orm:"size(100)"`
|
||||
User *User `orm:"rel(fk)"`
|
||||
}
|
||||
type Post struct {
|
||||
Id int `orm:"auto"`
|
||||
Title string `orm:"size(100)"`
|
||||
User *User `orm:"rel(fk)"`
|
||||
}
|
||||
|
||||
var posts []*Post
|
||||
qs := o.QueryTable("post")
|
||||
num, err := qs.Filter("User__Name", "slene").All(&posts)
|
||||
var posts []*Post
|
||||
qs := o.QueryTable("post")
|
||||
num, err := qs.Filter("User__Name", "slene").All(&posts)
|
||||
|
||||
```
|
||||
上面代码中我们看到了一个struct关联查询
|
||||
@@ -386,11 +386,11 @@ Delete 操作会对反向关系进行操作,此例中 Post 拥有一个到 Use
|
||||
针对有些应用需要用到group by的功能,beego orm也提供了一个简陋的实现
|
||||
```Go
|
||||
|
||||
qs.OrderBy("id", "-profile__age")
|
||||
// ORDER BY id ASC, profile.age DESC
|
||||
qs.OrderBy("id", "-profile__age")
|
||||
// ORDER BY id ASC, profile.age DESC
|
||||
|
||||
qs.OrderBy("-profile__age", "profile")
|
||||
// ORDER BY profile.age DESC, profile_id ASC
|
||||
qs.OrderBy("-profile__age", "profile")
|
||||
// ORDER BY profile.age DESC, profile_id ASC
|
||||
|
||||
```
|
||||
上面的代码中出现了两个新接口函数
|
||||
@@ -406,30 +406,30 @@ Having:用来指定having执行的时候的条件
|
||||
|
||||
```Go
|
||||
|
||||
o := NewOrm()
|
||||
var r RawSeter
|
||||
r = o.Raw("UPDATE user SET name = ? WHERE name = ?", "testing", "slene")
|
||||
o := NewOrm()
|
||||
var r RawSeter
|
||||
r = o.Raw("UPDATE user SET name = ? WHERE name = ?", "testing", "slene")
|
||||
```
|
||||
|
||||
复杂原生sql使用:
|
||||
|
||||
```Go
|
||||
func (m *User) Query(name string) []User {
|
||||
var o orm.Ormer
|
||||
var rs orm.RawSeter
|
||||
o = orm.NewOrm()
|
||||
rs = o.Raw("SELECT * FROM user "+
|
||||
"WHERE name=? AND uid>10 "+
|
||||
"ORDER BY uid DESC "+
|
||||
"LIMIT 100", name)
|
||||
var user []User
|
||||
num, err := rs.QueryRows(&user)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println(num)
|
||||
return user
|
||||
}
|
||||
var o orm.Ormer
|
||||
var rs orm.RawSeter
|
||||
o = orm.NewOrm()
|
||||
rs = o.Raw("SELECT * FROM user "+
|
||||
"WHERE name=? AND uid>10 "+
|
||||
"ORDER BY uid DESC "+
|
||||
"LIMIT 100", name)
|
||||
var user []User
|
||||
num, err := rs.QueryRows(&user)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println(num)
|
||||
return user
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
242
zh/05.6.md
242
zh/05.6.md
@@ -18,78 +18,78 @@ Go目前支持redis的驱动有如下
|
||||
我以redigo驱动为例来演示如何进行数据的操作:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"os"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
Pool *redis.Pool
|
||||
)
|
||||
var (
|
||||
Pool *redis.Pool
|
||||
)
|
||||
|
||||
func init() {
|
||||
redisHost := ":6379"
|
||||
Pool = newPool(redisHost)
|
||||
close()
|
||||
func init() {
|
||||
redisHost := ":6379"
|
||||
Pool = newPool(redisHost)
|
||||
close()
|
||||
}
|
||||
|
||||
func newPool(server string) *redis.Pool {
|
||||
|
||||
return &redis.Pool{
|
||||
|
||||
MaxIdle: 3,
|
||||
IdleTimeout: 240 * time.Second,
|
||||
|
||||
Dial: func() (redis.Conn, error) {
|
||||
c, err := redis.Dial("tcp", server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
},
|
||||
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newPool(server string) *redis.Pool {
|
||||
func close() {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
signal.Notify(c, syscall.SIGTERM)
|
||||
signal.Notify(c, syscall.SIGKILL)
|
||||
go func() {
|
||||
<-c
|
||||
Pool.Close()
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
return &redis.Pool{
|
||||
func Get(key string) ([]byte, error) {
|
||||
|
||||
MaxIdle: 3,
|
||||
IdleTimeout: 240 * time.Second,
|
||||
conn := Pool.Get()
|
||||
defer conn.Close()
|
||||
|
||||
Dial: func() (redis.Conn, error) {
|
||||
c, err := redis.Dial("tcp", server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
},
|
||||
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
var data []byte
|
||||
data, err := redis.Bytes(conn.Do("GET", key))
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("error get key %s: %v", key, err)
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func close() {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
signal.Notify(c, syscall.SIGTERM)
|
||||
signal.Notify(c, syscall.SIGKILL)
|
||||
go func() {
|
||||
<-c
|
||||
Pool.Close()
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
func Get(key string) ([]byte, error) {
|
||||
|
||||
conn := Pool.Get()
|
||||
defer conn.Close()
|
||||
|
||||
var data []byte
|
||||
data, err := redis.Bytes(conn.Do("GET", key))
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("error get key %s: %v", key, err)
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func main() {
|
||||
test, err := Get("test")
|
||||
fmt.Println(test, err)
|
||||
}
|
||||
func main() {
|
||||
test, err := Get("test")
|
||||
fmt.Println(test, err)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -100,35 +100,35 @@ https://github.com/astaxie/goredis
|
||||
接下来的以我自己fork的这个redis驱动为例来演示如何进行数据的操作
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/goredis"
|
||||
"fmt"
|
||||
)
|
||||
import (
|
||||
"github.com/astaxie/goredis"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var client goredis.Client
|
||||
// 设置端口为redis默认端口
|
||||
client.Addr = "127.0.0.1:6379"
|
||||
|
||||
//字符串操作
|
||||
client.Set("a", []byte("hello"))
|
||||
val, _ := client.Get("a")
|
||||
fmt.Println(string(val))
|
||||
client.Del("a")
|
||||
func main() {
|
||||
var client goredis.Client
|
||||
// 设置端口为redis默认端口
|
||||
client.Addr = "127.0.0.1:6379"
|
||||
|
||||
//字符串操作
|
||||
client.Set("a", []byte("hello"))
|
||||
val, _ := client.Get("a")
|
||||
fmt.Println(string(val))
|
||||
client.Del("a")
|
||||
|
||||
//list操作
|
||||
vals := []string{"a", "b", "c", "d", "e"}
|
||||
for _, v := range vals {
|
||||
client.Rpush("l", []byte(v))
|
||||
}
|
||||
dbvals,_ := client.Lrange("l", 0, 4)
|
||||
for i, v := range dbvals {
|
||||
println(i,":",string(v))
|
||||
}
|
||||
client.Del("l")
|
||||
//list操作
|
||||
vals := []string{"a", "b", "c", "d", "e"}
|
||||
for _, v := range vals {
|
||||
client.Rpush("l", []byte(v))
|
||||
}
|
||||
dbvals,_ := client.Lrange("l", 0, 4)
|
||||
for i, v := range dbvals {
|
||||
println(i,":",string(v))
|
||||
}
|
||||
client.Del("l")
|
||||
}
|
||||
|
||||
```
|
||||
我们可以看到操作redis非常的方便,而且我实际项目中应用下来性能也很高。client的命令和redis的命令基本保持一致。所以和原生态操作redis非常类似。
|
||||
@@ -148,52 +148,52 @@ MongoDB是一个高性能,开源,无模式的文档型数据库,是一个
|
||||
安装mgo:
|
||||
|
||||
```Go
|
||||
go get gopkg.in/mgo.v2
|
||||
go get gopkg.in/mgo.v2
|
||||
```
|
||||
|
||||
下面我将演示如何通过Go来操作mongoDB:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopkg.in/mgo.v2"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"log"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"gopkg.in/mgo.v2"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Phone string
|
||||
type Person struct {
|
||||
Name string
|
||||
Phone string
|
||||
}
|
||||
|
||||
func main() {
|
||||
session, err := mgo.Dial("server1.example.com,server2.example.com")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// Optional. Switch the session to a monotonic behavior.
|
||||
session.SetMode(mgo.Monotonic, true)
|
||||
|
||||
c := session.DB("test").C("people")
|
||||
err = c.Insert(&Person{"Ale", "+55 53 8116 9639"},
|
||||
&Person{"Cla", "+55 53 8402 8510"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
func main() {
|
||||
session, err := mgo.Dial("server1.example.com,server2.example.com")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// Optional. Switch the session to a monotonic behavior.
|
||||
session.SetMode(mgo.Monotonic, true)
|
||||
|
||||
c := session.DB("test").C("people")
|
||||
err = c.Insert(&Person{"Ale", "+55 53 8116 9639"},
|
||||
&Person{"Cla", "+55 53 8402 8510"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
result := Person{}
|
||||
err = c.Find(bson.M{"name": "Ale"}).One(&result)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Phone:", result.Phone)
|
||||
result := Person{}
|
||||
err = c.Find(bson.M{"name": "Ale"}).One(&result)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Phone:", result.Phone)
|
||||
}
|
||||
|
||||
```
|
||||
我们可以看出来mgo的操作方式和beedb的操作方式几乎类似,都是基于struct的操作方式,这个就是Go Style。
|
||||
|
||||
|
||||
52
zh/06.1.md
52
zh/06.1.md
@@ -37,52 +37,52 @@ cookie是有时间限制的,根据生命期不同分成两种:会话cookie
|
||||
Go语言中通过net/http包中的SetCookie来设置:
|
||||
```Go
|
||||
|
||||
http.SetCookie(w ResponseWriter, cookie *Cookie)
|
||||
http.SetCookie(w ResponseWriter, cookie *Cookie)
|
||||
```
|
||||
w表示需要写入的response,cookie是一个struct,让我们来看一下cookie对象是怎么样的
|
||||
```Go
|
||||
|
||||
type Cookie struct {
|
||||
Name string
|
||||
Value string
|
||||
Path string
|
||||
Domain string
|
||||
Expires time.Time
|
||||
RawExpires string
|
||||
type Cookie struct {
|
||||
Name string
|
||||
Value string
|
||||
Path string
|
||||
Domain string
|
||||
Expires time.Time
|
||||
RawExpires string
|
||||
|
||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||
// MaxAge>0 means Max-Age attribute present and given in seconds
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
Raw string
|
||||
Unparsed []string // Raw text of unparsed attribute-value pairs
|
||||
}
|
||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||
// MaxAge>0 means Max-Age attribute present and given in seconds
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
Raw string
|
||||
Unparsed []string // Raw text of unparsed attribute-value pairs
|
||||
}
|
||||
|
||||
```
|
||||
我们来看一个例子,如何设置cookie
|
||||
```Go
|
||||
|
||||
expiration := time.Now()
|
||||
expiration = expiration.AddDate(1, 0, 0)
|
||||
cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration}
|
||||
http.SetCookie(w, &cookie)
|
||||
expiration := time.Now()
|
||||
expiration = expiration.AddDate(1, 0, 0)
|
||||
cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration}
|
||||
http.SetCookie(w, &cookie)
|
||||
```
|
||||
|
||||
### Go读取cookie
|
||||
上面的例子演示了如何设置cookie数据,我们这里来演示一下如何读取cookie
|
||||
```Go
|
||||
|
||||
cookie, _ := r.Cookie("username")
|
||||
fmt.Fprint(w, cookie)
|
||||
cookie, _ := r.Cookie("username")
|
||||
fmt.Fprint(w, cookie)
|
||||
```
|
||||
还有另外一种读取方式
|
||||
```Go
|
||||
|
||||
for _, cookie := range r.Cookies() {
|
||||
fmt.Fprint(w, cookie.Name)
|
||||
}
|
||||
for _, cookie := range r.Cookies() {
|
||||
fmt.Fprint(w, cookie.Name)
|
||||
}
|
||||
```
|
||||
可以看到通过request获取cookie非常方便。
|
||||
|
||||
|
||||
224
zh/06.2.md
224
zh/06.2.md
@@ -34,40 +34,40 @@ session的基本原理是由服务器为每个会话维护一份信息数据,
|
||||
定义一个全局的session管理器
|
||||
```Go
|
||||
|
||||
type Manager struct {
|
||||
cookieName string //private cookiename
|
||||
lock sync.Mutex // protects session
|
||||
provider Provider
|
||||
maxlifetime int64
|
||||
}
|
||||
type Manager struct {
|
||||
cookieName string //private cookiename
|
||||
lock sync.Mutex // protects session
|
||||
provider Provider
|
||||
maxlifetime int64
|
||||
}
|
||||
|
||||
func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) {
|
||||
provider, ok := provides[provideName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
|
||||
}
|
||||
return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
|
||||
func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) {
|
||||
provider, ok := provides[provideName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
|
||||
}
|
||||
return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
|
||||
}
|
||||
|
||||
```
|
||||
Go实现整个的流程应该也是这样的,在main包中创建一个全局的session管理器
|
||||
```Go
|
||||
|
||||
var globalSessions *session.Manager
|
||||
//然后在init函数中初始化
|
||||
func init() {
|
||||
globalSessions, _ = NewManager("memory","gosessionid",3600)
|
||||
}
|
||||
var globalSessions *session.Manager
|
||||
//然后在init函数中初始化
|
||||
func init() {
|
||||
globalSessions, _ = NewManager("memory","gosessionid",3600)
|
||||
}
|
||||
```
|
||||
我们知道session是保存在服务器端的数据,它可以以任何的方式存储,比如存储在内存、数据库或者文件中。因此我们抽象出一个Provider接口,用以表征session管理器底层存储结构。
|
||||
```Go
|
||||
|
||||
type Provider interface {
|
||||
SessionInit(sid string) (Session, error)
|
||||
SessionRead(sid string) (Session, error)
|
||||
SessionDestroy(sid string) error
|
||||
SessionGC(maxLifeTime int64)
|
||||
}
|
||||
type Provider interface {
|
||||
SessionInit(sid string) (Session, error)
|
||||
SessionRead(sid string) (Session, error)
|
||||
SessionDestroy(sid string) error
|
||||
SessionGC(maxLifeTime int64)
|
||||
}
|
||||
```
|
||||
- SessionInit函数实现Session的初始化,操作成功则返回此新的Session变量
|
||||
- SessionRead函数返回sid所代表的Session变量,如果不存在,那么将以sid为参数调用SessionInit函数创建并返回一个新的Session变量
|
||||
@@ -77,31 +77,31 @@ Go实现整个的流程应该也是这样的,在main包中创建一个全局
|
||||
那么Session接口需要实现什么样的功能呢?有过Web开发经验的读者知道,对Session的处理基本就 设置值、读取值、删除值以及获取当前sessionID这四个操作,所以我们的Session接口也就实现这四个操作。
|
||||
```Go
|
||||
|
||||
type Session interface {
|
||||
Set(key, value interface{}) error //set session value
|
||||
Get(key interface{}) interface{} //get session value
|
||||
Delete(key interface{}) error //delete session value
|
||||
SessionID() string //back current sessionID
|
||||
}
|
||||
type Session interface {
|
||||
Set(key, value interface{}) error //set session value
|
||||
Get(key interface{}) interface{} //get session value
|
||||
Delete(key interface{}) error //delete session value
|
||||
SessionID() string //back current sessionID
|
||||
}
|
||||
```
|
||||
>以上设计思路来源于database/sql/driver,先定义好接口,然后具体的存储session的结构实现相应的接口并注册后,相应功能这样就可以使用了,以下是用来随需注册存储session的结构的Register函数的实现。
|
||||
|
||||
```Go
|
||||
|
||||
var provides = make(map[string]Provider)
|
||||
var provides = make(map[string]Provider)
|
||||
|
||||
// Register makes a session provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, provider Provider) {
|
||||
if provider == nil {
|
||||
panic("session: Register provide is nil")
|
||||
}
|
||||
if _, dup := provides[name]; dup {
|
||||
panic("session: Register called twice for provide " + name)
|
||||
}
|
||||
provides[name] = provider
|
||||
// Register makes a session provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, provider Provider) {
|
||||
if provider == nil {
|
||||
panic("session: Register provide is nil")
|
||||
}
|
||||
if _, dup := provides[name]; dup {
|
||||
panic("session: Register called twice for provide " + name)
|
||||
}
|
||||
provides[name] = provider
|
||||
}
|
||||
```
|
||||
### 全局唯一的Session ID
|
||||
|
||||
@@ -109,49 +109,49 @@ Session ID是用来识别访问Web应用的每一个用户,因此必须保证
|
||||
|
||||
```Go
|
||||
|
||||
func (manager *Manager) sessionId() string {
|
||||
b := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return ""
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(b)
|
||||
func (manager *Manager) sessionId() string {
|
||||
b := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return ""
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(b)
|
||||
}
|
||||
```
|
||||
### session创建
|
||||
我们需要为每个来访用户分配或获取与他相关连的Session,以便后面根据Session信息来验证操作。SessionStart这个函数就是用来检测是否已经有某个Session与当前来访用户发生了关联,如果没有则创建之。
|
||||
```Go
|
||||
|
||||
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
cookie, err := r.Cookie(manager.cookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
sid := manager.sessionId()
|
||||
session, _ = manager.provider.SessionInit(sid)
|
||||
cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)}
|
||||
http.SetCookie(w, &cookie)
|
||||
} else {
|
||||
sid, _ := url.QueryUnescape(cookie.Value)
|
||||
session, _ = manager.provider.SessionRead(sid)
|
||||
}
|
||||
return
|
||||
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
cookie, err := r.Cookie(manager.cookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
sid := manager.sessionId()
|
||||
session, _ = manager.provider.SessionInit(sid)
|
||||
cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)}
|
||||
http.SetCookie(w, &cookie)
|
||||
} else {
|
||||
sid, _ := url.QueryUnescape(cookie.Value)
|
||||
session, _ = manager.provider.SessionRead(sid)
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
||||
我们用前面login操作来演示session的运用:
|
||||
```Go
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
sess := globalSessions.SessionStart(w, r)
|
||||
r.ParseForm()
|
||||
if r.Method == "GET" {
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
t.Execute(w, sess.Get("username"))
|
||||
} else {
|
||||
sess.Set("username", r.Form["username"])
|
||||
http.Redirect(w, r, "/", 302)
|
||||
}
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
sess := globalSessions.SessionStart(w, r)
|
||||
r.ParseForm()
|
||||
if r.Method == "GET" {
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
t.Execute(w, sess.Get("username"))
|
||||
} else {
|
||||
sess.Set("username", r.Form["username"])
|
||||
http.Redirect(w, r, "/", 302)
|
||||
}
|
||||
}
|
||||
```
|
||||
### 操作值:设置、读取和删除
|
||||
SessionStart函数返回的是一个满足Session接口的变量,那么我们该如何用他来对session数据进行操作呢?
|
||||
@@ -159,25 +159,25 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们
|
||||
上面的例子中的代码`session.Get("uid")`已经展示了基本的读取数据的操作,现在我们再来看一下详细的操作:
|
||||
```Go
|
||||
|
||||
func count(w http.ResponseWriter, r *http.Request) {
|
||||
sess := globalSessions.SessionStart(w, r)
|
||||
createtime := sess.Get("createtime")
|
||||
if createtime == nil {
|
||||
sess.Set("createtime", time.Now().Unix())
|
||||
} else if (createtime.(int64) + 360) < (time.Now().Unix()) {
|
||||
globalSessions.SessionDestroy(w, r)
|
||||
sess = globalSessions.SessionStart(w, r)
|
||||
}
|
||||
ct := sess.Get("countnum")
|
||||
if ct == nil {
|
||||
sess.Set("countnum", 1)
|
||||
} else {
|
||||
sess.Set("countnum", (ct.(int) + 1))
|
||||
}
|
||||
t, _ := template.ParseFiles("count.gtpl")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
t.Execute(w, sess.Get("countnum"))
|
||||
func count(w http.ResponseWriter, r *http.Request) {
|
||||
sess := globalSessions.SessionStart(w, r)
|
||||
createtime := sess.Get("createtime")
|
||||
if createtime == nil {
|
||||
sess.Set("createtime", time.Now().Unix())
|
||||
} else if (createtime.(int64) + 360) < (time.Now().Unix()) {
|
||||
globalSessions.SessionDestroy(w, r)
|
||||
sess = globalSessions.SessionStart(w, r)
|
||||
}
|
||||
ct := sess.Get("countnum")
|
||||
if ct == nil {
|
||||
sess.Set("countnum", 1)
|
||||
} else {
|
||||
sess.Set("countnum", (ct.(int) + 1))
|
||||
}
|
||||
t, _ := template.ParseFiles("count.gtpl")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
t.Execute(w, sess.Get("countnum"))
|
||||
}
|
||||
```
|
||||
通过上面的例子可以看到,Session的操作和操作key/value数据库类似:Set、Get、Delete等操作
|
||||
|
||||
@@ -187,36 +187,36 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们
|
||||
我们知道,Web应用中有用户退出这个操作,那么当用户退出应用的时候,我们需要对该用户的session数据进行销毁操作,上面的代码已经演示了如何使用session重置操作,下面这个函数就是实现了这个功能:
|
||||
```Go
|
||||
|
||||
//Destroy sessionid
|
||||
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){
|
||||
cookie, err := r.Cookie(manager.cookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
return
|
||||
} else {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
manager.provider.SessionDestroy(cookie.Value)
|
||||
expiration := time.Now()
|
||||
cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
//Destroy sessionid
|
||||
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){
|
||||
cookie, err := r.Cookie(manager.cookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
return
|
||||
} else {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
manager.provider.SessionDestroy(cookie.Value)
|
||||
expiration := time.Now()
|
||||
cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
### session销毁
|
||||
我们来看一下Session管理器如何来管理销毁,只要我们在Main启动的时候启动:
|
||||
```Go
|
||||
|
||||
func init() {
|
||||
go globalSessions.GC()
|
||||
}
|
||||
func init() {
|
||||
go globalSessions.GC()
|
||||
}
|
||||
|
||||
func (manager *Manager) GC() {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
manager.provider.SessionGC(manager.maxlifetime)
|
||||
time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() })
|
||||
}
|
||||
func (manager *Manager) GC() {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
manager.provider.SessionGC(manager.maxlifetime)
|
||||
time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() })
|
||||
}
|
||||
```
|
||||
我们可以看到GC充分利用了time包中的定时器功能,当超时`maxLifeTime`之后调用GC函数,这样就可以保证`maxLifeTime`时间内的session都是可用的,类似的方案也可以用于统计在线用户数之类的。
|
||||
|
||||
|
||||
216
zh/06.3.md
216
zh/06.3.md
@@ -2,136 +2,136 @@
|
||||
上一节我们介绍了Session管理器的实现原理,定义了存储session的接口,这小节我们将示例一个基于内存的session存储接口的实现,其他的存储方式,读者可以自行参考示例来实现,内存的实现请看下面的例子代码
|
||||
```Go
|
||||
|
||||
package memory
|
||||
package memory
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"github.com/astaxie/session"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
import (
|
||||
"container/list"
|
||||
"github.com/astaxie/session"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var pder = &Provider{list: list.New()}
|
||||
var pder = &Provider{list: list.New()}
|
||||
|
||||
type SessionStore struct {
|
||||
sid string //session id唯一标示
|
||||
timeAccessed time.Time //最后访问时间
|
||||
value map[interface{}]interface{} //session里面存储的值
|
||||
}
|
||||
type SessionStore struct {
|
||||
sid string //session id唯一标示
|
||||
timeAccessed time.Time //最后访问时间
|
||||
value map[interface{}]interface{} //session里面存储的值
|
||||
}
|
||||
|
||||
func (st *SessionStore) Set(key, value interface{}) error {
|
||||
st.value[key] = value
|
||||
pder.SessionUpdate(st.sid)
|
||||
func (st *SessionStore) Set(key, value interface{}) error {
|
||||
st.value[key] = value
|
||||
pder.SessionUpdate(st.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) Get(key interface{}) interface{} {
|
||||
pder.SessionUpdate(st.sid)
|
||||
if v, ok := st.value[key]; ok {
|
||||
return v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) Get(key interface{}) interface{} {
|
||||
pder.SessionUpdate(st.sid)
|
||||
if v, ok := st.value[key]; ok {
|
||||
return v
|
||||
} else {
|
||||
return nil
|
||||
func (st *SessionStore) Delete(key interface{}) error {
|
||||
delete(st.value, key)
|
||||
pder.SessionUpdate(st.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) SessionID() string {
|
||||
return st.sid
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
lock sync.Mutex //用来锁
|
||||
sessions map[string]*list.Element //用来存储在内存
|
||||
list *list.List //用来做gc
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionInit(sid string) (session.Session, error) {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
v := make(map[interface{}]interface{}, 0)
|
||||
newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v}
|
||||
element := pder.list.PushBack(newsess)
|
||||
pder.sessions[sid] = element
|
||||
return newsess, nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionRead(sid string) (session.Session, error) {
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
return element.Value.(*SessionStore), nil
|
||||
} else {
|
||||
sess, err := pder.SessionInit(sid)
|
||||
return sess, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionDestroy(sid string) error {
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
delete(pder.sessions, sid)
|
||||
pder.list.Remove(element)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionGC(maxlifetime int64) {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
|
||||
for {
|
||||
element := pder.list.Back()
|
||||
if element == nil {
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) Delete(key interface{}) error {
|
||||
delete(st.value, key)
|
||||
pder.SessionUpdate(st.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) SessionID() string {
|
||||
return st.sid
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
lock sync.Mutex //用来锁
|
||||
sessions map[string]*list.Element //用来存储在内存
|
||||
list *list.List //用来做gc
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionInit(sid string) (session.Session, error) {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
v := make(map[interface{}]interface{}, 0)
|
||||
newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v}
|
||||
element := pder.list.PushBack(newsess)
|
||||
pder.sessions[sid] = element
|
||||
return newsess, nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionRead(sid string) (session.Session, error) {
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
return element.Value.(*SessionStore), nil
|
||||
} else {
|
||||
sess, err := pder.SessionInit(sid)
|
||||
return sess, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionDestroy(sid string) error {
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
delete(pder.sessions, sid)
|
||||
if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() {
|
||||
pder.list.Remove(element)
|
||||
return nil
|
||||
delete(pder.sessions, element.Value.(*SessionStore).sid)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionUpdate(sid string) error {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
element.Value.(*SessionStore).timeAccessed = time.Now()
|
||||
pder.list.MoveToFront(element)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionGC(maxlifetime int64) {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
|
||||
for {
|
||||
element := pder.list.Back()
|
||||
if element == nil {
|
||||
break
|
||||
}
|
||||
if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() {
|
||||
pder.list.Remove(element)
|
||||
delete(pder.sessions, element.Value.(*SessionStore).sid)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionUpdate(sid string) error {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
element.Value.(*SessionStore).timeAccessed = time.Now()
|
||||
pder.list.MoveToFront(element)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
pder.sessions = make(map[string]*list.Element, 0)
|
||||
session.Register("memory", pder)
|
||||
}
|
||||
func init() {
|
||||
pder.sessions = make(map[string]*list.Element, 0)
|
||||
session.Register("memory", pder)
|
||||
}
|
||||
```
|
||||
上面这个代码实现了一个内存存储的session机制。通过init函数注册到session管理器中。这样就可以方便的调用了。我们如何来调用该引擎呢?请看下面的代码
|
||||
```Go
|
||||
|
||||
import (
|
||||
"github.com/astaxie/session"
|
||||
_ "github.com/astaxie/session/providers/memory"
|
||||
)
|
||||
import (
|
||||
"github.com/astaxie/session"
|
||||
_ "github.com/astaxie/session/providers/memory"
|
||||
)
|
||||
```
|
||||
当import的时候已经执行了memory函数里面的init函数,这样就已经注册到session管理器中,我们就可以使用了,通过如下方式就可以初始化一个session管理器:
|
||||
```Go
|
||||
|
||||
var globalSessions *session.Manager
|
||||
var globalSessions *session.Manager
|
||||
|
||||
//然后在init函数中初始化
|
||||
func init() {
|
||||
globalSessions, _ = session.NewManager("memory", "gosessionid", 3600)
|
||||
go globalSessions.GC()
|
||||
}
|
||||
//然后在init函数中初始化
|
||||
func init() {
|
||||
globalSessions, _ = session.NewManager("memory", "gosessionid", 3600)
|
||||
go globalSessions.GC()
|
||||
}
|
||||
|
||||
```
|
||||
## links
|
||||
|
||||
54
zh/06.4.md
54
zh/06.4.md
@@ -6,24 +6,24 @@ session劫持是一种广泛存在的比较严重的安全威胁,在session技
|
||||
我们写了如下的代码来展示一个count计数器:
|
||||
```Go
|
||||
|
||||
func count(w http.ResponseWriter, r *http.Request) {
|
||||
sess := globalSessions.SessionStart(w, r)
|
||||
ct := sess.Get("countnum")
|
||||
if ct == nil {
|
||||
sess.Set("countnum", 1)
|
||||
} else {
|
||||
sess.Set("countnum", (ct.(int) + 1))
|
||||
}
|
||||
t, _ := template.ParseFiles("count.gtpl")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
t.Execute(w, sess.Get("countnum"))
|
||||
func count(w http.ResponseWriter, r *http.Request) {
|
||||
sess := globalSessions.SessionStart(w, r)
|
||||
ct := sess.Get("countnum")
|
||||
if ct == nil {
|
||||
sess.Set("countnum", 1)
|
||||
} else {
|
||||
sess.Set("countnum", (ct.(int) + 1))
|
||||
}
|
||||
t, _ := template.ParseFiles("count.gtpl")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
t.Execute(w, sess.Get("countnum"))
|
||||
}
|
||||
|
||||
```
|
||||
count.gtpl的代码如下所示:
|
||||
```Go
|
||||
|
||||
Hi. Now count:{{.}}
|
||||
Hi. Now count:{{.}}
|
||||
```
|
||||
然后我们在浏览器里面刷新可以看到如下内容:
|
||||
|
||||
@@ -60,27 +60,27 @@ count.gtpl的代码如下所示:
|
||||
第二步就是在每个请求里面加上token,实现类似前面章节里面讲的防止form重复递交类似的功能,我们在每个请求里面加上一个隐藏的token,然后每次验证这个token,从而保证用户的请求都是唯一性。
|
||||
```Go
|
||||
|
||||
h := md5.New()
|
||||
salt:="astaxie%^7&8888"
|
||||
io.WriteString(h,salt+time.Now().String())
|
||||
token:=fmt.Sprintf("%x",h.Sum(nil))
|
||||
if r.Form["token"]!=token{
|
||||
//提示登录
|
||||
}
|
||||
sess.Set("token",token)
|
||||
h := md5.New()
|
||||
salt:="astaxie%^7&8888"
|
||||
io.WriteString(h,salt+time.Now().String())
|
||||
token:=fmt.Sprintf("%x",h.Sum(nil))
|
||||
if r.Form["token"]!=token{
|
||||
//提示登录
|
||||
}
|
||||
sess.Set("token",token)
|
||||
|
||||
```
|
||||
### 间隔生成新的SID
|
||||
还有一个解决方案就是,我们给session额外设置一个创建时间的值,一旦过了一定的时间,我们销毁这个sessionID,重新生成新的session,这样可以一定程度上防止session劫持的问题。
|
||||
```Go
|
||||
|
||||
createtime := sess.Get("createtime")
|
||||
if createtime == nil {
|
||||
sess.Set("createtime", time.Now().Unix())
|
||||
} else if (createtime.(int64) + 60) < (time.Now().Unix()) {
|
||||
globalSessions.SessionDestroy(w, r)
|
||||
sess = globalSessions.SessionStart(w, r)
|
||||
}
|
||||
createtime := sess.Get("createtime")
|
||||
if createtime == nil {
|
||||
sess.Set("createtime", time.Now().Unix())
|
||||
} else if (createtime.(int64) + 60) < (time.Now().Unix()) {
|
||||
globalSessions.SessionDestroy(w, r)
|
||||
sess = globalSessions.SessionStart(w, r)
|
||||
}
|
||||
```
|
||||
session启动后,我们设置了一个值,用于记录生成sessionID的时间。通过判断每次请求是否过期(这里设置了60秒)定期生成新的ID,这样使得攻击者获取有效sessionID的机会大大降低。
|
||||
|
||||
|
||||
258
zh/07.1.md
258
zh/07.1.md
@@ -6,80 +6,8 @@ XML作为一种数据交换和信息传递的格式已经十分普及。而随
|
||||
假如你是一名运维人员,你为你所管理的所有服务器生成了如下内容的xml的配置文件:
|
||||
```xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<servers version="1">
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
</servers>
|
||||
```
|
||||
上面的XML文档描述了两个服务器的信息,包含了服务器名和服务器的IP信息,接下来的Go例子以此XML描述的信息进行操作。
|
||||
|
||||
## 解析XML
|
||||
如何解析如上这个XML文件呢? 我们可以通过xml包的`Unmarshal`函数来达到我们的目的
|
||||
```Go
|
||||
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
```
|
||||
data接收的是XML数据流,v是需要输出的结构,定义为interface,也就是可以把XML转换为任意的格式。我们这里主要介绍struct的转换,因为struct和XML都有类似树结构的特征。
|
||||
|
||||
示例代码如下:
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Recurlyservers struct {
|
||||
XMLName xml.Name `xml:"servers"`
|
||||
Version string `xml:"version,attr"`
|
||||
Svs []server `xml:"server"`
|
||||
Description string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type server struct {
|
||||
XMLName xml.Name `xml:"server"`
|
||||
ServerName string `xml:"serverName"`
|
||||
ServerIP string `xml:"serverIP"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
file, err := os.Open("servers.xml") // For read access.
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
v := Recurlyservers{}
|
||||
err = xml.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(v)
|
||||
}
|
||||
|
||||
```
|
||||
XML本质上是一种树形的数据格式,而我们可以定义与之匹配的go 语言的 struct类型,然后通过xml.Unmarshal来将xml中的数据解析成对应的struct对象。如上例子输出如下数据
|
||||
```xml
|
||||
|
||||
{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}]
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<servers version="1">
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
@@ -88,13 +16,85 @@ XML本质上是一种树形的数据格式,而我们可以定义与之匹配
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
</servers>
|
||||
```
|
||||
上面的XML文档描述了两个服务器的信息,包含了服务器名和服务器的IP信息,接下来的Go例子以此XML描述的信息进行操作。
|
||||
|
||||
## 解析XML
|
||||
如何解析如上这个XML文件呢? 我们可以通过xml包的`Unmarshal`函数来达到我们的目的
|
||||
```Go
|
||||
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
```
|
||||
data接收的是XML数据流,v是需要输出的结构,定义为interface,也就是可以把XML转换为任意的格式。我们这里主要介绍struct的转换,因为struct和XML都有类似树结构的特征。
|
||||
|
||||
示例代码如下:
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Recurlyservers struct {
|
||||
XMLName xml.Name `xml:"servers"`
|
||||
Version string `xml:"version,attr"`
|
||||
Svs []server `xml:"server"`
|
||||
Description string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type server struct {
|
||||
XMLName xml.Name `xml:"server"`
|
||||
ServerName string `xml:"serverName"`
|
||||
ServerIP string `xml:"serverIP"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
file, err := os.Open("servers.xml") // For read access.
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
v := Recurlyservers{}
|
||||
err = xml.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(v)
|
||||
}
|
||||
|
||||
```
|
||||
XML本质上是一种树形的数据格式,而我们可以定义与之匹配的go 语言的 struct类型,然后通过xml.Unmarshal来将xml中的数据解析成对应的struct对象。如上例子输出如下数据
|
||||
```xml
|
||||
|
||||
{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}]
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
}
|
||||
|
||||
```
|
||||
上面的例子中,将xml文件解析成对应的struct对象是通过`xml.Unmarshal`来完成的,这个过程是如何实现的?可以看到我们的struct定义后面多了一些类似于`xml:"serverName"`这样的内容,这个是struct的一个特性,它们被称为 struct tag,它们是用来辅助反射的。我们来看一下`Unmarshal`的定义:
|
||||
```Go
|
||||
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
```
|
||||
我们看到函数定义了两个参数,第一个是XML数据流,第二个是存储的对应类型,目前支持struct、slice和string,XML包内部采用了反射来进行数据的映射,所以v里面的字段必须是导出的。`Unmarshal`解析的时候XML元素和字段怎么对应起来的呢?这是有一个优先级读取流程的,首先会读取struct tag,如果没有,那么就会对应字段名。必须注意一点的是解析的时候tag、字段名、XML元素都是大小写敏感的,所以必须一一对应字段。
|
||||
|
||||
@@ -106,14 +106,14 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的
|
||||
|
||||
```xml
|
||||
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
|
||||
```
|
||||
|
||||
@@ -133,61 +133,61 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的
|
||||
假若我们不是要解析如上所示的XML文件,而是生成它,那么在go语言中又该如何实现呢? xml包中提供了`Marshal`和`MarshalIndent`两个函数,来满足我们的需求。这两个函数主要的区别是第二个函数会增加前缀和缩进,函数的定义如下所示:
|
||||
```Go
|
||||
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
|
||||
```
|
||||
两个函数第一个参数是用来生成XML的结构定义类型数据,都是返回生成的XML数据流。
|
||||
|
||||
下面我们来看一下如何输出如上的XML:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Servers struct {
|
||||
XMLName xml.Name `xml:"servers"`
|
||||
Version string `xml:"version,attr"`
|
||||
Svs []server `xml:"server"`
|
||||
type Servers struct {
|
||||
XMLName xml.Name `xml:"servers"`
|
||||
Version string `xml:"version,attr"`
|
||||
Svs []server `xml:"server"`
|
||||
}
|
||||
|
||||
type server struct {
|
||||
ServerName string `xml:"serverName"`
|
||||
ServerIP string `xml:"serverIP"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
v := &Servers{Version: "1"}
|
||||
v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"})
|
||||
v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"})
|
||||
output, err := xml.MarshalIndent(v, " ", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
}
|
||||
os.Stdout.Write([]byte(xml.Header))
|
||||
|
||||
type server struct {
|
||||
ServerName string `xml:"serverName"`
|
||||
ServerIP string `xml:"serverIP"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
v := &Servers{Version: "1"}
|
||||
v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"})
|
||||
v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"})
|
||||
output, err := xml.MarshalIndent(v, " ", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
}
|
||||
os.Stdout.Write([]byte(xml.Header))
|
||||
|
||||
os.Stdout.Write(output)
|
||||
}
|
||||
os.Stdout.Write(output)
|
||||
}
|
||||
|
||||
```
|
||||
上面的代码输出如下信息:
|
||||
```xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<servers version="1">
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
</servers>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<servers version="1">
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
```
|
||||
和我们之前定义的文件的格式一模一样,之所以会有`os.Stdout.Write([]byte(xml.Header))` 这句代码的出现,是因为`xml.MarshalIndent`或者`xml.Marshal`输出的信息都是不带XML头的,为了生成正确的xml文件,我们使用了xml包预定义的Header变量。
|
||||
@@ -220,13 +220,13 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的
|
||||
- tag中含有`"a>b>c"`,那么就会循环输出三个元素a包含b,b包含c,例如如下代码就会输出
|
||||
|
||||
```xml
|
||||
FirstName string `xml:"name>first"`
|
||||
LastName string `xml:"name>last"`
|
||||
FirstName string `xml:"name>first"`
|
||||
LastName string `xml:"name>last"`
|
||||
|
||||
<name>
|
||||
<first>Asta</first>
|
||||
<last>Xie</last>
|
||||
</name>
|
||||
<name>
|
||||
<first>Asta</first>
|
||||
<last>Xie</last>
|
||||
</name>
|
||||
|
||||
```
|
||||
上面我们介绍了如何使用Go语言的xml包来编/解码XML文件,重要的一点是对XML的所有操作都是通过struct tag来实现的,所以学会对struct tag的运用变得非常重要,在文章中我们简要的列举了如何定义tag。更多内容或tag定义请参看相应的官方资料。
|
||||
|
||||
220
zh/07.2.md
220
zh/07.2.md
@@ -4,7 +4,7 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言,
|
||||
前一小节的运维的例子用json来表示,结果描述如下:
|
||||
```json
|
||||
|
||||
{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}
|
||||
{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}
|
||||
```
|
||||
本小节余下的内容将以此JSON数据为基础,来介绍go语言的json包对JSON数据的编、解码。
|
||||
## 解析JSON
|
||||
@@ -13,33 +13,33 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言,
|
||||
假如有了上面的JSON串,那么我们如何来解析这个JSON串呢?Go的JSON包中有如下函数
|
||||
```Go
|
||||
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
```
|
||||
通过这个函数我们就可以实现解析的目的,详细的解析例子请看如下代码:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
ServerName string
|
||||
ServerIP string
|
||||
}
|
||||
type Server struct {
|
||||
ServerName string
|
||||
ServerIP string
|
||||
}
|
||||
|
||||
type Serverslice struct {
|
||||
Servers []Server
|
||||
}
|
||||
type Serverslice struct {
|
||||
Servers []Server
|
||||
}
|
||||
|
||||
func main() {
|
||||
var s Serverslice
|
||||
str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}`
|
||||
json.Unmarshal([]byte(str), &s)
|
||||
fmt.Println(s)
|
||||
}
|
||||
func main() {
|
||||
var s Serverslice
|
||||
str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}`
|
||||
json.Unmarshal([]byte(str), &s)
|
||||
fmt.Println(s)
|
||||
}
|
||||
```
|
||||
在上面的示例代码中,我们首先定义了与json数据对应的结构体,数组对应slice,字段名对应JSON里面的KEY,在解析的时候,如何将json数据与struct字段相匹配呢?例如JSON的key是`Foo`,那么怎么找对应的字段呢?
|
||||
|
||||
@@ -62,71 +62,71 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言,
|
||||
现在我们假设有如下的JSON数据
|
||||
```Go
|
||||
|
||||
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
|
||||
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
|
||||
```
|
||||
如果在我们不知道他的结构的情况下,我们把他解析到interface{}里面
|
||||
```Go
|
||||
|
||||
var f interface{}
|
||||
err := json.Unmarshal(b, &f)
|
||||
var f interface{}
|
||||
err := json.Unmarshal(b, &f)
|
||||
```
|
||||
这个时候f里面存储了一个map类型,他们的key是string,值存储在空的interface{}里
|
||||
```Go
|
||||
|
||||
f = map[string]interface{}{
|
||||
"Name": "Wednesday",
|
||||
"Age": 6,
|
||||
"Parents": []interface{}{
|
||||
"Gomez",
|
||||
"Morticia",
|
||||
},
|
||||
}
|
||||
f = map[string]interface{}{
|
||||
"Name": "Wednesday",
|
||||
"Age": 6,
|
||||
"Parents": []interface{}{
|
||||
"Gomez",
|
||||
"Morticia",
|
||||
},
|
||||
}
|
||||
```
|
||||
那么如何来访问这些数据呢?通过断言的方式:
|
||||
```Go
|
||||
|
||||
m := f.(map[string]interface{})
|
||||
m := f.(map[string]interface{})
|
||||
```
|
||||
通过断言之后,你就可以通过如下方式来访问里面的数据了
|
||||
```Go
|
||||
|
||||
for k, v := range m {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
fmt.Println(k, "is string", vv)
|
||||
case int:
|
||||
fmt.Println(k, "is int", vv)
|
||||
case float64:
|
||||
fmt.Println(k,"is float64",vv)
|
||||
case []interface{}:
|
||||
fmt.Println(k, "is an array:")
|
||||
for i, u := range vv {
|
||||
fmt.Println(i, u)
|
||||
}
|
||||
default:
|
||||
fmt.Println(k, "is of a type I don't know how to handle")
|
||||
for k, v := range m {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
fmt.Println(k, "is string", vv)
|
||||
case int:
|
||||
fmt.Println(k, "is int", vv)
|
||||
case float64:
|
||||
fmt.Println(k,"is float64",vv)
|
||||
case []interface{}:
|
||||
fmt.Println(k, "is an array:")
|
||||
for i, u := range vv {
|
||||
fmt.Println(i, u)
|
||||
}
|
||||
default:
|
||||
fmt.Println(k, "is of a type I don't know how to handle")
|
||||
}
|
||||
}
|
||||
```
|
||||
通过上面的示例可以看到,通过interface{}与type assert的配合,我们就可以解析未知结构的JSON数了。
|
||||
|
||||
上面这个是官方提供的解决方案,其实很多时候我们通过类型断言,操作起来不是很方便,目前bitly公司开源了一个叫做`simplejson`的包,在处理未知结构体的JSON时相当方便,详细例子如下所示:
|
||||
```Go
|
||||
|
||||
js, err := NewJson([]byte(`{
|
||||
"test": {
|
||||
"array": [1, "2", 3],
|
||||
"int": 10,
|
||||
"float": 5.150,
|
||||
"bignum": 9223372036854775807,
|
||||
"string": "simplejson",
|
||||
"bool": true
|
||||
}
|
||||
}`))
|
||||
js, err := NewJson([]byte(`{
|
||||
"test": {
|
||||
"array": [1, "2", 3],
|
||||
"int": 10,
|
||||
"float": 5.150,
|
||||
"bignum": 9223372036854775807,
|
||||
"string": "simplejson",
|
||||
"bool": true
|
||||
}
|
||||
}`))
|
||||
|
||||
arr, _ := js.Get("test").Get("array").Array()
|
||||
i, _ := js.Get("test").Get("int").Int()
|
||||
ms := js.Get("test").Get("string").MustString()
|
||||
arr, _ := js.Get("test").Get("array").Array()
|
||||
i, _ := js.Get("test").Get("int").Int()
|
||||
ms := js.Get("test").Get("string").MustString()
|
||||
|
||||
```
|
||||
可以看到,使用这个库操作JSON比起官方包来说,简单的多,详细的请参考如下地址:https://github.com/bitly/go-simplejson
|
||||
@@ -135,55 +135,55 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言,
|
||||
我们开发很多应用的时候,最后都是要输出JSON数据串,那么如何来处理呢?JSON包里面通过`Marshal`函数来处理,函数定义如下:
|
||||
```Go
|
||||
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
```
|
||||
假设我们还是需要生成上面的服务器列表信息,那么如何来处理呢?请看下面的例子:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
ServerName string
|
||||
ServerIP string
|
||||
}
|
||||
|
||||
type Serverslice struct {
|
||||
Servers []Server
|
||||
}
|
||||
|
||||
func main() {
|
||||
var s Serverslice
|
||||
s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"})
|
||||
s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"})
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
fmt.Println("json err:", err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
type Server struct {
|
||||
ServerName string
|
||||
ServerIP string
|
||||
}
|
||||
|
||||
type Serverslice struct {
|
||||
Servers []Server
|
||||
}
|
||||
|
||||
func main() {
|
||||
var s Serverslice
|
||||
s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"})
|
||||
s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"})
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
fmt.Println("json err:", err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
}
|
||||
|
||||
```
|
||||
输出如下内容:
|
||||
```json
|
||||
|
||||
{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}
|
||||
{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}
|
||||
```
|
||||
我们看到上面的输出字段名的首字母都是大写的,如果你想用小写的首字母怎么办呢?把结构体的字段名改成首字母小写的?JSON输出的时候必须注意,只有导出的字段才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过struct tag定义来实现:
|
||||
```Go
|
||||
|
||||
type Server struct {
|
||||
ServerName string `json:"serverName"`
|
||||
ServerIP string `json:"serverIP"`
|
||||
}
|
||||
type Server struct {
|
||||
ServerName string `json:"serverName"`
|
||||
ServerIP string `json:"serverIP"`
|
||||
}
|
||||
|
||||
type Serverslice struct {
|
||||
Servers []Server `json:"servers"`
|
||||
}
|
||||
type Serverslice struct {
|
||||
Servers []Server `json:"servers"`
|
||||
}
|
||||
```
|
||||
通过修改上面的结构体定义,输出的JSON串就和我们最开始定义的JSON串保持一致了。
|
||||
|
||||
@@ -198,32 +198,32 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言,
|
||||
举例来说:
|
||||
```Go
|
||||
|
||||
type Server struct {
|
||||
// ID 不会导出到JSON中
|
||||
ID int `json:"-"`
|
||||
type Server struct {
|
||||
// ID 不会导出到JSON中
|
||||
ID int `json:"-"`
|
||||
|
||||
// ServerName2 的值会进行二次JSON编码
|
||||
ServerName string `json:"serverName"`
|
||||
ServerName2 string `json:"serverName2,string"`
|
||||
// ServerName2 的值会进行二次JSON编码
|
||||
ServerName string `json:"serverName"`
|
||||
ServerName2 string `json:"serverName2,string"`
|
||||
|
||||
// 如果 ServerIP 为空,则不输出到JSON串中
|
||||
ServerIP string `json:"serverIP,omitempty"`
|
||||
}
|
||||
// 如果 ServerIP 为空,则不输出到JSON串中
|
||||
ServerIP string `json:"serverIP,omitempty"`
|
||||
}
|
||||
|
||||
s := Server {
|
||||
ID: 3,
|
||||
ServerName: `Go "1.0" `,
|
||||
ServerName2: `Go "1.0" `,
|
||||
ServerIP: ``,
|
||||
}
|
||||
b, _ := json.Marshal(s)
|
||||
os.Stdout.Write(b)
|
||||
s := Server {
|
||||
ID: 3,
|
||||
ServerName: `Go "1.0" `,
|
||||
ServerName2: `Go "1.0" `,
|
||||
ServerIP: ``,
|
||||
}
|
||||
b, _ := json.Marshal(s)
|
||||
os.Stdout.Write(b)
|
||||
|
||||
```
|
||||
会输出以下内容:
|
||||
```json
|
||||
|
||||
{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""}
|
||||
{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""}
|
||||
|
||||
```
|
||||
Marshal函数只有在转换成功的时候才会返回数据,在转换的过程中我们需要注意几点:
|
||||
|
||||
310
zh/07.3.md
310
zh/07.3.md
@@ -11,9 +11,9 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果
|
||||
`regexp`包中含有三个函数用来判断是否匹配,如果匹配返回true,否则返回false
|
||||
```Go
|
||||
|
||||
func Match(pattern string, b []byte) (matched bool, error error)
|
||||
func MatchReader(pattern string, r io.RuneReader) (matched bool, error error)
|
||||
func MatchString(pattern string, s string) (matched bool, error error)
|
||||
func Match(pattern string, b []byte) (matched bool, error error)
|
||||
func MatchReader(pattern string, r io.RuneReader) (matched bool, error error)
|
||||
func MatchString(pattern string, s string) (matched bool, error error)
|
||||
|
||||
```
|
||||
上面的三个函数实现了同一个功能,就是判断`pattern`是否和输入源匹配,匹配的话就返回true,如果解析正则出错则返回error。三个函数的输入源分别是byte slice、RuneReader和string。
|
||||
@@ -21,26 +21,26 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果
|
||||
如果要验证一个输入是不是IP地址,那么如何来判断呢?请看如下实现
|
||||
```Go
|
||||
|
||||
func IsIP(ip string) (b bool) {
|
||||
if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
func IsIP(ip string) (b bool) {
|
||||
if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
可以看到,`regexp`的pattern和我们平常使用的正则一模一样。再来看一个例子:当用户输入一个字符串,我们想知道是不是一次合法的输入:
|
||||
```Go
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 1 {
|
||||
fmt.Println("Usage: regexp [string]")
|
||||
os.Exit(1)
|
||||
} else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m {
|
||||
fmt.Println("数字")
|
||||
} else {
|
||||
fmt.Println("不是数字")
|
||||
}
|
||||
func main() {
|
||||
if len(os.Args) == 1 {
|
||||
fmt.Println("Usage: regexp [string]")
|
||||
os.Exit(1)
|
||||
} else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m {
|
||||
fmt.Println("数字")
|
||||
} else {
|
||||
fmt.Println("不是数字")
|
||||
}
|
||||
}
|
||||
```
|
||||
在上面的两个小例子中,我们采用了Match(Reader|String)来判断一些字符串是否符合我们的描述需求,它们使用起来非常方便。
|
||||
|
||||
@@ -50,52 +50,52 @@ Match模式只能用来对字符串的判断,而无法截取字符串的一部
|
||||
我们经常需要一些爬虫程序,下面就以爬虫为例来说明如何使用正则来过滤或截取抓取到的数据:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
resp, err := http.Get("http://www.baidu.com")
|
||||
if err != nil {
|
||||
fmt.Println("http get error.")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("http read error")
|
||||
return
|
||||
}
|
||||
|
||||
src := string(body)
|
||||
|
||||
//将HTML标签全转换成小写
|
||||
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
|
||||
src = re.ReplaceAllStringFunc(src, strings.ToLower)
|
||||
|
||||
//去除STYLE
|
||||
re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
|
||||
src = re.ReplaceAllString(src, "")
|
||||
|
||||
//去除SCRIPT
|
||||
re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
|
||||
src = re.ReplaceAllString(src, "")
|
||||
|
||||
//去除所有尖括号内的HTML代码,并换成换行符
|
||||
re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
|
||||
src = re.ReplaceAllString(src, "\n")
|
||||
|
||||
//去除连续的换行符
|
||||
re, _ = regexp.Compile("\\s{2,}")
|
||||
src = re.ReplaceAllString(src, "\n")
|
||||
|
||||
fmt.Println(strings.TrimSpace(src))
|
||||
func main() {
|
||||
resp, err := http.Get("http://www.baidu.com")
|
||||
if err != nil {
|
||||
fmt.Println("http get error.")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("http read error")
|
||||
return
|
||||
}
|
||||
|
||||
src := string(body)
|
||||
|
||||
//将HTML标签全转换成小写
|
||||
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
|
||||
src = re.ReplaceAllStringFunc(src, strings.ToLower)
|
||||
|
||||
//去除STYLE
|
||||
re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
|
||||
src = re.ReplaceAllString(src, "")
|
||||
|
||||
//去除SCRIPT
|
||||
re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
|
||||
src = re.ReplaceAllString(src, "")
|
||||
|
||||
//去除所有尖括号内的HTML代码,并换成换行符
|
||||
re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
|
||||
src = re.ReplaceAllString(src, "\n")
|
||||
|
||||
//去除连续的换行符
|
||||
re, _ = regexp.Compile("\\s{2,}")
|
||||
src = re.ReplaceAllString(src, "\n")
|
||||
|
||||
fmt.Println(strings.TrimSpace(src))
|
||||
}
|
||||
|
||||
```
|
||||
从这个示例可以看出,使用复杂的正则首先是Compile,它会解析正则表达式是否合法,如果正确,那么就会返回一个Regexp,然后就可以利用返回的Regexp在任意的字符串上面执行需要的操作。
|
||||
@@ -103,144 +103,144 @@ Match模式只能用来对字符串的判断,而无法截取字符串的一部
|
||||
解析正则表达式的有如下几个方法:
|
||||
```Go
|
||||
|
||||
func Compile(expr string) (*Regexp, error)
|
||||
func CompilePOSIX(expr string) (*Regexp, error)
|
||||
func MustCompile(str string) *Regexp
|
||||
func MustCompilePOSIX(str string) *Regexp
|
||||
func Compile(expr string) (*Regexp, error)
|
||||
func CompilePOSIX(expr string) (*Regexp, error)
|
||||
func MustCompile(str string) *Regexp
|
||||
func MustCompilePOSIX(str string) *Regexp
|
||||
```
|
||||
CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法,它使用最左最长方式搜索,而Compile是采用的则只采用最左方式搜索(例如[a-z]{2,4}这样一个正则表达式,应用于"aa09aaa88aaaa"这个文本串时,CompilePOSIX返回了aaaa,而Compile的返回的是aa)。前缀有Must的函数表示,在解析正则语法的时候,如果匹配模式串不满足正确的语法则直接panic,而不加Must的则只是返回错误。
|
||||
|
||||
在了解了如何新建一个Regexp之后,我们再来看一下这个struct提供了哪些方法来辅助我们操作字符串,首先我们来看下面这些用来搜索的函数:
|
||||
```Go
|
||||
|
||||
func (re *Regexp) Find(b []byte) []byte
|
||||
func (re *Regexp) FindAll(b []byte, n int) [][]byte
|
||||
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindAllString(s string, n int) []string
|
||||
func (re *Regexp) FindAllStringIndex(s string, n int) [][]int
|
||||
func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string
|
||||
func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int
|
||||
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
|
||||
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindIndex(b []byte) (loc []int)
|
||||
func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int)
|
||||
func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int
|
||||
func (re *Regexp) FindString(s string) string
|
||||
func (re *Regexp) FindStringIndex(s string) (loc []int)
|
||||
func (re *Regexp) FindStringSubmatch(s string) []string
|
||||
func (re *Regexp) FindStringSubmatchIndex(s string) []int
|
||||
func (re *Regexp) FindSubmatch(b []byte) [][]byte
|
||||
func (re *Regexp) FindSubmatchIndex(b []byte) []int
|
||||
func (re *Regexp) Find(b []byte) []byte
|
||||
func (re *Regexp) FindAll(b []byte, n int) [][]byte
|
||||
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindAllString(s string, n int) []string
|
||||
func (re *Regexp) FindAllStringIndex(s string, n int) [][]int
|
||||
func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string
|
||||
func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int
|
||||
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
|
||||
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindIndex(b []byte) (loc []int)
|
||||
func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int)
|
||||
func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int
|
||||
func (re *Regexp) FindString(s string) string
|
||||
func (re *Regexp) FindStringIndex(s string) (loc []int)
|
||||
func (re *Regexp) FindStringSubmatch(s string) []string
|
||||
func (re *Regexp) FindStringSubmatchIndex(s string) []int
|
||||
func (re *Regexp) FindSubmatch(b []byte) [][]byte
|
||||
func (re *Regexp) FindSubmatchIndex(b []byte) []int
|
||||
```
|
||||
上面这18个函数我们根据输入源(byte slice、string和io.RuneReader)不同还可以继续简化成如下几个,其他的只是输入源不一样,其他功能基本是一样的:
|
||||
```Go
|
||||
|
||||
func (re *Regexp) Find(b []byte) []byte
|
||||
func (re *Regexp) FindAll(b []byte, n int) [][]byte
|
||||
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
|
||||
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindIndex(b []byte) (loc []int)
|
||||
func (re *Regexp) FindSubmatch(b []byte) [][]byte
|
||||
func (re *Regexp) FindSubmatchIndex(b []byte) []int
|
||||
func (re *Regexp) Find(b []byte) []byte
|
||||
func (re *Regexp) FindAll(b []byte, n int) [][]byte
|
||||
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
|
||||
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindIndex(b []byte) (loc []int)
|
||||
func (re *Regexp) FindSubmatch(b []byte) [][]byte
|
||||
func (re *Regexp) FindSubmatchIndex(b []byte) []int
|
||||
```
|
||||
对于这些函数的使用我们来看下面这个例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := "I am learning Go language"
|
||||
func main() {
|
||||
a := "I am learning Go language"
|
||||
|
||||
re, _ := regexp.Compile("[a-z]{2,4}")
|
||||
re, _ := regexp.Compile("[a-z]{2,4}")
|
||||
|
||||
//查找符合正则的第一个
|
||||
one := re.Find([]byte(a))
|
||||
fmt.Println("Find:", string(one))
|
||||
//查找符合正则的第一个
|
||||
one := re.Find([]byte(a))
|
||||
fmt.Println("Find:", string(one))
|
||||
|
||||
//查找符合正则的所有slice,n小于0表示返回全部符合的字符串,不然就是返回指定的长度
|
||||
all := re.FindAll([]byte(a), -1)
|
||||
fmt.Println("FindAll", all)
|
||||
//查找符合正则的所有slice,n小于0表示返回全部符合的字符串,不然就是返回指定的长度
|
||||
all := re.FindAll([]byte(a), -1)
|
||||
fmt.Println("FindAll", all)
|
||||
|
||||
//查找符合条件的index位置,开始位置和结束位置
|
||||
index := re.FindIndex([]byte(a))
|
||||
fmt.Println("FindIndex", index)
|
||||
//查找符合条件的index位置,开始位置和结束位置
|
||||
index := re.FindIndex([]byte(a))
|
||||
fmt.Println("FindIndex", index)
|
||||
|
||||
//查找符合条件的所有的index位置,n同上
|
||||
allindex := re.FindAllIndex([]byte(a), -1)
|
||||
fmt.Println("FindAllIndex", allindex)
|
||||
//查找符合条件的所有的index位置,n同上
|
||||
allindex := re.FindAllIndex([]byte(a), -1)
|
||||
fmt.Println("FindAllIndex", allindex)
|
||||
|
||||
re2, _ := regexp.Compile("am(.*)lang(.*)")
|
||||
re2, _ := regexp.Compile("am(.*)lang(.*)")
|
||||
|
||||
//查找Submatch,返回数组,第一个元素是匹配的全部元素,第二个元素是第一个()里面的,第三个是第二个()里面的
|
||||
//下面的输出第一个元素是"am learning Go language"
|
||||
//第二个元素是" learning Go ",注意包含空格的输出
|
||||
//第三个元素是"uage"
|
||||
submatch := re2.FindSubmatch([]byte(a))
|
||||
fmt.Println("FindSubmatch", submatch)
|
||||
for _, v := range submatch {
|
||||
fmt.Println(string(v))
|
||||
}
|
||||
|
||||
//定义和上面的FindIndex一样
|
||||
submatchindex := re2.FindSubmatchIndex([]byte(a))
|
||||
fmt.Println(submatchindex)
|
||||
|
||||
//FindAllSubmatch,查找所有符合条件的子匹配
|
||||
submatchall := re2.FindAllSubmatch([]byte(a), -1)
|
||||
fmt.Println(submatchall)
|
||||
|
||||
//FindAllSubmatchIndex,查找所有字匹配的index
|
||||
submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1)
|
||||
fmt.Println(submatchallindex)
|
||||
//查找Submatch,返回数组,第一个元素是匹配的全部元素,第二个元素是第一个()里面的,第三个是第二个()里面的
|
||||
//下面的输出第一个元素是"am learning Go language"
|
||||
//第二个元素是" learning Go ",注意包含空格的输出
|
||||
//第三个元素是"uage"
|
||||
submatch := re2.FindSubmatch([]byte(a))
|
||||
fmt.Println("FindSubmatch", submatch)
|
||||
for _, v := range submatch {
|
||||
fmt.Println(string(v))
|
||||
}
|
||||
|
||||
//定义和上面的FindIndex一样
|
||||
submatchindex := re2.FindSubmatchIndex([]byte(a))
|
||||
fmt.Println(submatchindex)
|
||||
|
||||
//FindAllSubmatch,查找所有符合条件的子匹配
|
||||
submatchall := re2.FindAllSubmatch([]byte(a), -1)
|
||||
fmt.Println(submatchall)
|
||||
|
||||
//FindAllSubmatchIndex,查找所有字匹配的index
|
||||
submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1)
|
||||
fmt.Println(submatchallindex)
|
||||
}
|
||||
```
|
||||
前面介绍过匹配函数,Regexp也定义了三个函数,它们和同名的外部函数功能一模一样,其实外部函数就是调用了这Regexp的三个函数来实现的:
|
||||
```Go
|
||||
|
||||
func (re *Regexp) Match(b []byte) bool
|
||||
func (re *Regexp) MatchReader(r io.RuneReader) bool
|
||||
func (re *Regexp) MatchString(s string) bool
|
||||
func (re *Regexp) Match(b []byte) bool
|
||||
func (re *Regexp) MatchReader(r io.RuneReader) bool
|
||||
func (re *Regexp) MatchString(s string) bool
|
||||
```
|
||||
接下里让我们来了解替换函数是怎么操作的?
|
||||
```Go
|
||||
|
||||
func (re *Regexp) ReplaceAll(src, repl []byte) []byte
|
||||
func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
|
||||
func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte
|
||||
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
|
||||
func (re *Regexp) ReplaceAllString(src, repl string) string
|
||||
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string
|
||||
func (re *Regexp) ReplaceAll(src, repl []byte) []byte
|
||||
func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
|
||||
func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte
|
||||
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
|
||||
func (re *Regexp) ReplaceAllString(src, repl string) string
|
||||
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string
|
||||
```
|
||||
这些替换函数我们在上面的抓网页的例子有详细应用示例,
|
||||
|
||||
接下来我们看一下Expand的解释:
|
||||
```Go
|
||||
|
||||
func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
|
||||
func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte
|
||||
func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
|
||||
func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte
|
||||
```
|
||||
那么这个Expand到底用来干嘛的呢?请看下面的例子:
|
||||
```Go
|
||||
|
||||
func main() {
|
||||
src := []byte(`
|
||||
call hello alice
|
||||
hello bob
|
||||
call hello eve
|
||||
`)
|
||||
pat := regexp.MustCompile(`(?m)(call)\s+(?P<cmd>\w+)\s+(?P<arg>.+)\s*$`)
|
||||
res := []byte{}
|
||||
for _, s := range pat.FindAllSubmatchIndex(src, -1) {
|
||||
res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s)
|
||||
}
|
||||
fmt.Println(string(res))
|
||||
func main() {
|
||||
src := []byte(`
|
||||
call hello alice
|
||||
hello bob
|
||||
call hello eve
|
||||
`)
|
||||
pat := regexp.MustCompile(`(?m)(call)\s+(?P<cmd>\w+)\s+(?P<arg>.+)\s*$`)
|
||||
res := []byte{}
|
||||
for _, s := range pat.FindAllSubmatchIndex(src, -1) {
|
||||
res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s)
|
||||
}
|
||||
fmt.Println(string(res))
|
||||
}
|
||||
```
|
||||
至此我们已经全部介绍完Go语言的`regexp`包,通过对它的主要函数介绍及演示,相信大家应该能够通过Go语言的正则包进行一些基本的正则的操作了。
|
||||
|
||||
|
||||
414
zh/07.4.md
414
zh/07.4.md
@@ -14,12 +14,12 @@ Web应用反馈给客户端的信息中的大部分内容是静态的,不变
|
||||
在Go语言中,我们使用`template`包来进行模板处理,使用类似`Parse`、`ParseFile`、`Execute`等方法从文件或者字符串加载模板,然后执行类似上面图片展示的模板的merge操作。请看下面的例子:
|
||||
```Go
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
t := template.New("some template") //创建一个模板
|
||||
t, _ = t.ParseFiles("tmpl/welcome.html", nil) //解析模板文件
|
||||
user := GetUser() //获取当前用户信息
|
||||
t.Execute(w, user) //执行模板的merger操作
|
||||
}
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
t := template.New("some template") //创建一个模板
|
||||
t, _ = t.ParseFiles("tmpl/welcome.html", nil) //解析模板文件
|
||||
user := GetUser() //获取当前用户信息
|
||||
t.Execute(w, user) //执行模板的merger操作
|
||||
}
|
||||
```
|
||||
通过上面的例子我们可以看到Go语言的模板操作非常的简单方便,和其他语言的模板处理类似,都是先获取数据,然后渲染数据。
|
||||
|
||||
@@ -36,33 +36,33 @@ Web应用反馈给客户端的信息中的大部分内容是静态的,不变
|
||||
Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{.}}`表示当前的对象,这和Java或者C++中的this类似,如果要访问当前对象的字段通过`{{.FieldName}}`,但是需要注意一点:这个字段必须是导出的(字段首字母必须是大写的),否则在渲染的时候就会报错,请看下面的这个例子:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
UserName string
|
||||
}
|
||||
type Person struct {
|
||||
UserName string
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := template.New("fieldname example")
|
||||
t, _ = t.Parse("hello {{.UserName}}!")
|
||||
p := Person{UserName: "Astaxie"}
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
func main() {
|
||||
t := template.New("fieldname example")
|
||||
t, _ = t.Parse("hello {{.UserName}}!")
|
||||
p := Person{UserName: "Astaxie"}
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
```
|
||||
上面的代码我们可以正确的输出`hello Astaxie`,但是如果我们稍微修改一下代码,在模板中含有了未导出的字段,那么就会报错
|
||||
```Go
|
||||
|
||||
type Person struct {
|
||||
UserName string
|
||||
email string //未导出的字段,首字母是小写的
|
||||
}
|
||||
type Person struct {
|
||||
UserName string
|
||||
email string //未导出的字段,首字母是小写的
|
||||
}
|
||||
|
||||
t, _ = t.Parse("hello {{.UserName}}! {{.email}}")
|
||||
t, _ = t.Parse("hello {{.UserName}}! {{.email}}")
|
||||
```
|
||||
上面的代码就会报错,因为我们调用了一个未导出的字段,但是如果我们调用了一个不存在的字段是不会报错的,而是输出为空。
|
||||
|
||||
@@ -77,67 +77,67 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{
|
||||
详细的使用请看下面的例子:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Friend struct {
|
||||
Fname string
|
||||
}
|
||||
type Friend struct {
|
||||
Fname string
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
UserName string
|
||||
Emails []string
|
||||
Friends []*Friend
|
||||
}
|
||||
type Person struct {
|
||||
UserName string
|
||||
Emails []string
|
||||
Friends []*Friend
|
||||
}
|
||||
|
||||
func main() {
|
||||
f1 := Friend{Fname: "minux.ma"}
|
||||
f2 := Friend{Fname: "xushiwei"}
|
||||
t := template.New("fieldname example")
|
||||
t, _ = t.Parse(`hello {{.UserName}}!
|
||||
{{range .Emails}}
|
||||
an email {{.}}
|
||||
{{end}}
|
||||
{{with .Friends}}
|
||||
{{range .}}
|
||||
my friend name is {{.Fname}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`)
|
||||
p := Person{UserName: "Astaxie",
|
||||
Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"},
|
||||
Friends: []*Friend{&f1, &f2}}
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
func main() {
|
||||
f1 := Friend{Fname: "minux.ma"}
|
||||
f2 := Friend{Fname: "xushiwei"}
|
||||
t := template.New("fieldname example")
|
||||
t, _ = t.Parse(`hello {{.UserName}}!
|
||||
{{range .Emails}}
|
||||
an email {{.}}
|
||||
{{end}}
|
||||
{{with .Friends}}
|
||||
{{range .}}
|
||||
my friend name is {{.Fname}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`)
|
||||
p := Person{UserName: "Astaxie",
|
||||
Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"},
|
||||
Friends: []*Friend{&f1, &f2}}
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
```
|
||||
### 条件处理
|
||||
在Go模板里面如果需要进行条件判断,那么我们可以使用和Go语言的`if-else`语法类似的方式来处理,如果pipeline为空,那么if就认为是false,下面的例子展示了如何使用`if-else`语法:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
import (
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tEmpty := template.New("template test")
|
||||
tEmpty = template.Must(tEmpty.Parse("空 pipeline if demo: {{if ``}} 不会输出. {{end}}\n"))
|
||||
tEmpty.Execute(os.Stdout, nil)
|
||||
func main() {
|
||||
tEmpty := template.New("template test")
|
||||
tEmpty = template.Must(tEmpty.Parse("空 pipeline if demo: {{if ``}} 不会输出. {{end}}\n"))
|
||||
tEmpty.Execute(os.Stdout, nil)
|
||||
|
||||
tWithValue := template.New("template test")
|
||||
tWithValue = template.Must(tWithValue.Parse("不为空的 pipeline if demo: {{if `anything`}} 我有内容,我会输出. {{end}}\n"))
|
||||
tWithValue.Execute(os.Stdout, nil)
|
||||
tWithValue := template.New("template test")
|
||||
tWithValue = template.Must(tWithValue.Parse("不为空的 pipeline if demo: {{if `anything`}} 我有内容,我会输出. {{end}}\n"))
|
||||
tWithValue.Execute(os.Stdout, nil)
|
||||
|
||||
tIfElse := template.New("template test")
|
||||
tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if部分 {{else}} else部分.{{end}}\n"))
|
||||
tIfElse.Execute(os.Stdout, nil)
|
||||
}
|
||||
tIfElse := template.New("template test")
|
||||
tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if部分 {{else}} else部分.{{end}}\n"))
|
||||
tIfElse.Execute(os.Stdout, nil)
|
||||
}
|
||||
```
|
||||
通过上面的演示代码我们知道`if-else`语法相当的简单,在使用过程中很容易集成到我们的模板代码中。
|
||||
|
||||
@@ -147,8 +147,8 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{
|
||||
Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的语法你是不是经常使用,过滤当前目录下面的文件,显示含有"beego"的数据,表达的意思就是前面的输出可以当做后面的输入,最后显示我们想要的数据,而Go语言模板最强大的一点就是支持pipe数据,在Go语言里面任何`{{}}`里面的都是pipelines数据,例如我们上面输出的email里面如果还有一些可能引起XSS注入的,那么我们如何来进行转化呢?
|
||||
```Go
|
||||
|
||||
{{. | html}}
|
||||
|
||||
{{. | html}}
|
||||
|
||||
```
|
||||
在email输出的地方我们可以采用如上方式可以把输出全部转化html的实体,上面的这种方式和我们平常写Unix的方式是不是一模一样,操作起来相当的简便,调用其他的函数也是类似的方式。
|
||||
|
||||
@@ -156,14 +156,14 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的
|
||||
有时候,我们在模板使用过程中需要定义一些局部变量,我们可以在一些操作中申明局部变量,例如`with``range``if`过程中申明局部变量,这个变量的作用域是`{{end}}`之前,Go语言通过申明的局部变量格式如下所示:
|
||||
```Go
|
||||
|
||||
$variable := pipeline
|
||||
$variable := pipeline
|
||||
```
|
||||
详细的例子看下面的:
|
||||
```Go
|
||||
|
||||
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||
|
||||
```
|
||||
### 模板函数
|
||||
@@ -172,194 +172,194 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的
|
||||
每一个模板函数都有一个唯一值的名字,然后与一个Go函数关联,通过如下的方式来关联
|
||||
```Go
|
||||
|
||||
type FuncMap map[string]interface{}
|
||||
type FuncMap map[string]interface{}
|
||||
```
|
||||
例如,如果我们想要的email函数的模板函数名是`emailDeal`,它关联的Go函数名称是`EmailDealWith`,那么我们可以通过下面的方式来注册这个函数
|
||||
```Go
|
||||
|
||||
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
|
||||
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
|
||||
```
|
||||
`EmailDealWith`这个函数的参数和返回值定义如下:
|
||||
```Go
|
||||
|
||||
func EmailDealWith(args …interface{}) string
|
||||
func EmailDealWith(args …interface{}) string
|
||||
```
|
||||
我们来看下面的实现例子:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Friend struct {
|
||||
Fname string
|
||||
type Friend struct {
|
||||
Fname string
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
UserName string
|
||||
Emails []string
|
||||
Friends []*Friend
|
||||
}
|
||||
|
||||
func EmailDealWith(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
UserName string
|
||||
Emails []string
|
||||
Friends []*Friend
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
|
||||
func EmailDealWith(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
// find the @ symbol
|
||||
substrs := strings.Split(s, "@")
|
||||
if len(substrs) != 2 {
|
||||
return s
|
||||
}
|
||||
// replace the @ by " at "
|
||||
return (substrs[0] + " at " + substrs[1])
|
||||
// find the @ symbol
|
||||
substrs := strings.Split(s, "@")
|
||||
if len(substrs) != 2 {
|
||||
return s
|
||||
}
|
||||
// replace the @ by " at "
|
||||
return (substrs[0] + " at " + substrs[1])
|
||||
}
|
||||
|
||||
func main() {
|
||||
f1 := Friend{Fname: "minux.ma"}
|
||||
f2 := Friend{Fname: "xushiwei"}
|
||||
t := template.New("fieldname example")
|
||||
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
|
||||
t, _ = t.Parse(`hello {{.UserName}}!
|
||||
{{range .Emails}}
|
||||
an emails {{.|emailDeal}}
|
||||
{{end}}
|
||||
{{with .Friends}}
|
||||
{{range .}}
|
||||
my friend name is {{.Fname}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`)
|
||||
p := Person{UserName: "Astaxie",
|
||||
Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"},
|
||||
Friends: []*Friend{&f1, &f2}}
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
func main() {
|
||||
f1 := Friend{Fname: "minux.ma"}
|
||||
f2 := Friend{Fname: "xushiwei"}
|
||||
t := template.New("fieldname example")
|
||||
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
|
||||
t, _ = t.Parse(`hello {{.UserName}}!
|
||||
{{range .Emails}}
|
||||
an emails {{.|emailDeal}}
|
||||
{{end}}
|
||||
{{with .Friends}}
|
||||
{{range .}}
|
||||
my friend name is {{.Fname}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`)
|
||||
p := Person{UserName: "Astaxie",
|
||||
Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"},
|
||||
Friends: []*Friend{&f1, &f2}}
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
|
||||
```
|
||||
上面演示了如何自定义函数,其实,在模板包内部已经有内置的实现函数,下面代码截取自模板包里面
|
||||
```Go
|
||||
|
||||
var builtins = FuncMap{
|
||||
"and": and,
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
"or": or,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"urlquery": URLQueryEscaper,
|
||||
}
|
||||
var builtins = FuncMap{
|
||||
"and": and,
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
"or": or,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"urlquery": URLQueryEscaper,
|
||||
}
|
||||
|
||||
```
|
||||
## Must操作
|
||||
模板包里面有一个函数`Must`,它的作用是检测模板是否正确,例如大括号是否匹配,注释是否正确的关闭,变量是否正确的书写。接下来我们演示一个例子,用Must来判断模板是否正确:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"text/template"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tOk := template.New("first")
|
||||
template.Must(tOk.Parse(" some static text /* and a comment */"))
|
||||
fmt.Println("The first one parsed OK.")
|
||||
func main() {
|
||||
tOk := template.New("first")
|
||||
template.Must(tOk.Parse(" some static text /* and a comment */"))
|
||||
fmt.Println("The first one parsed OK.")
|
||||
|
||||
template.Must(template.New("second").Parse("some static text {{ .Name }}"))
|
||||
fmt.Println("The second one parsed OK.")
|
||||
template.Must(template.New("second").Parse("some static text {{ .Name }}"))
|
||||
fmt.Println("The second one parsed OK.")
|
||||
|
||||
fmt.Println("The next one ought to fail.")
|
||||
tErr := template.New("check parse error with Must")
|
||||
template.Must(tErr.Parse(" some static text {{ .Name }"))
|
||||
}
|
||||
fmt.Println("The next one ought to fail.")
|
||||
tErr := template.New("check parse error with Must")
|
||||
template.Must(tErr.Parse(" some static text {{ .Name }"))
|
||||
}
|
||||
|
||||
```
|
||||
将输出如下内容
|
||||
|
||||
```
|
||||
|
||||
The first one parsed OK.
|
||||
The second one parsed OK.
|
||||
The next one ought to fail.
|
||||
panic: template: check parse error with Must:1: unexpected "}" in command
|
||||
The first one parsed OK.
|
||||
The second one parsed OK.
|
||||
The next one ought to fail.
|
||||
panic: template: check parse error with Must:1: unexpected "}" in command
|
||||
```
|
||||
## 嵌套模板
|
||||
我们平常开发Web应用的时候,经常会遇到一些模板有些部分是固定不变的,然后可以抽取出来作为一个独立的部分,例如一个博客的头部和尾部是不变的,而唯一改变的是中间的内容部分。所以我们可以定义成`header`、`content`、`footer`三个部分。Go语言中通过如下的语法来申明
|
||||
```Go
|
||||
|
||||
{{define "子模板名称"}}内容{{end}}
|
||||
{{define "子模板名称"}}内容{{end}}
|
||||
```
|
||||
通过如下方式来调用:
|
||||
```Go
|
||||
|
||||
{{template "子模板名称"}}
|
||||
{{template "子模板名称"}}
|
||||
```
|
||||
接下来我们演示如何使用嵌套模板,我们定义三个文件,`header.tmpl`、`content.tmpl`、`footer.tmpl`文件,里面的内容如下
|
||||
```html
|
||||
|
||||
//header.tmpl
|
||||
{{define "header"}}
|
||||
<html>
|
||||
<head>
|
||||
<title>演示信息</title>
|
||||
</head>
|
||||
<body>
|
||||
{{end}}
|
||||
//header.tmpl
|
||||
{{define "header"}}
|
||||
<html>
|
||||
<head>
|
||||
<title>演示信息</title>
|
||||
</head>
|
||||
<body>
|
||||
{{end}}
|
||||
|
||||
//content.tmpl
|
||||
{{define "content"}}
|
||||
{{template "header"}}
|
||||
<h1>演示嵌套</h1>
|
||||
<ul>
|
||||
<li>嵌套使用define定义子模板</li>
|
||||
<li>调用使用template</li>
|
||||
</ul>
|
||||
{{template "footer"}}
|
||||
{{end}}
|
||||
//content.tmpl
|
||||
{{define "content"}}
|
||||
{{template "header"}}
|
||||
<h1>演示嵌套</h1>
|
||||
<ul>
|
||||
<li>嵌套使用define定义子模板</li>
|
||||
<li>调用使用template</li>
|
||||
</ul>
|
||||
{{template "footer"}}
|
||||
{{end}}
|
||||
|
||||
//footer.tmpl
|
||||
{{define "footer"}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
//footer.tmpl
|
||||
{{define "footer"}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
```
|
||||
演示代码如下:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s1, _ := template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl")
|
||||
s1.ExecuteTemplate(os.Stdout, "header", nil)
|
||||
fmt.Println()
|
||||
s1.ExecuteTemplate(os.Stdout, "content", nil)
|
||||
fmt.Println()
|
||||
s1.ExecuteTemplate(os.Stdout, "footer", nil)
|
||||
fmt.Println()
|
||||
s1.Execute(os.Stdout, nil)
|
||||
}
|
||||
func main() {
|
||||
s1, _ := template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl")
|
||||
s1.ExecuteTemplate(os.Stdout, "header", nil)
|
||||
fmt.Println()
|
||||
s1.ExecuteTemplate(os.Stdout, "content", nil)
|
||||
fmt.Println()
|
||||
s1.ExecuteTemplate(os.Stdout, "footer", nil)
|
||||
fmt.Println()
|
||||
s1.Execute(os.Stdout, nil)
|
||||
}
|
||||
```
|
||||
通过上面的例子我们可以看到通过`template.ParseFiles`把所有的嵌套模板全部解析到模板里面,其实每一个定义的{{define}}都是一个独立的模板,他们相互独立,是并行存在的关系,内部其实存储的是类似map的一种关系(key是模板的名称,value是模板的内容),然后我们通过`ExecuteTemplate`来执行相应的子模板内容,我们可以看到header、footer都是相对独立的,都能输出内容,content 中因为嵌套了header和footer的内容,就会同时输出三个的内容。但是当我们执行`s1.Execute`,没有任何的输出,因为在默认的情况下没有默认的子模板,所以不会输出任何的东西。
|
||||
|
||||
|
||||
110
zh/07.5.md
110
zh/07.5.md
@@ -23,22 +23,22 @@
|
||||
下面是演示代码:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Mkdir("astaxie", 0777)
|
||||
os.MkdirAll("astaxie/test1/test2", 0777)
|
||||
err := os.Remove("astaxie")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
os.RemoveAll("astaxie")
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Mkdir("astaxie", 0777)
|
||||
os.MkdirAll("astaxie/test1/test2", 0777)
|
||||
err := os.Remove("astaxie")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
os.RemoveAll("astaxie")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -84,26 +84,26 @@
|
||||
写文件的示例代码
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
userFile := "astaxie.txt"
|
||||
fout, err := os.Create(userFile)
|
||||
if err != nil {
|
||||
fmt.Println(userFile, err)
|
||||
return
|
||||
}
|
||||
defer fout.Close()
|
||||
for i := 0; i < 10; i++ {
|
||||
fout.WriteString("Just a test!\r\n")
|
||||
fout.Write([]byte("Just a test!\r\n"))
|
||||
}
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
userFile := "astaxie.txt"
|
||||
fout, err := os.Create(userFile)
|
||||
if err != nil {
|
||||
fmt.Println(userFile, err)
|
||||
return
|
||||
}
|
||||
defer fout.Close()
|
||||
for i := 0; i < 10; i++ {
|
||||
fout.WriteString("Just a test!\r\n")
|
||||
fout.Write([]byte("Just a test!\r\n"))
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
### 读文件
|
||||
@@ -120,31 +120,31 @@
|
||||
读文件的示例代码:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
userFile := "asatxie.txt"
|
||||
fl, err := os.Open(userFile)
|
||||
if err != nil {
|
||||
fmt.Println(userFile, err)
|
||||
return
|
||||
}
|
||||
defer fl.Close()
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, _ := fl.Read(buf)
|
||||
if 0 == n {
|
||||
break
|
||||
}
|
||||
os.Stdout.Write(buf[:n])
|
||||
}
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
userFile := "asatxie.txt"
|
||||
fl, err := os.Open(userFile)
|
||||
if err != nil {
|
||||
fmt.Println(userFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
defer fl.Close()
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, _ := fl.Read(buf)
|
||||
if 0 == n {
|
||||
break
|
||||
}
|
||||
os.Stdout.Write(buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
### 删除文件
|
||||
Go语言里面删除文件和删除文件夹是同一个函数
|
||||
|
||||
174
zh/07.6.md
174
zh/07.6.md
@@ -9,15 +9,15 @@
|
||||
|
||||
```Go
|
||||
|
||||
fmt.Println(strings.Contains("seafood", "foo"))
|
||||
fmt.Println(strings.Contains("seafood", "bar"))
|
||||
fmt.Println(strings.Contains("seafood", ""))
|
||||
fmt.Println(strings.Contains("", ""))
|
||||
//Output:
|
||||
//true
|
||||
//false
|
||||
//true
|
||||
//true
|
||||
fmt.Println(strings.Contains("seafood", "foo"))
|
||||
fmt.Println(strings.Contains("seafood", "bar"))
|
||||
fmt.Println(strings.Contains("seafood", ""))
|
||||
fmt.Println(strings.Contains("", ""))
|
||||
//Output:
|
||||
//true
|
||||
//false
|
||||
//true
|
||||
//true
|
||||
|
||||
```
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
```Go
|
||||
|
||||
s := []string{"foo", "bar", "baz"}
|
||||
fmt.Println(strings.Join(s, ", "))
|
||||
//Output:foo, bar, baz
|
||||
s := []string{"foo", "bar", "baz"}
|
||||
fmt.Println(strings.Join(s, ", "))
|
||||
//Output:foo, bar, baz
|
||||
```
|
||||
|
||||
- func Index(s, sep string) int
|
||||
@@ -38,10 +38,10 @@
|
||||
|
||||
```Go
|
||||
|
||||
fmt.Println(strings.Index("chicken", "ken"))
|
||||
fmt.Println(strings.Index("chicken", "dmr"))
|
||||
//Output:4
|
||||
//-1
|
||||
fmt.Println(strings.Index("chicken", "ken"))
|
||||
fmt.Println(strings.Index("chicken", "dmr"))
|
||||
//Output:4
|
||||
//-1
|
||||
```
|
||||
- func Repeat(s string, count int) string
|
||||
|
||||
@@ -49,8 +49,8 @@
|
||||
|
||||
```Go
|
||||
|
||||
fmt.Println("ba" + strings.Repeat("na", 2))
|
||||
//Output:banana
|
||||
fmt.Println("ba" + strings.Repeat("na", 2))
|
||||
//Output:banana
|
||||
```
|
||||
- func Replace(s, old, new string, n int) string
|
||||
|
||||
@@ -58,10 +58,10 @@
|
||||
|
||||
```Go
|
||||
|
||||
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
|
||||
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
|
||||
//Output:oinky oinky oink
|
||||
//moo moo moo
|
||||
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
|
||||
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
|
||||
//Output:oinky oinky oink
|
||||
//moo moo moo
|
||||
```
|
||||
- func Split(s, sep string) []string
|
||||
|
||||
@@ -69,14 +69,14 @@
|
||||
|
||||
```Go
|
||||
|
||||
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
|
||||
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
|
||||
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
|
||||
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
|
||||
//Output:["a" "b" "c"]
|
||||
//["" "man " "plan " "canal panama"]
|
||||
//[" " "x" "y" "z" " "]
|
||||
//[""]
|
||||
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
|
||||
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
|
||||
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
|
||||
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
|
||||
//Output:["a" "b" "c"]
|
||||
//["" "man " "plan " "canal panama"]
|
||||
//[" " "x" "y" "z" " "]
|
||||
//[""]
|
||||
```
|
||||
|
||||
- func Trim(s string, cutset string) string
|
||||
@@ -85,8 +85,8 @@
|
||||
|
||||
```Go
|
||||
|
||||
fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! "))
|
||||
//Output:["Achtung"]
|
||||
fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! "))
|
||||
//Output:["Achtung"]
|
||||
```
|
||||
|
||||
- func Fields(s string) []string
|
||||
@@ -95,8 +95,8 @@
|
||||
|
||||
```Go
|
||||
|
||||
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
|
||||
//Output:Fields are: ["foo" "bar" "baz"]
|
||||
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
|
||||
//Output:Fields are: ["foo" "bar" "baz"]
|
||||
```
|
||||
|
||||
## 字符串转换
|
||||
@@ -106,41 +106,41 @@
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
str := make([]byte, 0, 100)
|
||||
str = strconv.AppendInt(str, 4567, 10)
|
||||
str = strconv.AppendBool(str, false)
|
||||
str = strconv.AppendQuote(str, "abcdefg")
|
||||
str = strconv.AppendQuoteRune(str, '单')
|
||||
fmt.Println(string(str))
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
str := make([]byte, 0, 100)
|
||||
str = strconv.AppendInt(str, 4567, 10)
|
||||
str = strconv.AppendBool(str, false)
|
||||
str = strconv.AppendQuote(str, "abcdefg")
|
||||
str = strconv.AppendQuoteRune(str, '单')
|
||||
fmt.Println(string(str))
|
||||
}
|
||||
```
|
||||
|
||||
- Format 系列函数把其他类型的转换为字符串
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := strconv.FormatBool(false)
|
||||
b := strconv.FormatFloat(123.23, 'g', 12, 64)
|
||||
c := strconv.FormatInt(1234, 10)
|
||||
d := strconv.FormatUint(12345, 10)
|
||||
e := strconv.Itoa(1023)
|
||||
fmt.Println(a, b, c, d, e)
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := strconv.FormatBool(false)
|
||||
b := strconv.FormatFloat(123.23, 'g', 12, 64)
|
||||
c := strconv.FormatInt(1234, 10)
|
||||
d := strconv.FormatUint(12345, 10)
|
||||
e := strconv.Itoa(1023)
|
||||
fmt.Println(a, b, c, d, e)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -148,30 +148,30 @@
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
func checkError(e error){
|
||||
if e != nil{
|
||||
fmt.Println(e)
|
||||
}
|
||||
}
|
||||
func main() {
|
||||
a, err := strconv.ParseBool("false")
|
||||
checkError(err)
|
||||
b, err := strconv.ParseFloat("123.23", 64)
|
||||
checkError(err)
|
||||
c, err := strconv.ParseInt("1234", 10, 64)
|
||||
checkError(err)
|
||||
d, err := strconv.ParseUint("12345", 10, 64)
|
||||
checkError(err)
|
||||
e, err := strconv.Atoi("1023")
|
||||
checkError(err)
|
||||
fmt.Println(a, b, c, d, e)
|
||||
}
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
func checkError(e error){
|
||||
if e != nil{
|
||||
fmt.Println(e)
|
||||
}
|
||||
}
|
||||
func main() {
|
||||
a, err := strconv.ParseBool("false")
|
||||
checkError(err)
|
||||
b, err := strconv.ParseFloat("123.23", 64)
|
||||
checkError(err)
|
||||
c, err := strconv.ParseInt("1234", 10, 64)
|
||||
checkError(err)
|
||||
d, err := strconv.ParseUint("12345", 10, 64)
|
||||
checkError(err)
|
||||
e, err := strconv.Atoi("1023")
|
||||
checkError(err)
|
||||
fmt.Println(a, b, c, d, e)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user