35、Go 结构体 - 面向对象之方法案例
hi,我是温新
了解了方法的声明与返回值,现在通过案例来演示方法。
下面是一个基本的方法案例,同时也是一个值案例。
package main
import "fmt"
func main() {
p1 := NewPerson1("王美丽", 19)
p1.GetName()
}
// 定义一个 Person1 结构体
type Person1 struct {
name string
age int8
}
/**
* 定义一个 NewPerson1 构造函数
* 返回值为指针类型的 Person1
*/
func NewPerson1(name string, age int8) *Person1 {
return &Person1{
name: name,
age: age,
}
}
// 为 Person1 添加一个方法
func (p Person1) GetName() {
fmt.Println(p.name)
}
在这个案例中,需要注意,定义了一个构造方法用于初始化数据。
为类型添加方法
结构体作为接收者
package main
import "fmt"
func main() {
// 值类型调用方法
u1 := User{"王美丽", "wangmeili@ziruchu.com"}
u1.GetUserInfo()
// 指针类型调用方法
u2 := User{"郝帅", "hao@ziruchu.com"}
u3 := &u2
u3.GetUserInfo() // 这样调用
(*u3).GetUserInfo() // 也可以这样调用
}
type User struct {
name, email string
}
func (u *User) GetUserInfo() {
fmt.Printf("%v : %v \n", u.name, u.email)
}
在这个案例中,使用值方法时,有两种调用方法的方式,u3.GetUserInfo()
和 (*u3).GetUserInfo()
。
注意:当接收者是指针时,即使用值类型调用那么函数内部也是对指针的操作。
也就是说,
u3.GetUserInfo()
仍旧是对指针的操作。
输出结果:
王美丽 : wangmeili@ziruchu.com
郝帅 : hao@ziruchu.com
郝帅 : hao@ziruchu.com
基础类型作为接收者
package main
import "fmt"
func main() {
var a MyInt = 1
var b MyInt = 1
// 方法调用
fmt.Println(a.Add(b))
// 函数调用
fmt.Println(Add(a, b))
}
// 自定义类型,将 int 修改为 MyInt
type MyInt int
// 定义一个方法
func (m MyInt) Add(n MyInt) MyInt {
return m + n
}
// 定义一个函数
func Add(x, y MyInt) MyInt { //面向过程
return x + y
}
方法是函数的语法糖,而接收者其实是方法所接收的第一个参数。
在这个案例中,虽然方法和函数的名称都是 Add
,但接收者是不一样的,因此方法也就不一样。
a.Add(b)
的接收者是自定义类型 MyInt
。
值语义和引用语义
package main
import "fmt"
func main() {
// 指针类型作为接收者
p1 := Person2{"匿名者1", 1}
fmt.Println("调用前=", p1)
p1.SetAge1()
fmt.Println("调用后=", p1)
fmt.Println("==============")
p2 := Person2{"匿名者2", 2}
fmt.Println("调用前=", p2)
p2.SetAge2()
fmt.Println("调用后=", p2)
}
type Person2 struct {
name string
age int8
}
// 定义一个方法,其接收器为指针类型(引用)
func (p *Person2) SetAge1() {
p.name = "王美丽"
p.age = 19
}
// 定义一个方法,其接收器为值类型
func (p Person2) SetAge2() {
p.name = "赵帅"
p.age = 30
}
输出结果如下:
调用前= {匿名者1 1}
调用后= {王美丽 19}
==============
调用前= {匿名者2 2}
调用后= {匿名者2 2}
从输出结果中可以到,指针引用会影响数据,作为值使用,并不会影响数据。
函数和方法的区别
package main
import "fmt"
func main() {
GetValue()
GetName()
}
/**
* 普通函数
*/
// 值类型函数
func ValueInt(a int) int {
return a + 1
}
// 指针类型
func PointerInt(a *int) int {
return *a + 1
}
func GetValue() {
a := 2
fmt.Println("ValueInt:", ValueInt(a))
// 参数为值类型,不能接收指针类型的参数的
// fmt.Println("ValueInt:", ValueInt(&a))
// Cannot use '&a' (type *int) as the type int
b := 4
fmt.Println("PointerValue:", PointerInt(&b))
// 参数为指针类型,不能接收值类型作为参数
//fmt.Println("PointerValue:", PointerInt(b))
// Cannot use 'b' (type int) as the type *int
}
/**
* 方法
*/
type Person3 struct {
name string
age int8
}
// 接收者为值类型
func (p Person3) getValueName() {
fmt.Println(p.name)
}
// 接收者为指针类型
func (p Person3) getPonitValueName() {
fmt.Println(p.name)
}
func GetName() {
// 值类型
p1 := Person3{"王小丽", 19}
p1.getValueName()
p1.getPonitValueName()
// 指针类型
p2 := &Person3{"王美丽", 20}
p2.getValueName()
p2.getPonitValueName()
}
输出结果如下:
ValueInt: 3
PointerValue: 5
王小丽
王小丽
王美丽
王美丽
函数和方法的区别:
- 1、对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。
- 2、对于方法,接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以。
匿名字段与方法继承
匿名字段
Go 中的匿名字段,可以把字段当成成员那样访问匿名字段。
package main
import "fmt"
func main() {
u := Manager{User1{"王美丽"}}
fmt.Printf("Manager: %p\n", &u)
fmt.Println(u.SayName())
}
type User1 struct {
name string
}
type Manager struct {
User1 // 匿名字段
}
func (u *User1) SayName() string {
return u.name
}
通过匿名字段可以实现方法的继承。下面来看看方法继承的案例。
方法继承
如果匿名字段实现了一个方法,那么包含这个匿名字段 的 struct 也能调用该匿名字段中的方法。
package main
import "fmt"
func main() {
s1 := Student{Human{"王美丽"}, "一中学"}
s1.Say()
e1 := Employee{Human{"王大力"}, "一公司"}
e1.Say()
}
type Human struct {
name string
}
type Student struct {
Human // 匿名字段
school string
}
type Employee struct {
Human // 匿名字段
componay string
}
func (h *Human) Say() {
fmt.Printf("Hi,我是 %s\n", h.name)
}
输出结果:
Hi,我是 王美丽
Hi,我是 王大力
在这个案例中,为 Human
添加一个 Say
方法,而 Student
和Employee
两个结构体没有添加方法,但是在这两个结构体中使用了匿名字段Human
,而Human
中添加了Say
方法,因此 Student 和 Employee 就这样实现了方法的继承,因此也就可以调用 Say 方法。
多重继承
多重继承在生活中比较常见,如孩子继承父母的特征,而父母就是两个父级类。
在 Go 语言中,多重继承不支持多重嵌套(即父级类型内部不允许有匿名结构体字段)。
package main
import "fmt"
func main() {
cp := new(CameraPhone)
fmt.Println("手机有很多功能:")
fmt.Println("相机:", cp.TakeAPicture())
fmt.Println("手机:", cp.Call())
}
type Camera struct {
}
type Phone struct {
}
// 多重继承
type CameraPhone struct {
Camera
Phone
}
func (c *Camera) TakeAPicture() string {
return "拍照"
}
func (p *Phone) Call() string {
return "打电话"
}
方法重写
在 Go 语言中,方法重写是指一个包含了匿名字段 的 struct 也实现了该匿名字段实现的方法。案例如下:
package main
import "fmt"
func main() {
s1 := Student1{Human1{"王美丽"}, "一中学"}
s1.Say1()
}
type Human1 struct {
name string
}
type Student1 struct {
Human1 // 匿名字段
school string
}
func (h *Human1) Say1() {
fmt.Printf("Hi,我是 %s\n", h.name)
}
// 方法重写
func (s *Student1) Say1() {
fmt.Printf("Hi,我是 %s,我调用的方法是 Say1\n", s.name)
}
输出结果:
Hi,我是 王美丽,我调用的方法是 Say1
从案例中可以看出,当结构体存在关系时,方法调用按照就近原则。