Go 语言学习:Go 语言之旅(四)

Methods(方法)

Go 语言没有类。然而,你可以在类型上定义方法

一个方法是一个带有特殊的接收者(receiver)参数的函数。

接收者参数在 func 关键字和方法名之间。

在示例代码中,Abs 方法有一个名为 v,类型为 Vertex 的接收者:

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
    
    
	X, Y float64
}

func (v Vertex) Abs() float64 {
    
     // 方法 Abs 有一个接收者参数,接收者参数在 func 关键字和方法名之间
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    
    
	v := Vertex{
    
    3, 4}
	fmt.Println(v.Abs())
}

输出:

5

Methods are functions(方法是函数)

记住:一个方法仅仅是一个带有一个接收者参数的函数。

在示例代码中,Abs 被写成一个一般的函数,它的功能没有改变:

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
    
    
	X, Y float64
}

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))
}

Methods continued(继续讲方法)

你也可以在一个非结构体类型上声明一个方法。

在示例代码中,我们看到一个数值类型 MyFloat ,它有 Abs 方法:

package main

import (
	"fmt"
	"math"
)

type MyFloat float64 // 定义一个新的数值类型 MyFloat

func (f MyFloat) Abs() float64 {
    
     // 接收者参数类型是 MyFloat
	if f < 0 {
    
    
		return float64(-f)
	}
	return float64(f)
}

func main() {
    
    
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}

输出:

1.4142135623730951

一个方法的接收者类型定义必须和方法声明在相同的包中。 你不能声明一个方法,它的接收者类型在另一个包中定义(包括 int 这些内置的类型)。

Pointer receivers(指针接收者)

你可以声明带有指针接收者的方法。

这意思是说接收者类型的字面语法是 *TT 是某个类型(T 不能是一个指针类型,比如 *int)。

示例代码中,Scale 方法的接收者类型是指针类型 *Vertex

带有指针接收者的方法可以修改接收者的值(就像 Scale 方法所做的)。由于方法经常需要修改接收者的值,因此指针接收者比值接收者更常见。

当将 Scale 方法改为值接收者后,方法修改的是值接收者的一个副本。程序会输出 5。

示例代码如下:

package main

import (
	"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 (v *Vertex) Scale(f float64) {
    
     // 将 *Vertex 改成 Vertex 后,程序会输出 5
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
    
    
	v := Vertex{
    
    3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())
}

输出:

50

Pointers and functions(指针与函数)

在示例代码中,我们看到 AbsScale 方法重新写成了函数:

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
    
    
	X, Y float64
}

func Abs(v Vertex) float64 {
    
    
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func Scale(v *Vertex, f float64) {
    
    
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
    
    
	v := Vertex{
    
    3, 4}
	Scale(&v, 10)
	fmt.Println(Abs(v))
}

输出:

50

当将 Scale 函数的参数类型改成 Vertex时,编译器报错:

./prog.go:23:8: cannot use &v (type *Vertex) as type Vertex in argument to Scale

Methods and pointer indirection(方法和指针间接引用)

比较前面两个示例程序,你可能注意到给函数的指针参数传参时必须是指针:

var v Vertex
ScaleFunc(v, 5)  // Compile error!
ScaleFunc(&v, 5) // OK

然而当调用带有指针接收者的方法时,值或者指针都可以作为接收者

var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK

对于语句 v.Scale(5),尽管 v 是一个值不是一个指针,带有指针接收者的方法会被自动地调用。因为Scale 方法有一个指针接收者,Go 会将 v.Scale(5) 语句解释成 (&v).Scale(5)

示例代码:

package main

import "fmt"

type Vertex struct {
    
    
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
    
     // 指针接收者
	v.X = v.X * f
	v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
    
    
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
    
    
	v := Vertex{
    
    3, 4}
	v.Scale(2) // 值作为接收者调用方法
	ScaleFunc(&v, 10) // 参数必须是指针

	p := &Vertex{
    
    4, 3}
	p.Scale(3) // 指针作为接收者调用方法
	ScaleFunc(p, 8) // 参数必须是指针

	fmt.Println(v, p)
}

输出:

{60 80} &{96 72}

Methods and pointer indirection (2)

相同的事发生在另一个方向。

给参数是值的函数传参时必须是值:

var v Vertex
fmt.Println(AbsFunc(v))  // OK
fmt.Println(AbsFunc(&v)) // Compile error!

然而当调用带有值接收者的方法时,值或者指针都可以作为接收者

var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK

在这个情形下,方法调用 p.Abs() 被解释成 (*p).Abs()

示例代码:

package main

import (
	"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 AbsFunc(v Vertex) float64 {
    
    
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    
    
	v := Vertex{
    
    3, 4}
	fmt.Println(v.Abs()) // 值作为接收者调用方法
	fmt.Println(AbsFunc(v)) //参数必须是值

	p := &Vertex{
    
    4, 3}
	fmt.Println(p.Abs()) // 指针作为接收者调用方法
	fmt.Println(AbsFunc(*p)) // 参数必须是值
}

输出:

5
5
5
5

Choosing a value or pointer receiver(选择一个值或指针接收者)

有两个理由使用一个指针接收者:

  • 方法可以修改指针指向的接收者的值。
  • 避免在每次方法调用时复制值。尤其是当接收者是一个大的结构体时。

在示例代码中,ScaleAbs 方法的接收者都是指针接收者 *Vertex,尽管 Abs 方法不需要改变接收者的值。

通常,在一个给定类型上的所有方法的接收者要么都是值接收者,要么都是指针接收者,不会两者混合。

示例代码:

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
    
    
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
    
     //指针接收者
	v.X = v.X * f
	v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
    
     //指针接收者
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    
    
	v := &Vertex{
    
    3, 4}
	fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
	v.Scale(5)
	fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

输出:

Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25

Interfaces(接口)

一个接口类型由一组方法签名来定义。

一个接口类型的值可以持【存】有任何实现了这些方法的值。

示例代码:

package main

import (
	"fmt"
	"math"
)

type Abser interface {
    
    
	Abs() float64
}

func main() {
    
    
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{
    
    3, 4}

	a = f  // a MyFloat implements Abser
	a = &v // a *Vertex implements Abser

	// In the following line, v is a Vertex (not *Vertex)
	// and does NOT implement Abser.
	a = v

	fmt.Println(a.Abs())
}

type MyFloat float64

// MyFloat 类型实现了 Abser 接口,因为它实现了接口的 Abs  方法
func (f MyFloat) Abs() float64 {
    
    
	if f < 0 {
    
    
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
    
    
	X, Y float64
}

// *Vertex 类型实现了 Abser 接口,因为它实现了接口的 Abs  方法
func (v *Vertex) Abs() float64 {
    
    
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

注意: 22行的代码有一个错误。因为 Vertex 类型并没有实现 Abser 接口因为 Abs 方法的接收者是指针类型 *Vertex*Vertex 类型实现了 Abser 接口】。

所以,编译报错:

./prog.go:22:4: cannot use v (type Vertex) as type Abser in assignment:
	Vertex does not implement Abser (Abs method has pointer receiver)

Interfaces are implemented implicitly(接口是隐式实现的)

一个类型通过实现接口包含的所有方法来隐式地实现这个接口。没有类似 “implements” 的关键字来显示地声明实现一个接口。因此,接口的定义与实现是分离的(它们不需要预先安排,可以出现在任何包中)。

package main

import "fmt"

type I interface {
    
    
	M()
}

type T struct {
    
    
	S string
}

// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
    
    
	fmt.Println(t.S)
}

func main() {
    
    
	var i I = T{
    
    "hello"} //类型 T 实现了 I 接口,i 的值就可以存放类型 T 的值
	i.M()
}

输出:

hello

Interface values(接口的值)

在底层,接口的值可以被想象成是由一个值和一个底层类型组成的一个元组

(value, type)

一个接口的值持有一个特定的底层具体类型的值。

在接口的值上调用一个方法就是执行它的底层类型中相同名字的方法。

package main

import (
	"fmt"
	"math"
)

type I interface {
    
    
	M()
}

type T struct {
    
    
	S string
}

// 类型 T 实现了接口 I
func (t T) M() {
    
    
	fmt.Println(t.S)
}

type F float64

// 类型 F 实现了接口 I
func (f F) M() {
    
    
	fmt.Println(f)
}

func main() {
    
    
	var i I

	i = T{
    
    "Hello"}
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
}

func describe(i I) {
    
    
	fmt.Printf("(%v, %T)\n", i, i)
}

输出:

({Hello}, main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793

Interface values with nil underlying values(底层的值是 nil 的接口的值)

如果在接口中底层的值是 nil,那么调用方法时接收者就是 nil

注意:一个持有底层的值是 nil 的接口,它自身的值不是 nil

在一些语言中,这会触发一个空指针异常,但是在 Go 中,写一个优雅地处理 nil 接收者的方法很常见(就像示例代码中的 M 方法)。

package main

import "fmt"

type I interface {
    
    
	M()
}

type T struct {
    
    
	S string
}

func (t *T) M() {
    
    
	if t == nil {
    
    
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

func main() {
    
    
	var i I

	var t *T // t 没有初始化,它的值就是 nil
	i = t // i 的底层值是 nil 
	describe(i)
	i.M()

	i = &T{
    
    "hello"} // i 的底层值不为 nil 
	describe(i)
	i.M()
}

func describe(i I) {
    
    
	fmt.Printf("(%v, %T)\n", i, i)
}

输出:

(<nil>, *main.T)
<nil>
(&{hello}, *main.T)
hello

Nil interface values(nil 接口值)

一个 nil 接口值不持有任何值或具体的类型。

在一个 nil 接口上调用方法会出现运行时错误。

package main

import "fmt"

type I interface {
    
    
	M()
}

func main() {
    
    
	var i I // 接口类型的零值是 nil
	describe(i)
	i.M()
}

func describe(i I) {
    
    
	fmt.Printf("(%v, %T)\n", i, i)
}

输出:

(<nil>, <nil>)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x48e82f]

The empty interface(空接口)

不包含任何方法的接口类型称作空接口:

interface{}

一个空接口可以持有任何类型的任何值(每个类型实现了至少零个方法)。

空接口通常在处理未知类型的值时被使用。 比如,fmt.Print 函数的参数是任意个 interface{} 类型的值。

package main

import "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)
}

输出:

(<nil>, <nil>)
(42, int)
(hello, string)

Type assertions(类型断言)

一个 type assertion (类型断言)提供了对接口值的底层具体类型值的访问。

t := i.(T)

这个语句断言接口值 i 是否持有具体类型 T,并将底层类型为 T 的值赋值给变量 t

如果 i 不持有类型 T,这个语句就会触发一个 panic。

为了测试一个接口值是否持有一个特定的类型,一个类型断言可以返回两个值:底层的值和一个报告断言是否成功的布尔值。

t, ok := i.(T)

如果 i 持有 T,那么 t 是底层的值,oktrue

如果不是这样,ok 就会是 falset 就会是类型 T 的零值,不会出现 panic。

package main

import "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) // panic
	fmt.Println(f)
}

输出:

hello
hello true
0 false
panic: interface conversion: interface {} is string, not float64

Type switches(类型 switches)

一个 type switch(类型 switch)是一个允许一系列类型断言的 switch 结构。

一个类型 switch 就像通常的 switch 语句一样,但是在它的 cases 中指定的是类型(不是值),这些类型用来与给定的接口值中持有的值的类型做比较。

switch v := i.(type) {
    
    
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}

在类型 switch 中的声明和类型断言 i.(T) 的语法相同,但是指定的类型 T 被关键字 type 替代。

上面的 switch 语句测试接口值 i 是否持有一个类型为 TS 的值。在每个 TS 的 case 中,变量 v 将会是类型 TS ,它的值就是 i 持有的底层值。在 default case (没有匹配到任何类型)中,变量 v 的类型和值和 i 持有的底层值相同。

package main

import "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)
}

输出:

Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!

Stringers

一个十分常见的接口是在 fmt 包中定义的 Stringer

type Stringer interface {
    
    
    String() string
}

一个 Stringer 是一个可以将自己描述成字符串的类型。fmt (和许多其他的)包查找这个接口来打印值。

package main

import "fmt"

type Person struct {
    
    
	Name string
	Age  int
}

// Person 类型是一个 Stringer
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)
}

输出:

Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)

Errors(错误)

Go 程序使用 error 值来表达错误。

error 类型是一个内置的接口类型,和 fmt.Stringer 类似:

type error interface {
    
    
    Error() string
}

函数常常返回一个 error 值,调用函数的代码测试返回的错误值是不是 nil 来处理错误。

i, err := strconv.Atoi("42")
if err != nil {
    
    
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

示例代码:

package main

import (
	"fmt"
	"time"
)

type MyError struct {
    
    
	When time.Time
	What string
}

// *MyError 类型实现了 error 接口
func (e *MyError) Error() string {
    
    
	return fmt.Sprintf("at %v, %s",
		e.When, e.What)
}

func run() error {
    
    
    // 返回一个 *MyError 类型的值,它实现了 error 接口
	return &MyError{
    
    
		time.Now(),
		"it didn't work",
	}
}

func main() {
    
    
	if err := run(); err != nil {
    
    
		fmt.Println(err)
	}
}

输出:

at 2020-11-21 22:16:45.405713232 +0800 CST m=+0.000068175, it didn't work

Readers

io 包规定了 io.Reader 接口, 它代表了数据流的读取端。

Go 标准库包含该接口的许多实现,包括文件,网络连接,压缩器,加密等。

io.Reader 接口有一个 Read 方法:

func (T) Read(b []byte) (n int, err error)

Read 使用数据填充给定的字节切片并返回填充的字节数和一个错误值。当数据流终止时它返回一个 io.EOF 错误。

示例代码创建了一个 strings.Reader【它实现了 io.Reader 接口】,在循环中,每次消费它 8 字节数据并输出。

package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
    
    
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte, 8)
	for {
    
    
		n, err := r.Read(b) // 读取 8 字节数据到切片 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
		}
	}
}

输出:

n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""

猜你喜欢

转载自blog.csdn.net/woay2008/article/details/109908947