Go语言面向对象编程 - Go语言学习笔记


面向对象编程

概述

go语言支持面向对象编程,但是没有面向对象的概念。
面向对象特性的go语言实现方式:

  1. 继承:通过匿名字段实现 ==> 代码复用,减少重复工作
  2. 封装:通过方法实现
  3. 多态:通过接口实现

匿名组合(继承)

匿名字段

定义

在一个结构体中定义另一个结构体的匿名字段,默认继承另一结构体的所有字段。

// 人
type Person struct {
    name string
    sex byte
    age int
}
// 学生
type Student struct {
    Person // 只有类型,没有名称。匿名字段,默认Student中就继承了Person的所有字段
    id int
    addr string
}

初始化

指定成员进行初始化,没有初始化的成员自动赋值为0,字符串类型自动为空。

func main() {
    // 顺序初始化
    var s1 Student = Student{Person{"mike", 'm', 18}, 1, "bj"}
    // 自动推导类型
    s2 := Student{Person{"mike", 'm', 18}, 1, "bj"}
    // %+v,显示更详细的信息
    fmt.Printf("s2 = %+v\n", s2)
    // 指定成员初始化,没有初始化的成员自动赋值为0,字符串类型自动为空
    s3 := Student{id:1}
    // 给继承来的内层元素赋值时,要再套一层
    s4 := Student{Person: Person{name: "mike"}, id:1}
}

成员操作

s1.name = "yoyo"
fmt.Println(s1.name)

同名字段

就近原则:如果能在本作用域找到此成员,如果没有找到,再去找继承的字段。

// 人
type Person struct {
    name string
    sex byte
    age int
}
// 学生
type Student struct {
    Person // 只有类型,没有名称。匿名字段,默认Student中就继承了Person的所有字段
    id int
    addr string
    name string // 和Person中的字段同名了
}
func main() {
    // 变量声明
    var s Student
    s.name = "mike" // 操作的是Student里面的name
    // 显式调用
    s.Person.name = "yoyo" // 操作的是Person里面的name
}

非结构体匿名字段

普通基础类型也可定义匿名字段。

type mystr string // 自定义类型,给一个类型改名
// 人
type Person struct {
    name string
    sex byte
    age int
}
// 学生
type Student struct {
    Person // 结构体匿名字段
    int // 普通基础类型的匿名字段
    mystr
}
func main() {
    s := Student{Person{"mike", 'm', 18}, 666, "hehehe"}
    fmt.Println(s.name, s.int, s.mystr)
}

结构体指针类型匿名字段

type mystr string // 自定义类型,给一个类型改名
// 人
type Person struct {
    name string
    sex byte
    age int
}
// 学生
type Student struct {
    *Person // 指针类型
    id int
}
func main(){
    s1 := Student{Person{"mike", 'm', 18}, 666, "bj"}
    fmt.Println(s1.name)
    // 先定义变量
    var s2 Student
    // 分配空间再赋值
    s2.Person = new(Person) 
    s2.name = "yoyo"
}

方法(封装)

在面向对象编程中,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些函数,这种带有接收者的函数,我们称为方法(method),本质上,一个方法则是一个和特殊类型关联的函数。
三种形式的函数:

func xxx(){
// 普通类型函数
}
 func (){
// 匿名函数
}
 func ( xxx Type ){
// 方法(指定入参类型--接收者)
}

不同接收者,可以定义同名方法。

面相过程和面相对象函数的区别

// 实现两数相加
// 面相过程函数
 func Add01(a ,b int) int {
     return a+b
 }
 
// 面相对象函数,方法:给某个类型绑定一个函数
type long int
// tmp叫接收者,接收者就是传递的一个参数
func (tmp long) Add02(other long) long {
    return tmp + other
}

func main() {
    var result int
    result = Add01(1, 1) // 面相过程函数调用
    
    // 定义一个变量
    var a long = 2
    // 调用方法格式:变量名.函数(所需参数)
    r := a.Add02(3) // 面相对象函数调用
    
    // 实质上,面相对象只是换了一种表现形式
}

为结构体类型添加方法

带有接收者的函数叫方法。

type Person struct {
    name string
    sex byte
    age int
}
// 带有接收者的函数叫方法
func (tmp Person) PrintInfo() {
 fmt.Println("tmp = ", tmp)
}

// 通过一个函数,给成员赋值
func (p * Person) SetInfo(n string, s byte, a int) {
    p.name = n
    p.sex = s
    p.age = a
}

func main() {
    // 定义同时初始化
    p := Person{"mike", 'm', 18}
    p.PrintInfo()
    
    // 定义一个结构体变量
    var p2 Person
    (&p2).SetInfo("yoyo", 'f', 22)
    p2.PrintInfo()
}

值语义和引用语义

值语义,接收者为普通变量,非指针,传递时将值拷贝一份;
引用语义,接收者为指针变量,值为引用传递。

type Person struct {
    name string
    sex byte
    age int
}

// 修改成员变量的值

// 接收者为普通变量,非指针,值语义=拷贝一份
func (p Person) SetInfoValue(n string, s byte, a int) {
    p.name = n
    p.sex = s
    p.age = a
}

// 接收者为指针变量,引用传递
func (p *Person) SetInfoPointer(n string, s byte, a int) {
    p.name = n
    p.sex = s
    p.age = a
}

func main() {
    var s1 Person
    // 值语义 赋值后,接收者变量地址改变
    s1.SetInfoValue("mike", 'm', 18)
    // 引用语义 赋值后,接收者变量地址不变
    (&s1).SetInfoPointer("mike", 'm', 18)
}

方法集

类型的方法集是指可以被该类型的值调用的所有方法的集合。
用实例value和pointer调用方法(含匿名字段)不受方法集的约束,编译器总是查找全部方法,并自动转换receiver实参。

type Person struct {
    name string
    sex byte
    age int
}

func (p Person) SetInfoValue() {
    fmt.Println("SetInfoValue")
}

// 接收者为指针变量,引用传递
func (p *Person) SetInfoPointer() {
    fmt.Println("SetInfoPointer")
}

func main() {
    p := &Person{"mike", 'm', 18}
    
    p.SetInfoPointer() // 调用时,会先把指针p转成*p后再调用 = (*p).SetInfoPointer()
    
    (*p).SetInfoValue() // 调用时,会先把指针*p转成p后再调用 = p.SetInfoValue()
}

方法的继承

结构体将另一结构体作为匿名字段继承,则另一结构体的成员和方法都被继承了。

type Person struct {
    name string
    sex byte
    age int
}
// Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
    fmt.Printf("name = %s",tmp.name)
}
// 有个学生,继承Person字段,成员和方法都继承了
type Student struct{
    Person // 匿名字段
    id int
    addr string
}

func main() {
    s := Student{Person{"mike",'m',18}, 666, "bj"}
    s.PrintInfo()
}

方法的重写(同名方法)

子结构体实现父结构体中的方法称为方法重写,调用时遵循就近原则。

type Person struct {
    name string
    sex byte
    age int
}
// Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
    fmt.Printf("name = %s",tmp.name)
}
// 有个学生,继承Person字段,成员和方法都继承了
type Student struct{
    Person // 匿名字段
    id int
    addr string
}

// Student也实现了一个方法,这个方法和Person方法同名,这种方法叫重写
func (tmp *Student) PrintInfo() {
    fmt.Println("Student: tmp =", tmp)
}

func main() {
    s := Student{Person{"mike",'m',18}, 666, "bj"}
    s.PrintInfo() // 就近原则,调用的是Student的PrintInfo
    
    // 显式调用继承的方法
    s.Person.PrintInfo()
}

方法值

隐藏接收者

pFunc := p.SetInfoPointer
pFunc()

vFunc := p.SetInfoValue
vFunc()

方法表达式

显示传递接收者

f := (*Person).SetInfoPointer
f(&p)

f2 := (Person).SetInfoValue
f2(p)

接口

在Go语言中,接口(interface)是一个自定义类型,接口类型具体描述了一系列方法的集合。
接口类型是一种抽象的类型,它不会暴露出它所代表的的对象内部值的结构和这个对象支持的基础操作的集合,它们只会展示出它们自己的方法。因此接口类型不能将其实例化
我们并不关心对象是什么类型,只关心行为。

接口定义与实现

  • 接口命名习惯以er结尾
  • 接口只有方法声明,没有实现,没有数据字段
  • 接口可以匿名嵌入其它接口,或嵌入到结构中
// 定义接口类型
type Humaner interface {
    // 方法只有声明,没有实现;由别的类型(自定义类型)实现
    sayhi()
}

type Student struct {
    name string
    id int
}

// Student实现了此方法
func (tmp *Student) sayhi() {
    fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}

type Teacher struct{
    addr string
    group string
}

// Teacher 实现了此方法
func (tmp *Teacher) sayhi() {
    fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)
}

type MyStr string

// MyStr 实现了此方法
func (tmp *MyStr) sayhi() {
    fmt.Printf("MyStr[%s] sayhi\n", *tmp)
}

func main() {
    // 定义接口类型的变量
    var i Humaner
    // 只要实现了此接口方法类型,那么这个类型的变量(接收者类型)就可以给i赋值
    
    s := &Student{"mike", 666}
    i = s
    i.sayhi()
    
    t := &Teacher{"bj", "go"}
    i = t
    i.sayhi()
    
    var str Mystr = "hello mike"
    i = &str
    i.sayhi()
}

多态

只有一个函数,可以有不同表现。

func WhoSayHi(i Humaner) {
    i.sayhi()
}
func main() {
    s := &Student{"mike", 666}
    t := &Teacher{"bj", "go"}
    var str Mystr = "hello mike"
    
    // 调用同一函数,不同表现 ==》多态
    WhoSayHi(s)
    WhoSayHi(t)
    WhoSayHi(&str)
    
    // 创建一个切片
    x := make([]Humaner,3)
    x[0] = s
    x[1] = t
    x[2] = &str
    
    // 第一个返回下标,第二个返回下标所对应的值
    for _, i := range x{
        i.sayhi()
    }
}

接口继承

在超集中定义子集的匿名字段,继承子集接口。

// 定义接口类型
type Humaner interface { // 子集
    sayhi()
}

type Personer interface { // 超集 (东西多的为超集)
    Humaner // 匿名字段,继承了sayhi()方法
    sing(lrc string)
}

// Student实现了 sayhi()
func (tmp *Student) sayhi() {
    fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}

// Student实现了 sing()
func (tmp *Student) sing(lrc string) {
    fmt.Println("Student 在唱着:",lrc)
}

func main() {
    // 定义一个接口类型的变量
    var i Personer
    s := &Student{"mike", 666}
    i = s
    
    i.sayhi() // 继承过来的方法
    i.sing("大海")
}

接口转换

超集可以转换为子集,反之不可以。(由大缩小可以)

// 定义接口类型
type Humaner interface { // 子集
    sayhi()
}

type Personer interface { // 超集 
    Humaner // 匿名字段,继承了sayhi()方法
    sing(lrc string)
}

// Student实现了 sayhi()
func (tmp *Student) sayhi() {
    fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}

// Student实现了 sing()
func (tmp *Student) sing(lrc string) {
    fmt.Println("Student 在唱着:",lrc)
}

func main() {
    var iPro Personer // 超集
    iPro = &Student{"mike", 666}
    var i Humaner // 子集
    
    iPro = i // error
    i = iPro // 可以
}

空接口

万能类型
空接口不包含任何方法,所以,所有类型都实现了空接口。
因此,空接口可以存储任意类型的数值。有点类似于C语言中的void * 类型。

var v1 interface{} = 1 // 将int类型赋值给 interface{}
var v1 interface{} = "abc" // 将 string 类型赋值给 interface{}

当函数可以接受任意对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,例如:

func Printf(fmt string, arg ...interface{})
func Println(arg ...interface{})

类型查询

一种操作手法,用来查询接口类型。

if实现类型断言

func main() {
    i := make([]interface{}, 3)
    i[0] = 1 // int
    i[1] = "hello go" // string
    i[2] = &Student{"mike", 666} // Student
    
    // 类型查询,类型断言
    // 第一个返回下标,第二个返回下标对应的值;data分别是i[0],i[1],i[2]
    for index, data := range i {
        // 第一个返回值为接口变量本身,第二个为判断结果真假
        if value, ok := data.(int) ; ok == true {
            fmt.Printf("x[%d] 类型为int , 内容为 %d \n", index, value)
        } else if value, ok := data.(string) ; ok == true {
            fmt.Printf("x[%d] 类型为 string , 内容为 %s \n", index, value)
        } else if value, ok := data.(Student) ; ok == true {
            fmt.Printf("x[%d] 类型为 Student , 内容为 name = %s, id = %d \n", index, value.name, value.id)
        }
    }
}

switch实现类型断言

func main() {
    i := make([]interface{}, 3)
    i[0] = 1 // int
    i[1] = "hello go" // string
    i[2] = &Student{"mike", 666} // Student
    
    // 类型查询,类型断言
    // 第一个返回下标,第二个返回下标对应的值;data分别是i[0],i[1],i[2]
    for index, data := range i {
        switch value := data.(type) {
            case int :
                fmt.Printf("x[%d] 类型为int , 内容为 %d \n", index, value)
            case string :
                fmt.Printf("x[%d] 类型为 string , 内容为 %s \n", index, value)
            case Student:
                fmt.Printf("x[%d] 类型为 Student , 内容为 name = %s, id = %d \n", index, value.name, value.id)
        }
    }
}

本文发表于2019年12月26日 21:32
阅读 147 讨论 0 喜欢 0

讨论

周娱

君子和而不同
按照自己的方式,去度过人生

8031 3921127
抢先体验

扫码体验
趣味小程序
文字表情生成器

加入组织

扫码添加周娱微信
备注“加入组织”
邀请进开发群

闪念胶囊

人活一辈子,不是一年两年。时间是有连续性的,做抉择的时候要多看几步。保持警惕,大丈夫有所为,有所不为。

跟人接触,不要想:我能从你身上得到什么,要想:我能给你什么。 想通了,内核就稳了。

这个世界上,别人只会看你现在的样子而不是以后的样子。你以后的样子只有自己才相信。如果没有执行力,一切都是虚妄。

对普通人来说,人和人相处其实最重要的是感觉。感觉不好,你说什么都没用,怎么解释都没用,越说越错,反正最后不好的锅都往你身上扣。所谓“说你行你就行,不行也行。说你不行,你就不行,行也不行”就是这个意思。狼要吃人根本不需要理由,你也同样叫不醒装睡的人。遇到这种情况,早点闪人才是上策。不过大部分人的问题是没有闪人的心态,能力,和资源。

考985不牛逼,考上才牛逼。创业不牛逼,创业成功才牛逼。这个社会上很多人把目标当成牛逼的资本,牛逼哄哄的,死活不听劝,然后做的一塌糊涂,给别人添麻烦,让别人帮他料理后事,对此只能呵呵。

当你尝到用生气解决问题的甜头后,你就懒得再用其他方式了。你却忽略了,生气是鸩毒啊,剂量用够了,你的关系也玩完了。

年轻的时候你只搞事业不谈恋爱,等你事业有成了,钱相对自由了,你可能已经没有荷尔蒙了。

如果你经常雇佣比你矮小的人,将来我们就会变成矮人国,变成一家侏儒公司。相反,如果你每次都雇用比你高大的人,日后我们必能成为一家巨人公司。

如果一个人有充裕的时间去完成一项工作,那么他就会放慢节奏或者增加其他不必要的工作,直到花光所有的时间。

Copyright © 2016 - 2020 Cion.
All Rights Reserved.
备案:鲁ICP备19012333号-4.