结构体定义
Go语言没有类的概念,但是可以通过结构体实现面向对象编程。
结构体是一种自定义数据类型,其可以封装任何类型。
示例:
type house struct {
size, price float64
style string
}
上例house
是自定义结构体类型,包括size
、price
、style
三个字段,可以用来描述房子的面积、价格和风格。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"}
上例中family
是struct { Mom string; Dad string }
类型的变量,因为没有用type
关键字定义名字,每次使用都得写清楚结构体类型。
结构体指针
- 使用
new
关键字
p1 := new(house)
p1.style = "欧式"
fmt.Printf("%#v\n", p1) // &main.house{size:0, price:0, style:"欧式"}
Go语言中支持对结构体指针直接使用.
来访问结构体的成员。在访问的时候编译器会自动把 p1.style
转为 (*p1).style
。
- 使用取地址符
&
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格式的字符串。
示例:
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", h2) // 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":"中国风"}
}