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)
}
}