Go面向对象
面向对象
结构体
Go语言中没有class类的概念 只有struct结构体的概念
定义结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import "fmt"
type Profile struct { name string age int gender string mother *Profile father *Profile }
func (person Profile) fmtProfile() { fmt.Println("名字:", person.name) fmt.Println("年龄:", person.age) fmt.Println("性别:", person.gender) }
func main() { individual := Profile{name: "Tom", age: 18, gender: "male"} individual.fmtProfile() }
|
输出结果
函数的参数传递方式
若要在函数内改变实例的属性 必须要用指针作为函数的接受者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import "fmt"
type Profile struct { name string age int gender string mother *Profile father *Profile }
func (person *Profile) increase() { person.age ++ } func main() { individual := Profile{name: "Tom", age: 18, gender: "male"} fmt.Println("当前年龄:",individual.age) individual.increase() fmt.Println("增加年龄后:",individual.age) }
|
输出结果为
若不使用指针作为函数的接收者 实例的age是不会改变的
结构体利用组合实现”继承”
因为Go语言本身没有继承 但我们用组合的方式 实现继承的效果
组合 是指把一个结构体嵌入到另一个结构体
1 2 3 4 5 6 7 8 9 10 11 12
| type company struct { companyName string companyAddr string }
type staff struct { name string age int gender string position string }
|
若要将公司和员工关联起来 有很多方法
若采用将公司结构体的内容直接抄录到员工中 实现效果方面没有任何问题 但实际操作中会出现对同一个公司的多个员工初始化的时候 都得重复初始化这同一个公司的信息 借助继承的思想 可以将公司的属性继承给员工
Go中没有类的概念 可以将公司company这个结构体直接嵌入在员工staff中 成为staff的一个匿名字段 此时staff就直接拥有了company的所有属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| type company struct { companyName string companyAddr string }
type staff struct { name string age int gender string position string company }
func main() { ComInfo := company{ companyName: "Facebook", companyAddr: "USA", } StaffInfo := staff{ name: "Ruojhen", age: 24, gender: "Male", position: "Go Developer", company: ComInfo, } fmt.Printf("%s 在 %s 工作\n",StaffInfo.name,StaffInfo.company.companyName) fmt.Printf("%s 在 %s 工作",StaffInfo.name,StaffInfo.companyName) }
|
输出结果为
1 2
| Ruojhen 在 Facebook 工作 Ruojhen 在 Facebook 工作
|
由此可见 StaffInfo.company.companyName
与StaffInfo.companyName
实现效果相同
内部/外部
Go语言中 函数名的首字母大小写被用来实现控制函数的访问权限
函数首字母为大写时 这个函数对所有包都是Public
函数首字母为小写时 这个函数为Private 其他包无法访问这个函数
接口
接口定义了一个对象的行为 即接口只说明了对象该做什么 而具体如何实现 则不关心 那需要对象自己本身去确定
定义接口
使用type
+接口名+interface
来定义接口
1 2 3
| type Phone interface { call() }
|
以上代码中 定义了一个电话的接口 接口要求实现call函数
实现接口
实现了某一接口中的所有方法 就称之为实现了这个接口
1 2 3 4 5 6 7
| type Apple struct{ name string }
func (phone Apple) call() { fmt.Println("Apple is a phone) }
|
接口实现多态
代码举例演示 先定义一个商品Good的接口 只要某个类型或结构体实现了接口Good内的settleAccount()
和orderInfo()
这个类型或结构体就是一个Good
1 2 3 4
| type Good interface { settleAccount() int orderInfo() string }
|
然后定义两个结构体 手机 礼物
1 2 3 4 5 6 7 8 9 10 11
| type Phone struct { name string quantity int price int }
type Gift struct { name string quantity int price int }
|
分别为手机Phone和礼物Gift实现Good接口的两个函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| func (phone Phone) settleAccount() int { return phone.price * phone.quantity } func (phone Phone) orderInfo() string { return "您要购买" + strconv.Itoa(phone.quantity) + "个" + phone.name + "共计:" + strconv.Itoa(phone.settleAccount()) + "元" }
func (gift Gift) settleAccount() int { return 0 } func (gift Gift) orderInfo() string { return "您要购买" + strconv.Itoa(gift.quantity) + "个" + gift.name + "共计:" + strconv.Itoa(gift.settleAccount()) + "元" }
|
实现了这两个方法后 手机和礼物就是Good类型了
此时 创建两个商品的实例化
1 2 3 4 5 6 7 8 9 10
| iPhone := Phone{ name: "iPhone", quantity: 1, price: 8888, } earphones := Gift{ name: "耳机", quantity: 1, price: 400, }
|
创建一个购物车 来存放这些商品 创建类型为Good的切片来作为购物车
1
| goods :=[]Good{iPhone,earphones}
|
新建一个函数用来计算购物车内的订单金额
1 2 3 4 5 6 7 8
| func caculate(goods []Good) int { var allPrice int for _,j := range goods{ fmt.Println(j.orderInfo()) allPrice += j.settleAccount() } return allPrice }
|
最后输出订单总金额
1 2
| allPrice := caculate(goods) fmt.Println("订单共需要支付",allPrice)
|
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package main
import ( "fmt" "strconv" )
type Good interface { settleAccount() int orderInfo() string }
type Phone struct { name string quantity int price int }
type Gift struct { name string quantity int price int }
func (phone Phone) settleAccount() int { return phone.price * phone.quantity } func (phone Phone) orderInfo() string { return "您要购买" + strconv.Itoa(phone.quantity) + "个" + phone.name + "共计:" + strconv.Itoa(phone.settleAccount()) + "元" }
func (gift Gift) settleAccount() int { return 0 } func (gift Gift) orderInfo() string { return "您要购买" + strconv.Itoa(gift.quantity) + "个" + gift.name + "共计:" + strconv.Itoa(gift.settleAccount()) + "元" }
func caculate(goods []Good) int { var allPrice int for _,j := range goods{ fmt.Println(j.orderInfo()) allPrice += j.settleAccount() } return allPrice } func main() { iPhone := Phone{ name: "iPhone", quantity: 1, price: 8888, } earphones := Gift{ name: "耳机", quantity: 1, price: 400, } goods :=[]Good{iPhone,earphones} allPrice := caculate(goods) fmt.Println("订单共需要支付",allPrice) }
|
运行结果为
1 2 3
| 您要购买1个iPhone共计:8888元 您要购买1个耳机共计:0元 订单共需要支付 8888
|
空接口
空接口是特殊的接口 普通的接口内都有函数 但空接口内没有定义任何的函数
每一个接口都包含两个属性 一是接口值 二是接口类型
但对于空接口来说 这两者都是nil
1 2 3 4
| func main() { var i interface{} fmt.Printf("type: %T,value: %T",i,i) }
|
输出结果为
1
| type: <nil>,value: <nil>
|
使用空接口
一 可以直接使用interface{}
作为类型声明的一个实例 这个实例可以承载任何类型的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func main() { var i interface{}
i = 1 fmt.Println(i)
i = "hello" fmt.Println(i)
i = false fmt.Println(i) }
|
二 希望函数可以接收任意类型的值 可以用空接口
1 2 3 4 5 6 7 8 9 10 11 12
| func receiveValue(i interface{}){ fmt.Println(i) }
func main() { a := 10086 b := "Ruojhen" c := false receiveValue(a) receiveValue(b) receiveValue(c) }
|
也可以一次接收人一个任意类型的值
1 2 3 4 5 6 7 8 9 10 11 12
| func receiveValue(it ...interface{}){ for _,i := range it{ fmt.Println(i) } }
func main() { a := 10086 b := "Ruojhen" c := false receiveValue(a,b,c) }
|
三 也可以定义一个可以接收任意类型的 数组 切片 哈希表 结构体
1 2 3 4 5 6 7 8 9
| func main() { any := make([]interface{}, 5) any[0] = 11 any[1] = "hello world" any[2] = []int{11, 22, 33, 44} for _, value := range any { fmt.Println(value) } }
|
输出内容为
1 2 3 4 5
| 11 hello world [11 22 33 44] <nil> <nil>
|
使用空接口的注意事项
一 可以将任何类型的值赋予给空接口 但反过来将一个空接口类型的变量赋予给一个固定类型的变量就不可以 Go禁止这种反向操作 底层原理之后再总结
1 2 3 4 5 6 7 8 9 10
| func main() { var a int = 1
var i interface{} = a
var b int = i }
|
控制台报错 cannot use i (type interface {}) as type int in assignment: need type assertion
二 当空接口被赋值数组或切片后 这个空接口类型的变量不能再被切片
1 2 3 4 5 6 7 8 9
| func main() { sli := []int{2, 3, 5, 7, 11, 13}
var i interface{} i = sli
g := i[1:3] fmt.Println(g) }
|
控制台报错 cannot slice i (type interface {})
三 当使用空接口来接收任意类型的赋值的时候 这个空接口变量的静态类型是interface{}
但它的动态类型(int string ..) 并不清楚 所以需要类型断言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func myfunc(i interface{}) {
switch i.(type) { case int: fmt.Println("参数的类型是 int") case string: fmt.Println("参数的类型是 string") } }
func main() { a := 10 b := "hello" myfunc(a) myfunc(b) }
|
参数的类型是 int
参数的类型是 string
类型断言
Type Assertion
类型断言 通常用来检查某个接口对象i是否为nil 或用来检查某个接口对象i是否为某个类型
第一种方式:写一段代码测试i是否为整型和字符型
1 2 3 4 5 6 7 8 9
| func main() { var i interface{} = 10 t1 := i.(int) fmt.Println(t1) fmt.Println("---------") t2 := i.(string) fmt.Println(t2) }
|
运行结果为
1 2 3 4 5 6 7 8
| 10 --------- panic: interface conversion: interface {} is int, not string
goroutine 1 [running]: main.main() d:/Code/My Code/Golang/Hexomd/src/github.com/Ruojhen/hexomd01/test.go:13 +0x10e exit status 2
|
可以发现在执行第二次断言的时候失败了 并且触发panic
下面测试若接口值为nil 观察是否也会触发panic
1 2 3 4 5
| func main() { var i interface{} t1 := i.(interface{}) fmt.Println(t1) }
|
运行结果为
1 2 3 4 5 6
| panic: interface conversion: interface is nil, not interface {}
goroutine 1 [running]: main.main() d:/Code/My Code/Golang/Hexomd/src/github.com/Ruojhen/hexomd01/test.go:10 +0x34 exit status 2
|
同样触发了panic
第二种方式:返回两个值
若断言成功 第一个值为返回类型 第二个值为true
若断言失败 不会触发panic 第二个值为false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func main() { var i interface{} = 10 t1, judge := i.(int) fmt.Println(t1, " - ", judge) fmt.Println("----------")
t2,judge := i.(string) fmt.Println(t2, " - ", judge) fmt.Println("----------")
var j interface{} t3,judge := j.(interface{}) fmt.Println(t3, " - ", judge) fmt.Println("----------") j = 20 t4,judge := j.(interface{}) fmt.Println(t4, " - ", judge) t5,judge :=j.(int) fmt.Println(t5, " - ", judge) }
|
输出结果为
1 2 3 4 5 6 7 8
| 10 - true ---------- - false ---------- <nil> - false ---------- 20 - true 20 - true
|
发现在执行第二次断言的时候 虽然失败 但是没有触发panic
值得注意的是 第二次断言失败 t2并不是没有输出值 而是因为断言失败 t2得到的是string的零值 即空字符串 因为是0长度的 所以看不到输出
Type Switch
要对多个变量进行断言 使用type switch 更快速直接高效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| func returnType(i interface{}) { switch x := i.(type) { case int : fmt.Println(x,"is int") case string : fmt.Println(x,"is string") case nil : fmt.Println(x,"is nil") default : fmt.Println("It is not matched") } } func main() { returnType(10) returnType("Ruojhen") var k interface{} returnType(k) returnType(3.14) }
|
输出结果为
1 2 3 4
| 10 is int Ruojhen is string <nil> is nil It is not matched
|
使用接口中的限制
对函数的调用限制
接口是一组固定的函数集合 静态类型的限制 导致接口变量有时候仅能调用期中的一些特定的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| type Phone interface { call() }
type iPhone struct { name string }
func (phone iPhone)call() { fmt.Println("Hello, iPhone.") }
func (phone iPhone)send_wechat() { fmt.Println("Hello, Wechat.") }
func main() { var phone Phone phone = iPhone{name:"ming's iphone"} phone.call() phone.send_wechat() }
|
以上代码在 phone.send_wechat()
处会报错 提示Phone接口中无法找到send_wechat()
phone.send_wechat undefined (type Phone has no field or method send_wechat)
但我还想让phone可以调用send_wechat()
那就只能用不显示的声明为Phone接口类型
将main()
中phone的声明变为隐式
1 2 3 4 5 6
| func main() { phone := iPhone{name: "ming's iphone"} phone.call() phone.send_wechat() }
|
在这样修改后 运行一切正常
一定要清楚phone实际上是隐式的实现了Phone接口 这样函数的调用就不会受到接口类型的限制
调用函数时的隐式转换
Go语言中的函数调用都是值传递的 变量会在调用前进行类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func printType(i interface{}) {
switch i.(type) { case int: fmt.Println("参数的类型是 int") case string: fmt.Println("参数的类型是 string") } }
func main() { a := 10 printType(a) }
|
运行后一切正常
做一些修改 将printType函数内的代码直接搬运到主函数中运行
1 2 3 4 5 6 7 8 9 10
| func main() { a := 10
switch a.(type) { case int: fmt.Println("参数的类型是 int") case string: fmt.Println("参数的类型是 string") } }
|
控制台报错 cannot type switch on non-interface value a (type int)
分析原因 Go语言中 空接口类型可以接收任意类型的赋值 其实背后过程中 Go语言会默默的替我们把传入参数的值隐式转换为接口类型 然后才赋值给空接口
所以要想使得修改后的代码可用 我们必须手动对a进行类型转换 给他转换成接口类型
1 2 3 4 5 6 7 8 9 10
| func main() { a := 10
switch interface{}(a).(type) { case int: fmt.Println("参数的类型是 int") case string: fmt.Println("参数的类型是 string") } }
|
这样运行才会正常
类型断言中的隐式转换
因为只有静态类型为接口类型的对象变量才可以进行类型断言
而当对某个接口类型断言完成后 会返回一个静态类型为这个接口接收的数据类型
静态类型与动态类型
应该会有很多小伙伴在看到上一个类型断言隐式转换的内容时对变量动态类型和静态类型有点茫然 这个篇幅就好好的区分一下
静态类型 (static type)
静态类型 即声明变量的时候的类型 需要注意的是 这个类型不是底层数据类型
1 2 3 4 5 6 7
| var age int var name string
type MyInt int
var i int var j MyInt
|
尽管i j底层类型都是int 但i j的静态类型不同 除非进行类型转换 否则i和j不能互相赋值 j的静态类型就是MyInt
动态类型 (concrete type)
动态类型 是程序运行时系统才能看见的类型
空接口可以承接任意类型的值
1 2 3
| var i interface{} i = 25 i = "Ruojhen"
|
接口组成
每一个接口类型的变量 都由一对type data组成 记录着实际变量的类型和值
因此声明接口类型的变量也可以有多种方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func main() { var age interface{} = 25 fmt.Printf("type: %T data: %v",age,age) }
func main() { age := (int)(25) fmt.Printf("type: %T data: %v",age,age) }
func main() { age := (interface{})(25) fmt.Printf("type: %T data: %v",age,age) }
|
输出结果均为
接口细分
根据接口中是否包含方法 将接口分为iface
和eface
iface
iface
表示带有一组函数的接口
iface
源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
type iface struct { tab *itab data unsafe.Pointer }
type itab struct { inter *interfacetype _type *_type link *itab bad int32 inhash int32 fun [1]uintptr }
type interfacetype struct { typ _type pkgpath name mhdr []imethod }
type imethod struct { name nameOff ityp typeOff }
|
eface
eface
表示不带有函数的接口
eface1
源码如下
1 2 3 4 5 6
|
type eface struct { _type *_type data unsafe.Pointer }
|
总结动态类型
在知道了何为动态类型 如何让一个对象拥有动态类型后
也知晓了两种接口的内部结构
那么做一个实验 在给一个空接口类型的变量赋值的时候 观察接口内部结构会发生什么变化
iface
1 2 3 4 5 6 7 8 9 10 11 12
| import ( "io" "os" )
var reader io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } reader = tty
|
由于io.Reader接口中包含Read() 所以io.Reader为iface类型的接口 所以此时reader的静态类型为io.Reader 暂无动态类型
之后声明了tty 为os.File类型的实例 并且将tty的值赋予给了reader
所以此时reader的静态类型为io.Reader 动态类型为os.File 数据指针为tty 这些静态类型指针和动态类型指针都存在iface的itab指针内
eface
1 2 3 4 5 6
| var empty interface{} tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } empty = tty
|
因为interface{}是一个eface 且只有一个_type能存变量类型 所以empty的(静态)类型为nil
后将tty赋值给empty 此时empty的_type成为了*os.File 数据指针为tty
引入反射
由于动态类型的存在 在一个函数中接收的参数类型有可能无法预测 此时就需要反射机制 根据不同的类型做不同的处理 反射的内容将放置在 Go反射