Go语言之方法和接口篇四

/ Go技术 / 1 条评论 / 3546浏览

alt

方法和接口

方法

代码:

package main

import (
	"fmt"
	"math"
)

type VertexTest struct {
	X, Y float64
}

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

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

方法即函数

package main

import (
	"math"
	"fmt"
)

type VertexTest1 struct {
	X, Y float64
}

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

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

现在这个 Abs 的写法就是个正常的函数,功能并没有什么变化。

方法(续)

代码:

package main

import (
	"fmt"
	"math"
)

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

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

指针接收者

代码:

package main

import (
	"fmt"
	"math"
)

type VertexTest2 struct {
	X,Y float64
}

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

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

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

注意:指针指向的是源对象,因此操作的是原对象,非指针操作的是原实例的拷贝

指针与函数

代码:

package main

import (
	"fmt"
	"math"
)

type VertexTest3 struct{
	X,Y float64
}

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

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

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

注意:这里和上一小节实现的功能是一样的只不过上一小节是通过指针接受者,也就是结构体的方法来实现的,本小节是通过传递结构体指针或者引用给函数来实现的和C++中的引用传递类似

方法与指针重定向

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"

type VertexTest4 struct{
	X,Y float64
}

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

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


func main(){
	v := VertexTest4{3,4}
	v.Scale(2)
	ScaleFunc(&v , 10)

	p := &VertexTest4{6,8}
	p.Scale(2)
	ScaleFunc(p,10)

	fmt.Println(v,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() 。

总结:调用方法时,调用者既可以为值也可以为引用或指针(此时引用或指针被go解释为值:p.Abs()-> (*p).Abs()),方法参数为值时,只能是值不能是引用

代码如下:

package main

import (
	"fmt"
	"math"
)

type VertexTest5 struct {
	X,Y float64
}

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

func AbsFunc(v VertexTest5)float64  {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

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

	p := &VertexTest5{6,8}
	fmt.Println(p.Abs())
	fmt.Println(AbsFunc(*p))
}

选择值或指针作为接收者

代码:

package main

import (
	"fmt"
	"math"
)

type VertexTest6 struct{
	X,Y float64
}

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

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

func main()  {
	v := &VertexTest6{6,8}
	fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
	v.Scale(10)
	fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

接口

代码如下:

package main

import (
	"fmt"
	"math"
)

type Abser interface {
	Abs()float64
}

type MyFloat1 float64

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

type VertexTest7 struct{
	X,Y float64
}

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

func main() {
	var a Abser
	f := MyFloat1(math.Sqrt2)
	v := VertexTest7{3,4}

	a = f //a MyFloat 实现了 Abser
	a = &v //a *Vertex 实现了 Abser

	// 下面一行,v 是一个 Vertex(而不是 *Vertex)
	// 所以没有实现 Abser。
	//a = v //放开改行注释会报编译错误,因为实现Abs是选择指针作为接受者,给a赋值时必须是指针或引用

	fmt.Println(a.Abs())
}

注意:实现接口的接受者是值还是指针,直接影响给接口变量赋值,接受者是指针必须赋值为引用,接受者是值赋值就是值

接口与隐式实现

接口值

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

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

结果:

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

底层值为 nil 的接口值

代码如下:

package main

import (
	"fmt"
)

type I1 interface {
	M()
}

type T1 struct {
	S string
}

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

func main() {
	var i I1
	var t *T1
	i = t
	describe1(i)
	i.M()

	i = &T1{"Hello"}
	describe1(i)
	i.M()
}

func describe1(i I1)  {
	fmt.Printf("(%v, %T)\n",i,i)

}

结果:

(<nil>, *main.T1)
<nil>
(&{Hello}, *main.T1)
Hello

nil 接口值

代码:

package main

import "fmt"

type I2 interface {
	M()
}

func main() {
	var i I2
	describe2(i)
	i.M()

}

func describe2(i I2) {
	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=0x20 pc=0x1095118]

goroutine 1 [running]:
main.main()
	/Users/zealzhangz/Documents/dev/p/go/src/hello-go/main/nil-interface-values.go:12 +0x38

空接口

interface{}

代码:

package main

import "fmt"

func main() {
	var i interface{}
	describe3(i)

	i = 42
	describe3(i)

	i = "Hello"
	describe3(i)
}

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

结果:

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

类型断言

t := i.(T)
t, ok := i.(T)

代码:

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
panic: interface conversion: interface {} is string, not float64
hello true
0 false

类型选择

switch v := i.(type) {
case T:
    // v 的类型为 T
case S:
    // v 的类型为 S
default:
    // 没有匹配,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)
}

Stringer

type Stringer interface {
    String() string
}
package main

import "fmt"

type Person struct {
	Age uint8
	Name string
}

func (p Person)string()string  {
	return fmt.Sprintf("%v (%v years)",p.Name,p.Age)
}

func main() {
	p := Person{25,"San Zhang"}
	fmt.Println(p.string())
}

注意:这里实现string()这个接口就相当于Java中重写自己的toString()方法

练习:Stringer

代码:

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr)String()string  {
	ipStr := ""
	for i := 0;i < len(ip);i++{
		ipStr += fmt.Sprintf("%d",ip[i]) + "."
	}
	return ipStr[:len(ipStr)-1]
}

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

注意:难点之一是byte转string,这里使用了fmt.Sprintf("%d",ip[i]),go还有专门的函数可以转换 str1 := strconv.Itoa(i),需要引入包strconv

错误

type error interface {
    Error() string
}
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
}

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

注意:run()返回的是引用而不是值,这跟实现接口Erro()使用的是指针接受者是一致的

练习:错误

type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string
"cannot Sqrt negative number: -2"

注意: 在 Error 方法内调用 fmt.Sprint(e) 会让程序陷入死循环。可以通过先转换 e 来避免这个问题:fmt.Sprint(float64(e)) 。这是为什么呢?

代码如下:

package main

import "fmt"

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt)Error()string  {
	if float64(e) < 0{
		return fmt.Sprintf("cannot Sqrt negative number: %v",float64(e))
	}
	return ""
}

func Sqrt1(x float64)(float64, error){
	z := float64(1)
	for i := 0; i < 10;i++{
		z = z - (z*z-x)/2*z
	}
	return z,ErrNegativeSqrt(x)
}

func main() {
	fmt.Println(Sqrt1(2))
	fmt.Println(Sqrt1(-2))
}

Reader

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

代码:

package main

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

练习:Reader

代码:

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.

func (r MyReader)Read(b []byte)(int,error)  {
	for index,_ := range b{
		b[index]='A'
	}
	return len(b),nil
}

func main() {
	reader.Validate(MyReader{})
}

注意:仔细理解题意,这里的无限并不是真正的无限而是传入多大一个b []byte数组来接受,就产生多大一个流

练习:rot13Reader

代码:

package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}

func (ro *rot13Reader)Read(b []byte)(int,error)  {
	n,err := ro.r.Read(b)
	for i:=0;i < len(b);i++{
		if (b[i] >= 'A' && b[i] <= 'M') || (b[i] >= 'a' && b[i] <= 'm') {
			b[i] += 13
		} else if  (b[i] >= 'N' && b[i] <= 'Z') || (b[i] >= 'n' && b[i] <= 'z'){
			b[i] -= 13
		}
	}
	return n,err
}


func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}

图像

package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

注意: Bounds 方法的返回值 Rectangle 实际上是一个 image.Rectangle, 它在 image 包中声明。 (请参阅文档了解全部信息。)

代码:

package main

import(
	"fmt"
	"image"
)

func main() {
	m := image.NewRGBA(image.Rect(0,0,100,100))
	fmt.Println(m.Bounds())
	fmt.Println(m.At(0,0).RGBA())
}

练习:图像

代码:

package main

import (
	"golang.org/x/tour/pic"
	"image"
	"image/color"
)

type Image struct{x, y, w, h int}

func (i Image)ColorModel()color.Model  {
	return color.RGBAModel
}

func (i Image)Bounds() image.Rectangle {
	return image.Rect(i.x,i.y,i.w,i.h)
}
func (i Image)At(x,y int)color.Color  {
	return color.RGBA{uint8(x), uint8(y), uint8(255), uint8(255)}
}

func main() {
	m := Image{0,0,200,200}
	pic.ShowImage(m)
}

__注意:__这里各个接口的具体实现直接调用库的实现

  1. 音乐播放器不错

    回复