GO方法和接口

方法

​ Go 没有类。不过你可以为结构体类型定义方法。

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

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

​ 在此例中,Abs 方法拥有一个名为 v,类型为 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 main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}

方法即函数

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

​ 你只能为在同一包内定义的类型的接收者声明方法,而不能为其它包内定义的类型(包括 int 之类的内建类型)的接收者声明方法。

​ (译注:就是接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。)

指针接收者

​ go可以为指针接收者声明方法,若使用值接收者,那么 Scale 方法会对原始 Vertex 值的副本进行操作。

指针接收者的方法可以修改接收者指向的值(就像 Scale 在这做的)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。

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 main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())//50
}

方法与指针重定向

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

​ 比较前两个程序,你大概会注意到带指针参数的函数必须接受一个指针:

var v Vertex
ScaleFunc(v, 5)  // 编译错误!
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"
	"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))
}

​ 接受一个值作为参数的函数必须接受一个指定类型的值:

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

​ 看样子方法比函数好用一些,不管方法定义的时候是否需要传入指针类型,所以主要是定义方法时的方法接受者(*Object 还是 Object)很重要,这决定了是否会改变原对象还是只改变副本。

接口

接口类型 是由一组方法签名定义的集合。

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

	// 下面一行,v 是一个 Vertex(而不是 *Vertex)
	// 所以没有实现 Abser。
	// a = v
	fmt.Println(v.Abs())
	fmt.Println(a.Abs())
}

接口这里如果要赋值,必须要和方法接受者一致,上面的v是对象不是指针,虽然使用v.Abs()会变成(*v).Abs(),但是直接将a赋值给v会报错:

由于 Abs 方法只为 *Vertex (指针类型)定义,因此 Vertex(值类型)并未实现 Abser

cannot use v (type Vertex) as type Abser in assignment:
	Vertex does not implement Abser (Abs method has pointer receiver)

接口与隐式实现

​ 类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有“implements”关键字。 (要重新看看python、额)

​ 隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。

​ 因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。

package main

import "fmt"

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

接口值

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

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

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

(value, type)

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

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

package main

import (
	"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 main() {
	var i I

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

	i = F(math.Pi)
	describe(i)
	i.M()
}
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793

底层值为 nil 的接口值

​ 即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用。

​ 在一些语言中,这会触发一个空指针异常,但在 Go 中通常会写一些方法来优雅地处理它(如本例中的 M 方法)。

注意: 保存了 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 main() {
	var i I

	var t *T
	i = t
	describe(i)
	i.M()

	i = &T{"hello"}
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
(<nil>, *main.T)
<nil>
(&{hello}, *main.T)
hello

nil 接口值

​ nil 接口值既不保存值也不保存具体类型。

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

空接口

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

interface{}

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

​ 空接口被用来处理未知类型的值。例如,fmt.Print 可接受类型为 interface{} 的任意数量的参数。

类型断言

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

t := i.(T)

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

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

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

t, ok := i.(T)

​ 若 i 保存了一个 T,那么 t 将会是其底层值,而 oktrue

​ 否则,ok 将为 falset 将为 T 类型的零值,程序并不会产生恐慌。

请注意这种语法和读取一个映射时的相同之处。

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

类型选择

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

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

switch v := i.(type) {
case T:
    // v 的类型为 T
case S:
    // v 的类型为 S
default:
    // 没有匹配,v 与 i 的类型相同
}

​ 类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type

​ 此选择语句判断接口值 i 保存的值类型是 T 还是 S。在 TS 的情况下,变量 v 会分别按 TS 类型保存 i 拥有的值。在默认(即没有匹配)的情况下,变量 vi 的接口类型和值相同。

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

Stringer(类似java的 toString)

fmt 包中定义的 Stringer 是最普遍的接口之一。

type Stringer interface {
    String() string
}

Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值。

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}
//实现String接口
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)
}

练习:Stringer

​ 通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址。

​ 例如,IPAddr{1, 2, 3, 4} 应当打印为 "1.2.3.4"

package main

import "fmt"

type IPAddr [4]byte

// TODO: 给 IPAddr 添加一个 "String() string" 方法

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}
}

func (ipaddr IPAddr) String() string{
	str := ""
	for i, v := range ipaddr{
		fmt.Println(i,string(v))
		if i+1 != len(ipaddr){
			str = str +  string(v) + ","
		}else{
			str = str + string(v)
		}
	}
	return fmt.Sprintf(str)
}

aaaaaaaaaaaaaaaaaaaaa到底怎么将byte转化为string类型啊

posted @ 2021-10-20 17:29  _Salvatore  阅读(56)  评论(0)    收藏  举报