15. 理解 Go 语言面向对象编程:结构体与继承

Hi,大家好,我是明哥。

在自己学习 Golang 的这段时间里,我写了详细的学习笔记放在我的个人微信公众号 《Go编程时光》,对于 Go 语言,我也算是个初学者,因此写的东西应该会比较适合刚接触的同学,如果你也是刚学习 Go 语言,不防关注一下,一起学习,一起成长。

我的在线博客:http://golang.iswbm.com
我的 Github:github.com/iswbm/GolangCodingTime


0. 什么是结构体?

在之前学过的数据类型中,数组与切片,只能存储同一类型的变量。若要存储多个类型的变量,就需要用到结构体,它是将多个容易类型的命令变量组合在一起的聚合数据类型。

每个变量都成为该结构体的成员变量。

可以理解为 Go语言 的结构体struct和其他语言的class有相等的地位,但是Go语言放弃大量面向对象的特性,所有的Go语言类型除了指针类型外,都可以有自己的方法,提高了可扩展性。

在 Go 语言中没有没有 class 类的概念,只有 struct 结构体的概念,因此也没有继承,本篇文章,带你学习一下结构体相关的内容。

1. 定义结构体

声明结构体

type 结构体名 struct {
    属性名   属性类型
    属性名   属性类型
    ...
}

比如我要定义一个可以存储个人资料名为 Profile 的结构体,可以这么写

type Profile struct {
    name   string
    age    int
    gender string
    mother *Profile // 指针
    father *Profile // 指针
}

2. 定义方法

在 Go 语言中,我们无法在结构体内定义方法,那如何给一个结构体定义方法呢,答案是可以使用组合函数的方式来定义结构体方法。它和普通函数的定义方式有些不一样,比如下面这个方法

func (person Profile) FmtProfile() {
    fmt.Printf("名字:%s\n", person.name)
    fmt.Printf("年龄:%d\n", person.age)
    fmt.Printf("性别:%s\n", person.gender)
}

其中fmt_profile 是方法名,而(person Profile) :表示将 fmt_profile 方法与 Profile 的实例绑定。我们把 Profile 称为方法的接收者,而 person 表示实例本身,它相当于 Python 中的 self,在方法内可以使用 person.属性名 的方法来访问实例属性。

完整代码如下:

package main

import "fmt"

// 定义一个名为Profile 的结构体
type Profile struct {
    name   string
    age    int
    gender string
    mother *Profile // 指针
    father *Profile // 指针
}

// 定义一个与 Profile 的绑定的方法
func (person Profile) FmtProfile() {
    fmt.Printf("名字:%s\n", person.name)
    fmt.Printf("年龄:%d\n", person.age)
    fmt.Printf("性别:%s\n", person.gender)
}

func main() {
    // 实例化
    myself := Profile{name: "小明", age: 24, gender: "male"}
    // 调用函数
    myself.FmtProfile()
}

输出如下

名字:小明
年龄:24
性别:male

3. 方法的参数传递方式

上面定义方法的方式叫当你想要在方法内改变实例的属性的时候,必须使用指针做为方法的接收者。

package main

import "fmt"

// 声明一个 Profile 的结构体
type Profile struct {
    name   string
    age    int
    gender string
    mother *Profile // 指针
    father *Profile // 指针
}

// 重点在于这个星号: *
func (person *Profile) increase_age() {
    person.age += 1
}

func main() {
    myself := Profile{name: "小明", age: 24, gender: "male"}
    fmt.Printf("当前年龄:%d\n", myself.age)
    myself.increase_age()
    fmt.Printf("当前年龄:%d", myself.age)
}

输出结果 如下,可以看到在方法内部对 age 的修改已经生效。你可以尝试去掉 *,使用值做为方法接收者,看看age是否会发生改变。

当前年龄:24
当前年龄:25

至此,我们知道了两种定义方法的方式:

  • 以值做为方法接收者
  • 以指针做为方法接收者

那我们如何进行选择呢?以下几种情况,应当直接使用指针做为方法的接收者。

  1. 你需要在方法内部改变结构体内容的时候
  2. 出于性能的问题,当结构体过大的时候

有些情况下,以值或指针做为接收者都可以,但是考虑到代码一致性,建议都使用指针做为接收者。

不管你使用哪种方法定义方法,指针实例对象、值实例对象都可以直接调用,而没有什么约束。这一点Go语言做得非常好。

4. 结构体实现 “继承”

为什么标题的继承,加了双引号,因为Go 语言本身并不支持继承。

但我们可以使用组合的方法,实现类似继承的效果。

在生活中,组合的例子非常多,比如一台电脑,是由机身外壳,主板,CPU,内存等零部件组合在一起,最后才有了我们用的电脑。

同样的,在 Go 语言中,把一个结构体嵌入到另一个结构体的方法,称之为组合。

现在这里有一个表示公司(company)的结构体,还有一个表示公司职员(staff)的结构体。

type company struct {
    companyName string
    companyAddr string
}

type staff struct {
    name string
    age int
    gender string
    position string
}

若要将公司信息与公司职员关联起来,一般都会想到将 company 结构体的内容照抄到 staff 里。

type staff struct {
    name string
    age int
    gender string
    companyName string
    companyAddr string
    position string
}

虽然在实现上并没有什么问题,但在你对同一公司的多个staff初始化的时候,都得重复初始化相同的公司信息,这做得并不好,借鉴继承的思想,我们可以将公司的属性都“继承”过来。

但是在 Go 中没有类的概念,只有组合,你可以将 company 这个 结构体嵌入到 staff 中,做为 staff 的一个匿名字段,staff 就直接拥有了 company 的所有属性了。

type staff struct {
    name string
    age int
    gender string
    position string
    company   // 匿名字段 
}

来写个完整的程序验证一下。

package main

import "fmt"

type company struct {
    companyName string
    companyAddr string
}

type staff struct {
    name string
    age int
    gender string
    position string
    company
}

func main()  {
    myCom := company{
        companyName: "Tencent",
        companyAddr: "深圳市南山区",
    }
    staffInfo := staff{
        name:     "小明",
        age:      28,
        gender:   "男",
        position: "云计算开发工程师",
        company: myCom,
    }

    fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.companyName)
    fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.company.companyName)
}

输出结果如下,可见staffInfo.companyNamestaffInfo.company.companyName 的效果是一样的。

小明 在 Tencent 工作
小明 在 Tencent 工作

5. 内部方法与外部方法

在 Go 语言中,函数名的首字母大小写非常重要,它被来实现控制对方法的访问权限。

  • 当方法的首字母为大写时,这个方法对于所有包都是Public,其他包可以随意调用
  • 当方法的首字母为小写时,这个方法是Private,其他包是无法访问的。

系列导读

01. 开发环境的搭建(Goland & VS Code)

02. 学习五种变量创建的方法

03. 详解数据类型:****整形与浮点型

04. 详解数据类型:byte、rune与string

05. 详解数据类型:数组与切片

06. 详解数据类型:字典与布尔类型

07. 详解数据类型:指针

08. 面向对象编程:结构体与继承

09. 一篇文章理解 Go 里的函数

10. Go语言流程控制:if-else 条件语句

11. Go语言流程控制:switch-case 选择语句

12. Go语言流程控制:for 循环语句

13. Go语言流程控制:goto 无条件跳转

14. Go语言流程控制:defer 延迟调用

15. 面向对象编程:接口与多态

16. 关键字:make 和 new 的区别?

17. 一篇文章理解 Go 里的语句块与作用域

18. 学习 Go 协程:goroutine

19. 学习 Go 协程:详解信道/通道

20. 几个信道死锁经典错误案例详解

21. 学习 Go 协程:WaitGroup

22. 学习 Go 协程:互斥锁和读写锁

23. Go 里的异常处理:panic 和 recover

24. 超详细解读 Go Modules 前世今生及入门使用

25. Go 语言中关于包导入必学的 8 个知识点

26. 如何开源自己写的模块给别人用?

27. 说说 Go 语言中的类型断言?

28. 这五点带你理解Go语言的select用法

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343