基础
导入包
1
2
3
4
5
6
7import "fmt"
import "math"
//圆括号“打包”导入
import (
"fmt"
"math/rand"
)函数
1
2
3func add(x int, y int) int { return x + y }
func add(x, y int) int {}
func swap(x, y string) (string, string) { return y, x } //多值返回裸返回,仅有
return
,返回各个返回变量的当前值。变量
1
2
3
4
5
6
7var a, b bool
var i, j int = 1, 2
k := 3 //简洁赋值,不能用于函数外
var (
m int = 6
n int = 7
)变量没有明确初始化时会赋值为零值,
0
,false
,""
。基本类型
1
2
3bool string [u]int[8-64] float[32,64] complex[64,128]
byte //uint8 的别名
rune //int32 的别名,代表一个 Unicode 码类型转换
T(v)
将值v
转换为类型T
的。需要显式转换。类型推导
定义了变量却不显式指定类型时,类型由右侧值推导得出。若右侧值有类型,则与右侧值类型相同。若式未指明类型的数字,还与常量的精度有关。
常量
常量使用关键字
const
,不能使用:=
语法定义。
控制结构
循环
Go 只有一种循环结构,
for
循环。1
2
3
4
5
6
7
8for i:=0; i < 10; i++ {
sum += i
}
// 初始化语句和后置语句可选
sum := 1
for ; sum < 1000; {
sum += sum
}由于分号可省略,替代
while
的作用:1
2
3
4
5
6
7sum := 1
for sum < 1000 {
sum += sum
}
// 死循环
for {
}条件
1
2
3
4
5
6
7
8
9if x < 0 {
return y
}
if v := math.Pow(x, n); v < lim {
return v
} else {
blahblah
}
// 便捷语句定义变量有效范围Switch
1
2
3
4
5
6switch fruit := "apple"; fruit {
case "banana":
do-sth
default:
do-sth
}没有条件的 switch 等同于
switch true
。defer
延迟函数的执行直到上层函数返回。参数会立刻生成,但是上层函数返回前不会被调用。
1
2
3
4
5func main() {
defer fmt.Println("world")
fmt.Println("hello")
}延迟的函数调用压入栈中,后进先出调用。
复杂类型
指针
指针保存了变量的内存地址。类型
*T
是指向类型T
的值的指针。零值是nil
。1
2
3
4
5var p *int
i := 42
p = &i
fmt.Println(*p)
*p = 21间接引用。Go 没有指针运算。
结构体
一个结构体(
struct
)就是一个字段的集合。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15type Vertex struct {
X int
Y int
}
...
fmt.Println(Vertex{1, 2})
// 结构体访问
v := Vertex{1, 2}
v.X = 2
fmt.Println(v.Y)
// 通过结构体指针访问
p := &v
p.X = 3结构体文法:通过结构体字段的值作为列表来新分配一个结构体。
Name:
仅列出部分字段(顺序无关)。&
前缀返回指向结构体的指针。1
2
3
4
5
6var (
v1 = Vertex{1, 2} // {1, 2}
v2 = Vertex{X: 1} // {1, 0}
v3 = Vertex{} // {0, 0}
p = &Vertex{1, 2} // &{1, 2}
)数组
[n]T
n 个类型T
的值的数组。1
2var a [10]int
slice
[]T
是一个元素类型为T
的 slice。len(s)
返回 s 的长度。零值是nil
。1
s := []int{2, 3, 4}
slice 可以包含任意的类型,包括 slice。可重新切片。
1
2
3
4// 使用 make 构造 slice,len(a)=5
a := make([]int, 5)
// 参数指定容量,长度 0,容量 5
b := make([]int, 0, 5)append
向末尾添加元素,若超出原大小,分配一个更大的数组:1
2append(a, 0, 1, 2)
切片操作并不会复制底层的数组,整个数组将被保存在内存中,直到它不再被引用。
range
1
2
3var pow = []int{1, 3, 5, 7}
for i, v := range pow {
blahblah // i 下标序号 v 对应元素的一个拷贝可通过赋值给
_
来忽略值。map
map 映射键值。使用前需要
make
创建;值为nil
的 map 是空的,并且不能对其赋值。1
2
3var m map[string]int
m = make(map[string]int)
m["apple"] = 2map 文法与结构体文法相似,但必须有键名。
1
2
3
4
5
6
7
8
9
10
11
12
13type Info struct {
num, price int
}
var n = map[string]Info{
"banana": Info{1, 2},
"pear": Info{3, 4},
}
// 顶级类型只是一个类型名则可以省略
var q = map[string]Info{
"banana": {1, 2},
"pear": {3, 4},
}修改 map
1
2
3
4m[key] = elem
elem = m[key]
delete(m, key)
elem, ok = m[key] // true or false读取不存在的键,结果是元素类型的零值。
函数值
函数也是值,可以传递,如作为函数参数或返回值。
1
2
3
4
5
6
7
8
9
10
11func compute(fn func(int, int) int) int {
return fn(1, 2)
}
func main() {
add := func(x, y int) int {
return x + y
}
fmt.Println(add(5, 12))
fmt.Println(compute(add))
}函数的闭包
Go 函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// fibonacci 函数会返回一个返回 int 的函数。
func fibonacci() func() int {
first := 0
second :=1
fib := 0
return func() int {
fib = first + second
first, second = second, first
first = fib
return fib
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
方法和接口
方法
Go 没有类,但仍可以在结构体类型上定义方法。 方法接收者 在
func
关键字和方法名之间的参数中。1
2
3
4
5
6
7
8
9
10type Fruit struct {
num, price int
}
func (m *Fruit) Total() int {
return m.num * m.price
}
...
m := &Fruit{2, 5}
fmt.Println(m.Total())可以对包中的 任意 类型定义方法,不仅仅是针对结构体,但不能对来自其它包的类型或基础类型进行定义。方法可以与命名类型或命令类型的指针关联,不使用指针的话,方法调用的是拷贝值。
接口
接口类型是一组方法定义的集合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
a = f
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}fmt
中的Stringer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15type IPAddr [4]byte
func (addr IPAddr) String() string {
return fmt.Sprintf("\"%v.%v.%v.%v\"", addr[0], addr[1], addr[2], addr[3])
}
func main() {
addrs := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for n, a := range addrs {
fmt.Printf("%v: %v\n", n, a)
}
}错误
error
类型是一个内建接口:1
2
3type error interface {
Error() string
}
并发
goroutine
goroutine 是由 Go 运行时环境管理的轻量级线程。
1
2
3
4func main() {
go hello("hey")
hello("hey")
}函数和参数均为当前 goroutine 中定义的,但在新的 goroutine 中运行函数。
channel
channel 是有类型的管道,可以用
<-
操作符对其发送或接收值。箭头就是数据流的方向。channel 使用前必须创建。1
2
3ch := make(chan int)
ch <- v //将 v 传入 channel ch
v := <- ch // 从 ch 接收并赋值给 v缓冲 channel
通过为
make
提供第二个参数作为缓冲长度来初始化 channel,向其发送数据时,只有缓冲区满的时候才会阻塞。缓冲区为空的时候接收操作会阻塞。close(ch)
可以关闭一个 channel 来表示不会再有值被发送了。通过v, ok := <- ch
的测试,channel 被关闭的话ok
会被设置为false
。for i := range ch
会不断从 channel 接收值,直到它被关闭。channel 与文件不同,通常情况下无需关闭它们。只有在需要告诉接收者没有更多数据的时候才有必要关闭,比如中断一个
range
。select
select
语句使得一个 goroutine 在多个通讯操作上等待。阻塞到条件分支中的某个可以继续执行,有多个可以执行的时候,随机选择一个。其它条件分支都没有准备好的时候,default
分支会被执行。sync.Mutex
互斥锁,
sync.Mutex
类型,方法:Lock
Unlock
。可以用defer
语句来保证互斥锁一定会被解锁。