2. 方法和接口

一、方法

Go没有类,但可以为类型定义方法。

方法就是一类带特殊的 接收者 参数的函数。

方法接收者在参数列表内,位于 func 关键字和方法名之间。

type Vertex struct {
    X, Y float64
}

// Abs方法拥有一个 名为v,类型为Vertex 的接收者
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

!!方法只是个带接收者参数的函数!!

1. 非结构体类型声明方法

package main

import (
	"fmt"
	"math"
)

//带Abs方法的数值类型MyFloat
type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

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

只能为在同一包中定义的接收者类型声明方法,而不能为其他别的包中定义的类型(包括int之类的内置类型)声明方法。即接收者的类型定义和方法声明必须在同一包中。

2. 指针类型的接收者

  1. 对于某类型T,接收者的类型可以用*T的文法(T本身不可以是指针)。
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

//为*Vertex定义了Scale方法
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}
    //Scale方法的接收者是指针类型(*Vertex),可以直接用值类型的变量(Vertex)来调用这个方法,
    //因为Go会自动将值类型转换为指针类型,以便调用该方法。这种自动转换称为“自动取地址”
	v.Scale(10)
	fmt.Println(v.Abs())
}

        2.        指针接收者的方法可以修改接收者指向的值(如上Scale所示)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。若使用值接收者,那么方法会对原始Vertex值的副本进行操作。

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) {
	v.X = v.X * f
	v.Y = v.Y * f
}

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

func main() {
	v := Vertex{3, 4}
	v.ScaleOri(10) //结果5, 因为v.X,v.Y值不变
	fmt.Println(v.Abs())

	v.Scale(10) //结果50,v.X,v.Y值放大10倍
	fmt.Println(v.Abs())
}

        3.        将Scale方法重写为函数,比较这两段代码:

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

/*
带指针参数的函数必须接受一个指针
    var v Vertex
    ScaleFunc(v, 5)  // 编译错误!
    ScaleFunc(&v, 5) // Ok
接收者为指针的方法被调用时,接收者既能是值又能是指针:
    var v Vertex
    v.Scale(5)  // OK
    p := &v
    p.Scale(10) // OK
即Go会将v.Scale解释为(&v).Scale
*/

反之一样

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

/* 反之一样
接受一个值作为参数的函数必须接受一个指定类型的值:
        var v Vertex
        fmt.Println(AbsFunc(v))  // OK
        fmt.Println(AbsFunc(&v)) // 编译错误!
而以值作为接收者的方法被调用时,接收者既能为值又能为指针:
        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 Abser interface {
	Abs() float64
}

type MyFloat float64

//MyFloat实现了Abser接口
func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

//*Vertex实现了Abs接口
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 MyFloat 实现了 Abser
	a = &v // a *Vertex 实现了 Abser

	fmt.Println(a.Abs())
}

1. 隐式实现

类型通过实现一个接口的所有方法来实现该接口。既然无需专门显示什么声明,也就没有“implements”关键字。隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。因此,也就无需在每一个实现上新增新的接口名称,这样同时也鼓励了明确的接口定义。

type I interface {
	M()
}

type T struct {
	S string
}

// 此方法表示类型 T 实现了接口 I,不过我们并不需要显式声明这一点。
func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I = T{"hello"}
	i.M()
}

2. 接口值

接口也是值,它们可以像其他值一样传递。

接口值可以用作函数的参数或返回值。

在内部,接口值可以看作包含值和具体类型的元组:

(value, type)

接口值保存了一个具体类型的具体值。

接口值调用方法时会执行其底层类型的同名方法。

1.         底层值为nil的接口值

即便接口内的具体值为nil,方法仍然会被nil接收者调用。在一些语言中,这会出发一个空指针异常,但在Go中通常会写一些方法来处理。

!!保存了nil具体值的接口其本身并不为nil !!(还有类型)

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 describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

func main() {
	var i I

	var t *T
	i = t
	describe(i) //(<nil>, *main.T)
	i.M()       //<nil>

	i = &T{"Hello"}
	describe(i) //(&{Hello}, *main.T)
	i.M()       //Hello
}

2.        nil接口值

nil接口值既不保存值也不保存具备类型。为nil接口调用方法会产生运行错误,因为接口的元组内并未包含能够指明该调用哪个具体方法的类型。

package main

import "fmt"

type I interface {
	M()
}

func main() {
	var i I
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)    //panic: runtime error: invalid memory address or nil pointer dereference
}

3. 空接口

指定了零个方法的接口值被称为 空接口 :

interface{}

空接口可接收任何类型的值。(因为每个类型都至少实现了零个方法)

空接口被用来处理未知类型的值。例如

fmt.Print可接受类型为interface{}的任意数量的参数。

package main

import "fmt"

func main() {
	var i interface{}
	describe(i)    //(<nil>, <nil>)

	i = 42
	describe(i)    //(42, int) 

	i = "hello"
	describe(i)    //(hello, string)
}

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

4. 类型断言

类型断言 提供了访问接口值底层具体值的方法。

t := i.(T) 

该语句断言接口值i保存了具体类型T,并将其底层类型为T的值赋予变量t。

若i并未保存T类型的值,该语句就会触发一个panic

 为了判断一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。

t, ok := i.(T)

若i保存了一个T,那么t将会是其底层值,而ok为true。

否则,ok将为false,而t将为T类型的零值,程序将不会产生panic。

package main

import "fmt"

func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)    //hello

	s, ok := i.(string)
	fmt.Println(s, ok)    //hello true

	f, ok := i.(float64)
	fmt.Println(f, ok)    //0 false

	f = i.(float64) 
	fmt.Println(f)    //panic: interface conversion: interface {} is string, not float64
}

5. 类型选择

类型选择是一种按顺序从几个类型断言中选择分支的结构。

类型选择与一般的switch语句相似,不过类型选择中的case为类型(非值),它们针对给定接口值所存储的值的类型进行比较。

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的接口类型和值相同。

package main

import "fmt"

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("二倍的 %v 是 %v\n", v, v*2)
	case string:
		fmt.Printf("%q 长度为 %v 字节\n", v, len(v))
	default:
		fmt.Printf("我不知道类型 %T!\n", v)
	}
}

func main() {
	do(21)    //二倍的 21 是 42 
	do("hello")    //"hello" 长度为 5 字节
	do(true)    //我不知道类型 bool!
}

  • 31
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值