08-Go语言基础之struct
学过python编程语言的话,必然学过面向对象、class类的处理。
GO语言没有类的概念,也没有类的继承等概念。
Go语言通过结构体,以及结构体嵌套、包括interface接口、可以实现比面向对象更强的扩展性、灵活性。
type自定义类型
Go语言内置的数据类型,都是通过type关键字定义而来。

我们也可以通过type关键字,自定义所需的数据类型。
// myInt就是一个全新的自定义类型,具有int的属性
type myInt int
//测试
package main
import "fmt"
type myInt int
func main() {
var x myInt
var y myInt
x, y = 7, 8
fmt.Printf("x类型:%T、值:%d\n", x, x)
fmt.Printf("y类型:%T、值:%d\n", y, y)
}
/*
x类型:main.myInt、值:7
y类型:main.myInt、值:8
*/
类型别名
如内置的rune、byte就是类型别名

Go1.9之后支持了类型别名,语法
https://colobu.com/2017/06/26/learn-go-type-aliases/
在Go 1.9中, 内部其实使用了类型别名的特性。
比如内建的byte类型,其实是uint8的类型别名,而rune其实是int32的类型别名。
注意:类型别名、类型定义区别
这俩只有一个等于号 = 的差异,来理解下他们的区别。
package main
import "fmt"
func main() {
//类型定义,没有等于号
type newInt int
//使用该类型
var num1 newInt
fmt.Printf("newInt类型定义 num1类型:%T、值:%#v\n", num1, num1)
// 类型别名,有等于号
type aliasInt = int
//使用该类型
var num2 aliasInt
fmt.Printf("aliasInt类型别名 num2类型:%T、值:%#v\n", num2, num2)
}
/*
类型定义 num1类型:main.newInt、值:0 可以看出,类型定义显示的是main.newInt 数据类型, go编译完成后,该类型也是存在的
类型别名 num2类型:int、值:0 ,类型别名,只是个代号、其实还是int本身,只是在代码里存在,Go编译后,该类型aliasInt是不存在的
*/
Go结构体
Go语言中的基础数据类型,int、string、float、bool都用于表示简单的事物属性;
但若要定义例如一个人的多种属性,名字、年龄,多个属性时,单一的数据类型就不合适了;
Go语言提供了一种自定义数据类型,通过type关键字,定义个结构体类型,可以封装多个基本数据类型;
该结构体类型,英文名struct;
并且Go语言支持面向对象编程,struct可以参考如python的class去理解;
Go通过struct实现面向对象。
结构体概念
- 将一类事物特征提取出一个新的数据类型、就是struct
- 通过struct可以创建多个实例
- Struct用于定义复杂数据类型
- Struct包含了多个字段
- Struct可以定义method(注意是方法,不是函数)
- Struct类型可以嵌套
- Struct是自定义类型,无法和其他类型强转
- Struct支持给字段添加tag、tag可以通过反射机制获取,用在如json序列化场景
结构体定义
语法
通过type 和struct两个关键字定义
type 结构体名 struct {
字段名 数据类型
字段名 数据类型
字段名 数据类型
}
注意
- 结构体名,同一个包内不得重复
- 字段名,结构体内字段名必须唯一
- 字段类型,对应字段的数据类型
结构体实例化、读写
package main
import "fmt"
//Go语言里,变量名,大小写开头是有区别的,先记住
type person struct {
//同样类型的字段,可以简写一行
name, city string
age int
}
func main() {
//这样就有了person这个结构体自定义类型,并且拥有name,city,age三个字段,可以用改类型存储用户的信息
//结构体实例化,只有实例化时,才会分配内存,才可以使用该结构体变量存储值
//结构体变量声明,和普通变量一样,支持var、以及简短声明
//注意改语法,是自定义类型,等于 main.persion
var p1 person
p1.name = "于超"
p1.city = "北京"
p1.age = 28
fmt.Printf("p1结构体 类型:%T、值:%#v\n", p1, p1)
// p1结构体 类型:main.person、值:main.person{name:"于超", city:"北京", age:28}
//设置、读写结构体字段的值,都是通过 实例化对象.字段 语法
fmt.Printf("p1结构体 名字:%s\n", p1.name)
fmt.Printf("p1结构体 年龄:%d\n", p1.age)
fmt.Printf("p1结构体 城市:%s\n", p1.city)
//修改结构体字段值
p1.name = "三胖"
p1.age = 34
p1.city = "外国人"
fmt.Println("p1结构体修改后——---")
fmt.Printf("p1结构体 名字:%s\n", p1.name)
fmt.Printf("p1结构体 年龄:%d\n", p1.age)
fmt.Printf("p1结构体 城市:%s\n", p1.city)
}
/*
p1结构体 类型:main.person、值:main.person{name:"于超", city:"北京", age:28}
p1结构体 名字:于超
p1结构体 年龄:28
p1结构体 城市:北京
p1结构体修改后——---
p1结构体 名字:三胖
p1结构体 年龄:34
p1结构体 城市:外国人
*/
匿名结构体
go语言支持该语法,了解即可,某些场景会见到。
直接实例化结构体,且没有名字
package main
import "fmt"
func main() {
p1 := struct {
name, city string
age int
}{
name: "于超",
city: "北京",
age: 28,
}
fmt.Printf("匿名结构体p1 类型:%T、值:%#v\n", p1, p1)
fmt.Println(p1.name)
fmt.Println(p1.age)
fmt.Println(p1.city)
}
/*
匿名结构体p1 类型:struct { name string; city string; age int }、值:struct { name string; city string; age int }{name:"于超", city:"北京", age:28}
于超
28
北京
*/
结构体多种初始化语法(重点)
package main
import "fmt"
// 结构体是值类型,不同的结构体实例有独立空间,互不影响
type hero struct {
name string
address string
price float64
}
// 更复杂的结构体字段
type person struct {
name string
age int
score [5]float64 //长度为5的数组
ptr *string //指针类型
friends []string //切片,注意初始化内存
info map[string]string //map类型,注意初始化内存
}
func main() {
//用法1,通过点字段,赋值字段
var h1 hero
h1.name = "吕布"
h1.address = "对抗路"
h1.price = 18888.88
fmt.Printf("h1英雄、类型:%T、值:%#v\n", h1, h1)
// 用法2,简短声明且赋值
//对应字段顺序赋值
h2 := hero{"程咬金", "对抗路", 13888.88}
fmt.Printf("h2英雄、类型:%T、值:%#v\n", h2, h2)
// 用法3,指定字段赋值,键值方式赋值
h3 := hero{
price: 8888.88,
address: "发育路",
name: "鲁班",
}
fmt.Printf("h3英雄、类型:%T、值:%#v\n", h3, h3)
//用法4,内存地址不一样,是独立的值
fmt.Printf("h1--%p、h2--%p,h3--%p、三个实例化对象,都是独立的值\n", &h1, &h2, &h3)
//用法5,结构体有初始化的零值
h4 := hero{}
fmt.Printf("h4英雄零值、类型:%T、值:%#v\n", h4, h4)
//用法6,对结构体指针操作、new()函数可以返回该类型的指针,或者直接 &hero{} > new(hero) > 都是获取结构体指针
var h5 *hero = new(hero)
fmt.Printf("h5英雄默认值、类型:%T、值:%#v\n", h5, h5)
//给h5结构体指针赋值,注意语法
(*h5).name = "马可波罗" //go编译器认识的语法
h5.address = "发育路" //go支持该语法糖,直接结构体指针.字段 即可赋值,是一个简写
(*h5).price = 18888.11
fmt.Printf("h5英雄赋值后、类型:%T、值:%#v\n", h5, h5)
// 用法7,结构体指针,赋值语法2,同上
h6 := &hero{
name: "貂蝉",
address: "中路",
price: 6888.88,
}
fmt.Printf("h6英雄赋值后、类型:%T、值:%#v\n", h6, h6)
fmt.Println("学习这些语法,是为了让大伙知道,go支持该多种方式,打扎实基本功,便于以后深入学习go语言")
fmt.Println("用法6、用法7、是go编译器支持的简写,让程序员降低心智负担,只要你看懂任何一种,会用即可")
}
/*
h1英雄、类型:main.hero、值:main.hero{name:"吕布", address:"对抗路", price:18888.88}
h2英雄、类型:main.hero、值:main.hero{name:"程咬金", address:"对抗路", price:13888.88}
h3英雄、类型:main.hero、值:main.hero{name:"鲁班", address:"发育路", price:8888.88}
h1--0xc00010e030、h2--0xc00010e0c0,h3--0xc00010e150、三个实例化对象,都是独立的值
h4英雄零值、类型:main.hero、值:main.hero{name:"", address:"", price:0}
h5英雄默认值、类型:*main.hero、值:&main.hero{name:"", address:"", price:0}
h5英雄赋值后、类型:*main.hero、值:&main.hero{name:"马可波罗", address:"发育路", price:18888.11}
h6英雄赋值后、类型:*main.hero、值:&main.hero{name:"貂蝉", address:"中路", price:6888.88}
学习这些语法,是为了让大伙知道,go支持该多种方式,打扎实基本功,便于以后深入学习go语言
用法6、用法7、是go编译器支持的简写,让程序员降低心智负担,只要你看懂任何一种,会用即可
author : www.yuchaoit.cn
*/
复杂结构体字段类型
package main
import "fmt"
// 更复杂的结构体字段
type person struct {
name string
age int
score [5]int //长度为5的数组
ptr *string //指针类型
friends []string //切片,注意初始化内存
info map[string]string //map类型,注意初始化内存
}
func main() {
ptr := "张飞"
score := [...]int{1, 2, 3, 4, 5}
friends := []string{"坦克", "盖伦"}
info := map[string]string{
"游戏": "王者",
}
p1 := person{
"张飞",
100,
score,
&ptr,
friends,
info,
}
fmt.Printf("复杂结构体字段类型,了解即可:%T、%#v\n", p1, p1)
fmt.Println(p1.name)
fmt.Println(p1.age)
fmt.Println(p1.score)
fmt.Println(p1.ptr)
fmt.Println(p1.friends)
fmt.Println(p1.info)
}
/*
复杂结构体字段类型,了解即可:main.person、main.person{name:"张飞", age:100, score:[5]int{1, 2, 3, 4, 5}, ptr:(*string)(0xc000096220), friends:[]string{"坦克", "盖string{"游戏":"王者"}}
张飞
100
[1 2 3 4 5]
0xc000096220
[坦克 盖伦]
map[游戏:王者]
*/
Go结构体内存分配
结构体字段内存地址是连续的
结构体是占用一块连续的内存,一个结构体变量的大小是由结构体中的字段决定。
package main
import "fmt"
type Test struct {
A int8
B int8
C int8
D int8
}
func main() {
var t Test
fmt.Printf("Test.A addr:%p\n", &t.A)
fmt.Printf("Test.B addr:%p\n", &t.B)
fmt.Printf("Test.C addr:%p\n", &t.C)
fmt.Printf("Test.D addr:%p\n", &t.D)
}
/*
Test.A addr:0xc0000200a4
Test.B addr:0xc0000200a5
Test.C addr:0xc0000200a6
Test.D addr:0xc0000200a7
*/
空结构体不占内存
package main
import (
"fmt"
"unsafe"
)
func main() {
//空结构体
var t2 struct{}
fmt.Println(unsafe.Sizeof(t2)) // 0
}
结构体指针实践
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
//p1有自己的结构体内存地址,
var p1 Person
p1.Age = 10
p1.Name = "三胖"
//定义P2 指针类型,指向p1的内存地址
var p2 *Person = &p1
//两种形式一样,go编译器自动识别
fmt.Printf("p1结构体 类型:%T、值:%v、地址:%p\n", p1, p1, &p1)
fmt.Printf("p2结构体指针 类型:%T、值:%v、地址:%p\n", p2, p2, p2)
//修改p2的结构体值,也就是修改了p1的结构体值
p2.Name = "狗蛋"
fmt.Printf("修改p2后,输出结果 p2.Name=%v p1.Name=%v\n", p2.Name, p1.Name)
fmt.Printf("修改p2后,输出结果(*p2).Name=%v p1.Name=%v\n", (*p2).Name, p1.Name)
//查看p1和p2的内存地址
fmt.Printf("p1内存地址%p\n", &p1)
//p2是指针变量,自己也有一块内存地址,p2的值指向p1
fmt.Printf("直接打印p2:%p 等于打印p1的内存地址\n", p2)
fmt.Printf("p2指针变量,本身自己也有内存地址,存储的值是,p1的地址:%p", &p2)
}
/*
p1结构体 类型:main.Person、值:{三胖 10}、地址:0xc0000a4000
p2结构体指针 类型:*main.Person、值:&{三胖 10}、地址:0xc0000a4000
修改p2后,输出结果 p2.Name=狗蛋 p1.Name=狗蛋
修改p2后,输出结果(*p2).Name=狗蛋 p1.Name=狗蛋
p1内存地址0xc0000a4000
直接打印p2:0xc0000a4000 等于打印p1的内存地址
p2指针变量,本身自己也有内存地址,存储的值是,p1的地址:0xc00008c018
*/
Go结构体指针面试题
package main
import "fmt"
type hero struct {
name string
age int
}
func main() {
m := make(map[string]*hero)
//切片,元素是3个结构体
stus := []hero{
{name: "张飞", age: 18},
{name: "吕布", age: 23},
{name: "鲁班", age: 9000},
}
fmt.Printf("stus类型:%T、值:%#v\n", stus, stus)
//分别处理3个结构体
for _, stu := range stus {
//张飞、吕布、鲁班
// 循环赋值,结构体的内存地址
m[stu.name] = &stu
}
fmt.Printf("m类型:%T、值:%v\n", m, m)
for k, v := range m {
fmt.Println(k, "=>", v.name)
}
}
/*
stus类型:[]main.hero、值:[]main.hero{main.hero{name:"张飞", age:18}, main.hero{name:"吕布", age:23}, main.hero{name:"鲁班", age:9000}}
m类型:map[string]*main.hero、值:map[吕布:0xc0000a0090 张飞:0xc0000a0090 鲁班:0xc0000a0090]
张飞 => 鲁班
吕布 => 鲁班
鲁班 => 鲁班
*/
Go构造函数
以python构造函数为基础知识
# 类是对象的模板,通过类模板,去创建实例对象,然后可以调用类定义的功能
# 那么每一个对象的属性怎么初始化?答案是构造函数,如python的 __init__
# 在类实例化的时候,python自动执行该方法
'''
我们通过类创建实例对象后,需要定义构造函数__init__()方法。
构造方法用于执行实例对象的初始化工作,即对象创建之后,初始化当前对象的相关的属性,无返回值
构造函数重点:
名称固定,必须为__init__()
第一个参数固定,必须是:self。self指的是就是刚刚创建好的实例对象。
构造函数通常用来初始化实例对象的实例属性
通过类名(参数列表)来调用构造函数,调用后,将创建好的对象返回给相应的变量
构造函数__init__()方法:初始化创建好的对象,初始化指的是给实例属性赋值值
创建实例__new__()方法:用于创建对象,开辟内存,但一般都无需重新定义该方法
'''
class Animal:
# 语法上就支持构造函数,用户可以直接定义属性
def __init__(self,name,food): # Animal的属性特征
self.name = name
self.food = food
def info(self): # Animal行为
print(self.name,'的最喜欢的食物是',self.food)
#创建猫实例
cat = Animal("cat","fish")
cat.info()
#创建狗实例
dog = Animal("dog","meat")
dog.info()
'''
cat 的最喜欢的食物是 fish
dog 的最喜欢的食物是 meat
总结,python的构造函数__init__用于创建对象后,自动设置对象的属性;
构造函数可以接收参数
'''
Go的构造函数
Go没有构造函数,但是可以用结构体初始化模拟构造函数
package main
import "fmt"
// 如同class,定义好 人的基础属性,然后实例化
// go没有class,是通过结构体定义
type Person struct {
name string
age int
gender string
}
// 女娲写了一个构造函数,去创建实例化对象,参考python的 __init__理解,当然底层还有一个__new__开辟内存空间
// 一个独立的人,就等于单独内存里开辟一个空间
// 但是go没有构造函数啊,因此可以对结构体实例化,模拟构造函数
// 该函数作用是创建结构体实例化
func newPerson(name, gender string, age int) Person {
return Person{
name: name,
gender: gender,
age: age,
}
}
func main() {
//普通实例化
//p1 := Person{
// "于超",
// 18,
// "男",
//}
//
//fmt.Println("人类一号创建完毕:", p1)
//创建用户
p1 := newPerson("于超", "男", 18)
fmt.Println("人类一号:", p1)
p1.name = "三胖"
fmt.Println("人类一号改了名字:", p1)
}
Go方法、接受者
图解函数、方法语法区别

理解method是什么
- 面向对象是一个编程范式,Go语言也支持OOP编程。
- 一个结构体实例化后就是一个对象了,这个对象有基本的属性(姓名、年龄、性别等)
- 对象还得有一些行为动作,例如(吃饭、跑步、唱跳)这些就需要定义
方法 - GO的
方法是作用在指定的数据类型上的,和数据类型绑定,自定义类型都可以绑定method,不仅仅是struct
1、官方定义:Methods are not mixed with the data definition (the structs): they are orthogonal to types; representation(data) and behavior (methods) are independent
2、方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。
自定义类型绑定方法
package main
import "fmt"
// 1. 自定义类型
type myInt int
// 2.绑定方法
func (m *myInt) increment() {
*m++ // 给源数字,+1操作
}
func main() {
// 3.使用自定义类型,调用方法
var num1 myInt
num1 = 10
num1.increment()
num1.increment()
num1.increment()
fmt.Println("自增3次后num1:", num1)
}
/*
➜ goStudy go run t1.go
自增3次后num1: 13
*/
图解

结构体+方法
Go语言的的方法(method)是一种用于特定类型变量的函数。
这个特性类型的变量有个名字,接受者,Receiver。
method方法定义语法
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self、this之类的命名。
例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
方法名、参数列表、返回参数:具体格式与函数定义相同。
方法声明与调用
package main
import "fmt"
// 定义一个结构体数据类型
type Student struct {
Username string
Age int
Sex string
}
// 表示给Person结构体,绑定添加test方法
func (s Student) show() {
fmt.Println("s接受者,对象属性查看:", s.Username)
}
func main() {
//
p1 := Student{
"二狗",
18,
"男",
}
p1.show()
}
/*
➜ goStudy go run t1.go
s接受者,对象属性查看: 二狗
总结:
1. show方法、只能和Student结构体类型绑定
2. show方法,只能通过Student结构体的实例化对象调用
*/
Go方法声明合集
好比是一个函数,有参、无参、有返回值、无返回值等;
一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。
所有给定类型的方法属于该类型的方法集。
方法定义:
func (recevier type) methodName(参数列表)(返回值列表){}
参数和返回值可以省略
package main
type Test struct{}
// 无参数、无返回值
func (t Test) method0() {
}
// 单参数、无返回值
func (t Test) method1(i int) {
}
// 多参数、无返回值
func (t Test) method2(x, y int) {
}
// 无参数、单返回值
func (t Test) method3() (i int) {
return
}
// 多参数、多返回值
func (t Test) method4(x, y int) (z int, err error) {
return
}
// 无参数、无返回值
func (t *Test) method5() {
}
// 单参数、无返回值
func (t *Test) method6(i int) {
}
// 多参数、无返回值
func (t *Test) method7(x, y int) {
}
// 无参数、单返回值
func (t *Test) method8() (i int) {
return
}
// 多参数、多返回值
func (t *Test) method9(x, y int) (z int, err error) {
return
}
func main() {}
Go方法实战(盖伦战斗)
package main
import "fmt"
// 1.玩家模板
type Player struct {
Name string
HealthPoint int
Level int
}
// 2.构造函数,创建玩家,具体实例化
// 由于需要绑定方法,传入接受者,存在值拷贝问题,传入指针即可
func NewPlayer(name string, hp int, level int) *Player {
return &Player{
name,
hp,
level,
}
}
func (p Player) attack() {
fmt.Printf("%s发起攻击!\n", p.Name)
}
// 需要修改接受者的属性,得传入指针
func (p *Player) attacked() {
p.HealthPoint -= 10
fmt.Printf("%s被攻击!血量剩余:%d\n", p.Name, p.HealthPoint)
}
func main() {
player := NewPlayer("盖伦", 100, 100)
player.attack()
player.attacked()
fmt.Printf("盖伦血量:%d\n", player.HealthPoint)
}
/*
➜ goStudy go run t1.go
盖伦发起攻击!
盖伦被攻击!血量剩余:90
盖伦血量:90
➜ goStudy
*/
Go匿名字段
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
顾明思议,就是结构体中的字段,只有数据类型、没名字。
匿名字段实践
package main
import "fmt"
type Person struct {
string
int
}
func main() {
p1 := Person{
"于超",
18,
}
fmt.Printf("%#v\n", p1)
fmt.Println(p1.string, p1.int)
}
注意:
这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名
结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
结构体作为匿名字段(嵌套结构体)
一个结构体中可以嵌套包含另一个结构体或结构体指针,就像下面的示例代码那样。
package main
import "fmt"
type Addr struct {
country string
province string
city string
}
// 省的给每一个人再去定义省份信息
type Person struct {
name string
age int
Addr // 结构体嵌套,你们字段
}
func main() {
p1 := Person{
"于超",
18,
Addr{"中国", "江苏", "淮安"},
}
fmt.Printf("%#v\n", p1)
fmt.Println(p1.name, p1.age)
fmt.Println(p1.Addr)
//当访问对象属性时,优先在结构体中找该字段,找不到去匿名字段中查找
fmt.Println(p1.Addr.country) //匿名字段可以作为字段名查找
fmt.Println(p1.city) //匿名字段也可直接省略
}
/*
➜ goStudy go run t1.go
main.Person{name:"于超", age:18, Addr:main.Addr{country:"中国", province:"江苏", city:"淮安"}}
于超 18
{中国 江苏 淮安}
中国
淮安
*/
嵌套结构体字段冲突怎么办
package main
import "fmt"
// Address 地址结构体
type Address struct {
Province string
City string
CreateTime string
}
// Email 邮箱结构体
type Email struct {
Account string
CreateTime string
}
// User 用户结构体
type User struct {
Name string
Gender string
Address
Email
}
func main() {
//实例化
var u1 User
u1.Name = "于超老师"
u1.Gender = "男"
//冲突的字段名,默认采用外层优先规则
//建议指定具体字段
u1.Email.CreateTime = "2023"
u1.Address.CreateTime = "2017"
fmt.Printf("u1值:%#v\n", u1)
}
/*
➜ goStudy go run t1.go
u1值:main.User{Name:"于超老师", Gender:"男", Address:main.Address{Province:"", City:"", CreateTime:"2017"}, Email:main.Email{Account:"", CreateTime:"2023"}}
*/
结构体继承
其实刚才的结构体嵌套,就已经实现了继承关系;
package main
import "fmt"
// Animal 动物
type Animal struct {
name string
}
func (a *Animal) eat() {
fmt.Printf("%s 正在干饭!!\n", a.name)
}
func (a *Animal) sleep() {
fmt.Printf("%s 又趴地上睡着了!!\n", a.name)
}
// Dog 类型
// Dog应该拥有Animal所有的特征,而无需反复定义
type Dog struct {
Kind string
*Animal //通过嵌套匿名结构体实现继承
}
// 狗天生喜欢 乱叫
func (d *Dog) bark() {
fmt.Printf("%s 汪汪汪。。。。。\n", d.name)
}
// Cat 类型
type Cat struct {
*Animal
}
// 猫天生会爬树
func (c *Cat) climbTree() {
fmt.Printf("%s 爬上树啦。。。\n", c.name)
}
func main() {
d1 := &Dog{
Kind: "哈士奇",
Animal: &Animal{ //注意这里嵌套的是结构体指针
"小狗儿",
},
}
fmt.Printf("d1小狗名:%s\n", d1.name)
fmt.Printf("d1小狗种类:%s\n", d1.Kind)
fmt.Println("---d1小狗技能发动---")
d1.eat()
d1.sleep()
d1.bark()
//猫猫也是一样玩法
c1 := &Cat{
Animal: &Animal{
name: "小猫儿",
},
}
fmt.Println("------")
fmt.Println(c1.name)
c1.eat()
c1.sleep()
c1.climbTree()
}
/*
➜ goStudy go run t1.go
d1小狗名:小狗儿
d1小狗种类:哈士奇
---d1小狗技能发动---
小狗儿 正在干饭!!
小狗儿 又趴地上睡着了!!
小狗儿 汪汪汪。。。。。
------
小猫儿
小猫儿 正在干饭!!
小猫儿 又趴地上睡着了!!
小猫儿 爬上树啦。。。
*/
结构体字段首字母大写
后面于超老师会讲解go的包管理,以及go的公开变量、私有变量。
结构体中的字段,首字母大写表示公开可访问,仅在当前结构体所处的包环境下。
小写字段表示私有。
结构体与JSON序列化
JSON基础
JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简
洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
易于人阅读和编写。同时也易于机器解析和生成。
JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号`""`包裹
使用冒号`:`分隔,然后紧接着值;
多个键值之间使用英文`,`分隔。
| go语言数据类型 | json支持的类型 |
|---|---|
| 整型、浮点型 | 整型、浮点型 |
| 字符串(在双引号中) | 字符串(双引号) |
逻辑值(true 或 false) |
逻辑值(true 或 false) |
| 数组,切片 | 数组(在方括号中) |
| map | 对象(在花括号中) |
| nil | null |
序列化
序列化: 通过某种方式把数据结构或对象写入到磁盘文件中或通过网络传到其他节点的过程。
反序列化:把磁盘中对象或者把网络节点中传输的数据恢复为golang的数据对象的过程。

在线解析json网址
Go结构体序列化、反序列化
package main
import "C"
import (
"encoding/json"
"fmt"
)
// Student 学生
type Student struct {
ID int
Gender string
Name string
}
// Class 班级
type Class struct {
Title string
Students []*Student //切片,管理每一个学生
}
func main() {
//创建班级
c1 := &Class{
Title: "运维开发班",
Students: make([]*Student, 0, 50),
}
//初始化10个学生对象,加入班级即可
for i := 1; i <= 10; i++ {
stu := &Student{
Name: fmt.Sprintf("stu%d", i),
ID: i,
Gender: "男",
}
c1.Students = append(c1.Students, stu)
}
//fmt.Printf("班级c1 :%v\n", c1)
//JSON序列化、结构体 > JSON格式字符串,使用json包
res, err := json.Marshal(c1)
if err != nil {
fmt.Println("json序列化出错")
return
}
fmt.Printf("json序列化结果:%s\n", res)
//JSON反序列化 json格式字符串 > 结构体
s1 := `{"Title":"运维开发班","Students":[{"ID":1,"Gender":"男","Name":"stu1"},{"ID":2,"Gender":"男","Name":"stu2"},{"ID":3,"Gender":"男","Name":"stu3"},{"ID":4,"Gender":"男","Name":"stu4"},{"ID":5,"Gender":"男","Name":"stu5"},{"ID":6,"Gender":"男","Name":"stu6"},{"ID":7,"Gender":"男","Name":"stu7"},{"ID":8,"Gender":"男","Name":"stu8"},{"ID":9,"Gender":"男","Name":"stu9"},{"ID":10,"Gender":"男","Name":"stu10"}]}`
//Go语言里的函数,传参,都是值拷贝,因此用地址,反序列化函数,也一样
c2 := &Class{}
err = json.Unmarshal([]byte(s1), c2)
if err != nil {
fmt.Println("json序列化出错")
return
}
fmt.Printf("s1反序列化:%#v\n", c2)
}
/*
{"Title":"运维开发班","Students":[{"ID":1,"Gender":"男","Name":"stu1"},{"ID":2,"Gender":"男","Name":"stu2"},{"ID":3,"Gender":"男","Name":"stu3"},{"ID":4,"Gender":"男","Name":"stu4"},{"ID":5,"Gender":"男","Name":"stu5"},{"ID":6,"Gender":"男","Name":"stu6"},{"ID":7,"Gender":"男","Name":"stu7"},{"ID":8,"Gender":"男","Name":"stu8"},{"ID":9,"Gender":"男","Name":"stu9"},{"ID":10,"Gender":"男","Name":"stu10"}]}
*/
Go结构体标签Tag
Tag是结构体的元信息,可以在编译运行时通过反射机制读取出来;
Tag定义在结构体字段的后面,由反引号包裹起来;
//自定制序列化后,json字符串的key名,可以用struct的tag标签
type Student struct {
Name string `json:"姓名"`
Age int `json:"年纪"`
Score float64 `json:"成绩"`
}
注意:不要加无用的空格,tag反射语法检测很弱,否则无法正确获取结构体字段。
结构体tag与json
package main
import "C"
import (
"encoding/json"
"fmt"
)
// Student 学生
type Student struct {
ID int `json:"id"`
Gender string `json:"gender"`
Name string `json:"name"`
}
// Class 班级
type Class struct {
Title string `json:"title"`
Students []*Student `json:"stu_list" xml:"stu_list"` //tag可以定义多个
}
func main() {
//创建班级
c1 := &Class{
Title: "运维开发班",
Students: make([]*Student, 0, 50),
}
//初始化10个学生对象,加入班级即可
for i := 1; i <= 10; i++ {
stu := &Student{
Name: fmt.Sprintf("stu%d", i),
ID: i,
Gender: "男",
}
c1.Students = append(c1.Students, stu)
}
//fmt.Printf("班级c1 :%v\n", c1)
//JSON序列化、结构体 > JSON格式字符串,使用json包
res, err := json.Marshal(c1)
if err != nil {
fmt.Println("json序列化出错")
return
}
fmt.Printf("json序列化结果:%s\n", res)
//JSON反序列化 json格式字符串 > 结构体
s1 := `{"Title":"运维开发班","Students":[{"ID":1,"Gender":"男","Name":"stu1"},{"ID":2,"Gender":"男","Name":"stu2"},{"ID":3,"Gender":"男","Name":"stu3"},{"ID":4,"Gender":"男","Name":"stu4"},{"ID":5,"Gender":"男","Name":"stu5"},{"ID":6,"Gender":"男","Name":"stu6"},{"ID":7,"Gender":"男","Name":"stu7"},{"ID":8,"Gender":"男","Name":"stu8"},{"ID":9,"Gender":"男","Name":"stu9"},{"ID":10,"Gender":"男","Name":"stu10"}]}`
//Go语言里的函数,传参,都是值拷贝,因此用地址,反序列化函数,也一样
c2 := &Class{}
err = json.Unmarshal([]byte(s1), c2)
if err != nil {
fmt.Println("json序列化出错")
return
}
fmt.Printf("s1反序列化:%#v\n", c2)
}
/*
{"Title":"运维开发班","Students":[{"ID":1,"Gender":"男","Name":"stu1"},{"ID":2,"Gender":"男","Name":"stu2"},{"ID":3,"Gender":"男","Name":"stu3"},{"ID":4,"Gender":"男","Name":"stu4"},{"ID":5,"Gender":"男","Name":"stu5"},{"ID":6,"Gender":"男","Name":"stu6"},{"ID":7,"Gender":"男","Name":"stu7"},{"ID":8,"Gender":"男","Name":"stu8"},{"ID":9,"Gender":"男","Name":"stu9"},{"ID":10,"Gender":"男","Name":"stu10"}]}
*/
Go其他类型序列化
序列化是指将key:value结构的数据类型,如struct、map、slice转化为json字符串。
package main
import (
"encoding/json"
"fmt"
)
// 自定制序列化后,json字符串的key名,可以用struct的tag标签
type Student struct {
Name string `json:"姓名"`
Age int `json:"年纪"`
Score float64 `json:"成绩"`
}
// 序列化struct
func StructToJson() {
stu := Student{
"老王",
19,
99.9,
}
//序列化结构体,序列化函数返回的是[]byte ,字节切片,可以string强转为字符串
data, err := json.Marshal(&stu)
if err != nil {
fmt.Printf("序列化出错,", err)
}
fmt.Printf("struct序列化结果:%v\n", string(data))
}
// 序列化map
func MapToJson() {
mymap := make(map[string]string)
mymap["名字"] = "于超老师"
mymap["年纪"] = "18"
mymap["住址"] = "北京"
//对map序列化
data, err := json.Marshal(mymap)
if err != nil {
fmt.Printf("序列化出错:%v\n", err)
}
fmt.Println("map序列化结果:", string(data))
}
// 序列化切片
func SliceToJson() {
//定义切片,存放map类型
var s1 []map[string]string
var m1 map[string]string
//对map初始化
m1 = make(map[string]string)
m1["名字"] = "超哥"
m1["年纪"] = "18"
m1["地址"] = "北京沙河"
//追加切片元素
s1 = append(s1, m1)
//对切片序列化
data, err := json.Marshal(s1)
if err != nil {
fmt.Printf("序列化出错:%v\n", err)
}
fmt.Println("序列化结果:", string(data))
}
func testFloat64() {
n1 := 123.22
data, _ := json.Marshal(n1)
fmt.Printf("%T %v\n", string(data), string(data))
}
func testSlice() {
s1 := []int{1, 2, 3, 4}
data, _ := json.Marshal(s1)
fmt.Printf("%T %v\n", string(data), string(data))
}
func main() {
StructToJson()
MapToJson()
SliceToJson()
testFloat64() //对基本数据类型序列化场景较少
testSlice() //对基本数据类型序列化场景较少
}
/*
➜ goStudy go run t1.go
struct序列化结果:{"姓名":"老王","年纪":19,"成绩":99.9}
map序列化结果: {"住址":"北京","名字":"于超老师","年纪":"18"}
序列化结果: [{"名字":"超哥","地址":"北京沙河","年纪":"18"}]
string 123.22
string [1,2,3,4]
*/
反序列化
package main
import (
"encoding/json"
"fmt"
)
// 自定制序列化后,json字符串的key名,可以用struct的tag标签
type Student struct {
Name string `json:"姓名"`
Age int `json:"年纪"`
Score float64 `json:"成绩"`
}
// 反序列化结构体
func unmarshalStruct() {
//模拟接收到的json数据,本地需要转义
jsonStr := `{"姓名":"老王","年纪":19,"成绩":99.9}`
//定义Stu实例
var stu Student
//反序列化接收字节切片,以及空接口类型
err := json.Unmarshal([]byte(jsonStr), &stu)
if err != nil {
fmt.Printf("反序列化出错:%v\n", err)
}
fmt.Printf("反序列化结果:stu=%v stu.Name=%v stu.Age=%v stu.Score=%v\n", stu, stu.Name, stu.Age, stu.Score)
}
// 反序列化map
func unmarshalMap() {
mapStr := `{"住址":"北京","名字":"于超老师","年纪":"18"}`
//定义变量 接收反序列化数据
var m1 map[string]string
//反序列化不需要对map进行make,unmarshal函数已经封装make初始化
err := json.Unmarshal([]byte(mapStr), &m1)
if err != nil {
fmt.Printf("反序列化出错:%v\n", err)
}
fmt.Printf("反序列化结果:%v\n", m1)
}
// 反序列化切片
func unmarishalSlice() {
sliceStr := `[{"名字":"超哥","地址":"北京沙河","年纪":"18"}]`
var slice []map[string]string
//切片也不需要在这里make,json.Unmarishal已经封装好make
err := json.Unmarshal([]byte(sliceStr), &slice)
if err != nil {
fmt.Println("反序列化出错了,", err)
}
fmt.Printf("反序列化结果是:%v\n", slice)
}
func main() {
unmarshalStruct()
unmarshalMap()
unmarishalSlice()
}
/*
➜ goStudy go run t1.go
反序列化结果:stu={老王 19 99.9} stu.Name=老王 stu.Age=19 stu.Score=99.9
反序列化结果:map[住址:北京 名字:于超老师 年纪:18]
反序列化结果是:[map[名字:超哥 地址:北京沙河 年纪:18]]
*/
结构体大作业
开发一个学员管理系统、基于struct的面向对象模式
- 学生属性
- id
- name
- age
- score
- 程序功能
- 展示学生列表
- 添加新学员
- 编辑学员信息
- 删除学员
参考写法
package main
import "fmt"
/*
开发一个学员管理系统、基于struct的面向对象模式
- 学生属性
- id
- name
- age
- score
- 程序功能
- 展示学生列表
- 添加新学员
- 编辑学员信息
- 删除学员
思路
1. 学员结构体,学生对象创建 struct
2. 管理系统,增删改查学员信息 struct
*/
// 1.创建学生对象
type Students struct {
Id int64
Name string
Age int8
Score int8
}
// 2.管理系统,且增删改查方法
type Manager struct {
StuInfo map[int64]*Students
}
// 3. 展示学生信息
func (m *Manager) ShowAll() {
fmt.Println("打印所有学生信息。。。")
//遍历map,打印信息
for id, stu := range m.StuInfo {
fmt.Printf("id:%v name:%v age:%v score:%v\n", id, stu.Name, stu.Age, stu.Score)
}
}
// 4.添加新学员
func (m *Manager) AddStu() {
fmt.Println("添加新学员中。。。")
//交互式系统,输入学生信息
fmt.Println("请依次输入学生的信息、id、姓名、年龄、成绩、且用空格分割!!")
var (
id int64
name string
age int8
score int8
)
//用户输入,异常处理先不考虑
fmt.Scanln(&id, &name, &age, &score)
//判断id是否存在
if _, ok := m.StuInfo[id]; ok {
fmt.Printf("该%d学生已存在,请勿重复添加!\n", id)
return
}
//正确添加新学生信息
newStu := &Students{
Id: id,
Name: name,
Age: age,
Score: score,
}
//新人加入管理系统,map多一个k-v
m.StuInfo[id] = newStu
fmt.Printf("学员%d添加成功\n", id)
}
// 5.编辑学生信息
func (m *Manager) EditStu() {
fmt.Println("编辑学生信息中。。。")
//输入你要编辑的学生id
fmt.Println("请输入要修改的学生ID号:")
var id int64
fmt.Scanln(&id)
if _, ok := m.StuInfo[id]; !ok {
fmt.Printf("该%d学生不存在!!\n", id)
return
}
fmt.Println("请依次输入学生,新的信息、姓名、年龄、成绩、且用空格分割!!")
var (
name string
age int8
score int8
)
//用户输入,异常处理先不考虑
fmt.Scanln(&name, &age, &score)
//更新学生信息 map
m.StuInfo[id].Name = name
m.StuInfo[id].Age = age
m.StuInfo[id].Score = score
fmt.Println("修改学员信息成功!!")
}
// 6.删除学员
func (m *Manager) DelStu() {
fmt.Println("删除学员信息中。。。")
//用户输入要删除的id
fmt.Println("请输入要删除的学生ID:")
var id int64
fmt.Scanln(&id)
if _, ok := m.StuInfo[id]; !ok {
fmt.Printf("该%d学生不存在!!\n", id)
return
}
//map删除数据即可
delete(m.StuInfo, id)
fmt.Println("删除成功!!")
}
func main() {
// 7.系统运行
//初始化管理系统
mgr := Manager{
StuInfo: make(map[int64]*Students),
}
//系统菜单打印
for {
fmt.Print(`
-----学员管理系统V1版------
选项:
1. 查看所有学员
2. 增加新学员
3. 编辑学员信息
4. 删除学员信息
5.退出系统
`)
var userInput int
fmt.Print("请输入序号:")
fmt.Scanln(&userInput) //输入非int类型会报错,该变量默认值是0
//判断用户输入
switch userInput {
case 1:
mgr.ShowAll()
case 2:
mgr.AddStu()
case 3:
mgr.EditStu()
case 4:
mgr.DelStu()
case 5:
return
default:
fmt.Println("请输入1~5正确序号!!")
}
fmt.Println("=====================================执行完毕==========================================")
}
}
面试题
看代码,说结果
package main
import "fmt"
type student struct {
name string
age int
}
func main() {
m := make(map[string]*student)
//切片,3个独立的结构体
stus := []student{
{name: "关羽", age: 18},
{name: "张飞", age: 19},
{name: "刘备", age: 20},
}
// fmt.Println(stus) [{关羽 18} {张飞 19} {刘备 20}]
// for循环拿走元素
// stu该变量是个指针,好比就是个一个镊子,逐个的夹走元素
// 常见坑
var stu student
for _, stu = range stus {
m[stu.name] = &stu // map的值,传入了结构体指针,指向同一个值
fmt.Printf("切片元素:%v stu.name地址:%p\n", stu.name, &stu.name)
}
//如何解决这个问题
//for i := 0; i < len(stus); i++ {
// //循环构造多个独立的结构体
// stu := stus[i]
// m[stu.name] = &stu
//}
//k 是name,
// v是内存地址
for k, v := range m {
fmt.Printf("k--%v v.name--%v v--%p \n", k, v.name, v)
}
}