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语言中能够识别的结构体

encoding/jsonjson.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))

}
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))

}

这时你应该发现了个问题,就是输出的字段也变成大写了。我能不能让输出的字段也变成小写呢??可以,只不过比较复杂,例如下面这段代码,严格遵循这段代码即可。

结构体标签(Tag)

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来,Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:key1:"value1" key2:"value2",可以看到它由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。 看下面这段代码:

type person struct{
    Name string	`json:"name" db:"name" ini:"name"`	//当我使用json解析时,按照这个规则来
    Age int
}

反序列化

这里有两个疑问点

[]bytejson.Unmarshal[]byte
string[]byte
  • string可以直接比较,[]byte不行,[]byte不能当map的key值
  • 需要操作的粒度为字符串的某个字符时候,用[]byte
  • string不可为nil值
  • []byte的切片特性更加灵活
  • 如果有大量的字符串操作,就用[]byte
&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()
}