based on a tour of go
method, interface, goroutine
method
go没有类,所有类相关的方法在func
和函数名中间描述。
1 | func (v Vertex) Abs() float64 { |
接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法.
对于某类型 T
,接收者的类型可以用 *T
的文法。(此外,T
不能是像 *int
这样的指针。)
注意传实参和传指针的区别。类似c,传指针才能在函数内修改接收者。
相比于带指针参数的函数,使用以指针作为接收者的方法有一个好处,既可以传值也可以传指针:
1 | func (v *Vertex) Scale(f float64) { |
同样的,对于以值作为参数的函数,传指针过去会报错,但以值作为接收者的函数既可以接值也可以接指针。(但这时函数内对值的修改只在函数内有效)
传指针的好处类似于c,在于省内存,避免复制,直接修改变量。
interface
接口
接口类型 是由一组方法签名定义的集合。接口类型的变量可以保存任何实现了这些方法的值。注意,这里如果只实现了指针类型的定义,值类型的定义并不能自动转换。
1 | type Abser interface { |
可以理解为(?)带有接收者的函数是相应的成员函数,接口中声明的函数即为类带有的成员函数,相当于对于接口实现了多态,对于实现函数的重载,而接口变量实际上可以是任一种类型,只要这种类型下实现了相应需要的所有函数。
为什么叫接口?实际上它提供了一个位置,让我们自己实现的类型能够服用已经有的逻辑,比如print,error,read等。
接口是值,但是即使接口接的是指针,在外部也不能直接*
,只能在相应方法中去用。在内部,它既存了值,也存了对应的类型,便于寻找相应的方法。
对于没有初始化或者本身就是nil
的接口值,如果有相应的方法,方法仍会被调用,因此需要在方法内部判断。
1 | var i I |
如果方法也没有(比如直接声明了一个接口就直接用了),在调用方法时会报错。
1 | type I interface { |
指定了零个方法的接口值被称为 空接口:
1 | var i interface{} |
空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)空接口被用来处理未知类型的值。例如,fmt.Print
可接受类型为 interface{}
的任意数量的参数.
类型断言
类型断言 type assertions 提供了访问接口值底层具体值的方式。类似于映射的判断方式
1 | t := i.(T) |
类型选择
类型选择 type switch 是一种按顺序从几个类型断言中选择分支的结构。类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类型(而非值), 它们针对给定接口值所存储的值的类型进行比较。
1 | switch v := i.(type) { |
Stringer
fmt
包中定义的 Stringer
是最普遍的接口之一。通过Stringer,所有类型都可以实现一个描述自己的字符串。
1 | type Stringer interface { |
error
Go 程序使用 error
值来表示错误状态。与 fmt.Stringer
类似,error
类型是一个内建接口:
1 | type error interface { |
io
io
包指定了 io.Reader
接口,它表示从数据流的末尾进行读取。io.Reader
接口有一个 Read
方法,Read
用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF
错误。
1 | func (T) Read(b []byte) (n int, err error) |
Goroutine
Go 程(goroutine)是由 Go 运行时管理的轻量级线程。命令go会启动一个新线程执行函数,函数和参数的求值在本线程执行。
1 | go f(x, y, z) |
信道(channel)是带有类型的管道,你可以通过它用信道操作符 <-
来发送或者接收值。信道在使用前必须创建:
1 | ch := make(chan int) |
默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。
信道可以是 带缓冲的。将缓冲长度作为第二个参数提供给 make
来初始化一个带缓冲的信道。仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
1 | ch := make(chan int, 100) |
发送者可通过 close
关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完
1 | v, ok := <-ch |
之后 ok
会被设置为 false
。循环 for i := range c
会不断从信道接收值,直到它被关闭。只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 range
循环。
select
语句使一个 Go 程可以等待多个通信操作。select
会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
1 | func fibonacci(c, quit chan int) { |
当 select
中的其它分支都没有准备好时,default
分支就会执行。为了在尝试发送或者接收时不发生阻塞,可使用 default
分支
1 | select { |
Go 标准库中提供了 sync.Mutex
互斥锁类型及其两个方法:
Lock
Unlock
我们可以通过在代码前调用 Lock
方法,在代码后调用 Unlock
方法来保证一段代码的互斥执行。参见 Inc
方法。我们也可以用 defer
语句来保证互斥锁一定会被解锁。参见 Value
方法。
1 | // Inc 增加给定 key 的计数器的值。 |