Golang通脉方法 Golang通脉方法详情
羌 人气:0方法和接收者
Go语言中的方法(Method
)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver
)。接收者的概念就类似于其他语言中的this或者 self。
Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集
方法只是一个函数,它带有一个特殊的接收器类型,它是在func关键字和方法名之间编写的。接收器可以是struct类型或非struct类型。接收方可以在方法内部访问。
方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。接收者可以是值接收者,也可以是指针接收者。
在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。
也就是说,不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。
方法的定义格式如下:
func (t Type) methodName(parameter)(return) { }
其中
t
:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self
、this
之类的命名。Type
:接收者类型和参数类似,可以是指针类型和非指针类型。methodName
、parameter
、return
:具体格式与函数定义相同。
//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, 2} s := p p.setVal() fmt.Println(p.height, s.height) // 2 2 }
什么时候应该使用指针类型接收者
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的大对象
- 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
方法和函数
已经有了函数,为什么还要使用方法?
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.。
为什么可以用函数来写相同的程序呢?有以下几个原因:
Go不是一种纯粹面向对象的编程语言,它不支持类。因此,类型的方法是一种实现类似于类的行为的方法。
相同名称的方法可以在不同的类型上定义,而具有相同名称的函数是不允许的。
任意类型添加方法
在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, I am %s you can call me on %s\n", h.name, 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.name, e.company, e.phone) //Yes you can split into 2 lines here. } 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, 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
的情况,在实际编码过程中一定要注意这个问题。
加载全部内容