go语言基础(四):继承、方法

本文深入探讨了面向对象编程的概念,通过修车的例子对比了面向过程和面向对象的区别。在Go语言中,虽然没有类的概念,但通过结构体和匿名字段可以实现类似的功能。介绍了如何定义对象、类的抽象以及方法的定义和调用,包括值接收者和指针接收者。此外,文章还讨论了Go语言中的方法继承、重写及其应用场景,强调了方法重写在子类中对父类方法功能定制的重要性。
摘要由CSDN通过智能技术生成
面向对象

在讲解具体面向对象编程之前,先说一下面向过程编程。我们前面学习都是面向过程的一种编程思想,接下来可以从生活中理解面向过程:

如果我们自己来修汽车,应该有哪些步骤呢?

第一步:找工具

第二步:判断问题的原因

第三步:暴力拆卸

这个修理的步骤就是面向过程,所谓的面向过程就是:强调的是步骤、过程、每一步都是自己亲自去实现的。

如果采用面向对象的思想,那么应该怎样修车呢?

找4s店的工作人员来帮我们修车,但是到底怎么修,我们是不用考虑的,也就是说我们不关心步骤与过程。

所谓的面向对象其实就是找一个专门做这个事的人来做,不用关心具体怎么实现的。

所以说,面向过程强调的是过程,步骤。而面向对象强调的是对象,也就是干事的人。

大家可以想一下,在生活中还有哪些事情是面向过程,面向对象的。

比如说,玩游戏,如果是面向过程,就是自己玩,自己打怪升级。如果是面向对象,可以找代练,一切打怪升级都有代练完成,不需要我们自己关心了。

比如说,做饭,面向过程就是自己做,自己买菜,自己洗,自己炒,整个过程都有自己来完成,但是如果是面向对象,可以叫外卖,不用关心饭是怎么做的。

所以通过以上案例,大家能够体会出,面向过程就是强调的步骤,过程,而面向对象强调的是对象,找个人来做。

在面向对象中,还有两个概念是比较重要的,一是对象,二是类。

什么是对象呢?

万物皆对象,例如张三同学是一个对象,李四同学也是一个对象

那么我们在生活中怎样描述一个对象呢?

比如,描述一下张三同学:

姓名:张三

性别:男

身高:180cm

体重:70kg

年龄:22岁

吃喝拉撒睡一切正常

通过以上的描述,可以总结出在生活中描述对象,可以通过特征(身高,体重,年龄等)和行为(爱好等)来进行描述。

那么在程序中,可以通过属性和方法(函数)来描述对象。属性就是特征,方法(函数)就是行为。所以说,对象必须具有属性和方法。虽然说,万物皆对象,但是在描述一个对象的时候,一定要具体不能泛指,例如,不能说“电灯”是一个对象,而是说具体的哪一台“电灯”。

下面我们在思考一下,下面这道题:

张三(一个学生)\杨老师\邻居售货员张阿姨\李四的爸爸\李四的妈妈

找出这道题中所有对象的共性(所谓共性,指的是相同的属性和方法)。

所以说,我们可以将这些具有相同属性和相同方法的对象进行进一步的封装,抽象出来类这个概念。

类就是个模子,确定了对象应该具有的属性和方法。

对象是根据类创建出来的

例如:上面的案例中,我们可以抽出一个“人”类(都有年龄,性别,姓名等属性,都有吃饭,走路等行为),“张三”这个对象就是根据“人”类创建出来的,也就是说先有类后有对象。

Go语言中的面向对象

前面我们了解了一下,什么是面向对象,以及类和对象的概念。但是,GO语言中的面向对象在某些概念上和其它的编程语言还是有差别的。

严格意义上说,GO语言中没有类(class)的概念,但是我们可以将结构体比作为类,因为在结构体中可以添加属性(成员),方法(函数)。

面向对象编程的好处比较多,我们先来说一下“继承”,

所谓继承指的是,我们可能会在一些类(结构体)中,写一些重复的成员,我们可以将这些重复的成员,

单独的封装到一个类(结构体)中,作为这些类的父类(结构体)。当然严格意义上,GO语言中是没有继承的,但是我们可以通过”匿名组合”来实现继承的效果。

匿名字段
创建与初始化

我们可以使用匿名字段,来做到属性的继承。那什么是匿名字段呢?

package main

import "fmt"

type Person struct {
	id   int
	name string
	age  int
}

type Student struct {
	Person
	score int
}

func main() {
	person := Person{name: "mark", id: 17, age: 25}
	student := Student{person, 990}
	fmt.Println(student)
	person.age = 20
	fmt.Println(student)
	// 操作成员
	fmt.Printf("age:%d\n", student.Person.age)
	fmt.Printf("age:%d\n", student.age)
	fmt.Printf("score:%d\n", student.score)
}

输出如下:

{{17 mark 25} 990}
{{17 mark 25} 990}
age:25
age:25
score:990

以上代码通过匿名字段实现了继承,将公共的属性封装在Person中,在Student中直接包含Person,那么Student中就有了Person中所有的成员,Person就是匿名字段。注意:Person匿名字段,只有类型,没有名字。

指针类型匿名对象

结构体(类)中的匿名字段的类型,也可以是指针。

package main

import "fmt"

type Person struct {
	id   int
	name string
	age  int
}

type Student struct {
	*Person
	score int
}

func main() {
	person := Person{name: "mark", id: 17, age: 25}
	student := Student{&person, 990}
	fmt.Println(student.Person)
	// 操作成员
	fmt.Printf("age:%d\n", student.age)
	fmt.Printf("score:%d\n", student.score)
}

输出如下:

&{17 mark 25}
age:25
score:990
方法

在Go语言中,函数和方法不太一样,有明确的概念区分。其他语言中,比如Java,一般来说,函数就是方法,方法就是函数,但是在Go语言中,函数是指不属于任何结构体、类型的方法,也就是说,函数是没有接收者的;而方法是有接收者的我们说的方法要么是属于一个结构体的,要么属于一个新定义的类型的

我们先定义一个传统的函数:

func test(a, b int) int {
	return a + b
}

func main() {
	result := test(1, 2)
	fmt.Println(result)
}

方法

方法的声明和函数类似,他们的区别是:方法在定义的时候,会在func和方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就和接收者绑定在了一起,称之为这个接收者的方法。在Go中,只有这样才能为一个结构体增加一个方法。

type person struct {
	name string
}

func (p person) String() string{
	return "the person name is "+p.name
}

留意例子中,func和方法名之间增加的参数(p person),这个就是接收者。现在我们说,类型person有了一个String方法,现在我们看下如何使用它。

func main() {
	p:=person{name:"张三"}
	fmt.Println(p.String())
}

调用的方法非常简单,使用类型的变量进行调用即可,类型变量和方法之前是一个.操作符,表示要调用这个类型变量的某个方法的意思。

Go语言里有两种类型的接收者:值接收者和指针接收者。我们上面的例子中,就是使用值类型接收者的示例。

使用值类型接收者定义的方法,在调用的时候,使用的其实是值接收者的一个副本,所以对该值的任何操作,不会影响原来的类型变量。

package main

import "fmt"

type Student struct {
   id int
   name string
   age int
}

func (stu Student ) PrintShow() {
	fmt.Println(stu)
}

func main() {
	//{101 zhangsan 19}
	s:= Student{101,"zhangsan",19}
	s.PrintShow()
}

用指针当接收者

package main

import "fmt"

type Student struct {
   id int
   name string
   age int
}

func (stu *Student ) PrintShow() {
	stu.age = 25
}

func main() {
	s:= Student{101,"zhangSan",19}
	fmt.Println("调用前:",s)
	s.PrintShow()
	fmt.Println("调用后:",s)
}

输出如下:

调用前: {101 zhangSan 19}
调用后: {101 zhangSan 25}
Go没有方法重载

在使用方法是,要注意如下几个问题:

第一:只要接收者类型不一样,这个方法就算同名,也是不同方法,不会出现重复定义函数的错误

type Person struct {
	id int
	name string
	level string
}

type Student struct {
   id int
   name string
   age int
}

func (stu Student ) PrintShow() {
}

func (teacher Person) PrintShow() {
}

但是,如果接收类型一样,但是方法的参数不一样,是会出现错误的,也就说,在GO中没有方法重载(所谓重载,指的是方法名称一致,参数类型,个数不一致)。

package main

import "fmt"

type Person struct {
	id int
	name string
	level string
}

type Student struct {
   id int
   name string
   age int
}

func (stu Student ) PrintShow() {
}

func (stu Student ) PrintShow(i int) {
}
方法继承
package main
import "fmt"

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)
}

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

上面由于 Student和Employee中都包含了Human,所以当使用mark.SayHi()或sam.SayHi()的时候,他会先查找本结构体中有没有包含SayHi()方法。然后它发现并没有包含,于是就在属性中去寻找,属性Human中有SayHi()方法,于是就调用了human的SayHi()方法。上面等价于:

mark.Human.SayHi()
sam.Human.SayHi()
方法重写

在前面的案例中,子类(结构体)可以继承父类中的方法,但是,如果父类中的方法与子类的方法是重名方法会怎样呢?

package main
import "fmt"

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

如果子类(结构体)中的方法名与父类(结构体)中的方法名同名,在调用的时候是先调用子类(结构体)中的方法,这就方法的重写。所谓的重写:就是子类(结构体)中的方法,将父类中的相同名称的方法的功能重新给改写了。

其实这也是就近原则,如果看懂了方法继承中的解释,这里也很容易理解。

为什么要重写父类(结构体)的方法呢?
通常,子类(结构体)继承父类(结构体)的方法,在调用对象继承方法的时候,调用和执行的是父类的实现。但是,有时候需要

对子类中的继承方法有不同的实现方式。

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

如果子类(结构体)中的方法名与父类(结构体)中的方法名同名,在调用的时候是先调用子类(结构体)中的方法,这就方法的重写。所谓的重写:就是子类(结构体)中的方法,将父类中的相同名称的方法的功能重新给改写了。

其实这也是就近原则,如果看懂了方法继承中的解释,这里也很容易理解。

为什么要重写父类(结构体)的方法呢?
通常,子类(结构体)继承父类(结构体)的方法,在调用对象继承方法的时候,调用和执行的是父类的实现。但是,有时候需要

对子类中的继承方法有不同的实现方式。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一彡十

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值