# Day04 结构体方法序列化
# 本节关键词
type 自定义类型名称 类型struct{组合属性}
结构体是值类型,初始化未指定值时,有默认类型的零值
方法是作用于特定类型的函数
序列化Marshal与Unmarshal调用参数与返回格式
# 自定义类型
type ReturnCode int // 自定义枚举
type MyFunc fun(int, int)int // 有节制的函数类型
type scoreMap map[string]int
自定义类型是程序员根据自己的需要创造的新类型
# 类型别名
让代码更易读
type NewInt = int
内置的byte
和rune
就是类型别名
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)
,接收者的概念类似其他语言中的this
和self
# 方法的定义
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"
# 课后作业
下面的代码执行结果为?为什么?
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) } }
编写学生管理系统
- 学生有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("无效输入!")
}
}
}