golang基础小记(14)——结构体:定义、初始化、构造函数、匿名字段、嵌套结构体、JSON序列化与反序列化

结构体定义

Go语言没有类的概念,但是可以通过结构体实现面向对象编程。
结构体是一种自定义数据类型,其可以封装任何类型。
示例:

type house struct {
	size, price float64
	style string
}

上例house是自定义结构体类型,包括sizepricestyle三个字段,可以用来描述房子的面积、价格和风格。house类型的变量可以很方便的存储房子信息。

结构体实例化

结构体是值类型,需要声明后才能使用,声明后内部成员的值默认是对应成员类型的零值。

基本实例化

使用上面定义的house举例:

var h1 house
h1.size = 130
h1.style = "中国风"
fmt.Printf("%v\n", h1) // {130 0 中国风}
fmt.Printf("%#v", h1)  // main.house{size:130, price:0, style:"中国风"}
fmt.Println(h1.size)   // 130

通过.来访问结构体的字段(成员变量)

匿名结构体

匿名结构体可以用来定义临时结构体。
示例:

var family struct {
	Mom string
	Dad string
}
family.Mom = "Mommy"
family.Dad = "Daddy"
fmt.Printf("%v\n", family)  // {Mommy Daddy}
fmt.Printf("%#v\n", family) // struct { Mom string; Dad string }{Mom:"Mommy", Dad:"Daddy"}

上例中familystruct { Mom string; Dad string }类型的变量,因为没有用type关键字定义名字,每次使用都得写清楚结构体类型。

结构体指针

  1. 使用new关键字
p1 := new(house)
p1.style = "欧式"
fmt.Printf("%#v\n", p1) // &main.house{size:0, price:0, style:"欧式"}

Go语言中支持对结构体指针直接使用.来访问结构体的成员。在访问的时候编译器会自动把 p1.style 转为 (*p1).style

  1. 使用取地址符&
p2 := &house{}
fmt.Printf("%T\n", p2)  // *main.house
fmt.Printf("%#v\n", p2) // &main.house{size:0, price:0, style:""}

结构体初始化

直接声明

成员默认初始化为对应类型的零值。比如之前例子中的var h1 house

利用键值对初始化

(1)对结构体进行键值对初始化

h3 := house{
	size:  110,
	price: 700,
}
fmt.Printf("%#v\n", h3) // main.house{size:110, price:700, style:""}

没必要对每一个成员都设置初始值,未设置初始值的默认为对应类型的零值。
(2)对结构体指针进行键值对初始化

p3 := &house{
	size:  110,
	price: 700,
}
fmt.Printf("%#v\n", p3) // &main.house{size:110, price:700, style:""}

(3)值的列表初始化
也就是初始化的时候省略键,只写值:

h4 := house{
	110,
	940,
	"中国风",
}
fmt.Printf("%#v\n", h4) // main.house{size:110, price:940, style:"中国风"}

需要注意:省略键后,所有成员的值都需要初始化,且顺序要和结构体定义顺序相同。

构造函数

Go语言没有结构体的构造函数,但可以手动实现。实现构造函数后可以十分方便的构造结构体变量。
示例:

type house struct {
	size, price float64
	style       string
}

// 返回结构体指针
func newHouse(size, price float64, style string) *house {
	return &house{
		size:  size,
		price: price,
		style: style,
	}
}

func main() {
	p1 := newHouse(100, 80, "中国风")
	fmt.Printf("%#v\n", p1) // &main.house{size:100, price:80, style:"中国风"}
}

上例中的newHouse函数就是一个构造函数,可以用来构造house类型的结构体。构造函数可以返回结构体,也可以返回结构体指针。当结构体比较大的时候,返回结构体会有较大的值拷贝性能开销,这时返回结构体指针更合适。

匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段匿名字段默认会采用类型名作为字段名,而结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
示例:

type house struct {
	int
	string
}

func main() {
	h1 := house{
		100,
		"中国风",
	}
	fmt.Printf("%#v\n", h1)        // main.house{int:100, string:"中国风"}
	fmt.Println(h1.string, h1.int) // 中国风 100
}

匿名字段也可以与非匿名字段混用,示例如下:

type person struct {
	height int
	int
	name string
	string
}

func main() {
	p1 := person{
		height: 178,
		int:    100,
		name:   "夏静怡",
		string: "篮球",
	}
	fmt.Printf("%#v\n", p1)                   // main.person{height:178, int:100, name:"夏静怡", string:"篮球"}
	fmt.Println(p1.height, p1.int, p1.string) // 178 100 篮球
}

嵌套结构体

嵌套结构体就是一个结构体中包含另一个结构体或结构体指针。
示例:

type address struct {
	province, city string
}

type house struct {
	size int
	addr address
}

func main() {
	h1 := house{
		size: 100,
		addr: address{
			"浙江",
			"杭州",
		},
	}
	fmt.Printf("%#v\n", h1)   // main.house{size:100, addr:main.address{province:"浙江", city:"杭州"}}
	// 嵌套结构体的成员可以通过'.'一层一层的访问
	fmt.Println(h1.addr.city) // 杭州
}

当嵌套结构体采用匿名字段的方式,其成员可以直接访问。
示例:

type address struct {
	province, city string
}

type house struct {
	size int
	address // 匿名嵌套结构体
}

func main() {
	h1 := house{
		size: 100,
		address: address{
			"浙江",
			"杭州",
		},
	}
	fmt.Printf("%#v\n", h1)               // main.house{size:100, address:main.address{province:"浙江", city:"杭州"}}
	// 匿名字段可以省略
	fmt.Println(h1.address.city, h1.city) // 杭州 杭州
}

当访问结构体成员时会先在结构体中查找该字段,找不到再去嵌套的匿名字段中查找。嵌套可以多层嵌套,中途的匿名字段都可以省略。
注意:如果嵌套结构体内部有相同字段,那么匿名字段也不可省略。
模拟“继承”:外层结构体能够使用匿名嵌套结构体的方法。(Go语言没有继承的概念,但可以利用结构体模拟实现)

结构体字段可见性

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

JSON序列化和反序列化

JSON在线解析及格式化验证网站

JSON序列化

结构体–>JSON格式的字符串。
示例:

import (
	"encoding/json" // 引入json包
	"fmt"
)

type house struct {
	Size  int  // 字段名首字母必须大写,否则json包访问不到字段数据
	Style string
}

func main() {
	h1 := house{
		100,
		"中国风",
	}
	data, err := json.Marshal(h1) // 序列化函数
	if err != nil {
		fmt.Println("json marshal failed")
		return
	}
	fmt.Printf("%s\n", data) // {"Size":100,"Style":"中国风"}
}

注意:当结构体需要被其他包访问时,结构体字段(成员)名首字母必须大写

JSON反序列化

JSON格式的字符串–>结构体。
示例:

import (
	"encoding/json"
	"fmt"
)

type house struct {
	Size  int
	Style string
}

func main() {
	str := `{"Size":100,"Style":"中国风"}` // JSON格式的字符串
	var h2 house
	// 反序列化函数,参数是[]byte类型的JSON格式字符串和结构体指针(因为要对结构体变量进行修改)
	err = json.Unmarshal([]byte(str), &h2)
	if err != nil {
		fmt.Println("json unmarshal failed!")
		return
	}
	fmt.Printf("%#v\n", h1) // main.house{Size:100, Style:"中国风"}
}

结构体标签(Tag)

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

`key1:"value1" key2:"value2"`

结构体Tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。
注意:同一个键值对之间不能加空格
在JSON序列化时,由于结构体字段名首字母必须大写,可能无法满足字段名首字母小写的需求,这时就需要使用Tag。示例如下:

import (
	"encoding/json" // 引入json包
	"fmt"
)

type house struct {
	Size  int    `json:"size"`
	Style string `json:"style"`
}

func main() {
	h1 := house{
		100,
		"中国风",
	}
	data, err := json.Marshal(h1) // 序列化函数
	if err != nil {
		fmt.Println("json marshal failed")
		return
	}
	fmt.Printf("%s\n", data) // {"size":100,"style":"中国风"}
}

可以看到JSON序列化后的字符串中,字段名首字母是小写的。
参考1
参考2

你可能感兴趣的:(golang基础小记)