# Day04 结构体方法序列化

# 本节关键词

  • type 自定义类型名称 类型struct{组合属性}
  • 结构体是值类型,初始化未指定值时,有默认类型的零值
  • 方法是作用于特定类型的函数
  • 序列化Marshal与Unmarshal调用参数与返回格式

# 自定义类型

type ReturnCode int				// 自定义枚举

type MyFunc fun(int, int)int	// 有节制的函数类型

type scoreMap map[string]int

自定义类型是程序员根据自己的需要创造的新类型

# 类型别名

让代码更易读

type NewInt = int

内置的byterune 就是类型别名

type byte = uint8
type rune = int32

区别:

类型别名只在源文件中生效,编译时会被替换成原始类型。

# 类型显式转换

var x MyInt = 100
fmt.Printf("x:%T\n", x)  // main.MyInt

var y NewInt = 100
fmt.Printf("y:%T\n", y)  // int

x = MyInt(y)  // 类型强制转换
y = NewInt(x) // 类型强制转换

f := 1.123
i := int(f) // 浮点数可以强制转换成整数,但是会丢失精度
fmt.Println(i)

# 结构体定义

通过结构体struct封装多个基本数据类型,实现自定义数据类型,也实现面向对象

// Student 定义一个学生类型
type Student struct {
	name     string
	age      int8
	married  bool
	mapScore map[string]int
}

// Order 定义一个订单类型
type Order struct {
	id         int64
	skuId      int64 // 商品id
	userId     int64
	createTime int64
}
  • Go语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值

# 结构体实例化

只有当结构体实例化时,才会真正地分配内存,也就是必须实例化后才能使用结构体的字段

结构体本身也是一种类型,所以用var来声明结构体类型

// 结构体是值类型,赋值或参数传递 深拷贝,stu.name 默认对应类型零值
var stu Student

// 结构体字面量初始化
func demo5() {
	stu2 := Student{
		name: "王俊翔",
		age:  26,
		mapScore: map[string]int{
			"语文": 6,
			"数学": 100,
		},
	}
	fmt.Printf("%+v\n", stu2)

	stu3 := Student{} // 其中 stu3.scoreMap == nil
	fmt.Printf("%+v\n", stu3)

	stu4 := &Student{} // 取地址  --》 new(Student) --> 结构体指针
	(*stu4).name = "linda"
	stu4.age = 18 // Go语言中提供的语法糖,支持 结构体指针类型.属性 简写
	fmt.Printf("%+v\n", stu4)

	// var stu5 *Student // nil
	// var stu5 = new(Student)
	var stu5 = &Student{}
	stu5.name = "tom" // (*nil).name =
	fmt.Printf("%+v\n", stu5)
	stu5 = &Student{
		name: "catherine",
	}
	stu5 = new(Student)

	// var x *int       // nil
	var x = new(int)
	*x = 100 // (*nil) = 100
	fmt.Println(x)

	// 列表初始化
	// 必须按结构体定义时候的属性顺序依次赋值
	var stu6 = Student{
		"吴勇",
		18,
		false,
		map[string]int{"语文": 1},
	}
	fmt.Println(stu6)
}

# 结构体指针

// 结构体字面量初始化
func demo5() {
	stu4 := &Student{} // 取地址  --》 new(Student) --> 结构体指针
	(*stu4).name = "李硕"
	stu4.age = 18 // Go语言中提供的语法糖,支持 结构体指针类型.属性 简写
	fmt.Printf("%+v\n", stu4)

	// var stu5 *Student // nil
	// var stu5 = new(Student)
	var stu5 = &Student{}
	stu5.name = "jade" // (*nil).name =
	fmt.Printf("%+v\n", stu5)
	stu5 = &Student{
		name: "大都督",
	}
	stu5 = new(Student)

	// var x *int       // nil
	var x = new(int)
	*x = 100 // (*nil) = 100
	fmt.Println(x)
}

# 空结构体

空结构体不占用空间

struct{}{}

# 结构体内存布局

  • 结构体占用连续的内存空间
  • 结构体占用的内存大小是由每个属性的大小和内存对齐决定的
  • 内存对齐的原理:CPU读取内存是以**word size(字长)**为单位,避免出现一个属性CPU分多次读取的问题
  • 内存对齐是编译器帮我们根据CPU和平台来自动处理的
  • 我们利用对齐的规则合理的减小结构体的体积
  • 对齐系数:对于 struct 类型的变量 x,计算 x 每一个字段 f 的 unsafe.Alignof(x.f)unsafe.Alignof(x) 等于其中的最大值。

# 方法与接收者

Go语言中的方法(method)是一种作用于特定类型变量的函数,这种特定类型变量叫做接收者(Receiver),接收者的概念类似其他语言中的thisself

# 方法的定义

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}
  • 方法与函数的区别是,函数不属于任何类型,方法属于特定的类型

# 接收者

  • 值接收者
  • 指针接收者
type Person struct {
	name string
	age  int
}

// newPerson 构造函数
func newPerson(name string, age int) Person {
	return Person{
		name: name,
		age:  age,
	}
}

// dream 给person定义一个方法
// p ==》 this, self
func (p Person) dream(s string) {
	fmt.Printf("%s的梦想是%s\n", p.name, s)
}

// 使用结构体值作为接收者定义方法
// func (p Person) incrAge() {
// 	p.age++ // 值拷贝,改副本
// }

// 使用结构体指针作为接收者定义方法
func (p *Person) incrAge() {
	p.age++
}

# 什么时候应该使用指针类型接收者

  • 需要修改接收者中的值
  • 接收者是拷贝代价比较大的大对象
  • 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者

# 嵌套结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针

//Address 地址结构体
type Address struct {
	Province string
	City     string
}

//User 用户结构体
type User struct {
	Name    string
	Gender  string
	Address Address
}

func main() {
	user1 := User{
		Name:   "linda",
		Gender: "女",
		Address: Address{
			Province: "江苏",
			City:     "南京",
		},
	}
	fmt.Printf("user1=%#v\n", user1)
   fmt.Println(user1.City) // 直接使用 .City
}

# 结构体"继承"

  • 在于构建合理的数据结构
type Animal struct {
	Name string
}

func (a *Animal) Move() {
	fmt.Printf("%s在移动~\n", a.Name)
}

type Dog struct {
	Feet    int8
	*Animal // 通过嵌套匿名结构体实现继承,Animal *Animal
}

func (d *Dog) Wang() {
	fmt.Printf("%s会汪汪~\n", d.Name)
}

func main() {
	d := &Dog{
		Feet: 4,
		Animal: &Animal{
			Name: "tonny",
		},
	}
	d.Move()
	d.Wang()
}

# 序列化json

type Address struct {
	Province string `json:"province"`
	City     string `json:"city"`
}

type Student struct {
	Name    string `json:"name"`
	Age     int8   `json:"age"`
	Address `json:"address"`
}

func main() {
	stu := Student{
		Name: "linda",
		Age:  18,
		Address: Address{
			Province: "江苏",
			City:     "南京",
		},
	}

	b, err := json.Marshal(stu)
	if err != nil {
		fmt.Println("json marshal err: ", err)
		return
	}
	fmt.Printf("%s\n", b)

	str := `{"name":"linda","age":18,"address":{"province":"江苏","city":"南京"}}`
	stu1 := &Student{}
	err = json.Unmarshal([]byte(str), stu1)
	if err != nil {
		fmt.Println("json unmarshal err: ", err)
		return
	}
	fmt.Printf("%#v\n", stu1)
}
  • json.Marshal与json.Unmarshal传参与接收值的方式
  • 结构体标签 json:"name"

# 课后作业

  1. 下面的代码执行结果为?为什么?

    type student struct {
    	name string
    	age  int
    }
    
    func main() {
    	m := make(map[string]*student)
    	stus := []student{
    		{name: "小王子", age: 18},
    		{name: "娜扎", age: 23},
    		{name: "大王八", age: 9000},
    	}
    
    	for _, stu := range stus {
    		m[stu.name] = &stu
    	}
    	for k, v := range m {
    		fmt.Println(k, "=>", v.name)
    	}
    }
    
  2. 编写学生管理系统

    • 学生有id、姓名、年龄、分数等信息
    • 程序提供展示学生列表、添加学生、编辑学生信息、删除学生等功能
package main

import "fmt"

// Student 学生结构体
type Student struct {
   Id    int64
   Name  string
   Age   int8
   Score int8
}

// Manager 学员管理系统结构体
type Manager struct {
   StuInfo map[int64]*Student
}

// ShowAll ...
func (m *Manager) ShowAll() {
   for id, stu := range m.StuInfo {
   	fmt.Printf("ID: %d, Name: %s\n", id, stu.Name)
   }
}

// AddStu ...
func (m *Manager) AddStu() {
   fmt.Print("请依次输入ID Name Age Score: ")
   var (
   	id    int64
   	name  string
   	age   int8
   	score int8
   )
   fmt.Scanln(&id, &name, &age, &score)
   newStu := Student{
   	Id:    id,
   	Name:  name,
   	Age:   age,
   	Score: score,
   }
   m.StuInfo[id] = &newStu
}

// EditStu ...
func (m *Manager) EditStu() {
   fmt.Print("请输入ID:")
   var id int64
   fmt.Scanln(&id)
   if _, ok := m.StuInfo[id]; !ok {
   	fmt.Println("查无此人!")
   }

   fmt.Print("请依次输入新值 Name Age Score: ")
   var (
   	name  string
   	age   int8
   	score int8
   )
   fmt.Scanln(&name, &age, &score)
   m.StuInfo[id].Name = name
   m.StuInfo[id].Age = age
   m.StuInfo[id].Score = score
}

// DelStu ...
func (m *Manager) DelStu() {
   fmt.Print("请输入ID:")
   var id int64
   fmt.Scanln(&id)
   if _, ok := m.StuInfo[id]; !ok {
   	fmt.Println("查无此人!")
   }

   delete(m.StuInfo, id)
}

func main() {

   mgr := Manager{
   	StuInfo: make(map[int64]*Student, 8),
   }

   for {
   	// 进入系统
   	fmt.Println(`
   欢迎使用学生管理系统v1.0
   菜单:
   	1. 查看学生
   	2. 增加学生
   	3. 编辑学生
   	4. 删除学生
   	5. 退出学生
   `)

   	var input int
   	fmt.Print("请输入:")
   	fmt.Scanln(&input)

   	switch input {
   	case 1:
   		mgr.ShowAll()
   	case 2:
   		mgr.AddStu()
   	case 3:
   		mgr.EditStu()
   	case 4:
   		mgr.DelStu()
   	case 5:
   		fmt.Println("欢迎使用学生管理系统,再见!")
   		return
   	default:
   		fmt.Println("无效输入!")
   	}

   }
}
上次更新: 1/10/2023, 12:41:52 PM