Golang通脉之方法

方法和接收者

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集

方法只是一个函数,它带有一个特殊的接收器类型,它是在func关键字和方法名之间编写的。接收器可以是struct类型或非struct类型。接收方可以在方法内部访问。

方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。接收者可以是值接收者,也可以是指针接收者

在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。

也就是说,不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。

方法的定义格式如下:

func (t Type) methodName(parameter)(return) {
  
}

其中,

  • t:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是selfthis之类的命名。
  • Type:接收者类型和参数类似,可以是指针类型和非指针类型。
  • methodNameparameterreturn :具体格式与函数定义相同。
//Person 结构体
type Person struct {
	name string
	age  int8
}

//NewPerson 构造函数
func NewPerson(name string,age int8) *Person {
	return &Person{
		name: name,age:  age,}
}

//Dream Person做梦的方法
func (p Person) Dream() {
	fmt.Printf("%s的梦想是学好Go语言!\n",p.name)
}

func main() {
	p1 := NewPerson("张三",25)
	p1.Dream()
}

方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。

可以定义相同的方法名

type Rectangle struct {
	width,height float64
}
type Circle struct {
	radius float64
}


func (r Rectangle) area() float64 {
	return r.width * r.height
}
//该 method 属于 Circle 类型对象中的方法
func (c Circle) area() float64 {
	return c.radius * c.radius * math.Pi
}
func main() {
	r1 := Rectangle{12,2}
	r2 := Rectangle{9,4}
	c1 := Circle{10}
	c2 := Circle{25}
	fmt.Println("Area of r1 is: ",r1.area())
	fmt.Println("Area of r2 is: ",r2.area())
	fmt.Println("Area of c1 is: ",c1.area())
	fmt.Println("Area of c2 is: ",c2.area())
}

运行结果

Area of r1 is:  24
Area of r2 is:  36
Area of c1 is:  314.1592653589793
Area of c2 is:  1963.4954084936207
  • 虽然method的名字一模一样,但是如果接收者不一样,那么method就不一样
  • method里面可以访问接收者的字段
  • 调用method通过.访问,就像struct里面访问字段一样

指针类型的接收者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self

type Rectangle struct {
	width,height int
}

func (r *Rectangle) setVal() {
	r.height = 20
}

func main() {
	p := Rectangle{1,2}
	s := p
	p.setVal()
	fmt.Println(p.height,s.height)
}

结果

20 2

值类型的接收者

方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身

type Rectangle struct {
	width,height int
}

func (r Rectangle) setVal() {
	r.height = 20
}

func main() {
	p := Rectangle{1,s.height) 	// 2 2
}

什么时候应该使用指针类型接收者

  1. 需要修改接收者中的值
  2. 接收者是拷贝代价比较大的大对象
  3. 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

方法和函数

已经有了函数,为什么还要使用方法?

type Employee struct {  
    name     string
    salary   int
    currency string
}

/*
 displaySalary() method converted to function with Employee as parameter
*/
func displaySalary(e Employee) {  
    fmt.Printf("Salary of %s is %s%d",e.name,e.currency,e.salary)
}

func main() {  
    emp1 := Employee{
        name:     "Sam Adolf",salary:   5000,currency: "$",}
    displaySalary(emp1)
}

在上面的程序中,displaySalary方法被转换为一个函数,而Employee struct作为参数传递给它。这个程序也产生了相同的输出:Salary of Sam Adolf is $5000.。

为什么可以用函数来写相同的程序呢?有以下几个原因:

  1. Go不是一种纯粹面向对象的编程语言,它不支持类。因此,类型的方法是一种实现类似于类的行为的方法。
  2. 相同名称的方法可以在不同的类型上定义,而具有相同名称的函数是不允许的。

任意类型添加方法

在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。

//MyInt 将int定义为自定义MyInt类型
type MyInt int

//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {
	fmt.Println("Hello,我是一个int。")
}
func main() {
	var m1 MyInt
	m1.SayHello() //Hello,我是一个int。
	m1 = 100
	fmt.Printf("%#v  %T\n",m1,m1) //100  main.MyInt
}

注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。

方法继承

方法是可以继承的,如果匿名字段实现了一个方法,那么包含这个匿名字段的struct也能调用该方法

type Human struct {
	name  string
	age   int
	phone string
}
type Student struct {
	Human  //匿名字段
	school string
}
type Employee struct {
	Human   //匿名字段
	company string
}

func (h *Human) SayHi() {
	fmt.Printf("Hi,I am %s you can call me on %s\n",h.name,h.phone)
}
func main() {
	mark := Student{Human{"Mark",25,"222-222-YYYY"},"MIT"}
	sam := Employee{Human{"Sam",45,"111-888-XXXX"},"Golang Inc"}
	mark.SayHi()
	sam.SayHi()
}

运行结果:

Hi,I am Mark you can call me on 222-222-YYYY
Hi,I am Sam you can call me on 111-888-XXXX

方法重写

type Human struct {
	name  string
	age   int
	phone string
}
type Student struct {
	Human  //匿名字段
	school string
}
type Employee struct {
	Human   //匿名字段
	company string
}

//Human定义method
func (h *Human) SayHi() {
	fmt.Printf("Hi,h.phone)
}

//Employee的method重写Human的method
func (e *Employee) SayHi() {
	fmt.Printf("Hi,I am %s,I work at %s. Call me on %s\n",e.company,e.phone) //Yes you can split into 2 lines here.
}
func main() {
	mark := Student{Human{"Mark",I am Sam,I work at Golang Inc. Call me on 111-888-XXXX
  • 方法是可以继承和重写的
  • 存在继承关系时,按照就近原则,进行调用

结构体和方法补充

因为slice和map这两种数据类型都包含了指向底层数据的指针,因此在需要复制它们时要特别注意:

type Person struct {
	name   string
	age    int8
	dreams []string
}

func (p *Person) SetDreams(dreams []string) {
	p.dreams = dreams
}

func main() {
	p1 := Person{name: "张三",age: 18}
	data := []string{"吃饭","睡觉","打豆豆"}
	fmt.Printf("%p\n",data)		//0xc00006e360
	p1.SetDreams(data) 					//传的是 data 的内存地址,此时p.dreams和data指向同一块内存空间
	fmt.Printf("%p\n",p1.dreams)	//0xc00006e360

	// 你真的想要修改 p1.dreams 吗?
	data[1] = "不睡觉"				//data值的修改会影响person结构体的dream字段
	fmt.Println(p1.dreams)  // [吃饭 不睡觉 打豆豆]
}

正确的做法是在方法中使用传入的slice的拷贝进行结构体赋值。

func (p *Person) SetDreams(dreams []string) {
	p.dreams = make([]string,len(dreams))
	copy(p.dreams,dreams)
}

同样的问题也存在于返回值slice和map的情况,在实际编码过程中一定要注意这个问题。

相关文章

类型转换 1、int转string 2、string转int 3、string转float ...
package main import s "strings" import...
类使用:实现一个people中有一个sayhi的方法调用功能,代码如...
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件...
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http:...