风在路上 风在路上
首页
导航站
  • Java-Se

    • Java基础
  • Java-Se进阶-多线程

    • 多线程
  • Java-Se进阶-java8新特性

    • java8新特性
  • Java-ee

    • JavaWeb
  • Java虚拟机

    • JVM
  • golang基础

    • golang基础
  • golang框架

    • gin
  • SQL 数据库

    • MySQL
  • NoSQL 数据库

    • Redis
    • ElasticSearch
    • MongoDB
  • ORM

    • MyBatis
    • MyBatis-Plus
  • Spring

    • Spring
  • SpringMVC

    • SpringMVC1
    • SpringMVC2
  • SpringCloud

    • SpringCloud
  • 中间件

    • RabbitMQ
    • Dubbo
  • 秒杀项目
  • Git
  • Linux
  • Docker
  • JWT
  • 面试
  • 刷题
开发问题😈
设计模式
关于💕
归档🕛
GitHub (opens new window)

风

摸鱼
首页
导航站
  • Java-Se

    • Java基础
  • Java-Se进阶-多线程

    • 多线程
  • Java-Se进阶-java8新特性

    • java8新特性
  • Java-ee

    • JavaWeb
  • Java虚拟机

    • JVM
  • golang基础

    • golang基础
  • golang框架

    • gin
  • SQL 数据库

    • MySQL
  • NoSQL 数据库

    • Redis
    • ElasticSearch
    • MongoDB
  • ORM

    • MyBatis
    • MyBatis-Plus
  • Spring

    • Spring
  • SpringMVC

    • SpringMVC1
    • SpringMVC2
  • SpringCloud

    • SpringCloud
  • 中间件

    • RabbitMQ
    • Dubbo
  • 秒杀项目
  • Git
  • Linux
  • Docker
  • JWT
  • 面试
  • 刷题
开发问题😈
设计模式
关于💕
归档🕛
GitHub (opens new window)
  • 基础

    • golang基础
    • 安装
    • 基本语法
    • 函数
    • 面向对象
      • struct
      • 方法
        • 重写
      • 继承
        • 多重继承
      • interface(多态)
        • 定义
        • 实现
        • 使用
        • 接口嵌套
        • 空接口
      • 反射
        • 变量的内置pair结构
        • reflect反射机制
        • 结构体
        • 通过reflect.Value设置实际变量的值
        • 通过reflect.ValueOf来进行方法的调用
        • struct tag 解析
        • tag在JSON中的应用(编解码/序列化)
    • Goroutine
  • golang
  • 基础
zdk
2023-04-25
目录

面向对象

# 面向对象相关

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

}
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

即,struct名、属性名、方法名等,大写表示public包外可访问,小写表示private包外不可访问

# 方法

方法总是绑定对象实例,并隐式的将实例作为第一实参(receiver),方法的语法如下

func(receiver ReceiverType) funcName(parameters) (results)
1
  • 参数receiver可重新命名.如方法中未曾使用,可省略参数名
  • 参数receiver类型可以是T或*T.基类型T不能是接口或指针
  • 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法

举例:为上面的UserModel对象定义的方法:

func (user *UserModel) updateUser() {
	user.age = 1111
}
1
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()
}
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

以上代码中,实现了SuperMan类继承了Human类,superMan对象可以直接.属性名来访问Human类的属性。

注意:

  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用
  2. 匿名结构体字段访问可以简化,即不需要superMan.Human.name,而是直接superMan.name
    • 这里是先看superMan有没有name属性,如果有就直接调用
    • 如果没有就回去看SuperMan中嵌入的结构体中的有没有name属性,如果有则调用,没有就继续找别的结构体,如果找完所有内嵌结构体都找不到,就报错
  3. 当结构体和匿名结构体有相同的字段或者方法时,编译器选择采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
  4. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的子弹和方法),在访问时,就必须指定匿名结构体名字,否则编译报错
  5. 如果一个struct嵌套了一个有名结构体这种模式就是组合,如果是组合关系,那么在访问结构体的字段或方法时,就必须带上结构体的名字

# 多重继承

  • 如果一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现多重继承
  • 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分
  • 为了保证代码的简洁性,尽量不使用多重继承

# interface(多态)

# 定义

定义格式如下:

type 接口名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
1
2
3
4
5

说明:

  1. 接口名:用type将接口自定义的一个类型名。Go语言的接口名单词后面通常会加 er,比如写操作的接口叫 Writer 等。接口名要尽量突出接口类型的含义
  2. 方法名:当方法名的首字母以及该接口名首字母都是大写时,那么这个方法可以被接口所在的包(package)之外的代码访问
  3. 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可省略

# 实现

存在如下接口:

type Say interface {
	say(str string)
	eat(str string) int
}
1
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
}
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)
}
1
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()
}
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

# 空接口

通用万能类型接口:空接口

interface{} // 
1

在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 
*/
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

一个函数或方法以空接口为参数时,可以传递任意实现了空接口的变量,诸如前面说明了的类型,同时,我们可以使用

value, ok := arg.(参数实际的类型)

这是go提供的类型断言机制,如果类型与我们判断的一致,ok变量为true,value为这个变量的实际值;如果不是这个类型,ok为false

# 反射

# 变量的内置pair结构

image-20230429165625923

# reflect反射机制

reflect包中的两个重要接口

func ValueOf(i interface{}) Value {...}
//ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0
1
2
func TypeOf(i interface{}) Type {...}
//TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil
1
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  
*/
1
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) 
*/
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
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"
}
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

输出:

number: 10086 pointer: 0xc00001a0a8

trueValue: 10086

type of pointer: int

settAbility of pointer: true new value of pointer: 110

这里首先通过变量拿到指向它的指针,通过指针获取到指针指向的具体元素,然后判断此元素是否可以被修改,再进行set修改元素值

  1. 需要传入的参数是* int这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针。
  2. 如果传入的参数不是指针,而是变量,那么
    • 通过Elem获取原始值对应的对象则直接panic
    • 通过CanSet方法查询是否可以设置返回false
  1. trueValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。
  2. reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的
  3. 也就是说如果要修改反射类型对象,其值必须是“addressable”【对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象】
  4. 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
*/
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

说明:

  1. 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到反射类型对象后才能做下一步处理
  2. reflect.Value.MethodByName这.MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字。注意方法必须为public才能被反射获取到,即命名大写
  3. []reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定。
  4. reflect.Value的 Call 这个方法,这个方法将最终调用真实的方法,参数需要保持一致,如果参数不正确或者不是一个方法,那么将直接panic。
  5. 本来可以用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:  性别 
*/
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

在结构体字段后面在"``"中间,以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 [小罗伯特唐尼 荷兰弟]}
*/
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

上述demo中,结构体属性增加名为json的tag,tag值就是序列化的json字符串中属性的key。

使用go标准库提供的json.Marshal(movie)对结构体进行序列化;

使用json.Unmarshal(jsonStr, &myMovie)对JSON字符串进行反序列化,反序列化时,还需要传递一个对象地址参数来接收反序列化后的数据

在 GitHub 上编辑此页 (opens new window)
#golang
最后更新: 2023/05/04, 21:05:00
函数
Goroutine

← 函数 Goroutine→

Theme by Vdoing | Copyright © 2022-2025 zdk | notes
湘ICP备2022001117号-1
川公网安备 51142102511562号
本网站由 提供CDN加速/云存储服务
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式