摘自 Go 语言之旅
目录
1. 包、变量和函数
1.1 包
- 每个 Go 程序都是由包构成的
- 程序从
main
包开始运行 - 包名与导入路径的最后一个元素一致
1.2 导入
import (
// 标准库
"fmt"
"math"
// 本地包
"apiserver/model"
"apiserver/log"
// 外部包
"github.com/spf13/viper"
)
1.3 导出名
导出名以大写字母开头:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Pi)
}
------
3.141592653589793
1.4 函数
函数可以有多个返回值:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Hello", "world")
fmt.Println(a, b)
}
------
world Hello
也可以分别为返回值命名:
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(26))
}
------
11 15
1.5 基本类型
Go 语言的基本类型如下:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名, 表示一个 Unicode 码点
float32 float64
complex64 complex128
测试一下:
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
fmt.Printf("Type: %T Value: %v\n", z, z)
}
------
Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)
1.6 零值
- 数值:
0
- 布尔:
false
- 字符串:
""
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
------
0 0 false ""
1.7 类型转换
表达式T(v)
将值v
转换为类型T
:
package main
import (
"fmt"
"math"
)
func main() {
var x, y int = 3, 4
var f float64 = math.Sqrt(float64(x*x + y*y))
var z uint = uint(f)
fmt.Println(x, y, z)
}
------
3 4 5
1.8 类型推导
package main
import "fmt"
func main() {
v1 := 42
fmt.Printf("v1 is of type %T\n", v1)
v2 := 3.1415926
fmt.Printf("v2 is of type %T\n", v2)
v3 := 0.867 + 0.5i
fmt.Printf("v3 is of type %T\n", v3)
}
------
v1 is of type int
v2 is of type float64
v3 is of type complex128
1.9 常量
常量使用const
关键字声明,可以是字符、字符、布尔值或数值,且不能用:=
语法声明:
package main
import "fmt"
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
------
Hello 世界
Happy 3.14 Day
Go rules? true
1.10 数值常量
数值常量是高精度的值,一个未指定类型的常量由上下文来决定其类型:
package main
import "fmt"
const (
// 将 1 左移 100 位来创建一个非常大的数字
// 即这个数的二进制是 1 后面跟着 100 个 0
Big = 1 << 100
// 再往右移 99 位,即 Small = 1 << 1,或者说 Small = 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(needFloat(Small))
fmt.Println(needFloat(Big))
}
------
21
0.2
1.2676506002282295e+29
2. 流程控制语句
2.1 for 循环
for i := 0; i < 10; i++ {
sum += i
}
省略初始语句及后置语句,可用作while
:
for sum < 1000 {
sum += sum
}
无限循环:
for {
// do something
}
2.2 if else 语句
if v := math.Pow(x, n); v <lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
return lim
2.3 switch 分支
switch
的case
语句从上到下顺次执行,直到匹配成功时停止:
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "windows":
fmt.Println("Windows.")
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.\n", os)
}
}
没有条件的switch
同switch true
一样,可以将一长串的if-else
写得更清晰:
package main
import (
"fmt"
"time"
)
func 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!")
}
}
2.4 defer 栈
defer
语句会将函数推迟到外层函数返回之后执行:
推迟调用的函数其参数会立即求值,但直到外层函数返回之前,该函数都不会被调用
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("Hello")
}
------
Hello
world
推迟的函数调用会被压入栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用:
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
------
counting
done
9
8
7
6
5
4
3
2
1
0
3. struct、slice、map
3.1 指针
指针保存了值的内存地址,类型*T
是指向T
类型值的指针,其零值为nil
:
var p *int
fmt.Println(p)
------
0xc000062070<nil>
与 C 不同,Go 没有指针运算。
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // 指向 i
fmt.Println(*p) // 通过指针读取 i 的值
*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值
p = &j // 指向
*p = *p / 37 // 通过指针对 j 进行除法运算
fmt.Println(j) // 查看 j 的值
fmt.Print(p) // 查看 j 的地址
}
------
42
21
73
0xc000062070
3.2 结构体
一个结构体struct
就是一组字段field
。当有一个指向结构体的指针p
时,Go 允许使用隐式间接引用,直接通过p.X
访问其字段:
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{
X: 1,
Y: 2,
}
p := &v
p.X = 1e9
fmt.Println(v)
}
------
{1000000000 2}
结构体文法通过直接列出字段的值来新分配一个结构体,使用Name:
语法可以仅列出部分字段,特殊的前缀&
返回一个指向结构体的指针:
package main
import "fmt"
type Vertex struct {
X int
Y int
}
var (
v1 = Vertex{1, 2}
v2 = Vertex{
X: 1,
Y: 6,
}
v3 = Vertex{}
p = &Vertex{1, 2}
)
func main() {
fmt.Println(v1, p, v2, v3)
}
------
{1 2} &{1 2} {1 6} {0 0}
3.3 数组
类型[n]T
是独立的类型:
var a [10]int
即数组的长度是其类型的一部分,因此数组不能改变大小。
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
------
Hello World
[Hello World]
[2 3 5 7 11 13]
3.4 切片
每个数组的大小都是固定的,而切片则为数组元素提供动态大小的灵活视角。
类型[]T
表示一个元素类型为T
的切片,通过上界和下界来界定:
a[low:high]
这是一个左开右闭区间,两个下标均可以省略:
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes, len(primes), cap(primes))
var s1 = primes[1:4]
fmt.Println(s1, len(s1), cap(s1))
var s2 = primes[:]
fmt.Println(s2, len(s2), cap(s2))
}
------
[2 3 5 7 11 13] 6 6
[3 5 7] 3 5
[2 3 5 7 11 13] 6 6
切片就像数组的引用
- 切片并不存储任何数据,它只是描述了底层数组中的一段
- 更改切片的元素会修改其底层数组中对应的元素
- 与它共享底层数组的切片都会观测到这些修改
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println("The original array:", names)
a := names[0:2]
b := names[1:3]
fmt.Println("\nSlice a:", a)
fmt.Println("Slice b:", b)
b[0] = "XXX"
fmt.Println("\nNow slice a:", a)
fmt.Println("Now slice b:", b)
fmt.Println("\nThe modified array:", names)
}
------
The original array: [John Paul George Ringo]
Slice a: [John Paul]
Slice b: [Paul George]
Now slice a: [John XXX]
Now slice b: [XXX George]
The modified array: [John XXX George Ringo]
切片文法
切片文法类似于没有长度的数组文法:
package main
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
------
[2 3 5 7 11 13]
[true false true true false true]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
默认行为
切片下界默认值为0
,上界则是该切片的长度。
package main
import (
"fmt"
"unsafe"
)
func main() {
s := []int{2, 3, 5, 7, 11, 13}
fmt.Println(" s:", s)
s1 := s[1:4]
fmt.Println("s1: ", s1)
s2 := s1[:2]
fmt.Println("s2: ", s2)
s3 := s2[1:]
fmt.Println("s3: ", s3)
s3[0] = 0
fmt.Println("\nSizeof int on a 64-bit machine:", unsafe.Sizeof(s[0]))
fmt.Println("\ns[0] location:", &s[0])
fmt.Println("s[1] location:", &s[1])
fmt.Println("s[2] location:", &s[2])
fmt.Println("s[3] location:", &s[3])
}
------
s: [2 3 5 7 11 13]
s1: [3 5 7]
s2: [3 5]
s3: [5]
Sizeof int on a 64-bit machine: 8
s[0] location: 0xc00008c030
s[1] location: 0xc00008c038
s[2] location: 0xc00008c040
s[3] location: 0xc00008c048
长度和容量
切片拥有长度和容量:
- 长度就是它所包含的元素个数
- 容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
------
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
nil 切片
切片的零值是nil
,长度和容量均为0
且没有底层数组:
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
------
[] 0 0
nil!
使用 make 创建切片
切片可以用内建函数make
来创建,make
函数会分配一个元素为零值的数组并返回一个引用它的切片:
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
------
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
切片的切片
切片可以包含切片,类似二维切片的概念:
package main
import (
"fmt"
"strings"
)
func main() {
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}
------
X _ X
O _ X
_ _ O
append 追加切片元素
Go 提供了内建的append()
函数,用于向切片追加新元素:
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// 向切片添加一个 0
s = append(s, 0)
printSlice(s)
// 这个切片会按需增长
s = append(s, 1)
printSlice(s)
// 可以一次性添加多个元素
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
------
len=0 cap=0 []
len=1 cap=2 [0]
len=2 cap=2 [0 1]
len=5 cap=8 [0 1 2 3 4]
// go1.12.6 windows/amd64:
len=5 cap=6 [0 1 2 3 4]
当切片长度不超过
1024
时,每次扩容为原切片长度的两倍(有待考证)
3.5 Range
for
循环的range
形式可遍历切片slice
或映射map
:
package main
import "fmt"
var 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)
}
}
------
2^0 = 1
2^1 = 2
2^2 = 4
2^3 = 8
2^4 = 16
2^5 = 32
2^6 = 64
2^7 = 128
当使用for range
遍历时,每次迭代都会返回两个值:
- 前者
i
为当前元素的下标 - 后者
v
为该下标对应元素的一份副本
因此,通过下标
s[i]
取值比直接通过v
效率更高
可以将下标或值赋予_
表示忽略:
for i, _ := range pow
for _, value := range pow
若只需要索引,忽略第二个变量即可:
for i := range pow
3.6 映射
映射map
将键映射到值。映射的文法与结构体相似,不过必须有键名:
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
func main() {
fmt.Println(m)
}
------
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
映射的文法
可以直接省略顶级类型的类型名:
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
修改映射
当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
// 插入或修改元素
m[key] = elem
// 删除元素
delete(m, key)
// 通过双赋值检测某个键是否存在
elem, ok := m[key]
3.7 函数值
函数也是值,可以向其他值一样传递。
函数值可以用作函数的参数或返回值:
package main
import (
"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))
}
------
13
5
81
3.8 函数的闭包
Go 的函数可以是一个闭包clojure
。
闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
例如,函数adder()
返回一个闭包,每个闭包都被绑定在其各自的sum
变量上:
package main
import "fmt"
func 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),
)
}
}
------
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
4. 方法和接口
4.1 方法
Go 没有类,不过可以为结构体类型定义方法。
方法就是一类带特殊的接收者参数的函数,接收者位于func
关键字和方法名之间:
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
------
5
也可以为非结构体类型声明方法,例如在下面的例子中看到了一个带Abs()
方法的数值类型MyFloat
:
package main
import (
"fmt"
"math"
)
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
------
1.4142135623730951
需要注意:
- 接收者(例如
MyFloat
)的类型定义和方法声明必须在同一包内 - 不能为内建类型声明方法
指针接收者
可以为指针接收者声明方法,这意味着方法内部可以修改接收者指向的值:
package main
import (
"fmt"
"math"
)
type 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 *= f
v.Y *= f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}
------
50
方法与指针重定向
带指针参数的函数必须接受一个指针:
var v Vertex
ScaleFunc(v, 5) // 编译错误!
ScaleFunc(&v, 5) // OK
而以指针为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
这是因为 Go 会对语句做以下翻译:
v.Scale(5)
// 以上语句解释为:
(&v).Scale(5)
同样的,以值为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
同样会做以下翻译:
p.Abs()
// 以上语句翻译为
(*p).Abs()
选择值或指针作为接收者
通常来说,所有给定类型的方法都应该有值或指针接收者,但不应该二者混用
使用指针接收者的原因有二:
- 方法能够修改其接收者指向的值
- 可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效
例如在下面的示例中,Scale()
和Abs()
接收者的类型均为*Vertex
,即便Abs()
并不需要修改其接收者:
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
v.Scale(5)
fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}
------
Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25
4.2 接口
接口类型是由一组方法签名定义的集合,接口类型的变量可以保存任何实现了这些方法的值:
package main
import (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat 实现了 Abser
fmt.Println(a.Abs())
a = &v // a *Vertex 实现了 Abser
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)
}
------
1.4142135623730951
5
接口的隐式实现
在 Go 中,接口是隐式实现的,即类型通过实现一个接口的所有方法来实现该接口。
既然无需专门显式声明,也就没有implements
关键字。
接口值
- 接口也是值,可以像其他值一样传递
- 接口值可以用作函数的参数或返回值
- 在内部,接口值可以看做包含值和具体类型的元组:
(value, type)
- 接口值保存了一个具体底层类型的具体值
- 接口值调用方法时会执行其底层类型的同名方法
package main
import (
"fmt"
"math"
)
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
type F float64
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I
i = &T{"Hello"}
describe(i)
i.M()
i = F(math.Pi)
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
------
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793
底层值为 nil 的接口值
即便接口内的具体值为nil
,方法仍然会被nil
接收者调用:
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
func (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()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
------
(<nil>, *main.T)
<nil>
(&{hello}, *main.T)
hello
注意:保存了
nil
具体值的接口,其自身并不为nil
nil 接口值
nil
接口值既不保存值也不保存具体类型- 为
nil
接口调用方法会产生运行时错误
package main
import "fmt"
type I interface {
M()
}
func main() {
var i I
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
------
(<nil>, <nil>)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xd9864]
goroutine 1 [running]:
main.main()
/tmp/sandbox062767685/prog.go:12 +0x84
空接口
指定了零个方法的接口值被称为空接口:
interface{}
- 空接口可以用来保存任何类型的值,因为每个类型都至少实现了零个方法
- 空接口被用来处理未知类型的值
package main
import "fmt"
func main() {
var i interface{}
describe(i)
i = 42
describe(i)
i = "hello"
describe(i)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
------
(<nil>, <nil>)
(42, int)
(hello, string)
4.3 类型断言
类型断言提供了访问接口值底层具体值的方式:
t, ok := i.(T)
- 若
i
保存了一个T
,那么t
将会是其底层值,而ok
为true
- 否则,
ok
将为false
,而t
将为T
类型的零值,程序并不会产生panic
package main
import "fmt"
func main() {
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
f = i.(float64) // 报错(panic)
fmt.Println(f)
}
------
hello
hello true
0 false
panic: interface conversion: interface {} is string, not float64
goroutine 1 [running]:
main.main()
/tmp/sandbox590758285/prog.go:17 +0x220
4.4 类型选择
类型选择是一种按顺序从几个类型断言中选择分支的结构:
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
------
Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!
4.5 Stringer
fmt
包中定义的Stringer
接口是最普遍的接口之一:
type Stringer interface {
String() string
}
因此只要实现了String()
方法,就可以打印结构体的信息:
package main
import "fmt"
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)
4.6 错误
Go 程序使用error
值来表示错误状态。与fmt.Stringer
类似,error
类型也是一个内建接口:
type error interface {
Error() string
}
通常函数会返回一个error
值,调用它的代码应当判断这个错误是否等于nil
来进行错误处理:
package main
import (
"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)
}
}
------
at 2019-08-18 12:53:38.540727 +0800 CST m=+0.002985501, it didn't work
4.7 Reader
io
包指定了io.Reader
接口,它表示从数据流的末尾进行读取。
io.Reader
接口有一个Read
方法:
func (T) Read(b []byte) (n int, err error)
该方法用数据填充给定的字节切片,并返回填充的字节数和错误值。在遇到数据流的结尾时,会返回一个io.EOF
错误:
package main
import (
"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
}
}
}
------
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""
4.8 图像
image
包定义了Image
接口:
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
这些接口和类型由image/color
包定义:
package main
import (
"fmt"
"image"
)
func main() {
m := image.NewRGBA(image.Rect(0, 0, 100, 100))
fmt.Println(m.Bounds())
fmt.Println(m.At(0, 0).RGBA())
}
------
(0,0)-(100,100)
0 0 0 0
5. 并发
5.1 协程 goroutine
Go 程goroutine
是由 Go 运行时管理的轻量级线程:
go f(x, y, z)
上面的语句会启动一个新的 Go 程并执行:
f(x, y, z)
f, x, y, z
的求值发生在当前 Go 程中f
的执行发生在新的 Go 程中
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
------
hello
world
world
hello
world
hello
world
hello
hello
Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。sync
包提供了这种能力,不过也可以利用其它方式实现。
5.2 通道 channel
信道channel
是带有类型的管道,可以通过它使用信道操作符<-
来发送或者接收值:
ch <- v // 将 v 发送至信道 ch
v := <-ch // 从 ch 接收值并赋予 v
根据箭头在信道的方向,左读右写。
和映射与切片一样,信道在使用前必须创建:
ch := make(chan int)
默认情况下是阻塞的,这使得 Go 程序可以在没有显式的锁或竞态变量的情况下进行同步:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for i := range s {
sum += s[i]
}
c <- sum // 将和送入 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 // 从 c 中接收
fmt.Println(x, y, x+y)
}
------
-5 17 12
5.3 带缓冲的通道
信道可以是带缓冲的:
ch := make(chan int, 100)
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接收方会阻塞。
5.4 range 和 close
发送者可通过close
关闭一个信道来表示没有需要发送的值了。
接收者可通过以下语句判断信道是否已被关闭:
v, ok := <-ch // 关闭时 v 为默认零值,ok 为 false
循环for i := range c
会不断从信道接收值,直到它被关闭:
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 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)
}
v, ok := <-c
fmt.Println(v, ok)
}
------
0
1
1
2
3
5
8
13
21
34
0 false
- 只有发送者才能关闭信道,而接收者不能
- 向一个已经关闭的信道发送数据会引发
panic
- 重复关闭信道会引发
panic
- 信道与文件不同,通常情况下无需关闭,只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个
range
循环
5.5 select 语句
select
语句使一个 Go 程可以等待多个通信操作。
select
会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时,会随机选择一个执行:
package main
import "fmt"
func 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)
}
------
0
1
1
2
3
5
8
13
21
34
quit
当select
中的其他分支都没有准备好时,default
分支就会执行:
select {
case i := <-c:
// 使用 i
default:
// 从 c 中接收会阻塞时执行
}
为了在尝试发送或者接收时不发生阻塞,可使用default
分支:
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
------
.
.
tick.
.
.
tick.
.
.
tick.
.
.
tick.
.
.
BOOM!
5.6 sync.Mutex
Go 标准库中提供了sync.Mutex
互斥锁类型及其两个方法:Lock()
、Unlock()
:
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter 的并发使用是安全的
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc 增加给定 key 的计数器的值
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
c.v[key]++
c.mux.Unlock()
}
// Value 返回给定 key 的计数器的当前值
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
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"))
}
------
1000
- 可以通过在代码前调用
Lock()
方法、在代码后调用Unlock()
方法来保证一段代码的互斥执行 - 也可以用
defer
语句来保证互斥锁一定会被解锁
6. 常用代码
6.1 标准库
// fmt
fmt.Errorf("%s", "db connect fail")
// io
// math
math.Pi
// sync
// strings
strings.Join(board[i], " ")
fileds := strings.Fileds(s)
// strconv
// net/http
// net/url
// log
// types
// json
// xml
// rand
fmt.Println("My favorite number is", rand.Intn(10))
%d 整型
%s 字符串
%f 浮点数
%T 类型
%v 值,例如 {3 4}
%+v 域+值,例如 {X:3 Y:4}
%q 带引号的字符串, "s"
// time
time.Now()
time.Now().Hour()
time.Sleep(time.Second)
// unsafe
unsafe.Sizeof()
// runtime
runtime.GOOS
runtime.GOARCH
runtime.Version()
fmt.Println(runtime.GOMAXPROCS(0))
// errors
errors.New()
// os.Open()
6.2 内建函数
make([]int, 4, 8)
// 切片 slice
len()
cap()
append()
copy()
// 映射 map
delete(m, key)
// 内建接口
type error interface {
Error()
}
type Stringer interface {
String() string
}
// 通道
for i := range ch
close(ch)
7. 练习解答
7.1 循环与函数:牛顿法求平方根
package main
import (
"fmt"
"math"
)
func sqrt(x float64) float64 {
z := float64(1)
for {
y := z - (z*z-x)/(2*z)
if math.Abs(y-z) < 1e-10 {
return y
}
z = y
}
}
func main() {
fmt.Println(sqrt(2))
fmt.Println(math.Sqrt(2))
}
------
1.4142135623730951
1.4142135623730951
7.2 切片:图像灰度值
package main
import (
"golang.org/x/tour/pic"
)
func Pic(dx, dy int) [][]uint8 {
ret := make([][]uint8, dy)
for x := 0; x < dy; x++ {
ret[x] = make([]uint8, dx)
for y := 0; y < dx; y++ {
ret[x][y] = uint8(x ^ y)
// ret[x][y] = uint8((x + y) / 2)
// ret[x][y] = uint8(x * y)
// ret[x][y] = uint8(float64(x) * math.Log(float64(y)))
// ret[x][y] = uint8(x % (y + 1))
}
}
return ret
}
func main() {
pic.Show(Pic)
}
7.3 映射:单词统计
package main
import (
"strings"
"golang.org/x/tour/wc"
)
func WordCount(s string) map[string]int {
count := make(map[string]int)
for _, word := range strings.Fields(s) {
count[word]++
}
return count
}
func main() {
wc.Test(WordCount)
}
7.4 闭包:斐波那契数列
package main
import "fmt"
// 返回一个“返回int的函数”
func fibonacci() func() int {
one := 0
two := 1
return func() int {
three := one + two
one = two
two = three
return three
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
------
1
2
3
5
8
13
21
34
55
89
7.5 Stringer
package main
import "fmt"
type IPAddr [4]byte
func (ip IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
------
loopback: 127.0.0.1
googleDNS: 8.8.8.8
7.6 错误
package main
import (
"fmt"
"math"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}
func sqrt(x float64) (float64, error) {
if x < 0 {
return 0, ErrNegativeSqrt(x)
}
z := float64(1)
for {
y := z - (z*z-x)/(2*z)
if math.Abs(y-z) < 1e-10 {
return y, nil
}
z = y
}
}
func main() {
fmt.Println(sqrt(2))
fmt.Println(sqrt(-2))
}
------
1.4142135623730951 <nil>
0 cannot Sqrt negative number: -2
7.7 Reader
package main
import (
"strings"
"golang.org/x/tour/reader"
)
type MyReader struct{}
func (MyReader) Read(b []byte) (int, error) {
r := strings.NewReader("A")
n, err := r.Read(b)
return n, err
}
func main() {
reader.Validate(MyReader{})
}
7.8 rot13Reader
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func (self rot13Reader) Read(buf []byte) (int, error) {
length, err := self.r.Read(buf)
if err != nil {
return length, err
}
for i := 0; i < length; i++ {
v := buf[i]
switch {
case 'a' <= v && v <= 'm':
fallthrough
case 'A' <= v && v <= 'M':
buf[i] = v + 13
case 'n' <= v && v <= 'z':
fallthrough
case 'N' <= v && v <= 'Z':
buf[i] = v - 13
}
}
return length, nil
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
------
You cracked the code!
7.9 图像
package main
import (
"image"
"image/color"
"golang.org/x/tour/pic"
)
type Image struct {
w int
h int
}
func (self Image) ColorModel() color.Model {
return color.RGBAModel
}
func (self Image) Bounds() image.Rectangle {
return image.Rect(0, 0, self.w, self.h)
}
func (self Image) At(x, y int) color.Color {
r := (uint8)((float64)(x) / (float64)(self.w) * 255.0)
g := (uint8)((float64)(y) / (float64)(self.h) * 255.0)
b := (uint8)((float64)(x*y) / (float64)(self.w*self.h) * 255.0)
return color.RGBA{r, g, b, 255}
}
func main() {
m := Image{255, 255}
pic.ShowImage(m)
}
7.10 等价二叉查找树
package main
import (
"fmt"
"golang.org/x/tour/tree"
)
// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
if t == nil {
return
}
Walk(t.Left, ch)
ch <- t.Value
Walk(t.Right, ch)
}
// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
ch1 := make(chan int)
ch2 := make(chan int)
go Walk(t1, ch1)
go Walk(t2, ch2)
for i := 0; i < 10; i++ {
x, y := <-ch1, <-ch2
fmt.Println(x, y)
if x != y {
return false
}
}
return true
}
func main() {
fmt.Println(Same(tree.New(1), tree.New(1)))
}
------
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
true
7.11 Web 爬虫
package main
import (
"fmt"
"sync"
)
type Fetcher interface {
// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
Fetch(url string) (body string, urls []string, err error)
}
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher, crawled Crawled, out chan string, end chan bool) {
// TODO: 并行的抓取 URL。
// TODO: 不重复抓取页面。
// 下面并没有实现上面两种情况:
if depth <= 0 {
end <- true
return
}
crawled.mux.Lock()
if _, ok := crawled.crawled[url]; ok {
crawled.mux.Unlock()
end <- true
return
}
crawled.crawled[url] = 1
crawled.mux.Unlock()
_, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
end <- true
return
}
out <- url
//fmt.Println("found: ", url, body)
for _, u := range urls {
go Crawl(u, depth-1, fetcher, crawled, out, end)
}
for i := 0; i < len(urls); i++ {
<-end
}
end <- true
return
}
type Crawled struct {
crawled map[string]int
mux sync.Mutex
}
func main() {
crawled := Crawled{make(map[string]int), sync.Mutex{}}
out := make(chan string)
end := make(chan bool)
go Crawl("http://golang.org/", 4, fetcher, crawled, out, end)
for {
select {
case url := <-out:
fmt.Println("found: ", url)
case <-end:
return
}
}
}
// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
body string
urls []string
}
func (f fakeFetcher) Fetch(url string) (string, []string, error) {
if res, ok := f[url]; ok {
return res.body, res.urls, nil
}
return "", nil, fmt.Errorf("not found: %s", url)
}
// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
"http://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"http://golang.org/pkg/",
"http://golang.org/cmd/",
},
},
"http://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"http://golang.org/",
"http://golang.org/cmd/",
"http://golang.org/pkg/fmt/",
"http://golang.org/pkg/os/",
},
},
"http://golang.org/pkg/fmt/": &fakeResult{
"Package fmt",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
"http://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
}
------
found: http://golang.org/
found: http://golang.org/pkg/
found: http://golang.org/pkg/os/
found: http://golang.org/pkg/fmt/
not found: http://golang.org/cmd/
8. 参考文章
官方文档
- Go Tour | Golang.org
- Go Wiki | Github
- 实效 Go 编程 | Golang.org
- Effective Go | Golang.org
- Package builtin - 内建函数 | Golang.org
- Packages - 标准库 | Golang.org
- GoDoc | Search for Go Packages
Go Blog
Go Tour 题解
Go 标准库
推荐一个知乎专栏作者:谢伟,知乎专栏『Gopher』- Go 上手指南
- Go 内置库第一季:strings - 谢伟 | 知乎
- Go 内置库第一季:strconv - 谢伟 | 知乎
- Go 内置库第一季:reflect - 谢伟 | 知乎
- Go 内置库第一季:json - 谢伟 | 知乎
- Go 内置库第一季:error - 谢伟 | 知乎
- Go 内置库第一季:time - 谢伟 | 知乎
- Go 内置库第一季:net/url - 谢伟 | 知乎
- 请教:FieldsFunc 函数的用法 | Golang 中国
- 【Go语言】基本类型排序和 slice 排序 | iTimeTraveler
其他不错的文章:
- Go Web 教程 - 谢伟 | 知乎
- Go GraphQL 教程 - 谢伟 | 知乎
- Go 与 Error 的前世今生 - 谢伟 | 知乎
- 自己构建节假日 API - 谢伟 | 知乎
- 打造一款 emoji 表情库 - 谢伟 | 知乎
51CTO 上的一个系列教程
- Go 语言开发学习教程 - 天山老妖S | 51CTO
- Go 语言常用标准库一 - 天山老妖S | 51CTO
- Go 语言常用标准库二 - 天山老妖S | 51CTO
- Go 语言常用标准库三 - 天山老妖S | 51CTO
- Go 语言常用标准库四 - 天山老妖S | 51CTO
- Go 语言常用标准库五 - 天山老妖S | 51CTO
- Go 语言常用标准库六 - 天山老妖S | 51CTO
- Go 语言 MySQL 数据库操作 - 天山老妖S | 51CTO
- Go 语言 database/sql 接口 - 天山老妖S | 51CTO
设计模式
演讲 PPT
- Go: a simple programming environment - Andrew Gerrand | Google
- Go Concurrency Patterns - Rob Pike | Google
Web 编程
- build-web-application-with-golang - astaxie | Github
- Writing Web Applications | golang.org
- Beego Framework
- Build a Single-Page App with Go and Vue | okta
- Go Vue Template - tdewolff | Github