方法与接口


1 方法

  Go语言没有面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性,我们都知道面向对象里面有类方法等概念。也可以声明一些方法,属于某个结构体。

1.1 语法

  Go中的方法,是一种特殊的函数,定义于struct之上(与struct关联、绑定),被称为struct的接受者(receiver)。

  通俗的讲,方法就是有接收者的函数。

  语法格式如下

type mytype struct{}

func (recv mytype) my_method(para) return_type {}
func (recv *mytype) my_method(para) return_type {}


// 参数说明:
   1.`mytype`:定义一个结构体
   2.`recv`:接受该方法的结构体(receiver)
   3.`my_method`:方法名称
   4.`para`:参数列表
   5.`return_type`:返回值类型

// 从语法格式可以看出,方法和函数非常相似,多了一个接受类型。

1.2 实例

type Person struct {
	name string
}

type Customer struct {
	name string
}

// 属性和方法分开写
// (per Person)---接收者(receiver)
// eat是属于Person这个结构体的
// 方法是通过接收者与结构体关联在一起的
func (per Person) eat() {
	fmt.Printf("%v,eat...", per.name)
}

func (per Person) sleep() {
	fmt.Printf("%v,sleep...", per.name)

}

func (cust Customer) login(name string, pwd string) bool {
	fmt.Printf("cust.name: %v\n", cust.name)
	if name == "kite" && pwd == "123" {
		return true
	} else {
		return false
	}

}

func main() {
	per_method := Person{
		name: "kite",
	}

	per_method.eat()
	per_method.sleep()

	cus := Customer{
		name: "kite",
	}
	b := cus.login("kite", "123")
	fmt.Printf("b: %v\n", b)
}

1.3 方法的注意事项

  1.方法的receiver type并非一定要是struct类型,type定义的类型别名、slice、map、channel、func类型等都可以。

  2.struct结合它的方法就等价于面向对象中的类。只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包。

  3.方法有两种接收类型:(T Type)(T *Type),它们之间有区别。

  4.方法就是函数,所以Go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。

  5.如果receiver是一个指针类型,则会自动解除引用。

  6.方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。

1.4 结构体类型

  结构体有值类型和指针类型,方法的接收者是结构体,那么也有值类型和指针类型。区别就是接收者是否复制结构体副本。值类型复制,指针类型不复制。

type Person struct {
	name string
}

func test1() {
	p1 := Person{
		name: "Tom",
	}

	p2 := &Person{
		name: "Tom",
	}

	fmt.Printf("p1: %T\n", p1)
	fmt.Printf("p2: %T\n", p2)
}
// 运行结果:p1: main.Person    p2: *main.Person
// 从运行结果,可以看出p1是值类型,p2是指针类型。

1.4.1 值类型结构体

type Person struct {
	name string
}

func showPerson1(per Person) {
	per.name = "Tom..."
}

func test2() {
	p1 := Person{
		name: "Tom",
	}

	showPerson1(p1)
	fmt.Printf("p1: %v\n", p1)
}
// 运行结果:p1: {Tom}
// 从运行结果看,p1是值传递,拷贝了副本,地址发生了改变

1.4.2 指针类型结构体

type Person struct {
	name string
}


func showPerson2(per *Person) {
	// per.name   自动解引用
	// (*per).name = "Tom..."
	per.name = "Tom..."
}

func test2() {
	p2 := &Person{
		name: "Tom",
	}

	fmt.Println("------------------")
	showPerson2(p2)
	fmt.Printf("p2: %v\n", *p2)
}

// 运行结果:p2: {Tom...}
// 从运行结果看,p2是指针类型,地址没有改变。

1.5 方法接收者类型

 结构体有值类型和指针类型,方法的接收者是结构体,那么也有值类型和指针类型。区别就是接收者是否复制结构体副本。值类型复制,指针类型不复制。

1.5.1 方法的值类型接收者

  值类型和指针类型接收者,本质上和函数传参道理相同。

type Person struct {
	name string
}

func (per Person) showPerson3() {
	per.name = "Tom..."
}

func test3() {
	p1 := Person{
		name: "Tom",
	}

	p1.showPerson3()
	fmt.Printf("p1: %v\n", p1)
}

// 运行结果:p1: {Tom}
// 从运行结果看,p1是值传递,拷贝了副本,地址发生了改变。

1.5.1 方法的指针类型接收者

  值类型和指针类型接收者,本质上和函数传参道理相同。

type Person struct {
	name string
}

func (per *Person) showPerson4() {
	// per.name   自动解引用
	// (*per).name = "Tom..."
	per.name = "Tom..."

}

func test3() {
	p2 := &Person{
		name: "Tom",
	}
    
	p2.showPerson4()
	fmt.Printf("p2: %v\n", *p2)
}
// 运行结果:p2: {Tom...}
// 从运行结果看,p2是指针类型,地址没有改变。

2 接口

  接口像是一个公司里面的领导,会定义一些通用规范,只设计规范,而不实现规范。

  Go语言的接口,是一种新的类型定义,把所有具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

  语法格式和方法非常类似。

2.1 语法格式

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

在接口定义中定义,若干个空方法。这些方法都具有通用性。

2.2 接口实例

  定义一个USB接口,有读(read)和写(write)两个方法,再定义一个电脑Computer和一个手机Mobile来实现这个接口。

// 系统中有一些接口,如:writer、Reader...
// USB接口
type USB interface {
	read()
	write()
}

// Computer结构体
type Computer struct {
	name string
}

// Mobile结构体
type Mobile struct {
	model string
}

// Computer实现USB接口的方法
func (c Computer) read() {
	fmt.Printf("c.name: %v\n", c.name)
	fmt.Println("Computer read...")
}

func (c Computer) write() {
	fmt.Printf("c.name: %v\n", c.name)
	fmt.Println("Computer wtite...")
}

// Mobile实现USB接口的方法
func (m Mobile) read() {
	fmt.Printf("m.model: %v\n", m.model)
	fmt.Println("Mobile read...")
}

func (m Mobile) wtite() {
	fmt.Printf("m.model: %v\n", m.model)
	fmt.Println("Mobile write...")
}

// 测试
func main() {
	c_windows := Computer{
		name: "联想",
	}
	c_windows.read()
	c_windows.write()

	m_mobile := Mobile{
		model: "华为Nova 15",
	}
	m_mobile.read()
	m_mobile.wtite()
}

2.3 接口注意事项

  实现接口必须实现接口中的所有方法。

  定义一个OpenClose接口,里面有两个方法open和close,定义一个Door结构体,实现其中一个方法。

type OpenClose interface {
	open()
	close()
}

type Door struct {
}

func (d Door) open() {
	fmt.Println("open door...")
}

func main() {
	var oc OpenClose
	oc = Door{} // 这里编译错误,提示只实现了一个接口
}

2.4 接口类型

2.4.1 值类型接收者

golang接口值类型接收者和指针类型接收者

这个话题,本质上和方法的值类型接收者和指针类型接收者,的思考方法是一样的,值接收者是一个拷贝,是一个副本,而指针接收者,传递的是指针。

实例演示

定义一个Pet接口

type Pet interface {
	eat()
}

定义一个Dog结构体

type Dog struct {
	name string
}

实现Pet接口(接收者是值类型)

func (dog Dog) eat() {
	fmt.Printf("dog: %p\\n", &dog)
	fmt.Println("dog eat..")
	dog.name = "黑黑"
}

测试

func main() {
	dog := Dog{name: "花花"}
	fmt.Printf("dog: %p\\n", &dog)
	dog.eat()
	fmt.Printf("dog: %v\\n", dog)
}

运行结果

dog: 0xc000046240
dog: 0xc000046250
dog eat..
dog: {花花}

从运行结果,我们看出dog的地址变了,说明是复制了一份,dog的name没有变说明,外面的dog变量没有被改变。

将Pet接口改为指针接收者

func (dog *Dog) eat() {
	fmt.Printf("dog: %p\\n", dog)
	fmt.Println("dog eat..")
	dog.name = "黑黑"
}

再测试

func main() {
	dog := &Dog{name: "花花"}
	fmt.Printf("dog: %p\\n", dog)
	dog.eat()
	fmt.Printf("dog: %v\\n", dog)
}

运行结果

dog: 0xc00008c230
dog: 0xc00008c230
dog eat..
dog: &{黑黑}

文章作者: 罗宇
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 罗宇 !
 上一篇
指针与结构体 指针与结构体
Go(Golang)是Google开发的一种静态强类型、编译型语言。语法与C相近,但功能上有:内存安全、GC(垃圾回收),结构形态及CSP-style并发计算。
下一篇 
包与模块 包与模块
Go(Golang)是Google开发的一种静态强类型、编译型语言。语法与C相近,但功能上有:内存安全、GC(垃圾回收),结构形态及CSP-style并发计算。
  目录