一个程序猿的奋斗史php开发

Go语言学习笔记15.面向对象

2019-11-06  本文已影响0人  快乐的提千万

概述

go的面向对象,可以说是C和C++的夹生版本。
利用结构体来表示一个类,如果有继承就用结构体套结构体,这个是C的做法。
但是go添加了方法这个概念,让一些函数属于某个结构体。
至于多态,则是引入了接口的概念,也有了面向接口编程的概念。

创建类(结构体)

package main

import "fmt"

type Person struct {
    name string 
    sex  byte  
    age  int   
}

type Student struct {
    Person //只有类型,没有名字,匿名字段,继承了Person的成员
    id     int
    addr   string
    name   string //和Person同名了
}

//结构体指针类型
type Student2 struct {
    *Person //指针类型
    id      int
    addr    string
}

func main() {
    //顺序初始化
    var s1 Student = Student{Person{"father", 'm', 18}, 1, "bj","child"}
    fmt.Println("s1 = ", s1)
    //自动推导类型
    s2 := Student{Person{"father", 'm', 18}, 1, "bj","child"}
    fmt.Printf("s2 = %+v\n", s2)//%+v, 显示更详细
    //指定成员初始化,没有初始化的自动赋值为0
    s3 := Student{id: 1}
    fmt.Printf("s3 = %+v\n", s3)

    //赋值
    s1.Person = Person{"go", 'm', 18}
    fmt.Printf("s1 = %+v\n", s1)

    //默认:在儿子这里找得到就用儿子的,否则就是父亲的。
    s1.name = "teemo" //操作的是Student的name
    fmt.Printf("s1 = %+v\n", s1)//s1 = {Person:{name:go sex:109 age:18} id:1 addr:bj name:teemo}

    //显式调用,非要用父亲或儿子的
    s1.Person.name = "fteemo" //Person的name
    fmt.Printf("s1 = %+v\n", s1)//s1 = {Person:{name:fteemo sex:109 age:18} id:1 addr:bj name:teemo}
    
    //指针匿名字段的初始化
    s4 := Student2{&Person{"teemo", 'm', 18}, 666, "bj"}
    fmt.Println(s4.name, s4.sex, s4.age, s4.id, s4.addr)

    //指针匿名字段可以用new申请空间
    var s5 Student2
    s5.Person = new(Person) //分配空间
    s5.name = "yoyo"
    fmt.Println(s5.name, s5.sex, s5.age, s5.id, s5.addr)
}

定义方法

package main

import "fmt"

type Person struct {
    name string 
    sex  byte  
    age  int   
}

//自定义类型,MyInt其实就可以看做是类名,这里int可以改成任意type,struct只是里面的一种
type MyInt int

//这里只能传MyInt,不然会报错:invalid operation: a + b (mismatched types MyInt and int)
//a1 MyInt 就是 表明这个方法属于MyInt,专业属于叫指定接收者
//b1属于参数
func (a1 MyInt) Add(b1 MyInt) MyInt  {
    return a1 + b1
}

//给结构体添加方法
func (p Person) getInfo()  {
    fmt.Println(p.name)
}

func main()  {
    var a MyInt = 1
    fmt.Println("a.Add(2) = ",a.Add(2))
    fmt.Printf("a= %+v\n", a)//a = 1
    
    var b MyInt = 1
    fmt.Println("a.Add(b) = ",a.Add(b))

    p1 := Person{"teemo",'a',1}
    p1.getInfo()
}

这个接收者也可以传值和传引用。

package main

import "fmt"

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
    fmt.Println("p = ", p) //p =  {teemo 109 18}
    fmt.Printf("SetInfoValue &p = %p\n", &p)//SetInfoValue &p = 0xc00004c480  和s1的地址不一样
}

//接收者为指针变量,引用传递
func (p *Person) SetInfoPointer(n string, s byte, a int) {
    p.name = n
    p.sex = s
    p.age = a
    fmt.Printf("SetInfoPointer p = %p\n", p)//SetInfoPointer p = 0xc00004c460  和s2的地址一样的
}

func main() {
    s1 := Person{"go", 'm', 22}
    fmt.Printf("&s1 = %p\n", &s1) //&s1 = 0xc00004c420
    s2 := Person{"go", 'm', 22}
    fmt.Printf("&s2 = %p\n", &s2) //&s2 = 0xc00004c460

    //值语义
    s1.SetInfoValue("teemo", 'm', 18)
    fmt.Println("s1 = ", s1) //s1 =  {go 109 22}  原始值没变

    //引用语义
    (&s2).SetInfoPointer("teemo", 'm', 18)
    fmt.Println("s2 = ", s2) //s2 =  {teemo 109 18}  原始值变了
}

方法的继承和多态

父类的方法子类可以直接调用,并没有私有的概念,方法名大写小写子类都可以用。
子类重写了方法,会优先调用子类的,也可以显示调用父类的。

package main

import "fmt"

type Person struct {
    name string //名字
    sex  byte   //性别, 字符类型
    age  int    //年龄
}

//Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
    fmt.Printf("name=%s, sex=%c, age=%d\n", tmp.name, tmp.sex, tmp.age)
}

//Person类型,试试小写会不会私有
func (tmp *Person) printInfo() {
    fmt.Printf("name=%s, sex=%c, age=%d\n", tmp.name, tmp.sex, tmp.age)
}


//有个学生,继承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{"teemo", 'm', 18}, 666, "bj"}
    s.PrintInfo() //默认调用子类的

    //显式调用父类的的方法
    s.Person.PrintInfo()

    //小写还是可以调用
    s.Person.printInfo()
}

方法值和方法表达式

如果将某个类的实例化的方法赋值给一个变量,则这个变量叫方法值,直接使用这个变量就可以调用方法。
如果将某个类的方法抽象赋值给一个变量,则这个变量加方法表达式,传递实例化作为参数就可以调用方法。
举个栗子:

package main

import "fmt"

type Person struct {
    name string //名字
    sex  byte   //性别, 字符类型
    age  int    //年龄
}

//值传递
func (p Person) SetInfoValue() {
    fmt.Printf("SetInfoValue: %p, %v\n", &p, p)
}

//引用传递
func (p *Person) SetInfoPointer() {
    fmt.Printf("SetInfoPointer: %p, %v\n", p, p)
}

func main() {
    p := Person{"mike", 'm', 18} //p是Person的实例化对象
    fmt.Printf("main: %p, %v\n", &p, p)

    //传统调用方式
    p.SetInfoPointer() 

    //这个就是方法值,调用函数时,无需再传递接收者,隐藏了接收者
    pFunc := p.SetInfoPointer 
    pFunc() //等价于 p.SetInfoPointer() 

    vFunc := p.SetInfoValue
    vFunc() //等价于 p.SetInfoValue() 

    
    //方法表达式
    f := (*Person).SetInfoPointer
    //需要传递实例化对象,方法需要指针就传地址,需要值就传值
    f(&p) 

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

}

有什么用?封装呗。

接口

接口其实就是纯虚函数,只定义方法,不实现。
面向接口编程就是向外界只提供调用的方法,至于怎么实现的你不用关心,至于实现多少个版本不用你关心,你只用关心你想用哪个版本就行了。

package main

import "fmt"

//定义接口类型
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 WhoSayHi(i Humaner) {
    i.sayhi()
}

func main() {
    s := &Student{"teemo", 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()
    }

}

接口的继承

A接口继承B接口,则可以用过A接口调用B接口的所有定义。至于实现与否要看类。

package main

import "fmt"

type Humaner interface { //父
    sayhi()
}

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

//我专门实现Humaner接口
type Humaner1 struct {
    name string
    id   int
}
func (tmp *Humaner1) sayhi() {
    fmt.Printf("Humaner1[%s, %d] sayhi\n", tmp.name, tmp.id)
}

//我专门实现Personer接口,但是我需要继承Humaner,不然我就要自己实现
type Personer1 struct {
    Humaner1
    name string
    id   int
}
func (tmp *Personer1) sing(lrc string) {
    fmt.Println("Personer1 sing", lrc)
}
func (tmp *Personer1) sayhi() {
    fmt.Printf("Personer1[%s, %d] sayhi\n", tmp.name, tmp.id)
}


func main() {
    s := &Personer1{}
    s.sayhi() //我自己的
    s.sing("我是Personer1")
    s.Humaner1.sayhi()//父类的
}

空接口

空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。类似于C语言的void *类型。

    var i interface{} = 1
    fmt.Println("i = ", i)

    i = "abc"
    fmt.Println("i = ", i)

常用语不知道参数是什么类型的情况

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

那么传进来了肯定需要判断类型:

    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)
        }
    }
    //类型查询,类型断言
    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)
        }

    }
上一篇下一篇

猜你喜欢

热点阅读