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
阅读 2254 讨论 0 喜欢 1

抢先体验

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

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1