• Golang基础-面向对象篇


    struct结构体

    在Go语言中,可以使用type 关键字来创建自定义类型,这对于提高代码的可读性和可维护性非常有用。如type myint int,myint 是一个基于内置类型 int 创建的自定义类型。你可以在代码中使用 myint 类型,并对其进行操作,而它实际上是基础的 int 类型。

    struct的定义如下:

    package main
    
    import "fmt"
    
    // Book 定义一个结构体
    type Book struct {
    	title string
    	auth  string
    }
    
    func main() {
    	var book1 Book
    	book1.title = "Golang"
    	book1.auth = "zhangsan"
    	fmt.Println(book1)
    }
    // {Golang zhangsan}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    需要注意的是 Go 语言中函数参数默认是值传递的。如果将book1对象传入函数中时,仅仅只是传进去一个副本,在函数中对book1的任何修改都是无效的。如果想在函数中修改原始变量,可以传递指向结构体的指针。如以下代码所示。

    package main
    
    import "fmt"
    
    // Book 定义一个结构体
    type Book struct {
    	title string
    	auth  string
    }
    
    func changeBook(book *Book) {
    	book.auth = "777"
    }
    
    func main() {
    	var book1 Book
    	book1.title = "Golang"
    	book1.auth = "zhangsan"
    	changeBook(&book1)
    	fmt.Println(book1)
    }
    // {Golang 777}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    类的表示与封装

    Go语言(通常称为Golang)不使用传统的类(class)和继承(inheritance)的概念,而是通过结构体(struct)来实现面向对象编程的特性。

    go中没有public和private的关键字,如果类名大写,则其他包也能够访问;如果标识符以小写字母开头,则它是未导出的,只能在同一个包内访问。导出的标识符(大写字母开头)不仅仅限于结构体名字,还包括结构体中的字段名、函数名等。这种命名规范有助于代码的可维护性和封装性。

    package main
    
    import "fmt"
    
    type Hero struct {
    	Name  string
    	Ad    int
    	Level int
    }
    
    func (this Hero) Show() {
    	fmt.Println("name =", this.Name)
    	fmt.Println("Ad =", this.Ad)
    	fmt.Println("Level =", this.Level)
    }
    
    func (this Hero) GetName() string {
    	return this.Name
    }
    
    func (this Hero) SetName(newName string) {
    	this.Name = newName
    }
    
    func main() {
    	hero := Hero{Name: "zhangsan", Ad: 100, Level: 1}
    	hero.Show()
    	hero.SetName("lisi")
    	nowName := hero.GetName()
    	fmt.Println("nowName =", nowName)
    }
    /*输出结果
    name = zhangsan
    Ad = 100
    Level = 1
    nowName = zhangsan
    */
    
    • 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

    可以看出,setName并没有把名字改为lisi。因为setName方法中使用的是值接收者(receiver),这意味着在该方法内部对Hero实例的修改不会影响到实际的hero变量。要使setName方法正确地修改Hero实例,需要将其改为指针接收者。如以下代码所示。

    package main
    
    import "fmt"
    
    type Hero struct {
    	Name  string
    	Ad    int
    	Level int
    }
    
    func (this *Hero) Show() {
    	fmt.Println("name =", this.Name)
    	fmt.Println("Ad =", this.Ad)
    	fmt.Println("Level =", this.Level)
    }
    
    func (this *Hero) GetName() string {
    	return this.Name
    }
    
    func (this *Hero) SetName(newName string) {
    	this.Name = newName
    }
    
    func main() {
    	hero := Hero{Name: "zhangsan", Ad: 100, Level: 1}
    	hero.Show()
    	hero.SetName("lisi")
    	nowName := hero.GetName()
    	fmt.Println("nowName =", nowName)
    }
    /*输出结果
    name = zhangsan
    Ad = 100
    Level = 1
    nowName = lisi
    */
    
    • 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

    类的继承

    现在有如下的一个Human类

    package main
    
    import "fmt"
    
    type Human struct {
    	name string
    	sex  string
    }
    
    func (this *Human) Eat() {
    	fmt.Println("Human.Eat()...")
    }
    
    func (this *Human) Walk() {
    	fmt.Println("Human.Walk()...")
    }
    
    func main() {
    	h := Human{name: "zhangsan", sex: "male"}
    	h.Eat()
    	h.Walk()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    现在有一个SuperMan类需要继承Human类,还有自己的level字段,并重写其中的Eat方法,还要能够写子类的新方法。代码如下:

    type SuperMan struct {
    	Human // SuperMan继承了Human类的方法
    	level int
    }
    
    // 重写父类的Eat()方法
    func (this *SuperMan) Eat() {
    	fmt.Println("SuperMan.Eat()...")
    }
    
    // 添加子类新方法
    func (this *SuperMan) Fly() {
    	fmt.Println("SuperMan.Fly()...")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在继承完成后,main函数中定义子类的对象有两种方法。
    第一种:一气通贯式

    s := SuperMan{Human{"lisi", "female"}, 5}
    
    • 1

    第二种:守旧派

    var s SuperMan
    s.name = "lisi"
    s.sex = "female"
    s.level = 5
    
    • 1
    • 2
    • 3
    • 4

    完整代码如下,都能成功运行。

    package main
    
    import "fmt"
    
    type Human struct {
    	name string
    	sex  string
    }
    
    func (this *Human) Eat() {
    	fmt.Println("Human.Eat()...")
    }
    
    func (this *Human) Walk() {
    	fmt.Println("Human.Walk()...")
    }
    
    // ===========================================
    
    type SuperMan struct {
    	Human // SuperMan继承了Human类的方法
    	level int
    }
    
    // 重写父类的Eat()方法
    func (this *SuperMan) Eat() {
    	fmt.Println("SuperMan.Eat()...")
    }
    
    // 添加子类新方法
    func (this *SuperMan) Fly() {
    	fmt.Println("SuperMan.Fly()...")
    }
    
    func (this *SuperMan) Show() {
    	fmt.Println("name =", this.name)
    	fmt.Println("sex =", this.sex)
    	fmt.Println("level =", this.level)
    }
    
    func main() {
    	h := Human{name: "zhangsan", sex: "male"}
    	h.Eat()
    	h.Walk()
    	fmt.Println("=============")
    
    	//方法一:定义子类新对象
    	//s := SuperMan{Human{"lisi", "female"}, 5}
    
    	//方法二
    	var s SuperMan
    	s.name = "lisi"
    	s.sex = "female"
    	s.level = 5
    	s.Walk() //父类方法
    	s.Eat()
    	s.Fly()
    	s.Show()
    }
    /*运行结果
    Human.Eat()...
    Human.Walk()...
    =============
    Human.Walk()...
    SuperMan.Eat()...
    SuperMan.Fly()...
    name = lisi
    sex = female
    level = 5
    */
    
    • 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
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    多态的基本要素与实现

    Go语言中用继承是无法实现多态的,需要用接口(interface)实现,它的本质是一个指针。
    基本要素:

    • 有一个父类(有接口)
    • 有子类(实现了父类的全部接口方法)
    • 父类类型的变量(指针)指向(引用)子类的具体数据变量

    以下代码展示了一个使用接口的例子,定义了一个 AnimalIF 接口和两个实现该接口的类型 Cat 和 Dog。然后,通过 showAnimal 函数展示了如何使用接口进行多态性调用。

    package main
    
    import "fmt"
    
    type AnimalIF interface {
    	Sleep()
    	GetColor() string
    	GetType() string
    }
    
    type Cat struct {
    	color string
    }
    
    func (this *Cat) Sleep() {
    	fmt.Println("cat is sleeping")
    }
    
    func (this *Cat) GetColor() string {
    	return this.color
    }
    
    func (this *Cat) GetType() string {
    	return "cat"
    }
    
    //=================
    
    type Dog struct {
    	color string
    }
    
    func (this *Dog) Sleep() {
    	fmt.Println("dog is sleeping")
    }
    
    func (this *Dog) GetColor() string {
    	return this.color
    }
    
    func (this *Dog) GetType() string {
    	return "Dog"
    }
    
    func showAnimal(animal AnimalIF) {
    	animal.Sleep()
    	fmt.Println("color =", animal.GetColor())
    	fmt.Println("kind =", animal.GetType())
    }
    
    func main() {
    	//var animal AnimalIF //接口的数据类型,父类指针
    	//animal = &Cat{"Green"}
    	//animal.Sleep()
    	//
    	//animal = &Dog{"yellow"}
    	//animal.Sleep()
    	cat := Cat{"green"}
    	dog := Dog{"yellow"}
    	showAnimal(&cat)
    	showAnimal(&dog)
    }
    
    • 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

    main 函数中,我们创建了 Cat 和 Dog 的实例,并通过showAnimal 函数调用展示它们的信息。这里利用了接口的多态性,通过相同的接口来处理不同的类型。这种设计使得代码更加灵活,可以方便地扩展和添加新的类型。

    interface空接口

    在Go语言中,空接口(empty interface)是一种特殊的接口,它不包含任何方法签名。由于不包含任何方法,空接口可以表示任意类型。在Go中,空接口的声明形式是interface{}
    空接口的特点是它可以保存任意类型的值,因为任何类型都至少实现了零个方法,因此都满足空接口的要求。

    package main
    
    import "fmt"
    
    func myFunc(arg interface{}) {
    	fmt.Println("myFunc is called...")
    	fmt.Println(arg)
    }
    
    type Books struct {
    	auth string
    }
    
    func main() {
    	book := Books{"zhangsan"}
    	myFunc(book)
    
    	myFunc(100)
    	myFunc("haha")
    	myFunc(3.14)
    }
    /*输出结果
    myFunc is called...
    {zhangsan}
    myFunc is called...
    100
    myFunc is called...
    haha
    myFunc is called...
    3.14
    */
    
    • 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

    在这个例子中,myFunc 函数接受一个空接口类型的参数,因此它可以接受任何类型的值。然后创建了一个 Books 结构体的实例 book,并将其作为参数传递给 myFunc 函数。

    但是,interface{}如何区分此时引用的底层数据类型是什么?主要是通过断言机制来实现的。

    func myFunc(arg interface{}) {
    	fmt.Println("myFunc is called...")
    	fmt.Println(arg)
    
    	value, ok := arg.(string)
    	if !ok {
    		fmt.Println("arg is not string")
    	} else {
    		fmt.Println("value =", value)
    		fmt.Println("arg is string")
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这种方式在运行时进行了类型检查,以确保转换的安全性。如果 arg 的实际类型不是 string,那么 ok 将为 false,并输出相应的提示信息。

    反射

    变量的内置pair

    在Go中,如果定义了一个变量,那么它内部实际构造由两部分组成:变量类型type和值value。type可以划分成两类:static type和concrete type。

    • static type指常见的数据类型,如int、string…
    • concrete type指interface所指向的具体数据类型,是系统看得见的类型

    变量类型type和值value组成了pair,反射主要是通过变量找到当前变量的具体类型或值。具体结构如下图所示。
    请添加图片描述

    package main
    
    import "fmt"
    
    func main() {
    	var a string
    	// pair
    	a = "aceld"
    
    	//pair
    	var alltype interface{}
    	alltype = a
    
    	str, _ := alltype.(string)
    	fmt.Println(str)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这段代码中,alltype.(string) 尝试将 alltype转为字符串类型,并返回两个值,一个是转换后的值 str,另一个是一个布尔值 ok 表示转换是否成功。在这里使用了 _ 来忽略不需要的第二个返回值。
    如果转换成功,oktrue,则会打印出字符串的值。如果转换失败,okfalse,则会输出相应的提示信息。这种模式在处理接口类型时常用于确保类型安全。

    reflect包

    reflect主要包含了两个关键的接口,ValueOf实现输入任意数据类型返回数据的值,TypeOf实现输入任意数据类型,动态获取这个数据类型是static type还是concrete type。
    请添加图片描述
    可以用以下代码来判断:

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func reflectNum(arg interface{}) {
    	fmt.Println("type:", reflect.TypeOf(arg))
    	fmt.Println("value:", reflect.ValueOf(arg))
    }
    
    func main() {
    	var num float64 = 3.14159
    	reflectNum(num)
    }
    /*输出结果
    type: float64
    value: 3.14159
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    下面的代码演示了如何使用反射(reflection)获取结构体实例的字段和方法信息。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type User struct {
    	Id   int
    	Name string
    	Age  int
    }
    
    func (this User) Call() {
    	fmt.Println("User is called...")
    	fmt.Printf("%v\n", this)
    }
    
    func DoFiledAndMethod(input interface{}) {
    	// 获取input的type
    	inputType := reflect.TypeOf(input)
    	fmt.Println("inputType is:", inputType)
    
    	// 获取input的value
    	inputValue := reflect.ValueOf(input)
    	fmt.Println("inputValue is:", inputValue)
    	// 通过type获取里面的字段
    	// 1.获取interface的reflect.Type,通过Type得到NumField,进行遍历
    	// 2.得到每个field,数据类型
    	// 3.通过field有一个interface()方法得到对应value
    	for i := 0; i < inputType.NumField(); i++ {
    		field := inputType.Field(i)
    		value := inputValue.Field(i).Interface()
    		fmt.Printf("%s:%v=%v\n", field.Name, field.Type, value)
    	}
    	// 通过type获取里面的方法、调用
    	for i := 0; i < inputType.NumMethod(); i++ {
    		m := inputType.Method(i)
    		fmt.Printf("%s:%v\n", m.Name, m.Type)
    	}
    }
    
    func main() {
    	user := User{1, "Aceld", 18}
    	DoFiledAndMethod(user)
    }
    /*输出结果
    inputType is: main.User
    inputValue is: {1 Aceld 18}
    Id:int=1
    Name:string=Aceld
    Age:int=18
    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

    解析Struct Tag

    在 Go 语言中,可以为结构体的字段添加标签(tag),这些标签可以在运行时通过反射获取。标签通常用于提供额外的元数据,如字段的注释、验证规则等。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type resume struct {
    	Name string `info:"name" doc:"我的名字"`
    	Sex  string `info:"sex"`
    }
    
    func findTag(str interface{}) {
    	t := reflect.TypeOf(str).Elem() //当前结构体的全部元素
    
    	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)
    		fmt.Println("doc:", tagdoc)
    	}
    }
    
    func main() {
    	var re resume
    	findTag(&re)
    }
    
    • 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

    在以上代码中,resume 结构体的字段 NameSex 都有标签信息。在 findTag 函数中,通过 reflect.TypeOf(str) 获取结构体的类型,并使用 Elem() 方法获取实际的类型。然后,通过 t.Field(i).Tag.Get("info")t.Field(i).Tag.Get("doc") 获取每个字段的 "info" "doc" 标签的值。

    Struct Tag在json中的应用

    定义如下代码

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    type Movie struct {
    	Title  string   `json:"title"`
    	Year   int      `json:"year"`
    	Price  int      `json:"rmb"`
    	Actors []string `json:"actors"`
    }
    
    func main() {
    	movie := Movie{"喜剧之王", 2000, 10, []string{"xingye", "zhangbozi"}}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    对movie编码,即从结构体->json。从运行结果可以看出json中的键就是上面定义的Struct Tag

    jsonStr, err := json.Marshal(movie)
    if err != nil {
    	fmt.Println("error:", err)
    	return
    }
    fmt.Printf("jsonStr = %s\n", jsonStr)
    //jsonStr = {"title":"喜剧之王","year":2000,"rmb":10,"actors":["xingye","zhangbozi"]}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对jsonStr解码,即从json->结构体

    my_movie := Movie{}
    err = json.Unmarshal(jsonStr, &my_movie)
    if err != nil {
    	fmt.Println("error:", err)
    	return
    }
    fmt.Printf("%v\n", my_movie)
    // {喜剧之王 2000 10 [xingye zhangbozi]}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    java高级——集合(中)
    JavaScript变量提升
    Vue父组件给子组件传参数
    S标签肽,H2N-KETAAAKFERQHMDS-OH
    一篇文章带您了解PHP数组
    【附源码】计算机毕业设计JAVA宠物管理系统
    Swift 周报 第十三期
    com.google.code:kaptcha-2.3.jar
    css 预处理器的相关使用
    web前端全套教程,入门必备
  • 原文地址:https://blog.csdn.net/weixin_46270220/article/details/134529495