Skip to content

方法与接口

定义

在go中没有类的概念,一般用struct来代替类的操作,但struct中只包含字段属性,所以go提供了一种特殊的函数,其通过作用在某个接收者上面来实现与其关联

go
// func (接收者 接收者类型) 方法名(参数列表) 返回值类型 {}

func main() {
    p := Person{"jack", 18}
	fmt.Printf("person: %p, person name: %s\n", &p, p.Name)
	p.printPerson()     // 基于值复制的传参
	(&p)._printPerson() // 基于指针的传参
	p._printPerson()    // 与(&p)效果相同,因为编译器会做隐式的转换

	var p1 *Person
	p1._printPerson() // 当指针类型作为接收者时,类型可以为nil,值类型不可以为nil
}

func (p Person) printPerson() { // person作为接收者类型
	fmt.Printf("person: %p, person name: %s\n", &p, p.Name)
}
// 允许nil作为接收者
func (p *Person) _printPerson() { // 指针作为接收者类型
	if p == nil {
		fmt.Printf("person: %p", p)
		return
	}
	(*p).Name = "lucy"
	fmt.Printf("person: %p, person name: %v\n", p, (*p).Name)
}

type Person struct {
	Name string
	Age  int
}
  1. 相比函数,方法与其的区别在于多了接收者,即把方法绑定到这个接收者对应的类型上
  2. 接收者可以是任何类型,但不能是接口,因为接口是抽象定义,方法确实具体实现。
  3. 允许出现接收者类型不同,但是方法名相同的方法。
  4. 接收者类型是指针时使用的是引用传递,其他则是值传递,针对同一个结构体的方法的接收者类型需要统一。
  5. 只有当接收者类型为指针时,可以将nil作为实参传入。
  6. 当实参是T类型的变量而形参的接收者是*T类型时,编译器会隐式的获取变量的地址

结构体内嵌的类型

go
type Inner struct{ A, B int }

func (i Inner) inner() {
	fmt.Printf("Inner: A: %d, B: %d\n", i.A, i.B)
}

func (o Outer) outer() {
	fmt.Printf("Outer: C: %s\n", o.C)
}

type Outer struct {
	Inner
	C string
}

func test3() {
	o := &Outer{Inner{1, 2}, "3"}
	o.outer()
	o.inner() // 作用等同于o.Inner.inner()
}

通过结构体的嵌套,可以无感的调用内嵌的结构体的方法。

方法变量和方法表达式

go
type Person struct {
	Name string
	Age  int
}

func (p *Person) modifyPerson() { // 指针作为实参的接收者类型
	fmt.Printf("person: %#v\n", p) // 打印接收者参数
}

func test5(f func(*Person)) { // 方法表达式作为形参需要指定类型
    p := &Person{"lucky", 18}
    f(p)
}

func main() {
    p := &Person{"jack", 20}
    person := p.modifyPerson // 获取方法变量,只能在方法所属的实例p上调用
    person() // 调用方法 效果等同于 p.modifyPerson() 不需要指定接收者
    
    person2 := (*Person).modifyPerson // 获取方法表达式
    person2(p) // 调用方法表达式时需要传入接收者实参
    
    test5(person2) // 将方法表达式作为函数参数传递
}
  1. 方法变量:接将方法赋值给一个变量,不需要指定接收者,只能在方法所属类型的实例上调用,不能在其他类型的实例上调用。
  2. 方法表达式:方法表达式的语法是T.methodName,需要显式指定接收者类型作为第一个参数。可以作为普通函数一样传递。

封装

如果变量或者方法不是通过对象访问到的,这称作封装的变量或者方法(又称数据隐藏)

go
// 在另一个包中调用Logger的方法
func test6() {
	l := &Logger{}
	l.SetFlags(1)
	fmt.Printf("flags: %d", l.Flags())
}

// Logger 定义实体,但字段是当前包私有,无法导出的
type Logger struct {
	flags  int
	prefix string
}

// Flags 等同于java中字段的getFlags方法,go默认去掉get,且可导出,所以方法名大写
func (logger *Logger) Flags() int {
	return logger.flags
}

func (logger *Logger) SetFlags(flag int) {
	logger.flags = flag
}
  1. go中的封装效果等同于Java中的类字段属性设为私有,但是对外提供getter和setter用来修改字段属性。
  2. go中的getter方法是默认去掉get的,比如getAge在go中就是Age,注意需要大写(因为需要对其他包导出)。

接口

接口类型是对其他类型行为的概括和抽象。相比java的显示声明,go的独特之处在于它是隐式实现,对于一个具体的类型,无须声明它实现了哪些接口,只需要提供接口所必须的方法即可。

go
// interface{}效果等同于java中的Object
func test1(args ...interface{}) {
	fmt.Println(args)
}
// any等同于interface{}
func test2(args ...any) {
	fmt.Println(args)
}

1.go中提供了一种接口类型,即interface{},等价于java的Object。1.18版本后可以使用any替换,效果是等价的。

go
type Object interface {
	equals(source any, target any) bool // 此处不需要定义接收者类型,与给struct定义方法不同
	Say                                 // 接口嵌套接口
}

type Say interface {
	doSay(something string) (word string)
}

type Phone interface {
	Call()
}

type Iphone struct {
}

type Android struct {
}

func (i Iphone) Call() { // 方法名、参数与返回值都必须相同
	fmt.Println("i'm iphone!")
}

func (a Android) Call() {
	fmt.Println("i'm android!")
}

func test3() {
	var phone Phone // 声明接口类型的变量
	phone = new(Iphone)
	phone.Call()

	phone = new(Android)
	phone.Call()
}
  1. 接口的动态值:接口的类型信息 & 接口值的具体值或底层对象的副本。
  2. go的接口是隐式实现,一个类型只需要实现接口所定义的所有方法,无需显示声明。
  3. 接口是值类型,可以作为函数的参数和返回值,也可以赋值给其他接口变量。接口类型的零值是nil
  4. 接口变量在运行时存放的是实际的对象和值,通过类型断言可以将接口变量转换为具体类型,并访问其属性和方法。

接口断言

类型断言是一个作用在接口值上的操作,即x.(T)其中x是一个接口类型的表达式,T是一个类型(称为断言类型)。即x的动态类型是否是T

go
func test5() {
	var p Phone
	p = Iphone{}
	i := p.(Iphone) // 接口断言:如果p的动态类型是Iphone,那么会返回p的动态值
	i.getIphone()   // 调用Iphone类型独有的方法(非实现的接口方法)

	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("s0 print error: %s\n", r)
		}
	}()
	s0 := p.(Say) // 会产生panic
	s0.doSay("hello")

	if s, ok := p.(Say); ok { // ok返回bool值,表示p的类型是否是Say
		fmt.Printf("%#v\n", s)
	} else {
		fmt.Println("panic: interface conversion")
	}
}