Google Go语言 golang 语法详解笔记
Author:cxy Date:2015-06-26 Version:1.0 Source:Fork me on GitHub Blog:yougg.github.io/static/gonote/gogrammar Description:学习Go语言过程中记录下来的语法详解笔记,可以帮助新接触的朋友快速熟悉理解Golang,也可以作为查询手册翻阅。其中若有错误的地方还请指正,或者在GitHub直接fork修改。 本文使用LiteIDE的Markdown编辑器编写,博客为LiteIDE导出的单页html,可以直接保存离线使用。
-
包 Package
- 包的声明 Declare
- 包的导入 Import
- 包内元素的可见性 Accessability
-
数据类型 Data Type
- 基础数据类型 Basic data type
- 变量 Variable
- 常量 Constant
- 数组 Array
- 切片 Slice
- 字典/映射 Map
- 结构体 Struct
- 指针 Pointer
- 通道 Channel
- 接口 Interface
- 自定义类型
-
语句 Statement
- 分号/括号 ; {
- 条件语句 if
- 分支选择 switch
- 循环语句 for
- 通道选择 select
- 延迟执行 defer
- 跳转语句 goto
-
函数 Function
- 函数声明 Declare
- 函数闭包 Closure
- 内建函数 Builtin
- 初始化函数 init
- 方法 Method
-
并发 Concurrency
-
测试 Testing
包 Package
包的声明 Declare
-
使用package 关键字声明当前源文件所在的包 包声明语句是所有源文件的第一行非注释语句 包名称中不能包含空白字符 包名推荐与源文件所在的目录名称保持一致 每个目录中只能定义一个package package cxy // 声明一个名为“cxy”的包
package 我的包 // 声明一个名为“我的包”的包
package main // main包,程序启动执行的入口包 错误的包声明 package "mypkg" // 错误
package a/b/c // 错误
pakcage a.b.c // 错误
包的导入 Import
-
导入包路径是对应包在$GOROOT/pkg/$GOOS_$GOARCH/ 、$GOPATH/pkg/$GOOS_$GOARCH/ 或当前路径 中的相对路径 // 导入$GOROOT/$GOOS_$GOARCH/中的相对路径包(官方标准库)
import "fmt"
import "math/rand"
// 导入$GOPATH/$GOOS_$GOARCH/中的相对路径包
import "github.com/user/project/pkg"
import "code.google.com/p/project/pkg" 导入当前包的相对路径包 例如有Go目录如下: $GOPATH/src ├─x0 │ ├─y0 │ │ └─z0 │ └─y1 │ └─z1 └─x1 └─y2 import "./y0/z0" // x0包中导入子包 z0包
import "../y0/z0" // y1包中导入子包 z0包
import "x0/y1/z1" // y2包中导入 z1包 错误的导入包路径 import a/b/c // 错误
import "a.b.c" // 错误
import a.b.c // 错误
-
用圆括号组合导入包路径 import ("fmt"; "math")
import (
"fmt"
"math"
)
-
导入包可以定义别名,防止同名称的包冲突 import (
"a/b/c"
c1 "x/y/c" // 将导入的包c定义别名为 c1
格式化 "fmt" // 将导入的包fmt定义别名为 格式化
m "math" // 将导入的包math定义别名为 m
)
-
引用包名是导入包路径的最后一个目录中定义的唯一包的名称 定义的包名与目录同名时,直接引用即可 // 引用普通名称的导入包
c.hello()
// 引用定义别名的包
格式化.Println(m.Pi) 定义的包名与所在目录名称不同时,导入包路径仍为目录所在路径,引用包名为定义的包名称 // 源文件路径: $GOPATH/src/proj/my-util/util.go
// 定义包名: util
package util // 导入util包路径
import "proj/my-util"
// 引用util包
util.doSomething()
-
静态导入,在导入的包路径之前增加一个小数点. // 类似C中的include 或Java中的import static
import . "fmt"
// 然后像使用本包元素一样使用fmt包中可见的元素,不需要通过包名引用
Println("no need package name")
-
导入包但不直接使用该包,在导入的包路径之前增加一个下划线_ // 如果当前go源文件中未引用过log包,将会导致编译错误
import "log" // 错误
import . "log" // 静态导入未使用同样报错
// 在包名前面增加下划线表示导入包但是不直接使用它,被导入的包中的init函数会在导入的时候执行
import _ "github.com/go-sql-driver/mysql"
包内元素的可见性 Accessability
-
名称首字符为Unicode包含的大写字母的元素是被导出的,对外部包是可见的 首字为非大写字母的元素只对本包可见(同包跨源文件可以访问,子包不能访问) var In int // In is exported
var in byte // in is unexported
var ?? string // ?? is unexported
const ?om bool = false // ?om is exported
const ?? uint8 = 1 // ?? is unexported
type ?nteger int // ?nteger is exported
type ブーリアン *bool // ブーリアン is unexported
func ?xport() {...} // ?xport is exported
func ?nner() {...} // ?nner is unexported
func (me *Integer) ?alueOf(s string) int {...} // ?alueOf is unexported
func (i ブーリアン) ?tring() string {...} // ?tring is exported
-
internal包(内部包)Go 1.4 internal包及其子包中的导出元素只能被与internal同父包的其他包访问 例如有Go目录如下: $GOPATH/src ├─x0 │ ├─internal │ │ └─z0 │ └─y0 │ └─z1 └─x1 └─y1 x0,y0,z1包中可以访问internal,z0包中的可见元素 x1,y1包中不能导入internal,z0包
-
规范导入包路径Canonical import pathsGo 1.4 包声明语句后面添加标记注释,用于标识这个包的规范导入路径。 package pdf // import "rsc.io/pdf" 如果使用此包的代码的导入的路径不是规范路径,go命令会拒绝编译。 例如有 rsc.io/pdf 的一个fork路径 github.com/rsc/pdf 如下程序代码导入路径时使用了非规范的路径则会被go拒绝编译 import "github.com/rsc/pdf"
数据类型 Data Type
基础数据类型 Basic data type
-
基本类型包含:数值类型,布尔类型,字符串
类型 |
取值范围 |
默认零值 |
类型 |
取值范围 |
默认零值 |
int |
int32,int64 |
0 |
uint |
uint32,uint64 |
0 |
int8 |
-27 ~ 27-1 |
0 |
uint8 ,byte |
0 ~ 28-1 |
0 |
int16 |
-215 ~ 215-1 |
0 |
uint16 |
0 ~ 216-1 |
0 |
int32 ,rune |
-231 ~ 231-1 |
0 |
uint32 |
0 ~ 232-1 |
0 |
int64 |
-263 ~ 263-1 |
0 |
uint64 |
0 ~ 264-1 |
0 |
float32 |
IEEE-754 32-bit |
0.0 |
float64 |
IEEE-754 64-bit |
0.0 |
complex64 |
float32+float32i |
0 + 0i |
complex128 |
float64+float64i |
0 + 0i |
bool |
true,false |
false |
string |
"" ~ "∞" |
"",`` |
uintptr |
uint32,uint64 |
0 |
error |
- |
nil |
byte 是 uint8 的别名 rune 是 int32 的别名,代表一个Unicode码点 int 与int32 或int64 是不同的类型,只是根据架构对应32/64位值 uint 与uint32 或uint64 是不同的类型,只是根据架构对应32/64位值
变量 Variable
-
变量声明,使用var 关键字 Go中只能使用var 声明变量,无需显式初始化值 var i int // i = 0
var s string // s = "" (Go中的string是值类型,默认零值是空串 "" 或 ``,不存在nil(null)值)
var e error // e = nil,error是Go的内建接口类型。 关键字的顺序错误或缺少都是编译错误的 var int a // 编译错误
a int // 编译错误
int a // 编译错误
-
var 语句可以声明一个变量列表,类型在变量名之后 var a,b,c int // a = 0,b = 0,c = 0
var (
a int // a = 0
b string // b = ""
c uint // c = 0
)
var (
a,c int
d string
)
-
变量定义时初始化赋值,每个变量对应一个值 var a int = 0
var a,b int = 0,1
-
变量定义并初始化时可以省略类型,Go自动根据初始值推导变量的类型 var a = 'A' // a int32
var a,"B" // a int,b string
-
使用组合符号:= 定义并初始化变量,根据符号右边表达式的值的类型声明变量并初始化它的值 := 不能在函数外使用,函数外的每个语法块都必须以关键字开始 a := 3 // a int
a,c := 8,'呴',true // a int,b int32,c bool
c := `formatted
string` // c string
c := 1 + 2i // c complex128
常量 Constant
-
常量可以是字符、字符串、布尔或数值类型的值,数值常量是高精度的值 const x int = 3
const y,z int = 1,2
const (
a byte = 'A'
b string = "B"
c bool = true
d int = 4
e float32 = 5.1
f complex64 = 6 + 6i
)
-
根据常量值自动推导类型 const a = 0 // a int
const (
b = 2.3 // b float64
c = true // c bool
)
-
常量组内定义时复用表达式 常量组内定义的常量只有名称时,其值会根据上一次最后出现的常量表达式计算相同的类型与值 const (
a = 3 // a = 3
b // b = 3
c // c = 3
d = len("asdf") // d = 4
e // e = 4
f // f = 4
g,h,i = 7,8,9 // 复用表达式要一一对应
x,y,z // x = 7,y = 8,z = 9
)
-
自动递增枚举常量 iota iota的枚举值可以赋值给数值兼容类型 每个常量单独声明时,iota 不会自动递增 const a int = iota // a = 0
const b int = iota // b = 0
const c byte = iota // c = 0
const d uint64 = iota // d = 0
-
常量组合声明时,iota 每次引用会逐步自增,初始值为0,步进值为1 const (
a uint8 = iota // a = 0
b int16 = iota // b = 1
c rune = iota // c = 2
d float64 = iota // d = 3
e uintptr = iota // e = 4
)
-
即使iota 不是在常量组内第一个开始引用,也会按组内常量数量递增 const (
a = "A"
b = 'B'
c = iota // c = 2
d = "D"
e = iota // e = 4
)
-
枚举的常量都为同一类型时,可以使用简单序列格式(组内复用表达式). const (
a = iota // a int32 = 0
b // b int32 = 1
c // c int32 = 2
)
-
枚举序列中的未指定类型的常量会跟随序列前面最后一次出现类型定义的类型 const (
a byte = iota // a uint8 = 0
b // b uint8 = 1
c // c uint8 = 2
d rune = iota // d int32 = 3
e // e int32 = 4
f // f int32 = 5
)
-
iota 自增值只在一个常量定义组合中有效,跳出常量组合定义后iota 初始值归0 const (
a = iota // a int32 = 0
b // b int32 = 1
c // c int32 = 2
)
const (
e = iota // e int32 = 0 (iota重新初始化并自增)
f // f int32 = 1
)
-
定制iota 序列初始值与步进值 (通过组合内复用表达式实现) const (
a = (iota + 2) * 3 // a int32 = 6 (a=(0+2)*3) 初始值为6,步进值为3
b // b int32 = 9 (b=(1+2)*3)
c // c int32 = 12 (c=(2+2)*3)
d // d int32 = 15 (d=(3+2)*3)
)
数组 Array
-
数组声明带有长度信息且长度固定,数组是值类型默认零值不是nil ,传递参数时会进行复制。 声明定义数组时中括号[ ] 在类型名称之前,赋值引用元素时中括号[ ] 在数组变量名之后。 var a [3]int = [3]int{0,1,2} // a = [0 1 2]
var b [3]int = [3]int{} // b = [0 0 0]
var c [3]int
c = [3]int{}
c = [3]int{0,0} // c = [0 0 0]
d := [3]int{} // d = [0 0 0]
fmt.Printf("%Tt%#vt%dt%dn",d,len(d),cap(d)) // [3]int [3]int{0,0} 3 3 使用... 自动计算数组的长度 var a = [...]int{0,2}
// 多维数组只能自动计算最外围数组长度
x := [...][3]int{{0,2},{3,4,5}}
y := [...][2][2]int{{{0,1},{2,3}},{{4,5},{6,7}}}
// 通过下标访问数组元素
println(y[1][1][0]) // 6 初始化指定索引的数组元素,未指定初始化的元素保持默认零值 var a = [3]int{2:3}
var b = [...]string{2:"c",3:"d"}
切片 Slice
-
slice 切片是对一个数组上的连续一段的引用,并且同时包含了长度和容量信息 因为是引用类型,所以未初始化时的默认零值是nil ,长度与容量都是0 var a []int
fmt.Printf("%Tt%#vt%dt%dn",a,len(a),cap(a)) // []int []int(nil) 0 0
// 可用类似数组的方式初始化slice
var d []int = []int{0,2}
fmt.Printf("%Tt%#vt%dt%dn",cap(d)) // []int []int{0,2} 3 3
var e = []string{2:"c",3:"d"} 使用内置函数make初始化slice,第一参数是slice类型,第二参数是长度,第三参数是容量(省略时与长度相同) var b = make([]int,0)
fmt.Printf("%Tt%#vt%dt%dn",len(b),cap(b)) // []int []int{} 0 0
var c = make([]int,3,10)
fmt.Printf("%Tt%#vt%dt%dn",c,len(c),cap(c)) // []int []int{} 3 10
var a = new([]int)
fmt.Printf("%Tt%#vt%dt%dn",len(*a),cap(*a)) // *[]int &[]int(nil) 0 0
-
基于slice或数组重新切片,创建一个新的 slice 值指向相同的数组 重新切片支持两种格式: 2个参数 slice[beginIndex:endIndex] 需要满足条件:0 <= beginIndex <= endIndex <= cap(slice) 截取从开始索引到结束索引-1 之间的片段 新slice的长度:length=(endIndex - beginIndex) 新slice的容量:capacity=(cap(slice) - beginIndex) beginIndex的值可省略,默认为0 endIndex 的值可省略,默认为len(slice) s := []int{0,2,4}
a := s[1:3] // a: [1 2],len: 2,cap: 4
b := s[:4] // b: [0 1 2 3],len: 4,cap: 5
c := s[1:] // c: [1 2 3 4],cap: 4
d := s[1:1] // d: [],len: 0,cap: 4
e := s[:] // e: [0 1 2 3 4],len: 5,cap: 5 3个参数 slice[beginIndex:endIndex:capIndex] 需要满足条件:0 <= beginIndex <= endIndex <= capIndex <= cap(slice) 新slice的长度:length=(endIndex - beginIndex) 新slice的容量:capacity=(capIndex - beginIndex) beginIndex的值可省略,默认为0 s := make([]int,5,10)
a := s[9:10:10] // a: [0],len: 1,cap: 1
b := s[:3:5] // b: [0 0 0],len: 3,cap: 5
-
向slice中增加/修改元素 s := []string{}
s = append(s,"a") // 添加一个元素
s = append(s,"b","c","d") // 添加一列元素
t = []string{"e","f","g"}
s = append(s,t...} // 添加另一个切片t的所有元素
s = append(s,t[:2]...} // 添加另一个切片t的部分元素
s[0] = "A" // 修改切片s的第一个元素
s[len(s)-1] = "G" // 修改切片s的最后一个元素
-
删除slice中指定的元素 因为slice引用指向底层数组,数组的长度不变元素是不能删除的,所以删除的原理就是排除待删除元素后用其他元素重新构造一个数组 func deleteByAppend() {
i := 3
s := []int{1,6,7}
//delete the fourth element(index is 3),using append
s = append(s[:i],s[i+1:]...)
}
func deleteByCopy() {
i := 3
s := []int{1,using copy
copy(s[i:],s[i+1:])
s = s[:len(s)-1]
}
字典/映射 Map
-
map是引用类型,使用内置函数 make 进行初始化,未初始化的map零值为 nil 长度为0,并且不能赋值元素 var m map[int]int
m[0] = 0 // × runtime error: assignment to entry in nil map
fmt.Printf("type: %Tn",m) // map[int]int
fmt.Printf("value: %#vn",m) // map[int]int(nil)
fmt.Printf("value: %vn",m) // map[]
fmt.Println("is nil: ",nil == m) // true
fmt.Println("length: ",len(m)) // 0,if m is nil,len(m) is zero. 使用内置函数make初始化map var m map[int]int = make(map[int]int)
m[0] = 0 // 插入或修改元素
fmt.Printf("type: %Tn",m) // map[int]int(0:0)
fmt.Printf("value: %vn",m) // map[0:0]
fmt.Println("is nil: ",nil == m) // false
fmt.Println("length: ",len(m)) // 1 直接赋值初始化map m := map[int]int{
0:0,1:1,// 最后的逗号是必须的
}
n := map[string]S{
"a":S{0,"b":{2,3},// 类型名称可省略
} map的使用:读取、添加、修改、删除元素 m[0] = 3 // 修改m中key为0的值为3
m[4] = 8 // 添加到m中key为4值为8
a := n["a"] // 获取n中key为“a“的值
b,ok := n["c"] // 取值,并通过ok(bool)判断key对应的元素是否存在.
delete(n,"a") // 使用内置函数delete删除key为”a“对应的元素.
结构体 Struct
-
结构体类型struct 是一个字段的集合 type S struct {
A int
B,c string
}
-
结构体初始化通过结构体字段的值作为列表来新分配一个结构体。 var s S = S{0,"1","2"}
-
使用 Name: 语法可以仅列出部分字段(字段名的顺序无关) var s S = S{B: "1",A: 0}
-
结构体是值类型,传递时会复制值,其默认零值不是nil var a S
var b = S{}
fmt.Println(a == b) // true
-
结构体组合 将一个命名类型 作为匿名字段嵌入一个结构体 嵌入匿名字段支持命名类型、命名类型的指针和接口类型 package main
type (
A struct {
v int
}
// 定义结构体B,嵌入结构体A作为匿名字段
B struct {
A
}
// 定义结构体C,嵌入结构体A的指针作为匿名字段
C struct {
*A
}
)
func (a *A) setV(v int) {
a.v = v
}
func (a A) getV() int {
return a.v
}
func (b B) getV() string {
return "B"
}
func (c *C) getV() bool {
return true
}
func main() {
a := A{}
b := B{} // 初始化结构体B,其内匿名字段A默认零值是A{}
c := C{&A{}} // 初始化结构体C,其内匿名指针字段*A默认零值是nil,需要初始化赋值
println(a.v)
// 结构体A嵌入B,A内字段自动提升到B
println(b.v)
// 结构体指针*A嵌入C,*A对应结构体内字段自动提升到C
println(c.v)
a.setV(3)
b.setV(5)
c.setV(7)
println(a.getV(),b.A.getV(),c.A.getV())
println(a.getV(),b.getV(),c.getV())
}
-
匿名结构体 匿名结构体声明时省略了type 关键字,并且没有名称 package main
import "fmt"
type Integer int
// 声明变量a为空的匿名结构体类型
var a struct{}
// 声明变量b为包含一个字段的匿名结构体类型
var b struct{ x int }
// 声明变量c为包含两个字段的匿名结构体类型
var c struct {
u int
v bool
}
func main() {
printa(a)
b.x = 1
fmt.Printf("bx: %#vn",printb(b)) // bx: struct { y uint8 }{y:0x19}
printc(c)
// 声明d为包含3个字段的匿名结构体并初始化部分字段
d := struct {
x int
y complex64
z string
}{
z: "asdf",x: 111,}
d.y = 22 + 333i
fmt.Printf("d: %#vn",d) // d: struct { x int; y complex64; z string }{x:111,y:(22+333i),z:"asdf"}
// 声明变量e为包含两个字段的匿名结构体类型
// 包含1个匿名结构体类型的命名字段和1个命名类型的匿名字段
e := struct {
a struct{ x int }
// 结构体组合嵌入匿名字段只支持命名类型
Integer
}{}
e.Integer = 444
fmt.Printf("e: %#vn",e) // e: struct { a struct { x int }; main.Integer }{a:struct { x int }{x:0},Integer:444}
}
// 函数参数为匿名结构体类型时,传入参数类型声明必须保持一致
func printa(s struct{}) {
fmt.Printf("a: %#vn",s) // a: struct {}{}
}
// 函数入参和返回值都支持匿名结构体类型
func printb(s struct{ x int }) (x struct{ y byte }) {
fmt.Printf("b: %#vn",s) // b: struct { x int }{x:1}
x.y = 25
return
}
func printc(s struct {u int; v bool }) {
fmt.Printf("c: %#vn",s) // c: struct { u int; v bool }{u:0,v:false}
}
指针 Pointer
-
通过取地址操作符& 获取指向值/引用对象的指针。 var i int = 1
pi := &i // 指向数值的指针
a := []int{0,2}
pa := &a // 指向引用对象的指针
var s *S = &S{0,"2"} // 指向值对象的指针
-
内置函数new(T) 分配了一个零初始化的 T 值,并返回指向它的指针 var i = new(int)
var s *S = new(S)
-
使用* 读取/修改指针指向的值 func main() {
i := new(int)
*i = 3
println(i,*i) // 0xc208031f80 3
i = new(int)
println(i,*i) // 0xc208031f78 0
}
-
指针使用点号来访问结构体字段 结构体字段/方法可以通过结构体指针来访问,通过指针间接的访问是透明的。 fmt.Println(s.A)
fmt.Println((*s).A)
-
指针的指针 func main() {
var i int
var p *int
var pp **int
var ppp ***int
var pppp ****int
println(i,p,pp,ppp,pppp) // 0 0x0 0x0 0x0 0x0
i,pppp = 123,&i,&p,&pp,&ppp
println(i,pppp) // 123 0xc208031f68 0xc208031f88 0xc208031f80 0xc208031f78
println(i,*p,**pp,***ppp,****pppp) // 123 123 123 123 123
}
-
跨层指针元素的使用 在指针引用多层对象时,指针是针对引用表达式的最后一位元素。 package a
type X struct {
A Y
}
type Y struct {
B Z
}
type Z struct {
C int
} package main
import (
"a"
"fmt"
)
func main() {
var x = a.X{}
var p = &x
fmt.Println("x: ",x) // x: {{{0}}}
println("p: ",p) // p: 0xc208055f20
fmt.Println("*p: ",*p) // *p: {{{0}}}
println("x.A.B.C: ",x.A.B.C) // x.A.B.C: 0
// println("*p.A.B.C: ",*p.A.B.C) // invalid indirect of p.A.B.C (type int)
println("(*p).A.B.C: ",(*p).A.B.C) // (*p).A.B.C: 0
}
-
Go的指针没有指针运算,但是 道高一尺,魔高一丈 Go语言中的指针运算 利用unsafe操作未导出变量
通道 Channel
-
channel用于两个goroutine之间传递指定类型的值来同步运行和通讯。 操作符<- 用于指定channel的方向,发送或接收。 如果未指定方向,则为双向channel。 var c0 chan int // 可用来发送和接收int类型的值
var c1 chan<- int // 可用来发送int类型的值
var c2 <-chan int // 可用来接收int类型的值
-
channel是引用类型,使用make 函数来初始化。 未初始化的channel零值是nil ,且不能用于发送和接收值。 c0 := make(chan int) // 不带缓冲的int类型channel
c1 := make(chan *int,10) // 带缓冲的*int类型指针channel 无缓冲的channe中有值时发送方会阻塞,直到接收方从channel中取出值。 带缓冲的channel在缓冲区已满时发送方会阻塞,直到接收方从channel中取出值。 接收方在channel中无值会一直阻塞。
-
通过channel发送一个值时,<- 作为二元操作符使用, c0 <- 3 通过channel接收一个值时,<- 作为一元操作符使用。 i := <-c1
-
关闭channel,只能用于双向或只发送类型的channel 只能由 发送方调用close 函数来关闭channel 接收方取出已关闭的channel中发送的值后,后续再从channel中取值时会以非阻塞的方式立即返回channel传递类型的零值。 ch := make(chan string,1)
// 发送方,发送值后关闭channel
ch <- "hello"
close(ch)
// 接收方,取出发送的值
fmt.Println(<-ch) // 输出: “hello”
// 再次从已关闭的channel中取值,返回channel传递类型的零值
fmt.Println(<-ch) // 输出: 零值,空字符串“”
// 接收方判断接收到的零值是由发送方发送的还是关闭channel返回的默认值
s,ok := <-ch
if ok {
fmt.Println("Receive value from sender:",s)
} else {
fmt.Println("Get zero value from closed channel")
}
// 向已关闭的通道发送值会产生运行时恐慌panic
ch <- "hi"
// 再次关闭已经关闭的通道也会产生运行时恐慌panic
close(ch)
-
使用for range 语句依次读取发送到channel的值,直到channel关闭。 package main
import "fmt"
func main() {
// 无缓冲和有缓冲的channel的range用法相同
var ch = make(chan int) // make(chan int,2) 或 make(chan int,100)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
// channel中无发送值且未关闭时会阻塞
for x := range ch {
fmt.Println(x)
}
} 下面方式与for range用法效果相同 loop:
for {
select {
case x,ok := <-c:
if !ok {
break loop
}
fmt.Println(x)
}
}
接口 Interface
-
接口类型是由一组方法定义的集合。 接口类型的值可以存放实现这些方法的任何值。 type Abser interface {
Abs() float64
}
-
类型通过实现定义的方法来实现接口, 不需要显式声明实现某接口。 type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
-
接口组合 type Reader interface {
Read(b []byte) (n int)
}
type Writer interface {
Write(b []byte) (n int)
}
// 接口ReadWriter组合了Reader和Writer两个接口
type ReadWriter interface {
Reader
Writer
}
type File struct {
// ...
}
func (f *File) Read(b []byte) (n int) {
println("Read","bytes data.")
return len(b)
}
func (f *File) Write(b []byte) (n int) {
println("Write","bytes data.")
return len(b)
}
func main() {
// *File 实现了Read方法和Write方法,所以实现了Reader接口和Writer接口以及组合接口ReadWriter
var f *File = &File{}
var r Reader = f
var w Writer = f
var rw ReadWriter = f
bs := []byte("asdf")
r.Read(bs)
rw.Read(bs)
w.Write(bs)
rw.Write(bs)
}
-
内置接口类型error 是一个用于表示错误情况的常规接口,其零值nil 表示没有错误 所有实现了Error 方法的类型都能表示为一个错误 type error interface {
Error() string
}
自定义类型
-
Go中支持自定义的类型可基于: 基本类型、数组类型、切片类型、字典类型、函数类型、结构体类型、通道类型、接口类型以及自定义类型的类型 type (
A int
B int8
C int16
D rune
E int32
F int64
G uint
H byte
I uint16
J uint32
K uint64
L float32
M float64
N complex64
O complex128
P uintptr
Q bool
R string
S [3]uint8
T []complex128
U map[string]uintptr
V func(i int) (b bool)
W struct {a,b int}
X chan int
Y interface {}
Z A
)
-
以及支持以上所有支持类型的指针类型 type (
A *int
B *int8
C *int16
D *rune
E *int32
F *int64
G *uint
H *byte
I *uint16
J *uint32
K *uint64
L *float32
M *float64
N *complex64
O *complex128
P *uintptr
Q *bool
R *string
S *[3]uint8
T *[]complex128
U *map[string]uintptr
V *func(i int) (b bool)
W *struct {a,b int}
X *chan int
Y *interface {}
Z *A
)
-
类型别名Go 1.9 type (
A struct{}
B struct{} // 定义两个结构相同的类型A,B
C = A // 定义类型A的别名
)
func main() {
var (
a A
b B
c C
)
// 因为类型名不同,所以a和b不是相同类型,此处编译错误
fmt.Println(a == b) // invalid operation: a == b (mismatched types A and B)
fmt.Println(a == c) // true
a = C{}
c = A{}
fmt.Println(c == a) // true
}
语句 Statement
分号/括号 ; {
-
Go是采用语法解析器自动在每行末尾增加分号,所以在写代码的时候可以省略分号。
-
Go编程中只有几个地方需要手工增加分号: for循环使用分号把初始化、条件和遍历元素分开。 if/switch的条件判断带有初始化语句时使用分号分开初始化语句与判断语句。 在一行中有多条语句时,需要增加分号。
-
控制语句(if,for,switch,select)、函数、方法 的左大括号不能单独放在一行, 语法解析器会在大括号之前自动插入一个分号,导致编译错误。
条件语句 if
-
if 语句 小括号 ( )是可选的,而大括号 { } 是必须的。 if (i < 0) // 编译错误.
println(i)
if i < 0 // 编译错误.
println(i)
if (i < 0) { // 编译通过.
println(i)
}
if (i < 0 || i > 10) {
println(i)
}
if i < 0 {
println(i)
} else if i > 5 && i <= 10 {
println(i)
} else {
println(i)
}
-
可以在条件之前执行一个简单的语句,由这个语句定义的变量的作用域仅在 if / else if / else 范围之内 if (i := 0; i < 1) { // 编译错误.
println(i)
}
if i := 0; (i < 1) { // 编译通过.
println(i)
}
if i := 0; i < 0 { // 使用gofmt格式化代码会自动移除代码中不必要的小括号( )
println(i)
} else if i == 0 {
println(i)
} else {
println(i)
}
-
if 语句作用域范围内定义的变量会覆盖外部同名变量,与方法函数内局部变量覆盖全局变量同理 a,b := 0,1
if a,b := 3,4; a > 1 && b > 2 {
println(a,b) // 3 4
}
println(a,b) // 0 1
-
if 判断语句类型断言 package main
func f0() int {return 333}
func main() {
x := 9
checkType(x)
checkType(f0)
}
func checkType(x interface{}) {
// 断言传入的x为int类型,并获取值
if i,ok := x.(int); ok {
println("int: ",i) // int: 0
}
if f,ok := x.(func() int); ok {
println("func: ",f()) // func: 333
}
// 如果传入x类型为int,则可以直接获取其值
a := x.(int)
println(a)
// 如果传入x类型不是byte,则会产生恐慌panic
b := x.(byte)
println(b)
}
分支选择 switch
-
switch 存在分支选择对象时,case 分支支持单个常量、常量列表 switch x {
case 0:
println("single const")
case 1,3:
println("const list")
default:
println("default")
}
-
分支选择对象之前可以有一个简单语句,case语句的大括号可以省略 switch x *= 2; x {
case 4: {
println("single const")
}
case 5,7: {
println("const list")
}
default: {
println("default")
}
}
-
switch 只有一个简单语句,没有分支选择对象时,case分支支持逻辑表达式语句 switch x /= 3; {
case x == 8:
println("expression")
case x >= 9:
println("expression")
default:
println("default")
}
-
switch 没有简单语句,没有分支选择对象时,case分支支持逻辑表达式语句 switch {
case x == 10:
println("expression")
case x >= 11:
println("expression")
default:
println("default")
}
-
switch 类型分支,只能在switch语句中使用的.(type) 获取对象的类型。 package main
import (
"fmt"
"code.google.com/p/go.crypto/openpgp/errors"
)
func main() {
var (
a = 0.1
b = 2+3i
c = "asdf"
d = [...]byte{1,3}
e = []complex128{1+2i}
f = map[string]uintptr{"a": 0}
g = func(int) bool {return true}
h = struct { a,b int }{}
i = &struct {}{}
j chan int
k chan <- bool
l <-chan string
m errors.SignatureError
)
values := []interface{}{nil,&c,e,f,g,&g,&h,i,j,k,l,m}
for _,v := range values {
typeswitch(v)
}
}
func typeswitch(x interface{}) {
// switch x.(type) { // 不使用类型值时
switch i := x.(type) {
case nil:
fmt.Println("x is nil")
case int,int8,int16,rune,int64,uint,byte,uint16,uint32,uint64,float32,float64,complex64,complex128,uintptr,bool,string:
fmt.Printf("basic type : %Tn",i)
case *int,*int8,*int16,*rune,*int64,*uint,*byte,*uint16,*uint32,*uint64,*float32,*float64,*complex64,*complex128,*uintptr,*bool,*string:
fmt.Printf("basic pointer type : %Tn",i)
case [3]byte,[]complex128,map[string]uintptr:
fmt.Printf("collection type : %Tn",i)
case func(i int) (b bool),*func():
fmt.Printf("function type : %Tn",i)
case struct {a,b int},*struct {}:
fmt.Printf("struct type : %Tn",i)
case chan int,chan <- bool,<-chan string:
fmt.Printf("channel type : %Tn",i)
case error,interface{a(); b()}:
fmt.Printf("interface type : %Tn",i)
default:
fmt.Printf("other type : %Tn",i)
}
}
// output:
// x is nil
// basic type : float64
// basic type : complex128
// basic pointer type : *string
// collection type : [3]uint8
// collection type : []complex128
// collection type : map[string]uintptr
// function type : func(int) bool
// other type : *func(int) bool
// struct type : struct { a int; b int }
// other type : *struct { a int; b int }
// struct type : *struct {}
// channel type : chan int
// channel type : chan<- bool
// channel type : <-chan string
// interface type : errors.SignatureError
-
switch 中每个case分支默认带有break效果,一个分支执行后就跳出switch,不会自动向下执行其他case。 使用fallthrough 强制向下继续执行后面的case代码。 在类型分支中不允许使用fallthrough 语句 switch {
case false:
println("case 1")
fallthrough
case true:
println("case 2")
fallthrough
case false:
println("case 3")
fallthrough
case true:
println("case 4")
case false:
println("case 5")
fallthrough
default:
println("default case")
}
// 输出:case 2 case 3 case 4
循环语句 for
-
Go只有一种循环结构:for 循环。 可以让前置(初始化)、中间(条件)、后置(迭代)语句为空,或者全为空。 for i := 0; i < 10; i++ {...}
for i := 0; i < 10; {...} // 省略迭代语句
for i := 0; ; i++; {...} // 省略条件语句
for ; i < 10; i++ {...} // 省略初始化语句
for i := 0; ; {...} // 省略条件和迭代语句,分号不能省略
for ; i < 10; {...} // 省略初始化和迭代语句,分号可省略
for ; ; i++ {...} // 省略初始化和条件语句,分号不能省略
for i < 10 {...}
for ; ; {...} // 分号可省略
for {...}
-
for 语句中小括号 ( )是可选的,而大括号 { } 是必须的。 for (i := 0; i < 10; i++) {...} // 编译错误.
for i := 0; (i < 10); i++ {...} // 编译通过.
for (i < 10) {...} // 编译通过.
-
Go的for each循环for range a := [5]int{2,6}
for k,v := range a {
fmt.Println(k,v) // 输出:0 2,1 3,2 4,3 5,4 6
}
for k := range a {
fmt.Println(k) // 输出:0 1 2 3 4
}
for _ = range a {
fmt.Println("print without care about the key and value")
}
for range a {
fmt.Println("new syntax – print without care about the key and value")
}
-
循环的继续、中断、跳转 for k,v := range s {
if v == 3 {
continue // 结束本次循环,进入下一次循环中
} else if v == 5 {
break // 结束整个for循环
} else {
goto SOMEWHERE // 跳转到标签指定的代码处
}
}
-
for range 只支持遍历数组 、数组指针 、slice 、string 、map 、channel 类型 package main
import "fmt"
func main() {
var arr = [...]int{33,22,11,0}
// 遍历数组,取一位值时为索引值
for k := range arr {
fmt.Printf("%d,",k) // 0,}
fmt.Println()
// 遍历数组,取两位值时,第一位为索引值,第二位为元素值
for k,v := range arr {
fmt.Printf("%d %d,v) // 0 33,1 22,2 11,3 0,}
fmt.Println()
// 遍历数组指针,取一位值时为索引值
for k := range &arr {
fmt.Printf("%d,}
fmt.Println()
// 遍历数组指针,取两位值时,第一位为索引值,第二位为元素值
for k,v := range &arr {
fmt.Printf("%d %d,}
fmt.Println()
var slc = []byte{44,55,66,77}
// 遍历切片,取一位值时为索引值
for k := range slc {
fmt.Printf("%d,}
fmt.Println()
// 遍历切片,取两位值时,第一位为索引值,第二位为元素值
for k,v := range slc {
fmt.Printf("%d %d,v) // 0 44,1 55,2 66,3 77,}
fmt.Println()
var str = "abc一二3"
// 遍历字符串,取一位值时为字节索引值
for k := range str {
fmt.Printf("%d,9,}
fmt.Println()
// 遍历字符串,取两位值时,第一位为字节索引值,第二位为Unicode字符
for k,v := range str {
fmt.Printf("%d %d %s,v,string(v)) // 0 97 a,1 98 b,2 99 c,3 19968 一,6 20108 二,9 51 3,}
fmt.Println()
var mp = map[int]string{5:"A",9:"B"}
// 遍历map,取一位值时为键key
for k := range mp {
fmt.Printf("%d,k) // 9,}
fmt.Println()
// 遍历map,取两位值时,第一位为键key,第二位为元素值value
for k,v := range mp {
fmt.Printf("%d %s,v) // 5 A,9 B,}
fmt.Println()
var ch = make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
// 遍历channel时,只能取一位值,为发送方发送到channel中的值
for x := range ch {
fmt.Printf("%d ",x) // 0 1 2 3 4
}
}
通道选择 select
-
select 用于当前goroutine从一组可能的通讯中选择一个进一步处理, 如果任意一个通讯都可以进一步处理,则从中随机选择一个,执行对应的语句, 否则在没有默认分支(default case)时,select语句则会阻塞,直到其中一个通讯完成。 select 的 case 里的操作语句只能是IO操作 ch1,ch2 := make(chan int),make(chan int)
// 因为没有值发送到select中的任一case的channel中,此select将会阻塞
select {
case <-ch1:
println("channel 1")
case <-ch2:
println("channel 2")
} ch1,make(chan int)
// 因为没有值发送到select中的任一case的channel中,此select将会执行default分支
select {
case <-ch1:
println("channel 1")
case <-ch2:
println("channel 2")
default:
println("default")
}
-
select只会执行一次case分支的逻辑,与for 组合使用实现多次遍历分支 func main() {
for {
select {
case <-time.Tick(time.Second):
println("Tick")
case <-time.After(5 * time.Second):
println("Finish")
default:
println("default")
time.Sleep(5e8)
}
}
}
延迟执行 defer
-
defer 语句调用函数,将调用的函数加入defer栈,栈中函数在defer所在的主函数返回时执行,执行顺序是先进后出/后进先出。 package main
func main() {
defer print(0)
defer print(1)
defer print(2)
defer print(3)
defer print(4)
for i := 5; i <= 9; i++ {
defer print(i)
}
// 输出:9876543210
}
-
defer在函数返回后执行,可以修改函数返回值 package main
func main() {
println(f()) // 返回: 15
}
func f() (i int) {
defer func() {
i *= 5
}()
return 3
}
-
defer用于释放资源 释放锁 mu.Lock()
defer mu.Unlock() 关闭channel ch <- "hello"
defer close(ch) 关闭IO流 f,err := os.Open("file.xxx")
defer f.Close() 关闭数据库连接 db,err := sql.Open("mysql","user:password@tcp(127.0.0.1:3306)/hello")
if err != nil {
log.Fatal(err)
}
defer db.Close()
-
defer用于恐慌的截获 panic 用于产生恐慌,recover 用于截获恐慌,recover只能在defer语句中使用,直接调用recover是无效的。 func main() {
f()
fmt.Println("main normal...")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("catch:",r)
}
}()
p()
fmt.Println("normal...")
}
func p() {
panic("exception...")
}
跳转语句 goto
-
goto 用于在一个函数内部运行跳转到指定标签的代码处,不能跳转到其他函数中定义的标签。
-
goto 模拟循环 package main
func main() {
i := 0
loop:
i++
if i < 5 {
goto loop
}
println(i)
}
-
goto 模拟continue ,break func main() {
i,sum := 0,0
head:
for ; i <= 10; i++ {
if i < 5 {
i++ // 此处必须单独调用一次,因为goto跳转时不会执行for循环的自增语句
goto head // continue
}
if i > 9 {
goto tail // break
}
sum += i
}
tail:
println(sum) // 输出:35
}
-
注意:任何时候都不建议使用goto
函数 Function
函数声明 Declare
-
使用关键字func 声明函数,函数可以没有参数或接受多个参数 func f0() {/*...*/}
func f1(a int) {/*...*/}
func f2(a int,b byte) {/*...*/}
-
在函数参数类型之前使用... 声明该参数为可变数量的参数 可变参数只能声明为函数的最后一个参数。 func f3(a ...int) {/*...*/}
func f4(a int,b bool,c ...string) {/*...*/}
-
函数可以返回任意数量的返回值 func f0() {
return
}
func f1() int {
return 0
}
func f2() (int,string) {
return 0,"A"
}
-
函数返回结果参数,可以像变量那样命名和使用 func f() (a int,b string) {
a = 1
b = "B"
return // 即使return后面没有跟变量,关键字在函数结尾也是必须的
// 或者 return a,b
}
-
当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略 func f0(a,c int) {/*...*/}
func f1() (a,c int) {/*...*/}
func f2(a,b int,d byte) (x,y int,z,s bool) {/*...*/}
函数闭包 Closure
-
匿名函数、闭包、函数值 Go中函数作为第一类对象,可以作为值对象赋值给变量 可以在函数体外/内定义匿名函数,命名函数不能嵌套定义到函数体内,只能定义在函数体外 package main
type Myfunc func(i int) int
func f0(name string){
println(name)
}
func main() {
var a = f0
a("hello") // hello
var f1 Myfunc = func(i int) int {
return i
}
fmt.Println(f1(3)) // 3
var f2 func() int = func() int {
return 0
}
fmt.Println(f2()) // 0
// 省略部分关键字
var f3 func() = func() {/*...*/}
var f4 = func() {/*...*/}
f5 := func() {/*...*/}
}
内建函数 Builtin
-
func append func append(slice []Type,elems ...Type) []Type
内建函数append将元素追加到切片的末尾。若它有足够的容量,其目标就会重新切片以容纳新的元素。否则,就会分配一个新的基本数组。append返回更新后的切片,因此必须存储追加后的结果。
slice = append(slice,elem1,elem2)
slice = append(slice,anotherSlice...)
作为特例,可以向一个字节切片append字符串,如下:
slice = append([]byte("hello "),"world"...)
-
func cap func cap(v Type) int
内建函数cap返回 v 的容量,这取决于具体类型: 数组:v中元素的数量,与 len(v) 相同 数组指针:*v中元素的数量,与len(v) 相同 切片:切片的容量(底层数组的长度);若 v为nil,cap(v) 即为零 信道:按照元素的单元,相应信道缓存的容量;若v为nil,cap(v)即为零
-
func complex func complex(r,i FloatType) ComplexType
使用实部r和虚部i生成一个复数。
c := complex(1,2)
fmt.Println(c) // (1+2i)
-
func copy func copy(dst,src []Type) int
内建函数copy将元素从来源切片复制到目标切片中,也能将字节从字符串复制到字节切片中。copy返回被复制的元素数量,它会是 len(src) 和 len(dst) 中较小的那个。来源和目标的底层内存可以重叠。
a,c := []byte{1,make([]byte,2),0
fmt.Println("a:"," b:"," c: ",c) // a: [1 2 3] b: [0 0] c: 0
c = copy(b,a)
fmt.Println("a:",c) // a: [1 2 3] b: [1 2] c: 2
b = make([]byte,5)
c = copy(b,c) // a: [1 2 3] b: [1 2 3 0 0] c: 3
s := "ABCD"
c = copy(b,s)
fmt.Println("s:",s,c) // s: ABCD b: [65 66 67 68 0] c: 4
-
func delete func delete(m map[Type]Type1,key Type)
内建函数delete按照指定的键将元素从映射中删除。若m为nil或无此元素,delete不进行操作。
m := map[int]string{
0: "A",1: "B",2: "C",}
delete(m,1)
fmt.Println(m) // map[2:C 0:A]
delete(m,3) // 此行代码执行没有任何操作,也不会报错。
-
func len func len(v Type) int
内建函数len返回 v 的长度,这取决于具体类型: 数组:v中元素的数量 数组指针:*v中元素的数量(v为nil时panic) 切片、映射:v中元素的数量;若v为nil,len(v)即为零 字符串:v中字节的数量,计算字符数量使用utf8.RuneCountInString() 通道:通道缓存中队列(未读取)元素的数量;若v为 nil,len(v)即为零
-
func new func new(Type) *Type
内建函数new分配内存。其第一个实参为类型,而非值。其返回值为指向该类型的新分配的零值的指针。
-
func print func print(args ...Type)
内建函数print以特有的方法格式化参数并将结果写入标准错误,用于自举和调试。
初始化函数 init
-
init 函数是用于程序执行前做包的初始化工作的函数 init 函数的声明没有参数和返回值 func init() {
// ...
}
-
一个package或go源文件可以包含零个或多个init函数 package main
func main() {
}
func init() {
println("init1...")
}
func init() {
println("init2...")
}
func init() {
println("init3...")
}
-
init函数被自动调用,在main函数之前执行,不能在其他函数中调用,显式调用会报错该函数未定义。 func init() {
println("init...")
}
func main() {
init() // undefined: init
}
-
所有init 函数都会被自动调用,调用顺序如下:
- 同一个go文件的init函数调用顺序是 从上到下的
- 同一个package中按go源文件名字符串比较 从小到大顺序调用各文件中的init函数
- 不同的package,如果不相互依赖的,按照main包中 先import的后调用的顺序调用其包中的init函数
- 如果package存在依赖,则先调用最早被依赖的package中的init函数
方法 Method
-
通过指定函数的接收者receiver,将函数绑定到一个类型或类型的指针上,使这个函数成为该类型的方法。 只能对命名类型和命名类型的指针编写方法。 只能在定义命名类型的那个包编写其方法。 不能对接口类型和接口类型的指针编写方法。 方法的接收者receiver是类型的值时,编译器会隐式的生成一个同名方法,其接收者receiver为该类型的指针,反过来却不会。 package main
type A struct {
x,y int
}
// 定义结构体的方法,'_'表示方法内忽略使用结构体、字段及其他方法
func (_ A) echo_A() {
println("(_ A)")
}
// 同上
func (A) echoA(s string) {
println("(A)",s)
}
// 定义结构体指针的方法,'_'表示方法内忽略使用结构体指针、字段及其他方法
func (_ *A) echo_жA() {
println("(_ *A)")
}
// 同上
func (*A) echoжA(s string) {
println("(*A)",s)
}
// 定义结构体的方法,方法内可以引用结构体、字段及其他方法
func (a A) setX(x int) {
a.x = x
}
// 定义结构体指针的方法,方法内可以引用结构体、结构体指针、字段及其他方法
func (a *A) setY(y int) {
a.y = y
}
func main() {
var a A // a = A{}
a.setX(3)
a.setY(6)
println(a.x,a.y) // 0 6
a.echo_A() // (_ A)
a.echoA("a") // (A) a
a.echo_жA() // (_ *A)
a.echoжA("a") // (*A) a
// 以下是定义在结构体值上的方法原型,通过调用结构体类型上定义的函数,传入结构体的值
A.echo_A(a) // (_ A)
A.echoA(a,"a") // (A) a
// A.echo_жA(a) // A.echo_жA未定义
// A.echoжA(a) // A.echoжA未定义
A.setX(a,4)
// A.setY(a,7) // A.setY未定义
println(a.x) // 0
b := &a
b.setX(2)
b.setY(5)
println(b.x,b.y) // 0 5
b.echo_A() // (_ A)
b.echoA("b") // (A) b
b.echo_жA() // (_ *A)
b.echoжA("b") // (*A) b
// 以下是定义在结构体指针上的方法原型,通过调用结构体类型指针上定义的函数,传入结构体的指针
(*A).echo_A(b) // (_ A)
(*A).echoA(b,"b") // (A) b
(*A).echo_жA(b) // (_ *A)
(*A).echoжA(b,"b") // (*A) b
(*A).setX(b,1)
(*A).setY(b,8)
println(b.x,b.y) // 0 8
// 调用结构体空指针上的方法,以下注释掉的代码都是空指针错误
var c *A // c = nil
// c.setX(2)
// c.setY(5)
// println(c.x,c.y)
// c.echo_A()
// c.echoA()
c.echo_жA() // (_ *A)
c.echoжA("c") // (*A) c
// (*A).echo_A(c)
// (*A).echoA(c)
(*A).echo_жA(c) // (_ *A)
(*A).echoжA(c,"c") // (*A) c
// (*A).setX(c,1)
// (*A).setY(c,8)
// println(c.x,c.y)
}
-
结构体中组合匿名字段时,匿名字段的方法会向外传递,其规则如下: 匿名字段为值类型时:值的方法会传递给结构体的值,指针的方法会传递给结构体的指针; 匿名字段为指针类型时:指针的方法会传递给值和指针; 匿名字段为接口类型时:方法会传递给值和指针;
-
Go中有匿名函数,但是没有匿名方法
并发 Concurrency
-
协程goroutine 是由Go运行时环境管理的轻量级线程。 使用关键字go 调用一个函数/方法,启动一个新的协程goroutine package main
import (
"time"
)
func say(i int) {
println("goroutine:",i)
}
func main() {
for i := 1; i <= 5; i++ {
go say(i)
}
say(0)
time.Sleep(5 * time.Second)
} 主协程goroutine输出0,其他由go启动的几个子协程分别输出1~5
goroutine: 0 goroutine: 1 goroutine: 2 goroutine: 3 goroutine: 4 goroutine: 5
-
goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。 package main
import (
"sync"
"time"
)
var mu sync.Mutex
var i int
func main() {
for range [5]byte{} {
go Add()
}
time.Sleep(5*time.Second)
println(i)
}
func Add() {
// 使用互斥锁防止多个协程goroutine同时修改共享变量
// 只能限制同时访问此方法修改变量,在方法外修改则限制是无效的
mu.Lock()
defer mu.Unlock()
i++
} 使用通道channel进行同步 package main
import (
"time"
)
var i int
var ch = make(chan byte,1)
func main() {
for range [5]byte{} {
go Add()
}
time.Sleep(5*time.Second)
println(i)
}
func Add() {
ch <- 0
i++
<-ch
}
-
使用channel在不同的goroutine之间通信 // 上一个例子只是将channel用作同步开关,稍做修改即可在不同goroutine间通信
package main
import (
"time"
)
var i int
var ch = make(chan int,1)
func main() {
for range [5]byte{} {
go Add()
}
ch <- i
time.Sleep(5*time.Second)
i = <-ch
println(i)
}
func Add() {
// 从channel中接收的值是来自其他goroutine发送的
x := <-ch
x++
ch <- x
}
测试 Testing
- Go中自带轻量级的测试框架testing和自带的go test命令来实现单元测试和基准测试
单元测试 Unit
-
有如下待测试testgo包,一段简单的求和代码 package testgo
import "math"
func Sum(min,max int) (sum int) {
if min < 0 || max < 0 || max > math.MaxInt32 || min > max {
return 0
}
for ; min <= max; min++ {
sum += min
}
return
}
-
测试源文件名必须是_test.go 结尾的,go test的时候才会执行到相应的代码 必须import testing包 所有的测试用例函数必须以Test 开头 测试用例按照源码中编写的顺序依次执行 测试函数TestXxx()的参数是*testing.T ,可以使用该类型来记录错误或者是测试状态 测试格式:func TestXxx (t *testing.T) ,Xxx部分可以为任意的字母数字的组合,首字母不能是小写字母[a-z],例如Testsum是错误的函数名。 函数中通过调用*testing.T的Error,Errorf,FailNow,Fatal,FatalIf方法标注测试不通过,调用Log方法用来记录测试的信息。 package testgo
import "testing"
func TestSum(t *testing.T) {
s := Sum(1,0)
t.Log("Sum 1 to 0:",s)
if 0 != s {
t.Error("not equal.")
}
s = Sum(1,10)
t.Log("Sum 1 to 10:",s)
if 55 != s {
t.Error("not equal.")
}
} 在当前包中执行测试:go test -v
=== RUN TestSum --- PASS: TestSum (0.00s) t0_test.go:7: Sum 1 to 0: 0 t0_test.go:12: Sum 1 to 10: 55 PASS ok /home/cxy/go/src/testgo 0.004s
基准测试 Benchmark
-
基准测试 Benchmark用来检测函数/方法的性能 基准测试用例函数必须以Benchmark 开头 go test默认不会执行基准测试的函数,需要加上参数-test.bench,语法:-test.bench="test_name_regex",例如go test -test.bench=".*"表示测试全部的基准测试函数 在基准测试用例中,在循环体内使用testing.B.N,使测试可以正常的运行 package testgo
import "testing"
func BenchmarkSum(b *testing.B) {
b.Logf("Sum 1 to %d: %dn",b.N,Sum(1,b.N))
} 在当前包中执行测试:go test -v -bench .
BenchmarkSum 2000000000 0.91 ns/op --- BENCH: BenchmarkSum t0_test.go:19: Sum 1 to 1: 1 t0_test.go:19: Sum 1 to 100: 5050 t0_test.go:19: Sum 1 to 10000: 50005000 t0_test.go:19: Sum 1 to 1000000: 500000500000 t0_test.go:19: Sum 1 to 100000000: 5000000050000000 t0_test.go:19: Sum 1 to 2000000000: 2000000001000000000 ok /home/cxy/go/src/testgo 1.922s
Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" _xhe_href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a> (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|