文章目录
  1. 1. 高级特性
    1. 1.1. 接口与多态
      1. 1.1.1. 作为接口
      2. 1.1.2. 作为类型
      3. 1.1.3. 接口与指针
      4. 1.1.4. 接口检查
      5. 1.1.5. Embedding
    2. 1.2. defer
    3. 1.3. goroutine
    4. 1.4. Channel
      1. 1.4.1. 基本用法
      2. 1.4.2. range&close
      3. 1.4.3. select
      4. 1.4.4. Timer
    5. 1.5. 异常处理
      1. 1.5.1. Panic
      2. 1.5.2. Recover
  2. 2. Reference

这个坑好像坑了很久,终于填好了……

高级特性

接口与多态

这里有一篇不错的文章How to use interfaces in Go
go的接口有点Trait的意思,并且不需要显式实现,这点不是很爽,虽然结果上没啥区别,但是总觉得看到定义时知道它实现了哪些接口比较方便。实际上interface除了是一组方法外,还是一种类型。

作为接口

一个简单的接口的例子:

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
35
36
37
38
39
40
41
package main
import "fmt"
type Animal interface {
Speak() string
}
type Dog struct {
}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct {
}
func (c Cat) Speak() string {
return "Meow!"
}
type Llama struct {
}
func (l Llama) Speak() string {
return "?????"
}
type JavaProgrammer struct {
}
func (j JavaProgrammer) Speak() string {
return "Design patterns!"
}
func main() {
animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}

作为类型

interface{}作为类型时可以看做是一个实现了0个方法的匿名接口,任何类型都至少实现了0个方法,因此任何类型都可以实现了interface{}接口,因为Go实现一个接口不需要显式实现,只要满足了接口的定义,就等于实现了这个接口,这里和c中的void *有点像。前文提到的那篇文章中有一个有趣的错例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//注意,这是一个错误的例子!!!
package main
import (
"fmt"
)
func PrintAll(vals []interface{}) {
for _, val := range vals {
fmt.Println(val)
}
}
func main() {
names := []string{"stanley", "david", "oscar"}
PrintAll(names)
}

这里并不能将[]T转化成[]interface{},除非显式的进行转换。这里我的理解是[]T本身是一个类型,而[]interface{}则是“一个组类型”的类型,因此二者显然不是同一个类型,不能进行传递。

接口与指针

Go在定义接口时是没有指定到底是值还是指针作为receiver的,这里怎么定义都行,但是使用时需要注意,如果是指针作为receiver,当使用值作为该接口的实例调用会出现编译错,而相反的,使用值作为receiver,使用指针作为接口实例调用没有问题。这里主要是因为一个指针同一时间只能指向同一固定的对象实例,而一个对象实例同一时间可能有很多指针存在。

接口检查

因为Go不要求显式实现接口,因此有些时候需要对对象是否实现接口进行判断。当需要做runtime接口时,一般的:

1
2
3
if _, ok := val.(json.Marshaler); ok {
//do something
}

但运行时接口检查比较麻烦,假如我们知道某类型需要实现某接口时,可以定义:

1
var _ json.Marshaler = (*RawMessage)(nil)

这样,可以在编译时确保*RawMessage实现了json.Marshaler接口。

Embedding

Go没啥继承的概念,所以发明了一个“嵌入”的概念,例如:

1
2
3
4
5
6
7
8
9
10
11
12
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}

实现ReadWriter就等于实现了ReaderWriter。这里需要主要接口里只能embedding接口。类似的,struct也可以embedding:

1
2
3
4
5
6
// ReadWriter stores pointers to a Reader and a Writer.
// It implements io.ReadWriter.
type ReadWriter struct {
*Reader // *bufio.Reader
*Writer // *bufio.Writer
}

注意这里不用写成员名,不写它是一个嵌入,写了就是一个成员了,嵌入用在struct里有点多重继承的味道。在struct的方法中可以直接以struct名调用这个“成员”:

1
2
3
func (job *Job) Logf(format string, args ...interface{}) {
job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}

defer

defer就是在当前区块生命周期结束前执行某函数,这点在关闭资源方面比较实用。defer相当于一个栈,每次调用会把方法推到栈里,执行defer时也是按出栈顺序(LIFO)。defer比较方便的是,可以在打开的时候写好defer,后面退出时就不需要再写了。c/c++里要实现类似的只写一处回收资源代码时,还得用上goto。一个defer的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close will run when we're finished.
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...) // append is discussed later.
if err != nil {
if err == io.EOF {
break
}
return "", err // f will be closed if we return here.
}
}
return string(result), nil // f will be closed if we return here.
}

goroutine

Go的并发模型是协程的,因此goroutine相当于轻量级线程,用法上和线程大同小异,只是协程间切换成本更低。Go中创建一个goroutine也非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import (
"fmt"
"time"
)
func main() {
//这里创建一个匿名函数非常方便
go func (s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}("world")
say("hello")
}

如果需要共享变量,还是需要同步的,可以使用sync包。

Channel

基本用法

channel是go中goroutine之间用来共享的一个FIFO队列,默认是阻塞的。存取数据均使用符号<-,这里存变量时为毛不用->,真是太不形象了。

1
2
3
4
5
6
//创建一个channel
ch := make(chan int)
//把v push到队列里
ch <- v
//取出来
v := <-ch

如果创建channel时指定了size,那么将创建一个buffered channel,buffered channel只有在channel满的时候存数据才会阻塞,而在空的时候读数据会阻塞。(好像废话一样)

1
2
//buffered channles
ch := make(chan int, 20)

range&close

channel的生产者可以显式close,这时消费者可以接收到这一状态,一般来说消费者不主动关闭channel:

1
v, ok := <-ch

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
}
}

select

select和linux的select类似,相当于同时监听多个channel,一直阻塞直到某个case分句可以执行,当多个case分句可以执行时,随机选择一个,一个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
}

select可以加上default分句,以实现非阻塞,当没有case分句满足时执行default分句。

Timer

很多异步框架里Timer是个很重要的组件,Go里也提供了Timer,它是一个特殊的channel,有两种方式设置Timer/Ticker, 他们在使用上有一些差别:

1
2
3
4
5
6
7
8
9
10
11
// 直接创造一个channel
timer1 = time.After(time.Second)
<-timer1
// 创造一个timer对象,它的C成员是我们需要的channel
timer2 = time.NewTimer(time.Second)
<-timer2.C
//同上
ticker1 = time.Tick(time.Second)
<-ticker1
ticker2 = time.NewTicker(time.Second)
<-ticker2.C

异常处理

自己定义一个异常比较简单,就是实现接口:

1
2
3
type error interface {
Error() string
}

这里上一个A Tour of Go的练习的例子说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import (
"fmt"
)
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)
}
return 0, nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}

注意,实现Error方法时float64(e)是必须的,否则就会导致无限循环,因为Sprintf会调用error接口的Error方法,而这里Error方法又调用Sprintf。

Panic

panic和linux kernel的panic意思差不多,就是出现fatal级错误,程序要挂了。和异常不一样,panic挂的非常彻底,所以一般的库中尽量少用panic,panic一般用在初始化阶段:

1
2
3
4
5
6
7
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}

Recover

recover一般用在defer函数中,当发生panic时可以捕获错误。一般的模块设计中可以用recover把panic和调用者隔离,一个recover的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Error is the type of a parse error; it satisfies the error interface.
type Error string
func (e Error) Error() string {
return string(e)
}
// error is a method of *Regexp that reports parsing errors by
// panicking with an Error.
func (regexp *Regexp) error(err string) {
panic(Error(err))
}
// Compile returns a parsed representation of the regular expression.
func Compile(str string) (regexp *Regexp, err error) {
regexp = new(Regexp)
// doParse will panic if there is a parse error.
defer func() {
if e := recover(); e != nil {
regexp = nil // Clear return value.
err = e.(Error) // Will re-panic if not a parse error.
}
}()
return regexp.doParse(str), nil
}

Reference

  1. Go 语言简介(上)— 语法
  2. Go 语言简介(下)— 特性
  3. A Tour of Go(FuckGFW)
  4. Go by Example
  5. Package Documentation(FuckGFW)
  6. Effective Go(FuckGFW)
  7. 学习Go语言
  8. The Go Programming Language Specification(FuckGFW)
文章目录
  1. 1. 高级特性
    1. 1.1. 接口与多态
      1. 1.1.1. 作为接口
      2. 1.1.2. 作为类型
      3. 1.1.3. 接口与指针
      4. 1.1.4. 接口检查
      5. 1.1.5. Embedding
    2. 1.2. defer
    3. 1.3. goroutine
    4. 1.4. Channel
      1. 1.4.1. 基本用法
      2. 1.4.2. range&close
      3. 1.4.3. select
      4. 1.4.4. Timer
    5. 1.5. 异常处理
      1. 1.5.1. Panic
      2. 1.5.2. Recover
  2. 2. Reference