A Tour of Go 摘记
最近在学习 golang,本文主要是 A Tour of Go 的一些摘记,涵盖了 go 的一些基本语法与数据类型、通过 struct 和 method 实现类的特性、以及 go 中重要的 concurrency 特性。
Basics
packages, variables and functions
程序的入口在
main
这个 package 中, 需要在文件头声明import
通过()
引入多个 package1
2
3
4import (
"fmt"
"math/rand"
)定义变量的几种形式(定义常量只能用第一种方法,且 var 改成 const)
1
2
3
4
5
6
7// 方式 1
var i, j int
var i, j int = 1, 2
var i, j int = 1, 2
// 方式 2
i, j := 1, 2golang 中的变量类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128函数声明时类型放在最后:参数类型放在参数后,返回类型放在函数名后; 且连续多个参数类型一样时可只为最后一个写类型,如下
1
2
3
4
5
6
7func add(x int, y int) int {
return x + y
}
// same as above
func add(x , y int) int {
return x + y
}函数可返回多个值, 且返回值可以被命名(此时的返回值相当函数里两个命名的变量)
1
2
3
4
5
6
7
8
9
10
11// 多个返回值
func swap(x, y string) (string, string) {
return y, x
}
// 具名返回值, 返回 x 和 y
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}函数可作为参数传入其他函数,也可作为返回值,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}闭包(一个函数访问并能更新在其函数域外的变量)一般会将函数作为返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14func fibonacci() func() int {
a, b := -1, 1
return func() int {
a, b = b, a + b
return b
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
for, if else, switch and defer
go 里面没有 while,for 相当于 while
包含 for 的三个元素的圆括号
()
是没有的 (不仅是 for 语句,if else 等其他语句也没有),且花括号{}
总是必须的 (哪怕只有一条语句)1
2
3
4
5
6
7
8
9
10sum := 0
for i := 0; i < 10; i++ {
sum += i
}
// 三个元素的头尾元素可缺省
sum := 1
for ; sum < 1000; {
sum += sum
}for 跟 range 结合可以遍历 slice 和 map, 每次返回两个值,对于 map 是 key:value,对于 slice 则是 index:value
1
2
3
4
5
6
7
8
9
10
11
12
13
14var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
// 不要第一个元素
for _, v := range pow {
}
// 不要第二个元素
for i := range pow {
}
}if 语句的判断条件中可带有一个短的声明语句
1
2
3if v := math.Pow(3, 5); v < 100 {
return v
}switch 语句不需要 break(实际上是 go 自动添加了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
}defer 后面的语句直到其所在的函数返回才执行,实际上 defer 后面的语句被 push 到了 stack 中,返回时就出栈
1
2
3
4
5
6
7
8
9
10// 输出 hello world
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
// 从 9 到 0 输出
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
pointer, struct, slice and map
go 中的指针跟 C/C++ 中的类似
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import "fmt"
func main() {
i, j := 42, 2701
var p1 *int
p1 = &i
fmt.Println(*p1) // read i through the pointer
*p1 = 21 // set i through the pointer
fmt.Println(i) // see the new value of i
p2 := &j // point to j
*p2 = *p2 / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j
}go 中的结构体也跟 C/C++ 的类似,只是声明方式不一样,多了 type 这个关键字,且类型 struct 放在最后
1
2
3
4
5
6
7
8
9type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
fmt.Println(v.X)
}指向结构体的指针访问结构体的元素的方式跟 C/C++ 不一样,C/C++ 要用
->
, go 中可直接使用.
1
2
3
4
5
6
7
8
9
10
11type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}array 与 slice 是两个不同的类型,区别在于 array 长度固定,slice 长度可变,声明时一个指定长度,一个不指定长度,语法均是 [] type
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24func main() {
// array 声明方式一
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
// array 声明方式二
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
// slice 声明方式一
s := prime[:4]
// slice 声明方式二
var s []int
// slice 声明方式三
s := []int{2, 3, 5, 7, 11, 13}
// slice 声明方式四
s := make([]int, 5)需要注意的是,每个 slice 都有一个 underlying array, slice 就是这个 array 的 reference ,当 slice 被其他 array 的部分元素初始化时,修改 slice 就是在修改这个 array;slice 有两个方法:
len()
和cap()
,len()
是 slice 的长度,cap()
则是 slice 对应的 underlying array 从 slice 的第一个元素开始计算的长度1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29func printSlice(s []string) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
printSlice(a)
printSlice(b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
// 输出如下
[John Paul George Ringo]
len=2 cap=4 [John Paul]
len=2 cap=3 [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]slice 是可变长的,通过 append 函数往 slice 末尾添加元素,如下
1
2
3
4
5
6
7
8
9
10
11
12func main() {
var s []int
printSlice(s)
// The slice grows as needed.
s = append(s, 1)
printSlice(s)
// We can add more than one element at a time.
s = append(s, 2, 3, 4)
printSlice(s)
}map 初始化方式有以下几种,map 声明的中括号里面是 key 的类型,外面是 value 的类型,即
map[key]value
1
2
3
4
5// method 1
m := make(map[int]int)
// method 2
var m = map[int]int{1:1, 2:2}删除 map 中某个元素可直接使用
delete (map, key)
测试某个 key 是否在 map 中可通过
map[key]
返回一个两元组实现,即elem, ok := m[key]
, 如果 ok 为 true,则表示 key 里面
Methods and interfaces
go 没有提供类,但是可以通过为 struct 定义 method 来提供类相近的特性;method 就是在普通函数基础上定义一个 receiver 参数(定义在
func
和函数名之间),表明这个方法是属于某个 struct 的1
2
3
4
5
6
7
8
9
10
11
12type Vertex struct {
X, Y float64
}
func (v1 Vertex) Abs() float64 {
return math.Sqrt(v1.X*v1.X + v1.Y*v1.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}reveiver 参数也可以是指针类型,这意味着通过 receiver 参数能够直接修改 struct 的值 (函数的普通参数也是需要指针才能修改实参的值), 同时也不用在调用 method 时创建新的内存来存储临时对象,因此指针类型的 receiver 也更加常用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10) // equal to (&v).Scale(10)
fmt.Println(v.Abs())
}go 中的接口(interface) 概念与 Java 中的类似,声明一系列的方法,实现了这些方法就是实现了这个接口 (无需显式声明)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18type I interface {
M()
}
type T struct {
S string
}
// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
fmt.Println(t.S)
}
func main() {
var i I = T{"hello"}
i.M()
}一个非常普遍的 interface 是
Stringer
, 这是由fmt
这个 package 中定义的,一旦实现过了这个接口里面的String() string
方法,fmt.Println()
时就会调用这个方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21type Stringer interface {
String() string
}
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}
// 输出
Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)类似于上面的接口
fmt.Stringer
,error
也是一个常用的接口,实现该接口需要实现Error
方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}go 的很多标准库都实现了
io.Reader
这个接口,接口主要定义了这个方法func (T) Read(b []byte) (n int, err error)
, 如下是一个简单地用法,每次从 string 中读取 8 个 byte1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}
Concurrency
goroutine 相当于是轻量级的线程,使用方法很简单
go f()
就启动了一个 goroutine 来执行函数f()
channel 类似于一个队列,但是出列时要等到所有入列的操作已完成则可进行,反之亦然,这就为 goroutine 提供了 synchronize 的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26ch := make(chan int) // create a channel of type int
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and assign value to v.
// simple example
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
// output
-5 17 12Buffered channel:可以为 channel 指定长度 (如
chan := make(chan int, 100)
),这样当 channel 满了之后不能再往其中写数据(再写会报错),这种 channel 也被称为 buffered channel;也可以close
一个 channel, 这样也不能继续入列通过
range
来遍历 channel 会自动判断 channel 是否已经为空,即for v := range channel
, 需要注意的是,用 for 来遍历一个 channel 时,该 channel 必须要先close
,否则会出现错误 fatal error: all goroutines are asleep - deadlock!select
包含的代码块中有多个case
语句,当其中的任一条件被满足时才会执行,否则会阻塞(可以添加default
选项使得select
不会被阻塞),当有多个被满足时则随机选一个;通常select
也被用来协调多个 goroutine 的通信,如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24func fibonacci(c, quit chan int) {
x, y := 0, 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)
}channel 是 goroutine 通信的一个有效工具,但是除了通信,多个 goroutine 往往还会存在着同时读写一个变量的情况,这时候就要加锁,os 中也将这样的锁机制成为互斥量(mutex),go 的
sync.Mutex
就是一个能够实现加锁和解锁的互斥量;如下是多个 goroutine 共享一个 Counter 的例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import (
"fmt"
"sync"
"time"
)
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mux.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mux.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}