Merge pull request #855 from vCaesar/u17-pr

Update go install version and Remove zh/x.md spaces
This commit is contained in:
astaxie
2017-06-10 15:03:16 +08:00
committed by GitHub
36 changed files with 3164 additions and 3166 deletions

View File

@@ -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.gz64位系统下载go1.8.1.linux-amd64.tar.gz
访问[下载地址][downlink]32位系统下载go1.8.3.linux-386.tar.gz64位系统下载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

View File

@@ -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))
}
```

View File

@@ -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)

View File

@@ -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")
}
```
输出如下:

View File

@@ -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为v1vname2为v2vname3为v3
*/
var vname1, vname2, vname3 type= v1, v2, v3
/*
定义三个类型都是"type"的变量,并且分别初始化为相应的值
vname1为v1vname2为v2vname3为v3
*/
var vname1, vname2, vname3 type= v1, v2, v3
```
你是不是觉得上面这样的定义有点繁琐没关系因为Go语言的设计者也发现了有一种写法可以让它变得简单一点。我们可以直接忽略类型声明那么上面的代码变成这样了
```Go
/*
定义三个变量,它们分别初始化为相应的值
vname1为v1vname2为v2vname3为v3
然后Go会根据其相应值的类型来帮你初始化它们
*/
var vname1, vname2, vname3 = v1, v2, v3
/*
定义三个变量,它们分别初始化为相应的值
vname1为v1vname2为v2vname3为v3
然后Go会根据其相应值的类型来帮你初始化它们
*/
var vname1, vname2, vname3 = v1, v2, v3
```
你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化:
```Go
/*
定义三个变量,它们分别初始化为相应的值
vname1为v1vname2为v2vname3为v3
编译器会根据初始化的值自动推导出相应的类型
*/
vname1, vname2, vname3 := v1, v2, v3
/*
定义三个变量,它们分别初始化为相应的值
vname1为v1vname2为v2vname3为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 常量和一般程序语言不同的是,可以指定相当多的小数位
这就是全部吗NoGo还支持复数。它的默认类型是`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`的时候采用它默认开始值是0const中每增加一行加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,glen=4cap=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,glen=4cap=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

View File

@@ -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函数。

View File

@@ -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

View File

@@ -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重写Humanmethod
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的设计惊叹

View File

@@ -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结构也定义了一个methodString。其实这也是实现了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)
```
上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。

View File

@@ -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

View File

@@ -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已经有过短暂的接触

View File

@@ -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链接请求了。

View File

@@ -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去服务相互不影响。

View File

@@ -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代码的执行流程

View File

@@ -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只会返回同名参数中的第一个若参数不存在则返回空字符串。

View File

@@ -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里面的正则处理。

View File

@@ -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>")
```
转义之后的输出:

View File

@@ -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"))) //输出到客户端
}
}
```
上面的代码输出到页面的源码如下:

View File

@@ -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方法把缓存传到服务器。

View File

@@ -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函数关闭当前的链接状态但是如果当前正在执行queryquery还是有效返回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是驱动必须能够操作的ValueValue要么是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,然后再返回之。

View File

@@ -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数据库是很方便的。

View File

@@ -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的方式打开。

View File

@@ -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格式不一样所以在使用时请注意它们的差异。

View File

@@ -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
```
例子3WHERE 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
```
例子2limit默认从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
}
}
```

View File

@@ -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。

View File

@@ -37,52 +37,52 @@ cookie是有时间限制的根据生命期不同分成两种会话cookie
Go语言中通过net/http包中的SetCookie来设置
```Go
http.SetCookie(w ResponseWriter, cookie *Cookie)
http.SetCookie(w ResponseWriter, cookie *Cookie)
```
w表示需要写入的responsecookie是一个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非常方便。

View File

@@ -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都是可用的类似的方案也可以用于统计在线用户数之类的。

View File

@@ -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

View File

@@ -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的机会大大降低。

View File

@@ -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和stringXML包内部采用了反射来进行数据的映射所以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包含bb包含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定义请参看相应的官方资料。

View File

@@ -4,7 +4,7 @@ JSONJavascript 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 @@ JSONJavascript 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 @@ JSONJavascript 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 @@ JSONJavascript 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 @@ JSONJavascript 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函数只有在转换成功的时候才会返回数据在转换的过程中我们需要注意几点

View File

@@ -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语言的正则包进行一些基本的正则的操作了。

View File

@@ -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`,没有任何的输出,因为在默认的情况下没有默认的子模板,所以不会输出任何的东西。

View File

@@ -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语言里面删除文件和删除文件夹是同一个函数

View File

@@ -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)
}
```