面向对象
# 面向对象相关
# struct
package main
import "fmt"
// UserModel 如果struct名是大写,则表示此结构体在别的包能够被访问 /**
type UserModel struct {
//如果属性名是大写,则表示该属性在别的包也能够被访问
Username string
Password string
//小写则只能包内访问
age int
}
// Show 不传入*UserModel,那么变量是数据副本而不是引用
// 默认传入调用当前方法的对象实例 即下面的 user变量
func (user UserModel) Show() {
fmt.Println("Username = ", user.Username)
fmt.Println("Password = ", user.Password)
fmt.Println("age = ", user.age)
}
// 方法也是一样的 方法名的大小写代表了此方法是否能被包外访问
// 这里传入*UserModel表示是引用传递
// 默认传入调用当前方法的对象实例 即下面的 user变量
func (user *UserModel) updateUser() {
user.age = 1111
}
func main() {
user := UserModel{Username: "zdk", Password: "123", age: 22}
user.Show()
fmt.Println("user:", user)
fmt.Println("-------------")
user.updateUser()
fmt.Println("update-user:", user)
}
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
即,struct名、属性名、方法名等,大写表示public包外可访问,小写表示private包外不可访问
# 方法
方法总是绑定对象实例,并隐式的将实例作为第一实参(receiver),方法的语法如下
func(receiver ReceiverType) funcName(parameters) (results)
- 参数receiver可重新命名.如方法中未曾使用,可省略参数名
- 参数receiver类型可以是T或*T.基类型T不能是接口或指针
- 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法
举例:为上面的UserModel对象定义的方法:
func (user *UserModel) updateUser() {
user.age = 1111
}
2
3
这里没有传入参数,user变量就是调用者本身
# 重写
子类可以重写父类的方法,如上述代码中的
func (superMan SuperMan) Walk() { fmt.Println("superMan--Eat()") }
1
2
3在superMan对象进行调用时,就不再是调用
func (human Human) Walk()
方法了,而是调用重写后的方法
# 继承
在golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现继承特性。
例如:
package main
import "fmt"
type Human struct {
name string
sex string
}
type SuperMan struct {
// SuperMan类继承了Human类的方法和属性
Human
level int
}
func (human Human) Eat() {
fmt.Println("Human--Eat()")
}
func (human Human) Walk() {
fmt.Println("Human--Eat()")
}
func (superMan SuperMan) Walk() {
fmt.Println("superMan--Eat()")
}
func main() {
human := Human{"test", "女"}
human.Walk()
fmt.Println("-----------------")
superMan := SuperMan{Human{"zdk", "男"}, 1}
superMan.name = "张迪凯"
superMan.Eat()
superMan.Walk()
}
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
以上代码中,实现了SuperMan类继承了Human类,superMan对象可以直接.属性名来访问Human类的属性。
注意:
- 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用
- 匿名结构体字段访问可以简化,即不需要
superMan.Human.name
,而是直接superMan.name
- 这里是先看superMan有没有name属性,如果有就直接调用
- 如果没有就回去看SuperMan中嵌入的结构体中的有没有name属性,如果有则调用,没有就继续找别的结构体,如果找完所有内嵌结构体都找不到,就报错
- 当结构体和匿名结构体有相同的字段或者方法时,编译器选择采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
- 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的子弹和方法),在访问时,就必须指定匿名结构体名字,否则编译报错
- 如果一个struct嵌套了一个有名结构体这种模式就是组合,如果是组合关系,那么在访问结构体的字段或方法时,就必须带上结构体的名字
# 多重继承
- 如果一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现多重继承
- 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分
- 为了保证代码的简洁性,尽量不使用多重继承
# interface(多态)
# 定义
定义格式如下:
type 接口名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
2
3
4
5
说明:
- 接口名:用type将接口自定义的一个类型名。Go语言的接口名单词后面通常会加 er,比如写操作的接口叫 Writer 等。接口名要尽量突出接口类型的含义
- 方法名:当方法名的首字母以及该接口名首字母都是大写时,那么这个方法可以被接口所在的包(package)之外的代码访问
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可省略
# 实现
存在如下接口:
type Say interface {
say(str string)
eat(str string) int
}
2
3
4
和java一样,要实现go的接口,也必须实现接口中的所有方法,所以实现类应该拥有以下方法
type Cat struct {
name string
}
func (cat *Cat) say(str string) {
cat.name = str
fmt.Println("cat say:", str)
}
func (cat *Cat) eat(str string) int {
fmt.Println("cat eat:", str)
return 1
}
type Dog struct {
name string
}
func (dog *Dog) say(str string) {
dog.name = str
fmt.Println("dog say:", str)
}
func (dog *Dog) eat(str string) int {
fmt.Println("dog eat:", str)
return 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
方法的返回值和参数列表必须与接口中的方法一致。还有一个类实现多个接口的情况,这里就不举例了
# 使用
使用也是和java相似,变量用接口定义,赋值用实现类赋值,即:
接口类型的变量(指针),指向(引用)实现类的具体数据变量
func main() {
var say Say = &Cat{}
say.say("cat")
fmt.Println(say)
fmt.Println("-------------------")
say = &Dog{}
say.say("dog")
fmt.Println(say)
}
2
3
4
5
6
7
8
9
10
11
# 接口嵌套
接口的嵌套即接口的继承,定义方法和struct 类的继承是一样的
type Jumper interface {
jump()
}
type Sayer interface {
say()
}
type animal interface {
Jumper
Sayer
}
type dog struct{
name string
}
func (d *dog) jump() {
fmt.Printf("%s会跳\n", d.name)
}
func (d *dog) say() {
fmt.Printf("%s会叫\n", d.name)
}
func main() {
var x animal
x = &dog{name: "小强"}
x.jump()
x.say()
}
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
# 空接口
通用万能类型接口:空接口
interface{} //
在go语言中,int、string、float32、float64、struct等都实现了
interface{}
接口,这样就可以使用interface{}类型去引用任意的数据类型,包括go的基本类型和我们自己定义的struct
举例说明一下空接口:
package main
import "fmt"
func show(arg interface{}) {
fmt.Println("arg : ", arg)
value, ok := arg.(string)
if ok {
fmt.Println("arg is string,value = ", value)
}
value1, ok1 := arg.(Article)
if ok1 {
fmt.Println("arg is Article(struct),value = ", value1)
}
value2, ok2 := arg.(int)
if ok2 {
fmt.Println("arg is int,value = ", value2)
}
}
type Article struct {
title string
content string
}
func main() {
article := Article{"标题", "内容"}
show(article)
fmt.Println("--------------")
show("woshi")
fmt.Println("--------------")
show(10086)
}
/** 输出
arg : {标题 内容}
arg is Article(struct),value = {标题 内容}
--------------
arg : woshi
arg is string,value = woshi
--------------
arg : 10086
arg is int,value = 10086
*/
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
一个函数或方法以空接口为参数时,可以传递任意实现了空接口的变量,诸如前面说明了的类型,同时,我们可以使用
value, ok := arg.(参数实际的类型)
这是go提供的类型断言机制,如果类型与我们判断的一致,ok变量为true,value为这个变量的实际值;如果不是这个类型,ok为false
# 反射
# 变量的内置pair结构
# reflect反射机制
reflect包中的两个重要接口
func ValueOf(i interface{}) Value {...}
//ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0
2
func TypeOf(i interface{}) Type {...}
//TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil
2
简单类型示例:
package main
import (
"fmt"
"reflect"
)
func reflectShow(arg interface{}) {
fmt.Println("type : ", reflect.TypeOf(arg))
fmt.Println("value : ", reflect.ValueOf(arg))
}
func main() {
var number float64 = 1.2345
reflectShow(number)
}
/**输出
type : float64
value : 1.2345
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 结构体
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (user User) Call() {
fmt.Println("user is called ..")
fmt.Printf("%v\n", user)
}
func getFiledAndMethod(arg interface{}) {
//获取arg的type
argType := reflect.TypeOf(arg)
fmt.Println("argType is : ", argType.Name())
//获取arg的value
argValue := reflect.ValueOf(arg)
fmt.Println("argValue is : ", argValue)
//通过type获取struct中的字段信息
//1.获取interface的reflect.Type,通过Type得到NumField,进行遍历
//2.得到每一个field数据类型
//3.通过field有一个Interface()方法 得到对应的value
// 这里只能获取名称大写的属性 即public属性 有private属性会报panic
for i := 0; i < argType.NumField(); i++ {
field := argType.Field(i)
value := argValue.Field(i).Interface()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
//通过type获取struct中的方法信息
//这里只能打印出方法名大写的方法 即public方法
for i := 0; i < argType.NumMethod(); i++ {
method := argType.Method(i)
fmt.Printf("%s: %v\n", method.Name, method.Type)
}
}
func main() {
user := User{1, "zdk", 22}
getFiledAndMethod(user)
}
/** 输出
argType is : User
argValue is : {1 zdk 22}
Id: int = 1
Name: string = zdk
Age: int = 22
Call: func(main.User)
*/
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
62
63
核心仍是通过reflect.TypeOf、reflect.ValueOf获取到变量的类型和值再进行后面的操作。
reflect.ValueOf(arg)的参数必须是地址
# 通过reflect.Value设置实际变量的值
reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的,即上面说的,参数必须是地址
package main
import (
"fmt"
"reflect"
)
func main() {
number := 10086
fmt.Println("number: ", number)
//这里的参数必须是地址
// 获取到的是变量值的指针
pointer := reflect.ValueOf(&number)
fmt.Println("pointer: ", pointer)
//通过Elem()方法获取到变量值
trueValue := pointer.Elem()
fmt.Println("trueValue: ", trueValue)
//指针指向的类型
fmt.Println("type of pointer:", trueValue.Type())
// 此值是否是可以修改的
fmt.Println("settAbility of pointer:", trueValue.CanSet())
// 重新赋值
trueValue.SetInt(110)
fmt.Println("new value of pointer:", number)
////////////////////
// 如果reflect.ValueOf的参数不是指针,会如何?
pointer = reflect.ValueOf(number)
//trueValue = pointer.Elem() // 如果非指针,这里直接panic,"panic: reflect: call of reflect.Value.Elem on int Value"
}
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
输出:
number: 10086 pointer: 0xc00001a0a8
trueValue: 10086
type of pointer: int
settAbility of pointer: true new value of pointer: 110
这里首先通过变量拿到指向它的指针,通过指针获取到指针指向的具体元素,然后判断此元素是否可以被修改,再进行set修改元素值
- 需要传入的参数是* int这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针。
- 如果传入的参数不是指针,而是变量,那么
- 通过Elem获取原始值对应的对象则直接panic
- 通过CanSet方法查询是否可以设置返回false
- trueValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。
- reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的
- 也就是说如果要修改反射类型对象,其值必须是“addressable”【对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象】
- struct 或者 struct 的嵌套都是一样的判断处理方式
# 通过reflect.ValueOf来进行方法的调用
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
}
func (user User) CallUserWithArg(str string, num int) {
fmt.Printf("id:%d 的 %s 喜欢 %s 和 %d\n", user.Id, user.Name, str, num)
}
func (user User) CallUserNoArg() {
fmt.Printf("这是id:%d 的 %s\n", user.Id, user.Name)
}
func main() {
user := User{1, "zdk"}
valueOf := reflect.ValueOf(user)
// 通过方法名获取到对应的方法
method := valueOf.MethodByName("CallUserWithArg")
//传递给方法对应的参数
args := []reflect.Value{reflect.ValueOf("打游戏"), reflect.ValueOf(10086)}
//执行调用
method.Call(args)
method = valueOf.MethodByName("CallUserNoArg")
args = make([]reflect.Value, 0)
method.Call(args)
}
/**输出
id:1 的 zdk 喜欢 打游戏 和 10086
这是id:1 的 zdk
*/
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
说明:
- 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到反射类型对象后才能做下一步处理
- reflect.Value.MethodByName这.MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字。
注意方法必须为public才能被反射获取到,即命名大写
- []reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定。
- reflect.Value的 Call 这个方法,这个方法将最终调用真实的方法,参数需要保持一致,如果参数不正确或者不是一个方法,那么将直接panic。
- 本来可以用u.ReflectCallFuncXXX直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调用method.Call
# struct tag 解析
package main
import (
"fmt"
"reflect"
)
type Resume struct {
Name string `info:"name" doc:"名字"`
sex string `info:"sex" doc:"性别"`
}
func findTag(inter interface{}) {
typeOf := reflect.TypeOf(inter)
fmt.Println("typeOf:", typeOf) // *main.Resume 指针
//拿到结构体的类型信息 其中就包含了Field、Method等
t := reflect.TypeOf(inter).Elem() // main.Resume 引用
fmt.Println("t:", t)
for i := 0; i < t.NumField(); i++ {
tagInfo := t.Field(i).Tag.Get("info")
tagDoc := t.Field(i).Tag.Get("doc")
fmt.Println("info: ", tagInfo, " doc: ", tagDoc)
}
}
func main() {
var resume Resume
findTag(&resume)
}
/**输出
typeOf: *main.Resume
t: main.Resume
info: name doc: 名字
info: sex doc: 性别
*/
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
在结构体字段后面在"``"中间,以key:"value"的形式书写属性的tag,多个tag之间以空格隔开
# tag在JSON中的应用(编解码/序列化)
package main
import (
"encoding/json"
"fmt"
)
type Movie struct {
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"price"`
Actors []string `json:"actors"`
}
func main() {
movie := Movie{"复仇者联盟4", 2019, 60, []string{"小罗伯特唐尼", "荷兰弟"}}
// 序列化为json
// 这里拿到的jsonStr实质是一个byte数组 用Printf的%s打印才能显示字符串
jsonStr, err := json.Marshal(movie)
if err != nil {
fmt.Println("json序列化失败:", err)
}
fmt.Printf("jsonStr:%s\n ", jsonStr)
// 反序列化
// jsonStr = {"title":"复仇者联盟4","year":2019,"price":60,"actors":["小罗伯特唐尼","荷兰弟"]}
myMovie := Movie{}
// 这里需要传入反序列化的对象地址
err = json.Unmarshal(jsonStr, &myMovie)
if err != nil {
fmt.Println("json反序列化失败:", err)
}
fmt.Println("myMovie: ", myMovie)
}
/**输出
jsonStr:{"title":"复仇者联盟4","year":2019,"price":60,"actors":["小罗伯特唐尼","荷兰弟"]}
myMovie: {复仇者联盟4 2019 60 [小罗伯特唐尼 荷兰弟]}
*/
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
上述demo中,结构体属性增加名为json的tag,tag值就是序列化的json字符串中属性的key。
使用go标准库提供的
json.Marshal(movie)
对结构体进行序列化;使用
json.Unmarshal(jsonStr, &myMovie)
对JSON字符串进行反序列化,反序列化时,还需要传递一个对象地址参数来接收反序列化后的数据