35、Go 结构体 - 面向对象之方法案例

作者: 温新

分类: 【Go基础】

阅读: 543

时间: 2023-12-05 00:28:13

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 方法,而 StudentEmployee 两个结构体没有添加方法,但是在这两个结构体中使用了匿名字段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

从案例中可以看出,当结构体存在关系时,方法调用按照就近原则。

请登录后再评论