为了去看Biscuit内核,学习一下Go语言,看起来简易教程挺短的。
fmt.Println格式化输出,fmt.Sprint格式化字符串strings.Join([]string, " ")strings.Fields(string) []string,返回被空格隔开的若干不含空格子串名字开头大写表示被导出,才能在包外使用。
函数参数类型在变量名之后。
1
func add(x, y int) int { return x + y }
多个相同类型参数
x int, y int-> x, y int函数可返回一个元组如
a, b := swap("hello", "world")函数返回值可被命名,如返回值为
(x, y int)只需在函数内分别给x,y赋值即可,return无参数var用来声明一个变量列表,var c, python bool,可以出现在包或函数级别变量初始化
var i, j int = 1, 2,可以自动类型推导,此时变量列表类型不必一致在函数内可以直接使用
k := 3代替变量声明,但函数外每条语句必须以关键词开头,所以函数外只能用var声明变量Go的基本类型有
bool,string,int(uint),int8,int16,int32,int64,byte=uint8,rune=int32,float32,float64,complex64,complex128int,uint,uintptr在32位系统上为32位,在64位系统上为64位在
Printf中,%T占位符表示变量类型字符串,%v表示变量值字符串零初始化:数值类型0,bool类型false,字符串空串
类型转化
T(v)将值v转换为类型T,没有隐式类型转换,因此int+float32会报错在变量初始化与赋值中支持自动类型推导
使用
const声明常量,常量不支持赋值运算符Go会在编译期一定程度上支持整数溢出直接报错
Go只有for循环
1
2
3
4sum := 0
for i := 0; i < 10; i++ {
sum += i
}
初始化语句若声明变量,则只在循环体中可见。
只有条件判断语句,可以省略前后两个分号实现while功能。
无限循环for{}
if在判断之前也可以加上一个赋值型变量声明语句如if i := 0; ... {},声明的变量在if-else块中均有效
switch运行第一个值等于表达式的case语句,switch后的变量之前可以插入变量声明,相比C语言,每个case后面会自动加break,最后有一个default,同时case后面的值不必为整数常量
switch后面不加比较变量,而在每个case后面接bool表达式,可以用来实现更为通用的if-else链情形。
deter语句会将函数推迟到外层函数返回后执行,推迟调用的函数参数立即求值,但外层函数返回后才会被调用;实现时将声明的函数压入defer栈,因此会按照声明顺序从后往前进行调用
*T表示指向T类型的指针,空指针nil,&,*语义同C语言Go不支持指针运算
结构体声明与输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
fmt.Println(v)
fmt.Println(v.X)
p := &v
p.X = 10 // shortcut of (*p).X = 10
fmt.Println(v)
v := Vertex{X: 5} // Y = 0
fmt.Println(v)
}
数组
[n]T表示拥有n个T类型值的数组。初始化
primes := [6]int{2,3,5,7,11,13},数组长度必须为常数数组切片
var s []int = primes[1: 4],左闭右开,切片是数组的引用而非复制,len(s)表示切片长度,cap(s)表示切片开头到源数组结尾长度,切片有零值nil在
cap(s)>len(s)时,可以对切片进行拓展,…使用
make动态分配数组并返回这个数组的切片,如a:=make([]int,0,5)参数分别表示len(a),cap(a),如果只有一个参数则表示len(a)=cap(a)。高维数组
1
2
3
4
5
6board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
board[1][2]...
可以使用func append(s []T, vs ...T) []T向切片末尾追加元素并返回新切片,如果cap不够,即源数组太小,则内存会被拓展。(一个能在中间插入的vector)
可以使用for i, v := range pow {...}遍历切片。下标或者值可以使用_来代替表示忽略。
映射
map[T1]T2将T1类型的值映射到T2,初值为nil。要使用make(map[T1]T2)初始化并返回此映射可以使用类数组访问或者结构体初始化映射
1
2
3
4
5
6
7
8
9
10
11
12type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
插入/修改元素m[key] = elem,获取元素elem = m[key],删除元素delete(m, key),通过双赋值检测某个键是否存在elem, ok = m[key],ok表示键key是否在映射m中,为true,false
闭包…
为结构体定义方法,在函数声明中加入接收者参数
1
2
3func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
接受者的类型定义与方法声明必须在同一个包内,不能为内建类型声明方法,但可以使用type给内建类型另一个名字。
可以为指针接收者*T声明方法,如C语言一样,这样才能对访问接收者内存进行修改。
如果同时实现了接收者为T,*T的两个相同方法m,则T.m()将会被重定向到(&T).m()调用接收者为*T的方法。如果接收者为*T的方法未实现,则*T.m()会被重定向到(*T).m()。二者不应该混用,而指针接收者版本由于无需复制、能够修改通常更好。
接口,一组方法签名定义的集合。
1
2
3
4
5
6
7
8type Abser interface {
Abs() float64
}
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // 需要接收者MyFloat实现接口Abser内所有函数
a = &v // 需要接收者*Vertex实现接口Abser内所有函数
我们无须显式声明某结构体实现了某接口,只需对应接收者实现接口内所有函数,因此没有implements关键字。
接口值在内部看成一个(value, type)的元组。即具体值,具体类型。
具体值为nil的底层方法依然会被调用,因此在实现*T接收者的方法时要注意判空指针。但具体值为nil的接口值自身不为nil。
空接口interface{}可保存任何类型的值,被用来处理未知类型的值。
类型断言t := i. (T)断言接口值i保存了具体类型T,并将类型为T的具体值赋予变量t。如果没有保存,就会运行时错误。因此,类比映射,最好使用t, ok := i. (T)。
类型选择参考代码如下
1 | func do(i interface{}) { |
实现Stringer接口type Stringer interface{ String() string }
可以将自身转为字符串使用Println/Printf输出。
使用error值表示错误状态type error interface { Error() string }
通常函数会返回error值,我们要:
if err != nil { fmt.Printf("%v", err) } else {...}
Go程(goroutine)
go f(x, y, z)启动一个新的Go程并执行f(x, y, z)信道使用
ch := make(chan T)创建可以使用
ch <- v将值v送入信道ch,可以使用v := <-ch从信道ch接收值并赋予v。信道可以有缓冲区,当缓冲区满时,向信道发送数据的Go程被阻塞;当缓冲区空时,从信道接收数据的Go程被阻塞。
发送者可以通过
close(c)关闭信道,对于接收者而言,v, ok := <-c可以判断信道是否被关闭。接收者可以用for i := range c不断从信道接受数据直到信道被关闭,而发送者关闭信道也主要是为了接收者可以退出接收循环。select语句使得一个Go程可以等待多个通信操作,会被阻塞到某个分支可以继续运行。