Go面向对象

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"

//Profile 定义结构体
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)
}
//fmtProfile是方法名
//(person Profile) 表示将fmtProfile方法与Profile的实例绑定
func main() {
individual := Profile{name: "Tom", age: 18, gender: "male"}
individual.fmtProfile()
}

输出结果

1
2
3
名字: Tom
年龄: 18
性别: male

函数的参数传递方式

若要在函数内改变实例的属性 必须要用指针作为函数的接受者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import "fmt"

//Profile 定义结构体
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)
}

输出结果为

1
2
当前年龄: 18
增加年龄后: 19

若不使用指针作为函数的接收者 实例的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.companyNameStaffInfo.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{}

// 存 int 没有问题
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() {
// 声明a变量, 类型int, 初始值为1
var a int = 1

// 声明i变量, 类型为interface{}, 初始值为a, 此时i的值变为1
var i interface{} = a

// 声明b变量, 尝试赋值i
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) //判断i是否为int类型
//若是int 就返回值给t1 若不是 触发panic
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{} //nil
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{} //nil
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"}//取消var声明 直接:=
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 //静态类型为int
var name string //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的静态类型为 interface{}
i = 25 //i的静态类型为 interface{} i动态类型为int
i = "Ruojhen" //i的静态类型为 interface{} i动态类型为string

接口组成

每一个接口类型的变量 都由一对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)
}

输出结果均为

1
type: int data: 25

接口细分

根据接口中是否包含方法 将接口分为ifaceeface

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
// runtime/runtime2.go
// 非空接口
type iface struct {
tab *itab //itab类型的指针
data unsafe.Pointer //数据指针
}

// 非空接口的类型信息
type itab struct {
inter *interfacetype // 接口定义的类型信息 即静态类型指针
_type *_type // 接口实际指向值的类型信息 即动态类型指针
link *itab
bad int32
inhash int32
fun [1]uintptr // 接口方法实现列表,即函数地址列表,按字典序排序 也称为fun方法集合
}

// runtime/type.go
// 非空接口类型,接口定义,包路径等。
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
// src/runtime/runtime2.go
// 空接口
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反射

End of reading! -- Thanks for your supporting