zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Go——方法和接口

2023-02-25 18:27:10 时间

方法和接口

方法

  • Go没有类,但可以结构体定义方法
  • 方法就是一类带特殊的==接受者==的函数,接受者可以是命名类型结构体类型的一个值或一个指针
  • 方法接收者在它自己的参数列表内,位于func关键字和方法名之间
type Vertex struct{
    X,Y float64
}

func (v vertex)Abs()float64{
    return math.Sqrt(v.X*v.X+v.Y*v.Y)
}//Abs方法的接受者为v,类型为Vertex

func main (){
    v:=Vertex{3,4} 
}
fmt.Println(v.Abs())//输出为5

方法即函数

  • 方法只是带接受者的函数
  • 把上面的Abs函数写为正常函数为,功能不变
func Abs(v Vertex)float64{
    return math.Sqrt(v.X*v.X+v.Y*v.Y)
}

方法(续)

  • 也可以声明非结构体类型的声明方法
  • 接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法
type MyFloat float64

func (f MyFloat)Abs()float64{//带 Abs 方法的数值类型 MyFloat。
    if(f<0)
    return float64(-f)
    else
    return float64(f)
}

指针接收者

  • 可以为指针接收者声明方法
  • 指针接收者的方法可以修改接收者指向的值
type Vertex struct {
    X,Y float64
}

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

指针与函数

  • 带指针参数的函数必须接受一个指针:func add(v *Vertex)int
  • 以指针为接收者的方法被调用时,接收者既能为又能为指针

选择 值or指针 为接收者

使用指针作为接收者的原因

  • 方法能够修改接收者指向的值
  • 避免每次调用方法时复制该值

接口

  • 接口类型是由一组方法签名定义的集合
  • 接口类型的变量可以保存任何实现了这些方法的值
type Abser interface{
    Abs()float64
}

func main (){
    var a Abser
    f:=MyFloat(-math.Sqrt2)
    v:=Vertex{3,4}
    
    a=f//a MyFloat实现了Abser
    a=&v//*Vertex实现了Abser
}

接口的隐式实现

  • 类型通过实现一个接口的所有方法来实现该接口
  • 隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中
  • 因为无需在每一个实现上增加新的接口名称,可以同时也鼓励了明确的接口定义
type I interface{
    M()
}

type T struct {
    string S
}
// 此方法表示类型 T 实现了接口 I,但我们无需显式声明此事。
func (t T)M(){
        fmt.Piintln(t.S)
    }

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

接口值

  • 接口值也是值,可以像其他值一样传递
  • 可以用作函数的参数或返回值
  • 在内部,接口值可以看做包含值和具体类型的元组
  • 接口值调用方法时会执行其底层类型的同名方法
type I interface{//定义接口类型
    M()
}

type T struct{
    string S
}

func (t*T)M(){//方法M
    fmt.Printf(t.S)    
}

type F float64

func(f F)M(){//f作为方法M()的接收者
    fmt.Println(f)
}

func main (){
    var i I//接口类型变量i
    i=&T{"hello"}
    describe(i)//接口值作为函数的参数
    i.M()//接口值调用方法,与此同时,会执行其底层类型的同名方法
    
    func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

}

底层值为nil的接口值

  • 即使接口内的具体值为nil,方法仍会被nil接收者调用
func (t*T)M(){
    if(t==nil){
        fmt.Println("<nil>")
        return 
    }
    fmt.Println(t.S)
}

nil接口值

  • nil接口值既不保存值也不保存具体类型
  • nil接口调用方法会发生错误,因为接口的元组没有包含指明调用哪个具体的方法的类型

空接口

  • 指定了零个方法的接口值为空接口
interface{}
  • 空接口可以保存任何类型的值(因为每个类型都至少实现了零个方法)
  • 空接口用来处理未知类型的值

类型断言

  • 类型断言提供访问接口值底层具体值的方式
t:=i.(T)
  • 为了判断一个接口值是否保存了一个特定类型,类型断言可返回两个值:底层值报告断言是否成功的布尔值
t,ok:=i(T)
func main (){
    var i interface{}="hello"
    
    s:=i.(string)
    
    s,ok:=i.(string)
}

类型选择

  • 类型选择是一种按顺序从几个类型断言中选择分支的结构
  • 类型选择与一般的switch类似,只不过case是类型而不是值
switch v:=i.(type){
    case T://V的类型为T
    case S:
    default:
}
  • 类型选择中的声明与类型断言的i.(T)相同,只是具体类型T换为了type

Stringer

  • fmt包中定义的Stringer是最普遍的接口之一
type Stringer interface{
    String()string
}
  • Stirng可以用字符串描述自己的类型
type person struct {
    Name String 
    Age int 
}

func (p person) String () stirng{
    return fmt.Sprintf("%v,(%v years)",P.Name,P.Age)
}

func main (){
    a:=person{"bob",32}
    b:=person{"tom",12}
    fmt.Println(a,b)
}

错误

  • error表示错误状态
  • 和fmt.Sprintf相似,error只是一个内建接口
type error interface{
    Error()string
}
  • 通常函数会返回一个error值,调用它的代码应该判断这个错误是否等于nil来错误处理
  • error为nil表示成功,非nil的error表示失败

Reader

  • io包指定了io.reader接口,表示从数据流的末尾进行读取
  • io.reader的一个接口方法
func (T)Read(b[]byte)(n int err error)
  • read用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流结尾时,会返回一个io.EOF错误

图像

  • image包定义了image接口
package image

type Image interface{
    ColorModel()color.Model
    Bounds()Rectangle
    At(x,y int)color.Color
}
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())
}