这是 Go 语言之旅 的笔记 P3, 内容较长,包括了 Go 中的方法、接口、并发等内容.
方法
Go 没有类。不过你可以为结构体类型定义方法,Go 中方法是带特殊的 {接收者} 参数的函数(方法其实就是函数,只是带了个接收者)。
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 package mainimport ( "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()) } func Abs (v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main () { v := Vertex{3 , 4 } fmt.Println(Abs(v)) }
非结构体也是可以声明方法的,比如对于 float:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type MyFloat float64 func (f MyFloat) Abs () float64 { if f < 0 { return float64 (-f) } return float64 (f) } f := MyFloat(-1 ) f.Abs()
但是,你只能为在同一包内定义的类型的接收者声明方法,而不能为其它包内定义的类型(包括 int 之类的内建类型)的接收者声明方法。
上面的接收者为值接收者,实际上接收者还有一种叫做指针接收者,因为使用指针接收者,你可以在方法内部修改值,所以指针接收者比值接收者更常用。
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 func (v Vertex) Abs () float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale (f float64 ) { v.X = v.X * f v.Y = v.Y * f } func main () { v := Vertex{3 , 4 } v.Scale(10 )
方法和指针重定向; 在函数中,如果要求的参数是指针,传值会报错;如果使用指针接收者,则既可以传值,也可以传指针;反之如果要求的参数是值,在函数中传指针也会出错,指针接收者则也可以避免这种情况。
使用指针接收者的好处是:
指针能够修改传进来的值
因为传的是指针,避免了值的复制,更高效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func ScaleFunc (v *Vertex, f float64 ) { v.X = v.X * f v.Y = v.Y * f } func (v *Vertex) Scale (f float64 ) { v.X = v.X * f v.Y = v.Y * f }
接口
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 42 43 44 45 46 47 48 49 50 51 52 package mainimport ( "fmt" "math" ) type geometry interface { area() float64 perim() float64 } type rect struct { width, height float64 } type circle struct { radius float64 } func (r rect) area () float64 { return r.width * r.height } func (r rect) perim () float64 { return 2 *r.width + 2 *r.height } func (c circle) area () float64 { return math.Pi * c.radius * c.radius } func (c circle) perim () float64 { return 2 * math.Pi * c.radius } func measure (g geometry) { fmt.Println(g) fmt.Println(g.area()) fmt.Println(g.perim()) } func main () { r := rect{width: 3 , height: 4 } c := circle{radius: 5 } measure(r) measure(c) }
关于接口的另一个例子, 首先是完整的代码:
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 42 43 package mainimport ( "fmt" "math" ) type Abser interface { Abs() float64 } 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) } func main () { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3 , 4 } a = f a = &v a = v fmt.Println(a.Abs()) }
我们逐一来看,方便研究,首先是直接将变量赋值给接口:
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 package mainimport ( "fmt" "math" ) type Abser interface { Abs() float64 } type MyFloat float64 func (f MyFloat) Abs () float64 { if f < 0 { return float64 (-f) } return float64 (f) } func main () { var a Abser f := MyFloat(-math.Sqrt2) a = f fmt.Println(f.Abs()) fmt.Println(a.Abs()) }
下面是将结构体赋值给接口:
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 package mainimport ( "fmt" "math" ) type Abser interface { Abs() float64 } type Vertex struct { X, Y float64 } func (v *Vertex) Abs () float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main () { var a Abser v := Vertex{3 , 4 } a = &v fmt.Println(v.Abs()) fmt.Println(a.Abs()) }
它们不同的地方在于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 f := MyFloat(-math.Sqrt2) v := Vertex{3 , 4 } func (f MyFloat) Abs () float64 { } func (v *Vertex) Abs () float64 { } a = f a = &v a = v
接口的隐式实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" type I interface { M() } type T struct { S string } func (t T) M () { fmt.Println(t.S) } func main () { var i I = T{"hello" } i.M() }
接口也是值,所以可以像值一样传递。可以用作参数和函数的返回值。
接口值可以用作函数的参数或返回值。
在内部,接口值可以看做包含值和具体类型的元组 (value, type)
, 接口值保存了一个具体底层类型的具体值。接口值调用方法时会执行其底层类型的同名方法。
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 42 43 44 45 46 47 48 package mainimport ( "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 describe (i I) { fmt.Printf("(%v, %T)\n" , i, i) } func main () { var i I i = &T{"Hello" } describe(i) i.M() i = F(math.Pi) describe(i) i.M() }
底层值为 nil 的接口值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func main () { var i I var t *T i = t describe(i) i.M() i = &T{"hello" } describe(i) i.M() }
空接口, 它没有规定接口的方法,空接口可保存任何类型的值; 空接口被用来处理未知类型的值。例如,fmt.Print 可接受类型为 interface{} 的任意数量的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "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) }
关于类型断言,它提供了访问接口值底层值的具体方式,并可以进行检测断言:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "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 ) fmt.Println(f) }
类型选择,可以按顺序从接口值中选择值的类型,文法如下:
1 2 3 4 5 6 7 8 switch v := i.(type ) {case T: case S: default : }
看一个类型选择的例子:
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 package mainimport "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 ) }
下面介绍一个普遍使用的接口,即 fmt 包中的 Stringer。一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "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) fmt.Println(a.String()) }
关于 Go 中的错误处理:
1 2 3 4 5 6 7 i, err := strconv.Atoi("42" ) if err != nil { fmt.Printf("couldn't convert number: %v\n" , err) return } fmt.Println("Converted integer:" , i)
关于文件读取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "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 } } }
关于图像处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" "image" ) func main () { m := image.NewRGBA(image.Rect(0 , 0 , 100 , 100 )) fmt.Println(m.Bounds()) fmt.Println(m.At(0 , 0 ).RGBA()) }
并发
说到并发,首先介绍Go 中的 Go 程(goroutine),它是由 Go 运行时管理的轻量级线程。当你写 go f(x, y, z)
会启动一个新的 Go 程并执行 f(x, y, z)
。
在 Go 程的运行过程中,f, x, y 和 z 的求值发生在当前的 Go 程中,而 f 的执行发生在新的 Go 程中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "time" ) func say (s string ) { for i := 0 ; i < 5 ; i++ { time.Sleep(1000 * time.Millisecond) fmt.Println(s) } } func main () { go say("world" ) say("hello" ) }
Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。
关于信道,在 Go 中,实质上是带有类型的管道。
信道操作符有 ->
, <-
,箭头的方向预示着数据流的方向。
以下示例对切片中的数进行求和,将任务分配给两个 Go 程。一旦两个 Go 程完成了它们的计算,它就能算出最终的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func sum (s []int , c chan int ) { sum := 0 for _, v := range s { sum += v } c <- sum } 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 fmt.Println(x, y, x+y) }
关于带缓冲的信道, 使用 make 创建
1 ch := make (chan int , 100 )
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { ch := make (chan int , 2 ) ch <- 1 ch <- 2 fmt.Println(<-ch) fmt.Println(<-ch) }
对于使用信道来说,还有两个基本的操作,即关闭信道,或者是检查一个信道是否关闭了:
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 package mainimport ( "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) } }
关于 Go 总的 Select 语句:
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 mainimport ( "fmt" "time" ) func main () { c1 := make (chan string ) c2 := make (chan string ) go func () { time.Sleep(1 * time.Second) c1 <- "one" }() go func () { time.Sleep(2 * time.Second) c2 <- "two" }() for i := 0 ; i < 2 ; i++ { select { case msg1 := <-c1: fmt.Println("received" , msg1) case msg2 := <-c2: fmt.Println("received" , msg2) } } }
当 select 中的其它分支都没有准备好时,default 分支就会执行。 为了在尝试发送或者接收时不发生阻塞,可使用 default 分支:
1 2 3 4 5 6 select {case i := <-c: default : }
Go 中信道非常适合在不同的 Go 程中进行通信。但如果我们不需要通信呢?若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?
这个概念叫互斥,Go 中有一个专门的数据结构来提供这一功能,互斥锁 Mutex,sync.Mutex
.
sync.Mutex
中有两个方法,Lock 和 Ulock。
我们可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行
我们也可以用 defer 语句来保证互斥锁一定会被解锁
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 package mainimport ( "fmt" "sync" "time" ) type SafeCounter struct { v map [string ]int mux sync.Mutex } func (c *SafeCounter) Inc (key string ) { c.mux.Lock() c.v[key]++ c.mux.Unlock() } 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" )) }