• Go 结构体


    类型别名&定制

    类型别名

       类型别名是Go的1.9版本中新添加的功能。

       大概意思就是给一个类型取一个别名,小名等,但是这个别名还是指向的相同类型。

       如uint32的别名rune,其底层还是uint32

       如uint8的别名byte,使用byte实际上还是uint8

       别名的作用在于在编程中更方便的进行使用类型,如下示例,我们为int64取一个别名long

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	type long = int64
    	var num long
    	num = 100000000
    	fmt.Printf("%T %v", num, num)
    	// int64 100000000
    }
    
    

    自定类型

       自定类型类似于继承某个内置的类型,它会以一种全新的类型出现,并且我们可以为该自定类型做一些定制方法。

       如下定义的small类型,是基于uint8的一个类型。它是一种全新的类型,但是具有uint8的特性。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	type small uint8
    	var num small = 32
    	fmt.Printf("%T %v", num, num)
    	// main.small 32
    }
    

    区别差异

       可以看到上面示例中的打印结果

    // int64 100000000
    // main.small 32
    

       结果显示自定义类型是main.small,其实自定义类型只在代码中存在,编译时会将其转换为uint8

    结构体

       结构体类似于其他语言中的面向对象,值得一提的是Go语言是一种面向接口的语言,所以弱化了对面向对象方面的处理。

       在结构体中,我们可以清晰的表示一个现实中的事物,注意:结构体其实就是一种自定义的类型。

       在Go中使用struct来定义结构体。

       以下是语法介绍,typestruct这两个关键字用于定义结构体。

    type 类型名 struct {
        字段名 字段类型
        字段名 字段类型
        …
    }
    

       类型名:标识自定义结构体的名称,在同一个包内不能重复。如果不是对外开放的接口,则首字母小写,否则大写。

       字段名:表示结构体字段名。结构体中的字段名必须唯一。

       字段类型:表示结构体字段的具体类型。

       下面我们来定义一个dog的结构体。

    // 命名小写,表示dog结构体不对外开放
    type dog struct{
    	dogName string 
    	dogAge int8
    	dogGender bool
    }
    

    .实例化

       当结构体定义完成后,必须对其进行实例化后才可使用。

       单纯的定义结构体是不会分配内存的。

       以下将介绍通过.进行实例化。

    基本实例化

       下面是基本实例化的示例。

       首先定义一个变量,声明它是dog类型,再通过.对其中的字段进行赋值。

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct{
    	dogName string 
    	dogAge int8
    	dogGender bool
    }
    
    func main() {
    	var d1 dog
    	d1.dogName = "大黄"
    	d1.dogAge = 12
    	d1.dogGender = true
    	fmt.Println(d1)
    	// {大黄 12 true}
    }
    
    

    匿名结构体

       有的结构体只使用一次,那么就可以使用匿名结构体在定义之初对其进行实例化。

       这个时候只使用stuct即可,不必使用type进行类型的自定义。

    package main
    
    import (
    	"fmt"
    )
    
    
    func main() {
    	var dog struct{
    		dogName string 
    		dogAge int8
    		dogGender bool
    	}
    	dog.dogName = "大黄"
    	dog.dogAge = 12
    	dog.dogGender = true
    	fmt.Println(dog)
    	// {大黄 12 true}
    }
    
    

    指针实例化

       通过new可以对结构体进行实例化,具体步骤是拿到其结构体指针后通过.对其字段填充,进而达到实例化的目的。

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct{
    	dogName string 
    	dogAge int8
    	dogGender bool
    }
    
    func main() {
    	var d1 = new(dog) // 拿到结构体指针
    	d1.dogName = "大黄"
    	d1.dogAge = 12
    	d1.dogGender = true
    	fmt.Println(d1)
    	// &{大黄 12 true}
    }
    
    

    地址实例化

       使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。

       与上面的方式本质都是相同的。

       d1.dogName= "大黄"其实在底层是(*d1).dogName= "大黄",这是Go语言帮我们实现的语法糖。

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct{
    	dogName string 
    	dogAge int8
    	dogGender bool
    }
    
    func main() {
    	d1 := &dog{}
    	d1.dogName = "大黄"
    	d1.dogAge = 12
    	d1.dogGender = true
    	fmt.Println(d1)
    	// &{大黄 12 true}
    }
    

    {}实例化

       实例化时,除开可以使用.也可以使用{}

       在实际开发中使用{}实例化的普遍更多。

    基本实例化

       以下是使用{}进行基本实例化的示例。

       key对应字段名,value对应实例化的填充值。

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct{
    	dogName string 
    	dogAge int8
    	dogGender bool
    }
    
    func main() {
    	d1 := dog{
    		dogName:"大黄",
    		dogAge:12,
    		dogGender:true,
    	}
    	fmt.Print(d1)
    }
    
    

    顺序实例化

       可以不填入key对其进行实例化,但是要与定义结构体时的字段位置一一对应。

    1. 必须初始化结构体的所有字段。
    2. 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
    3. 该方式不能和键值初始化方式混用。
    package main
    
    import (
    	"fmt"
    )
    
    type dog struct{
    	dogName string 
    	dogAge int8
    	dogGender bool
    }
    
    func main() {
    	d1 := dog{
    		"大黄",
    		12,
    		true,
    	}
    	fmt.Print(d1)
    }
    
    

    地址实例化

       下面是使用{}进行地址实例化。

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct{
    	dogName string 
    	dogAge int8
    	dogGender bool
    }
    
    func main() {
    	d1 := &dog{
    		"大黄",
    		12,
    		true,
    	}
    	fmt.Print(d1)
    }
    

    内存布局

    连续内存

       一个结构体中的字段,都是占据一整块连续内存。

       但有的字段可能看起来不会与前一个字段进行相邻,这是受到类型的影响,具体可查看:在 Go 中恰到好处的内存对齐

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct {
    	dogName   string
    	dogAge    int8
    	dogGender bool
    }
    
    func main() {
    	d1 := &dog{
    		"大黄",
    		12,
    		true,
    	}
    	fmt.Printf("%p 
    ", &d1.dogName)
    	fmt.Printf("%p 
    ", &d1.dogAge)
    	fmt.Printf("%p 
    ", &d1.dogGender)
    
    	// 0xc0000044a0
    	// 0xc0000044b0
    	// 0xc0000044b1
    
    }
    
    

    空结构体

       一个空的结构体是不占据任何内存的。

    package main
    
    import (
    	"fmt"
    	"unsafe"
    )
    
    
    func main() {
    	var dog struct{}
    	fmt.Print(unsafe.Sizeof(dog)) // 0 查看占据的内存
    }
    
    

    构造函数

       当一个函数返回一个结构体实例时,该函数将被称为构造函数。

       Go语言中没有构造函数,但是我们可以自己进行定义。

       注意构造函数的命名方式要用new进行开头。

    普通构造

       由于函数的参数传递都是值传递,所以每次需要将结构体拷贝到构造函数中,这是非常消耗内存的。

       所以在真实开发中不应该使用这种方式

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct {
    	dogName string
    	dogAge int8
    	dogGender bool
    }
    
    // dog每次都会进行拷贝,消耗内存
    func newDog(dogName string, dogAge int8, dogGender bool) dog {
    	return dog{
    		dogName: dogName,
    		dogAge: dogAge,
    		dogGender: dogGender,
    	}
    }
    
    func main(){
    	d1 := newDog("大黄",12,true)
    	fmt.Printf("%p 
    ",&d1)
    }
    

    指针构造

       如果让其使用指针构造,就不用每次都会进行拷贝了。推荐使用该方式。

       只需要加上*&即可。

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct {
    	dogName string
    	dogAge int8
    	dogGender bool
    }
    
    // 每次的传递都是dog的指针,所以不会进行值拷贝
    func newDog(dogName string, dogAge int8, dogGender bool) *dog {
    	return &dog{
    		dogName: dogName,
    		dogAge: dogAge,
    		dogGender: dogGender,
    	}
    }
    
    func main(){
    	d1 := newDog("大黄",12,true)
    	fmt.Printf("%p 
    ",&d1)
    }
    

    方法&接收者

       为任意类型定制一个自定义方法,必须要为该类型进行接收者限制。

       接收者类似于其他语言中的selfthis

       定义方法格式如下:

    func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
        函数体
    }
    

       接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是selfthis之类的命名。例如,Person类型的接收者变量应该命名为 pConnector类型的接收者变量应该命名为c等。

       接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。

       方法名、参数列表、返回参数:具体格式与函数定义相同。

    普通接收者

       以下示例是定义普通接收者方法。

       注意,这是值拷贝,意味着你的d会拷贝d1的数据。

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct {
    	dogName string
    	dogAge int8
    	dogGender bool
    }
    
    func newDog(dogName string, dogAge int8, dogGender bool) *dog {
    	return &dog{
    		dogName: dogName,
    		dogAge: dogAge,
    		dogGender: dogGender,
    	}
    }
    
    func (d dog)getAge() int8 {
    	return d.dogAge  // 返回狗狗的年龄
    }
    
    func main(){
    	d1 := newDog("大黄",12,true)
    	age := d1.getAge()
    	fmt.Print(age) // 12
    }
    

    指针接收者

       由于普通接收者方法无法做到修改原本实例化对象数据的需求,所以我们可以定义指针接收者方法进行引用传递。

       如下,调用addAge()方法会将原本的年龄加上十岁。

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct {
    	dogName   string
    	dogAge    int8
    	dogGender bool
    }
    
    func newDog(dogName string, dogAge int8, dogGender bool) *dog {
    	return &dog{
    		dogName:   dogName,
    		dogAge:    dogAge,
    		dogGender: dogGender,
    	}
    }
    
    func (d *dog) addAge() {
    	d.dogAge += 10
    }
    
    func main() {
    	d1 := newDog("大黄", 12, true)
    	fmt.Printf("旧年龄:%v", d1.dogAge) // 12
    	d1.addAge()
    	fmt.Printf("新年龄:%v", d1.dogAge) // 22
    }
    
    

       关于使用d1直接调用,这是一种语法糖形式。完整的形式应该是使用&取到地址后再进行传递,但是这样会出现一些问题。

       所以直接使用实例化对象调用即可。

       下面是关于指针方法的一些使用注意事项:

    1. 修改原本实例化对象中的值时,应该使用指针接收者方法
    2. 实例化对象的内容较多,拷贝代价较大时,应该使用指针接收者方法
    3. 如果该对象下某个方法是指针接收者方法,那么为了保持一致性,其他方法也应该使用指针方法。

    自定类型方法

       下面将对自定义类型small做一个方法,getBool获取其布尔值。

    package main
    
    import (
    	"fmt"
    )
    
    type small uint8
    
    
    func (s small) getBool() bool {
    	if s != 0 {
    		return true
    	}
    	return false
    }
    
    func main() {
    	var num small
    	num = 18
    	result := num.getBool()
    	fmt.Print(result) // true
    }
    
    

      

    匿名字段

    基本使用

       匿名字段即只使用字段类型,不使用字段名。

       使用较少

       注意:这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

    package main
    
    import (
    	"fmt"
    )
    
    type dog struct {
    	string // 只能出现一次同类型的字段。
    	int8
    	bool
    }
    
    func main() {
    	d1 := dog{
    		string: "大黄",
    		int8:   12,
    		bool:   true,
    	}
    	fmt.Print(d1)
    }
    
    

    结构体嵌套

    基本使用

       一个结构体中可以嵌套另一个结构体。

       通过这种方式,可以达到继承的效果。

    package main
    
    import (
    	"fmt"
    )
    
    type details struct {
    	phone string // 电话
    	addr  string // 地址
    }
    
    type person struct {
    	name    string
    	gender  bool
    	age     int8
    	details // 匿名字段,详细信息
    }
    
    func main() {
    	p1 := person{
    		name:   "云崖",
    		gender: true,
    		age:    18,
    		details: details{  // 对匿名字段的嵌套结构体进行实例化
    			phone: "1008611",
    			addr:  "北京市海淀区",
    		},
    	}
    	fmt.Print(p1)
    	// {云崖 true 18 {1008611 北京市海淀区}}
    }
    
    

    匿名简写

       如果要访问上例中的电话,可以使用简写形式。也可以使用全写形式。

       查找顺序是先查找具名字段,再查找匿名字段。

       要注意多个结构体嵌套产生的字段名冲突问题。

    package main
    
    import (
    	"fmt"
    )
    
    type details struct {
    	phone string // 电话
    	addr  string // 地址
    }
    
    type person struct {
    	name    string
    	gender  bool
    	age     int8
    	details // 匿名字段,详细信息
    }
    
    func main() {
    	p1 := person{
    		name:   "云崖",
    		gender: true,
    		age:    18,
    		details: details{  // 对匿名字段的嵌套结构体进行实例化
    			phone: "1008611",
    			addr:  "北京市海淀区",
    		},
    	}
    	fmt.Println(p1.phone)  // 简写
    	fmt.Println(p1.details.phone) // 全写
    
    }
    
    

    JSON

       使用JSON包可对结构体进行序列化操作。

       常用于前后端数据交互。

    字段可见性

       由于json包是再encoding/json中,所以我们要想让main包的结构体能被json包访问,需要将结构体名字,字段名字等进行首字母大写。

       结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

    基本使用

       以下是关于JSON序列化与反序列化的基本使用。

    package main
    
    import (
    	"encoding/json" // 导包
    	"fmt"
    )
    
    // Details 详情  对于大写的结构体,应该具有注释。注意空格
    type Details struct {
    	Phone string // 电话
    	Addr  string // 地址
    }
    
    // Person 人
    type Person struct {
    	Name    string
    	Gender  bool
    	Age     int8
    	Details // 匿名字段,详细信息
    }
    
    func main() {
    	p1 := Person{
    		Name:   "云崖",
    		Gender: true,
    		Age:    18,
    		Details: Details{ // 对匿名字段的嵌套结构体进行实例化
    			Phone: "1008611",
    			Addr:  "北京市海淀区",
    		},
    	}
    	// 序列化 得到一个[]bytes类型
    	data, err := json.Marshal(p1)
    	if err != nil {
    		fmt.Println("json error")
    		return
    	}
    	fmt.Println(string(data)) // 查看结果
    	// {"Name":"云崖","Gender":true,"Age":18,"Phone":"1008611","Addr":"北京市海淀区"}
    
    	// 反序列化
    	p2 := Person{}
    	json.Unmarshal(data, &p2) // 反序列化时需要实例化出该结构体。通过地址对其进行赋值
    	fmt.Println(p2)           // {云崖 true 18 {1008611 北京市海淀区}}
    }
    
    

    标签使用

       我们可以看到上面的序列化后的结果字段名都是大写名字开头的。

    {"Name":"云崖","Gender":true,"Age":18,"Phone":"1008611","Addr":"北京市海淀区"}
    

       怎么样把它转换为小写?这个需要使用到结构体标签。

       示例如下:

    package main
    
    import (
    	"encoding/json" // 导包
    	"fmt"
    )
    
    // Details 详情  对于大写的结构体,应该具有注释。注意空格
    type Details struct {
    	// 代表在json转换中,Phone更名为phone  orm中更名为phone  配置文件ini中更名为phone
    	Phone string `json:"phone" db:"phone" ini:"phone"`
    	Addr  string `json:"addr"`
    }
    
    // Person 人
    type Person struct {
    	Name    string `json:"name"`
    	Gender  bool   `json:"gender"`
    	Age     int8   `json:"age"`
    	Details        // 匿名字段,详细信息
    }
    
    func main() {
    	p1 := Person{
    		Name:   "云崖",
    		Gender: true,
    		Age:    18,
    		Details: Details{ // 对匿名字段的嵌套结构体进行实例化
    			Phone: "1008611",
    			Addr:  "北京市海淀区",
    		},
    	}
    	// 序列化 得到一个[]bytes类型
    	data, err := json.Marshal(p1)
    	if err != nil {
    		fmt.Println("json error")
    		return
    	}
    	fmt.Println(string(data)) // 查看结果
    	// {"name":"云崖","gender":true,"age":18,"phone":"1008611","addr":"北京市海淀区"}
    
    	// 反序列化
    	p2 := Person{}
    	json.Unmarshal(data, &p2) // 反序列化时需要实例化出该结构体。通过地址对其进行赋值
    	fmt.Println(p2)           // {云崖 true 18 {1008611 北京市海淀区}}
    }
    
    

    小题目

       使用“面向对象”的思维方式编写一个学生信息管理系统。

    1. 学生有id、姓名等信息
    2. 程序提供展示学生列表、添加学生、编辑学生信息、删除学生等功能

       main.go

    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    type student struct {
    	id   int64
    	name string
    }
    
    
    func showMenu() {
    	fmt.Println("welcome sms!")
    	fmt.Println(`
    		1.查看所有学生
    		2.添加学生
    		3.修改学生
    		4.删除学生
    		5.退出
    	`)
    }
    
    func main() {
    	smr := studentMgr{
    		allStudent: make(map[int64]student, 50), // 实例化出一个map,最多容纳50组键值对
    	}
    
    	for {
    
    		showMenu()
    		// 获取输入
    		fmt.Print("输入您的选项>>>")
    		var choice int
    		fmt.Scanln(&choice)
    		switch choice {
    		case 1:
    			smr.showStudent()
    		case 2:
    			smr.addStudent()
    		case 3:
    			smr.editStudent()
    		case 4:
    			smr.deleteStudent()
    		case 5:
    			os.Exit(1) // 退出
    		default:
    			fmt.Println("输入有误...")
    		}
    	}
    }
    
    

       studentMgr.go

    package main
    
    import "fmt"
    
    type studentMgr struct {
    	allStudent map[int64]student
    	// 管理者 增删改查
    }
    
    // 查
    func (s studentMgr) showStudent() {
    	for _, stu := range s.allStudent {
    		fmt.Printf("学号:%d 姓名:%s 
    ", stu.id, stu.name)
    	}
    }
    
    // 增
    func (s studentMgr) addStudent() {
    	var (
    		stuID   int64
    		stuName string
    	)
    	fmt.Print("请输入学号>>>")
    	fmt.Scanln(&stuID)
    	fmt.Print("请输入姓名>>>")
    	fmt.Scanln(&stuName)
    	newStu := student{
    		id:   stuID,
    		name: stuName,
    	}
    	s.allStudent[newStu.id] = newStu
    }
    
    // 改
    func (s studentMgr) editStudent() {
    	var stuID int64
    	fmt.Print("请输入学号>>>")
    	fmt.Scanln(&stuID)
    	stuObj, ok := s.allStudent[stuID] // 值拷贝
    	if !ok {
    		fmt.Println("没有该学生")
    		return
    	}
    	fmt.Printf("学生信息如下:
      学号:%d
      姓名:%s
    ", stuObj.id, stuObj.name)
    	fmt.Print("请输入学生的新名字>>>")
    	var newName string
    	fmt.Scanln(&newName)
    	stuObj.name = newName
    	s.allStudent[stuID] = stuObj
    }
    
    // 删
    func (s studentMgr) deleteStudent() {
    	var stuID int64
    	fmt.Print("请输入要删除的学生学号>>>")
    	fmt.Scanln(&stuID)
    	_, ok := s.allStudent[stuID]
    	if !ok {
    		fmt.Println("没有该学生")
    		return
    	}
    	delete(s.allStudent, stuID)
    	fmt.Println("删除成功")
    }
    
    

       测试使用命令:

    go run main.go studentMgr.go
    
  • 相关阅读:
    C++基础知识篇:C++ 存储类
    听说高手都用记事本写C语言代码?那你知道怎么编译运行吗?
    培训机构出来的程序员和科班比?看看这个科班毕业生怎么说~
    C++基础知识篇:C++ 修饰符类型
    从大学毕业到就业,程序员的人生如何走过?30岁以后的开发人员路在何方?
    终于有人把鸿蒙OS讲明白了,大佬讲解!快收藏!
    C++基础知识篇:C++ 常量
    Portrait Matting
    Deep-Trimap-Generation-for-Automatic-Video-Matting-using-GAN
    Automatic Trimap Generator
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13777898.html
Copyright © 2020-2023  润新知