0x00 结构体的匿名字段(了解)
这个玩意儿用的比较少,而且缺陷很大,不建议使用,能看得懂即可。
先来看一个正常的结构体:
type person struct {
name string
age int
}
func main() {
p1 := person{
"LiMing",
20,
}
fmt.Println("p1", p1.name)
fmt.Println("p1", p1.age)
}
那么匿名字段结构体就是,将结构体里面的字段名称去掉,即把name和age去掉。那么问题来了,下面代码块中的???填什么?
type person struct {
string
int
}
func main() {
p1 := person{
"LiMing",
20,
}
fmt.Println("p1",???)
}
答案是填:
type person struct {
string
int
}
func main() {
p1 := person{
"LiMing",
20,
}
fmt.Println("p1", p1.string)
fmt.Println("p1", p1.int)
}
看到这儿是不是就惊了!哪有这么玩儿的,把人家的数据类型当作字段名,这要闹哪样啊。
最大的弊端,假设里面的字段,出现了同为string或者同为int类型的字段,就无法使用了。好了,到此为止,了解一下即可。
注意、总结:这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
0x01 嵌套结构体
在说结构体嵌套前,先看一个例子,可以看出来这两个结构体中,都会有province和city两个字段。
type person struct {
name string
age int
province string
city string
}
type company struct {
name string
province string
city string
}
那我就完全可以将这两个字段新封装成一个结构体
type address struct {
province string
city string
}
然后那两个结构体再进行调用即可
type person struct {
name string
age int
address address //第一个address肯定是一个变量,字段名
}
type company struct {
name string
address address
}
给对应的结构体赋值,输出等
type address struct {
province string
city string
}
type person struct {
name string
age int
address address //这里也可以直接写成一个address,表示匿名嵌套结构体
}
func main() {
p1 := person{
name: "你好",
age: 90000,
address: address{
province: "云南",
city: "曲靖",
},
}
fmt.Println(p1, p1.name, p1.address.province)
//如果上面使用了匿名嵌套结构体,那么下面就可以使用p1.city来直接输出
//先在自己的结构体找这个字段,找不到就去匿名嵌套的结构体中查找该字段
//看自己的需求了
}
0x02 匿名嵌套结构体字段冲突
这是啥玩意儿,看着名字老长了。其实就是,在type新建结构体的时候,有两个字段一样的嵌套结构体,里面的字段一模一样。那么这时候在后面的调用,就不能直接使用p1.city等这样直接了当的指定方式了,因为你根本不知道你指定的是哪个??这时候你只能一个一个写,写清楚才可以,不然根本识别不了。
type address struct {
province string
city string
}
type workPlace struct {
province string
city string
}
type person struct {
name string
age int
address
workPlace
}
type company struct {
name string
add address
}
func main() {
p1 := person{
name: "nihao",
age: 18,
address: address{
province: "shandong",
city: "威海",
},
}
// fmt.Println(p1.city)这时候不能这么写了
//因为workPlace和address字段冲突
//这就是匿名嵌套结构体字段冲突
fmt.Println(p1.address.province)
fmt.Println(p1.workPlace.province)
}
0x03 结构体模拟实现其他语言的“继承”
Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。
先看下面这段代码,就是说,新建了一个animal结构体和一个dog结构体,然后都新建了对应的方法。
type animal struct {
name string
}
func (a animal) move() {
fmt.Printf("%s会动!", a.name)
}
type dog struct {
feet uint8
}
func (d dog) wang() {
fmt.Println("wangwangwang!")
}
这时候注意,dog是属于animal的,所以注意看下面这段代码
type animal struct {
name string
}
func (a animal) move() {
fmt.Printf("%s会动!", a.name)
}
type dog struct {
feet uint8
animal //这里直接嵌一个animal 这里直接嵌一个animal 这里直接嵌一个animal 这里直接嵌一个animal 这里直接嵌一个animal
}
func (d dog) wang() {
fmt.Println("wangwangwang!",d.name) //按理说,dog结构体中根本就没有name这个字段,而animal里面有,所以这里实现的就是一个类似继承的作用
}
综合练习,可以看出来,相当于继承了。
这段代码,包含了结构体几乎所有的关键知识点,可以重点敲几遍!
package main
import "fmt"
type animal struct {
name string
}
func (a animal) move() {
fmt.Printf("%s会动!", a.name)
}
type dog struct {
feet uint8
animal
}
func (d dog) wang() {
fmt.Println("wangwangwang!", d.name)
}
func main() {
d1 := dog{
animal: animal{
name: "wangcai",
},
feet: 4,
}
fmt.Println(d1)
d1.wang()
d1.move()
}
0x04 结构体与JSON(JavaScript Object Notation)
JSON其实最开始是在JS表示对象的一种模式,后来就越来越多地被引用到了前后端分离的、跨语言的一种数据格式了。
看下面这张图,说的就是,后端的语言开发出的网站,传到前端,前端的JS根本就不认识,那么怎么才能进行沟通呢??这就是引出来个JSON。
描述: JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,其优点是易于人阅读和编写,同时也易于机器解析和生成。
Tips : JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。在Go中我们可以通过结构体序列号生成json字符串,同时也能通过json字符串反序列化为结构体得实例化对象,在使用json字符串转换时, 我们需要用到"encoding/json"包。
其实后端和前端,之间相互传递的就是一个一个的对象,或者说是字符串。我们上json.cn这个网站来看看,试试。
我们在左边输入的这条字符串,交给前端,前端就会将其转换成JS的OBJ对象,从而让前端执行某些事情。
所以我们的根本目的,就是让Go语言,如何生成、转换成左边那么一长条字符串!
目的有二:
1、序列化:把Go语言中的结构体变量——>json格式的字符串
2、反序列化:json格式的字符串——>go语言中能够识别的结构体
可以看到在下面这段代码中,先声明和初始化一个结构体,然后将其专为序列化的json格式字符串。使用到的是encoding/json
包中的json.Marshal
函数。
但是我们可以看到在终端输出的结果,为什么会是这个样子??
package main
import (
"encoding/json"
"fmt"
)
type person struct {
name string
age int
}
func main() {
p1 := person{
name: "北京",
age: 10000,
}
slice, err := json.Marshal(p1)
if err != nil {
fmt.Printf("%v", err)
return
}
fmt.Println(slice)
fmt.Printf("%#v", string(slice))
}
是因为,我们将p1这样的结构体放入了json.Marshal
这个方法中,而p1调用person内部的属性时,属性名需要转为大写,不然会调用失败。可以理解为不为大写,就不是public,而是private
也就是下面这段代码的写法,这样子在终端输出的内容就很正确了。不过为什么会有反斜杠呢,那只是为了在终端中展示出来,真实环境下是不用的。
package main
import (
"encoding/json"
"fmt"
)
type person struct {
Name string //这里都换成了大写的字段
Age int
}
func main() {
p1 := person{
Name: "北京",
Age: 10000, //同理,这里也是大写的字段
}
slice, err := json.Marshal(p1)
if err != nil {
fmt.Printf("%v", err)
return
}
fmt.Println(slice)
fmt.Printf("%#v", string(slice))
}
这时你应该发现了个问题,就是输出的字段也变成大写了。我能不能让输出的字段也变成小写呢??可以,只不过比较复杂,例如下面这段代码,严格遵循这段代码即可。
结构体标签(Tag)
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来,Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:key1:"value1" key2:"value2",可以看到它由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。 看下面这段代码:
type person struct{
Name string `json:"name" db:"name" ini:"name"` //当我使用json解析时,按照这个规则来
Age int
}
反序列化
这里有两个疑问点
1、为什么要写[]byte
?首先json.Unmarshal
这个函数,前者是个字符切片,规定好了就必须要用[]byte
类型!后者是个值value。
什么时候用string
,什么时候用[]byte
?
- string可以直接比较,[]byte不行,[]byte不能当map的key值
- 需要操作的粒度为字符串的某个字符时候,用[]byte
- string不可为nil值
- []byte的切片特性更加灵活
- 如果有大量的字符串操作,就用[]byte
2、为什么要是&p2
?是因为函数总是复制值过去,如果想要真正的更改对应的值,需要写成指针。
str1 := `{"name":"beijing"}`
var p2 person
json.Unmarshal([]byte(str1), &p2)
0x05 综合练习
package main
import (
"encoding/json"
"fmt"
)
// 结构体转json字符串的三种示例
// 结构体中的字段首字母大小写影响的可见性,表示不能对外使用
type Person1 struct{ name, sex string }
// 结构体对象字段可以对外使用
type Person2 struct{ Name, Sex string }
// 但json字符串中键只要小写时可以采用此种方式
type Person3 struct {
Name string `json:"name"`
Sex string `json:"age"`
}
// # 结构体实例化对象转JSON字符串
func serialize() {
// 示例1.字段首字母大小写影响的可见性
person1 := &Person1{"weiyigeek", "男孩"}
person2 := &Person2{"WeiyiGeek", "男生"}
person3 := &Person3{"WeiyiGeek", "男人"}
//序列化
p1, err := json.Marshal(person1)
p2, err := json.Marshal(person2)
p3, err := json.Marshal(person3)
if err != nil {
fmt.Printf("Marshal Failed :%v", err)
return
}
// 由于返回是一个字节切片,所以需要强转为字符串
fmt.Printf("person1 -> %v\nperson2 -> %v\nperson3 -> %v\n", string(p1), string(p2), string(p3))
}
// # JSON字符串转结构体实例化对象
type Person4 struct {
Name string `json:"name"`
Sex string `json:"sex"`
Addr [3]string `json:"addr"`
}
func unserialize() {
jsonStr := `{"name": "WeiyiGeek","sex": "man","addr": ["中国","重庆","渝北"]}`
p4 := Person4{}
// 在其内部修改p4的值
err := json.Unmarshal([]byte(jsonStr), &p4)
if err != nil {
fmt.Printf("Unmarhal Failed: %v", err)
return
}
fmt.Printf("jsonStr -> Person4 : %#v\nPerson4.name : %v\n", p4, p4.Name)
}
func main() {
serialize()
unserialize()
}