本文是学习 A Tour of Go (中文参考 Go 之旅中文 ) 整理的笔记,介绍Go 语言方法,接口,类型的基本概念和使用。
1. 方法
$GOPATH/src/go_note/gotour/methods/method/method.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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
/** * go 语言 方法 */ package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } /** * 方法名: Abs_method * 方法接收者: Vertex */ func (v Vertex) Abs_method() 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 Abs_function(v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } // 为非结构体声明方法 type MyFloat float64 func (f MyFloat) Abs_myfloat() float64 { if f < 0 { return float64(-f) } return float64(f) } func main() { v := Vertex{3, 4} v.Scale(10) // 此时 v.Scale(10) 会隐式转为 (&v).Scale(10) fmt.Println(v.Abs_method()) fmt.Println(Abs_function(v)) // 方法只是个带接收者参数的函数 f := MyFloat(-2) fmt.Println(f.Abs_myfloat()) } |
Go 没有类。不过你可以为结构体类型定义方法。
方法是一类带特殊的 接收者
参数的函数,方法接收者位于方法 func
关键字和方法名之间。
1.1 非结构体类型声明方法
只能为在同一包内定义的类型添加方法, 而不能为其它包内定义的类型(包括 int
之类的内建类型)的接收者声明方法。即接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。
1 2 3 4 5 6 7 8 |
type MyFloat float64 func (f MyFloat) Abs_myfloat () float64 { if f < 0 { return float64(-f) } return float64(f) } |
1.2 指针接收者
为指针接收者声明方法:对于某类型 T
,指针接收者的类型可以用 *T
表示。(T
不能是像 *int
这样的指针。)
指针接收者的方法可以修改接收者指向的值。 由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。若使用值接收者,方法只会对原始值的副本进行操作。
1.3 方法与指针重定向(隐式转换)
以指针为接收者的方法被调用时,接收者既能为值又能为指针:
1 2 3 4 5 |
func (v *Vertex) Scale(f float64) {} v.Scale(5) (&v).Scale(5) |
由于 Scale
方法有一个指针接收者,为方便起见,Go 会将语句 v.Scale(5)
解释为 (&v).Scale(5)
。
而以值为接收者的方法被调用时,接收者既能为值又能为指针:
1 2 3 4 5 |
var v Vertex fmt.Println(v.Abs()) // OK p := &v fmt.Println(p.Abs()) // OK |
这种情况下,方法调用 p.Abs()
会被解释为 (*p).Abs()
。函数必须接受与定义相同的类型,不会隐式转换
1.4 选择值或指针作为接收者
使用指针接收者的原因有二:
- 方法能够修改接收者指向的值。
- 避免在每次调用方法时复制该值,若值的类型为大型结构体时,这样做会更加高效。
通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。
2. 接口
$GOPATH/src/go_note/gotour/methods/interface/interface.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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
/** * go 接口 */ package main import ( "fmt" "math" ) // 定义接口 type Abser interface { Abs() float64 } func main() { // 使用接口 var a Abser f := MyFloat(-math.Sqrt2) a = f fmt.Println(a.Abs()) v := Vertex{3, 4} a = &v fmt.Println(a.Abs()) var i I var t *T i = t i.M() i = &T{"hello"} i.M() // 空接口 var inter_empty interface{} inter_empty = 42 fmt.Printf("%v, %T\n", inter_empty, inter_empty) inter_empty = "hello" fmt.Printf("%v, %T\n", inter_empty, inter_empty) // 类型断言 var j interface{} = "hello" s := j.(string) fmt.Println(s) s, ok := j.(string) fmt.Println(s, ok) inter_float, ok := j.(float64) fmt.Println(inter_float, ok) // 类型选择 do(21) do("hello") do(true) } 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) } type I interface { M() } type T struct { S string } func (t *T) M() { if t == nil { fmt.Println("<nil>") return } fmt.Println(t.S) } |
接口类型
是由一组方法签名定义的集合, 接口类型的值可以保存任何实现了这些方法的值。
类型通过实现一个接口的所有方法来实现该接口, 既然无需专门显式声明,也就没有“implements“关键字。隐式接口将接口的实现与定义解耦,这样接口的实现可以出现在任何包中,无需提前定义。
2.1 接口值
在内部,接口值可以看做包含值和具体类型的元组:
1 2 |
(value, type) |
接口值保存了一个具体底层类型的具体值,接口值调用方法时会调用具体类型的的同名方法。
2.2 底层值为 nil 的接口值
即便接口内的具体值为 nil
,方法仍然会被 nil
接收者调用。保存了 nil
具体值的接口其自身并不为 nil
。
但是接口值nil
时,由于此时接口值既不保存值也不保存具体类型,调用方法会产生运行时错误,因为接口的元组内并未包含能够指明该调用哪个具体类型的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func main() { var i I i.M() // panic: runtime error var t *T i = t i.M() // <nil> } type I interface { M() } type T struct { S string } func (t *T) M() { if t == nil { fmt.Println("<nil>") return } fmt.Println(t.S) } |
2.3 空接口
指定了零个方法的接口值被称为空接口:
1 2 |
interface{} |
因为每个类型都至少实现了零个方法,空接口可保存任何类型的值。
2.4 类型断言
类型断言提供了访问接口值底层具体值的方式。
1 2 |
t := i.(T) |
该语句断言接口值 i
保存了具体类型 T
,并将其底层类型为 T
的值赋予变量 t
。如果 i
并未保存 T
类型的值,该语句就会触发一个错误。
为了判断一个接口值是否保存了一个特定的类型, 类型断言可返回两个值:其底层值和判断断言是否成功的布尔值。
1 2 |
t, ok := i.(T) |
若 i
保存了一个 T
,那么 t
将会是其底层值,而 ok
为 true 。否则, ok 将为 false
而 t
将为 T
类型的零值,程序并不会产生错误。
2.5 类型选择
类型选择是一种按顺序从几个类型断言中选择分支的结构。
类型选择与一般的 switch
语句相似,不过类型选择中的 case
为类型(而非值),它们针对给定接口值所存储值的类型进行比较
1 2 3 4 5 6 7 8 9 |
switch v := i.(type) { case T: // v 的类型为 T case S: // v 的类型为 S default: // 没有匹配,v 与 i 的类型相同 } |
类型选择中的声明与类型断言 i.(T)
的语法相同,只是具体类型 T
被替换成了关键字 type
。
此选择语句判断接口值 i
保存的值类型是 T
还是 S
。 在 T
或 S
的情况下,变量 v
会分别按 T
或 S
类型取保存在 i
中的值。在默认(没有匹配)的情况下,变量 v
与 i
的接口类型和值相同。
3. Stringer
$GOPATH/src/go_note/gotour/methods/stringer/stringer.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 |
/** * go String */ package main import ( "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{"Author", 42} z := Person{"Modifier", 1989} fmt.Println(a, z) } |
fmt
包中定义的 Stringer
是最普遍的接口之一。
1 2 3 4 |
type Stringer interface { String() string } |
Stringer
是一个可以用字符串描述自己的类型。fmt
包(还有很多包)都通过此接口来打印值。
4. 错误
$GOPATH/src/go_note/gotour/methods/error/error.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 |
/** * go语言 error */ package main import ( "fmt" "time" ) type MyError struct { When time.Time What string } func (e *MyError) Error() string { return fmt.Sprintf("at %v, %s", e.When, e.What) } func run() error { return &MyError{ time.Now(), "it didn't work", } } func main() { if err := run(); err != nil { fmt.Println(err) } } |
Go 程序使用 error
值来表示错误状态。与 fmt.Stringer
类似, error
类型是一个内建接口:
1 2 3 4 |
type error interface { Error() string } |
通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil 来进行错误处理。
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) |
error
为 nil
时表示成功;非 nil
的 error
表示失败。
5. Reader
$GOPATH/src/go_note/gotour/methods/reader/reader.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 |
/** * go read */ package main import ( "fmt" "io" "strings" ) func main() { r := strings.NewReader("Hello, world!") b := make([]byte, 4) 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 } } } |
Reader
接口表示读取数据。Go 标准库包含了该接口的许多实现,包括文件、网络连接、压缩和加密等等,示例代码是字符串的实现。
Reader
接口有一个 Read
方法:
1 2 |
func (r *Reader) Read(b []byte) (n int, err error) |
Read
用数据填充给定的字节切片并返回填充的字节数和错误值。 在遇到数据的结尾时,它会返回一个 io.EOF
错误。
参考
可以关注我的微博了解更多信息: @刚刚小码农
有疑问加站长微信联系(非本文作者)