A Tour of Go 笔记与练习答案
本文主要为《A Tour of Go》的笔记和练习题答案
建议读者直接阅读上面网站中的教程,建议阅读英文版(中文版有内容确实)
参考答案可通过导航栏快速跳转查阅
Packages, variables, and functions.
-
In Go, a name is exported if it begins with a capital letter. For example,
Pizza
is an exported name, as isPi
, which is exported from themath
package. -
Go’s return values may be named. If so, they are treated as variables defined at the top of the function.
Areturn
statement without arguments returns the named return values. This is known as a “naked” return.1
2
3
4
5
6
7// Naked return statements should be used only in short functions
// They can harm readability in longer functions.
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
} -
If an initializer is present, the type can be omitted; the variable will take the type of the initializer. e.g.
var c, python, java = true, false, "no!"
-
Variables declared without an explicit initial value are given their zero value.
The zero value is:
0
for numeric types,false
for the boolean type, and""
(the empty string) for strings.
-
Numeric constants are high-precision values.
An untyped constant takes the type needed by its context.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
// fmt.Println(needInt(Big))) // cannot use Big (untyped int constant 1267650600228229401496703205376) as int value in argument to needInt (overflows)
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
Flow control statements: for, if, else, switch and defer
- Unlike other languages like C, Java, or JavaScript there are no parentheses
( )
surrounding the three components of thefor
statement and the braces{ }
are always required. - At that point you can drop the semicolons: C’s
while
is spelledfor
in Go. - Like
for
, theif
statement can start with a short statement to execute before the condition.if v := math.Pow(x, n); v < lim {return v}
Exercise: Loops and Functions
1 | func Sqrt(x float64) float64 { |
-
Switch cases evaluate cases from top to bottom, stopping when a case succeeds.
会中途停下这一点和C/C++中完全不一样
-
Switch without a condition is the same as
switch true
.
This construct can be a clean way to write long if-then-else chains.1
2
3
4
5
6
7
8
9
10
11func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
} -
A
defer
statement defers the execution of a function until the surrounding function returns.
The deferred call’s arguments are evaluated immediately, but the function call is not executed until the surrounding function returns. -
Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.
More types: structs, slices, and maps.
-
The type
*T
is a pointer to aT
value. Its zero value isnil
.Go 中指针用
*T
, 数组用[]T
表示 -
To access the field
X
of a struct when we have the struct pointerp
we could write(*p).X
. However, that notation is cumbersome, so the language permits us instead to write justp.X
, without the explicit dereference. -
You can list just a subset of fields by using the
Name:
syntax. (And the order of named fields is irrelevant.)1
2
3
4
5type Vertex struct {
X, Y int
}
v2 = Vertex{X: 1} // Y:0 is implicit -
A
slice
, is a dynamically-sized, flexible view into the elements of an array. In practice, slices are much more common than arrays.
A slice does not store any data, it just describes a section of an underlying array.1
2primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4] -
Slices can be created with the built-in
make
function; this is how you create dynamically-sized arrays.The
make
function allocates a zeroed array and returns a slice that refers to that array:1
a := make([]int, 5) // len(a)=5
To specify a capacity, pass a third argument to
make
:1
2
3
4b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4为啥有时cap会变化,有时不会变化呢?
个人感觉好像是[first:last]中设定first时会变化,设定last不会变化 -
The zero value of a slice is
nil
. -
Slices can contain any type, including other slices.
1
2
3
4
5board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
} -
It is common to append new elements to a slice, and so Go provides a built-in
append
function.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var s []int
printSlice(s)
// append works on nil slices.
s = append(s, 0)
printSlice(s)
// The slice grows as needed.
s = append(s, 1)
printSlice(s)
/*
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
*/ -
The
range
form of thefor
loop iterates over a slice or map.
When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.1
2
3
4
5
6
7var 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)
}
} -
You can skip the index or value by assigning to
_
.1
2for i, _ := range pow
for _, value := range powIf you only want the index, you can omit the second variable.
1
for i := range pow
Exercise: Slices
1 | package main |
-
The zero value of a
map
isnil
. Anil
map has no keys, nor can keys be added. -
Mutating Maps
Insert or update an element in map
m
:1
m[key] = elem
Retrieve an element:
1
elem = m[key]
Delete an element:
1
delete(m, key)
Test that a key is present with a two-value assignment:
1
elem, ok = m[key]
-
Mutating Maps
Insert or update an element in map
m
:1
m[key] = elem
Retrieve an element:
1
elem = m[key]
Delete an element:
1
delete(m, key)
Test that a key is present with a two-value assignment:
1
elem, ok = m[key]
Note: If
elem
orok
have not yet been declared you could use a short declaration form:elem, ok := m[key]
Exercise: Maps
1 | func FieldsFunc(s string, f func(rune) bool) []string |
FieldsFunc splits the string s at each run of Unicode code points c satisfying f© and returns an array of slices of s. If all code points in s satisfy f© or the string is empty, an empty slice is returned.
1 | package main |
-
Go functions may be closures. A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables; in this sense the function is “bound” to the variables.
For example, theadder
function returns a closure. Each closure is bound to its ownsum
variable.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
Exercise: Fibonacci closure
1 | package main |
Methods and interfaces
-
A
method
is a function with a specialreceiver
argument.
The receiver appears in its own argument list between thefunc
keyword and the method name.func (v Vertex) Abs() float64 {...}
Remember: a method is just a function with a receiver argument. Here’sAbs
written as a regular function with no change in functionality.func Abs(v Vertex) float64 {...}
-
Functions with a pointer argument must take a pointer.
While methods with pointer receivers take either a value or a pointer as the receiver when they are called. -
Functions that take a value argument must take a value of that specific type
While methods with value receivers take either a value or a pointer as the receiver when they are called. -
There are two reasons to use a pointer receiver.
- The first is so that the method can modify the value that its receiver points to.
- The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct, for example.
-
An
interface
type is defined as a set of method signatures.
A value of interface type can hold any value that implements those methods.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
35type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
// In the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
a = v
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
} -
If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver (as with the methodM
in this example.)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21func (t *T) M() {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
func main() {
var i I
var t *T
i = t
describe(i)
i.M()
i = &T{"hello"}
describe(i)
i.M()
} -
A nil interface value holds neither value nor concrete type.
Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call. -
The interface type that specifies zero methods is known as the empty interface:
interface{}
An empty interface may hold values of any type. (Every type implements at least zero methods.) -
A
type assertion
provides access to an interface value’s underlying concrete value.1
2
3
4
5
6
7
8
9
10
11
12
13var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64) // 接收ok,则即使断言失败也不会触发panic
fmt.Println(f, ok)
f = i.(float64) // panic
fmt.Println(f) -
A type switch is a construct that permits several type assertions in series.
1
2
3
4
5
6
7
8switch v := i.(type) { // .(type) 是固定语法
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}The declaration in a type switch has the same syntax as a type assertion
i.(T)
, but the specific typeT
is replaced with the keywordtype
.
Exercise: Stringers
One of the most ubiquitous interfaces is Stringer
defined by the fmt
package.
1 | type Stringer interface { |
A Stringer
is a type that can describe itself as a string. The fmt
package (and many others) look for this interface to print values.
1 | type Person struct { |
Exercise Answer:
1 | package main |
Exercise: Errors
The error
type is a built-in interface similar to fmt.Stringer
:
1 | type error interface { |
(As with fmt.Stringer
, the fmt
package looks for the error
interface when printing values.)
Functions often return an error
value, and calling code should handle errors by testing whether the error equals nil
.
1 | i, err := strconv.Atoi("42") |
A nil error
denotes success; a non-nil error
denotes failure
Exercise Answer:
1 | package main |
Note: A call to fmt.Sprint(e)
inside the Error
method will send the program into an infinite loop. You can avoid this by converting e
first: fmt.Sprint(float64(e))
. Why?
因为e变量是一个通过实现Error()的接口函数成为了error类型,那么在fmt.Sprint(e)时就会调用e.Error()来输出错误的字符串信息,于是函数就变成了如下等价形式,因此会陷入死循环中。
1
2
3 func (e ErrNegativeSqrt) Error() string {
return fmt.Sprint(e.Error())
}
Exercise: Readers
The io
package specifies the io.Reader
interface, which represents the read end of a stream of data: func (T) Read(b []byte) (n int, err error)
Read
populates the given byte slice with data and returns the number of bytes populated and an error value. It returns an io.EOF
error when the stream ends.
1 | func main() { |
Exercise Answer:
1 | package main |
Exercise: rot13Reader
1 | package main |
Exercise: Images
Package image defines the Image
interface:
1 | package image |
The color.Color
and color.Model
types are also interfaces, but we’ll ignore that by using the predefined implementations color.RGBA
and color.RGBAModel
. These interfaces and types are specified by the image/color package
Exercise Answer:
1 | package main |
Generics
The type parameters of a function appear between brackets, before the function’s arguments.
1 | func Index[T comparable](s []T, x T) int |
comparable
is a useful constraint that makes it possible to use the ==
and !=
operators on values of the type. In this example, we use it to compare a value to all slice elements until a match is found. This Index
function works for any type that supports comparison.
In addition to generic functions, Go also supports generic types.
1 | // List represents a singly-linked list that holds |
Concurrency
-
Channels are a typed conduit through which you can send and receive values with the channel operator,
<-
.
By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables. -
Channels can be buffered. Provide the buffer length as the second argument to
make
to initialize a buffered channel:ch := make(chan int, 100)
Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.注意写法是
chan int
, chan表示channel -
A sender can
close
a channel to indicate that no more values will be sent.v, ok := <-ch
-
The
select
statement lets a goroutine wait on multiple communication operations.
Aselect
blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.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: // 接受者不再读取chan时就会阻塞发送
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)
}
Exercise: Equivalent Binary Trees
1 | package main |
Exercise: Web Crawler
1 | package main |
WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。
- Add(n) 把计数器设置为n
- Done() 每次把计数器-1
- wait() 会阻塞代码的运行,直到计数器地值减为0。