Go语言基础之面向对象编程

  • Go语言并没有沿袭传统面向对象编程中的诸多概念
  • 封装:通过方法实现
  • 继承:通过匿名字段实现
  • 多态:通过接口实现

一、匿名组合

1. 匿名字段

  • 匿名字段(嵌入字段):
    • go支持只提供类型,而不写字段名的方式
  • 匿名字段也是结构体时:
    • 这个结构体所拥有的全部字段都被隐式地引入了当前定义的这个结构体
//人
type Person struct {
    name string
    sex  byte
    age  int
}
//学生
type Student struct {
    Person // 匿名字段,那么默认Student就包含了Person的所有字段
    id     int
    addr   string
}

2. 初始化

//人
type Person struct {
    name string
    sex  byte
    age  int
}

//学生
type Student struct {
    Person // 匿名字段,那么默认Student就包含了Person的所有字段
    id     int
    addr   string
}

func main() {
    //顺序初始化
    s1 := Student{Person{"mike", 'm', 18}, 1, "sz"}
    //s1 = {Person:{name:mike sex:109 age:18} id:1 addr:sz}
    fmt.Printf("s1 = %+v\n", s1)

    //s2 := Student{"mike", 'm', 18, 1, "sz"} //err

    //部分成员初始化1
    s3 := Student{Person: Person{"lily", 'f', 19}, id: 2}
    //s3 = {Person:{name:lily sex:102 age:19} id:2 addr:}
    fmt.Printf("s3 = %+v\n", s3)

    //部分成员初始化2
    s4 := Student{Person: Person{name: "tom"}, id: 3}
     //s4 = {Person:{name:tom sex:0 age:0} id:3 addr:}
    fmt.Printf("s4 = %+v\n", s4)
}

3. 成员的操作

var s1 Student //变量声明
    //给成员赋值
    s1.name = "mike" //等价于 s1.Person.name = "mike"
    s1.sex = 'm'
    s1.age = 18
    s1.id = 1
    s1.addr = "sz"
    fmt.Println(s1) //{{mike 109 18} 1 sz}

    var s2 Student //变量声明
    s2.Person = Person{"lily", 'f', 19}
    s2.id = 2
    s2.addr = "bj"
    fmt.Println(s2) //{{lily 102 19} 2 bj}

4. 同名字段

//人
type Person struct {
    name string
    sex  byte
    age  int
}

//学生
type Student struct {
    Person // 匿名字段,那么默认Student就包含了Person的所有字段
    id     int
    addr   string
    name   string //和Person中的name同名
}

func main() {
    var s Student //变量声明

    //给Student的name,还是给Person赋值?
    s.name = "mike"

    //{Person:{name: sex:0 age:0} id:0 addr: name:mike}
    fmt.Printf("%+v\n", s)

    //默认只会给最外层的成员赋值
    //给匿名同名成员赋值,需要显示调用
    s.Person.name = "yoyo"
    //Person:{name:yoyo sex:0 age:0} id:0 addr: name:mike}
    fmt.Printf("%+v\n", s)
}

5. 其它匿名字段

1. 非结构体类型
  • 所有的内置类型和自定义类型都是可以作为匿名字段的
type mystr string //自定义类型

type Person struct {
    name string
    sex  byte
    age  int
}

type Student struct {
    Person // 匿名字段,结构体类型
    int     // 匿名字段,内置类型
    mystr   // 匿名字段,自定义类型
}

func main() {
    //初始化
    s1 := Student{Person{"mike", 'm', 18}, 1, "bj"}

    //{Person:{name:mike sex:109 age:18} int:1 mystr:bj}
    fmt.Printf("%+v\n", s1)

    //成员的操作,打印结果:mike, m, 18, 1, bj
    fmt.Printf("%s, %c, %d, %d, %s\n", s1.name, s1.sex, s1.age, s1.int, s1.mystr)
}
2. 结构体类型指针
type Person struct { //人
    name string
    sex  byte
    age  int
}

type Student struct { //学生
    *Person // 匿名字段,结构体指针类型
    id      int
    addr    string
}

func main() {
    //初始化
    s1 := Student{&Person{"mike", 'm', 18}, 1, "bj"}

    //{Person:0xc0420023e0 id:1 addr:bj}
    fmt.Printf("%+v\n", s1)
    //mike, m, 18
    fmt.Printf("%s, %c, %d\n", s1.name, s1.sex, s1.age)

    //声明变量
    var s2 Student
    s2.Person = new(Person) //分配空间
    s2.name = "yoyo"
    s2.sex = 'f'
    s2.age = 20

    s2.id = 2
    s2.addr = "sz"

    //yoyo 102 20 2 20
    fmt.Println(s2.name, s2.sex, s2.age, s2.id, s2.age)
}

二、方法

  • 带有接收者的函数,我们称为方法(method)

  • 本质上,一个方法则是一个和特殊类型关联的函数

  • ⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:

    func (receiver ReceiverType) funcName(parameters) (results)

    • 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
    • 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接⼝或指针。
    • 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
  • 在函数定义时,在其名字之前放上一个变量,即是一个方法

1. 为类型添加方法

基础类型作为接受者
type MyInt int //自定义类型,给int改名为MyInt

//在函数定义时,在其名字之前放上一个变量,即是一个方法
func (a MyInt) Add(b MyInt) MyInt { //面向对象
    return a + b
}

//传统方式的定义
func Add(a, b MyInt) MyInt { //面向过程
    return a + b
}

func main() {
    var a MyInt = 1
    var b MyInt = 1

    //调用func (a MyInt) Add(b MyInt)
    fmt.Println("a.Add(b) = ", a.Add(b)) //a.Add(b) =  2

    //调用func Add(a, b MyInt)
    fmt.Println("Add(a, b) = ", Add(a, b)) //Add(a, b) =  2
结构体作为接收者
type Person struct {
    name string
    sex  byte
    age  int
}

func (p Person) PrintInfo() { //给Person添加方法
    fmt.Println(p.name, p.sex, p.age)
}

func main() {
    p := Person{"mike", 'm', 18} //初始化
    p.PrintInfo() //调用func (p Person) PrintInfo()
值语义和引用语义
方法集
  1. 类型 *T 方法集
  • 一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。
  • 如果在指针上调用一个接受值的方法,Go语言会聪明地将该指针解引用,并将指针所指的底层值作为方法的接收者
  • 类型 *T ⽅法集包含全部 receiver T + *T ⽅法
  1. 类型 T 方法集
  • 一个自定义类型值的方法集则由为该类型定义的接收者类型为值类型的方法组成,但是不包含那些接收者类型为指针的方法。
  • 但这种限制通常并不像这里所说的那样,因为如果我们只有一个值,仍然可以调用一个接收者为指针类型的方法,这可以借助于Go语言传值的地址能力实现
匿名字段

三、接口

1. 概述

  • 在Go语言中,接口(interface)是一个自定义类型,接口类型具体描述了一系列方法的集合

2. 接口的使用

1. 定义
  • 接⼝命名习惯以 er 结尾
  • 接口只有方法声明,没有实现,没有数据字段
  • 接口可以匿名嵌入其它接口,或嵌入到结构中
    type Humaner interface {
        SayHi()
    }
    
2. 接口实现
  • 接口是用来定义行为的类型。
  • 被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现
  • 一个实现了这些方法的具体类型是这个接口类型的实例。

3. 接口组合

  • 如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的方法
  • 接口转换:
    • 超集接⼝对象可转换为⼦集接⼝,反之出错

4. 空接口:

  • 空接口(interface{})不包含任何的方法
  • 空接口可以存储任意类型的数值
  • 类似于C语言的void *类型

5. 类型查询

怎么反向知道这个变量里面实际保存了的是哪个类型的对象:comma-ok断言、switch测试

1. comma-ok断言

value, ok = element.(T)

  • value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型
2. switch测试

该笔记是本人通过学习视频,做的学习笔记~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章