创建结构体变量和访问结构体字段
1)方式1-直接声明
案例演示:var person Person
2)方式2-{}
案例演示:var person Person = Person{}
func main() {
p2 := Person{}
p2.Name = "tom"
p2.Age = 18
fmt.Println(p2)
}
3)方式3-&
案例:var person *Person = new(Person)
func main() {
//方式3
var p3 *Person = new(Person)
//因为p3是一个指针,因此标准的给字段赋值的方式
//(*p3).Name = "smith" 也可以这样写 p3.Name = "smith"
//原因是 go的设计者 为了程序员使用方便在底层会对 p3.Name = "smith" 进行处理
//会给p3 加上 取值运算 (*p3).Name = "smith"
(*p3).Name = "smith"
p3.Name = "john"
(*p3).Age = 30
p3.Age = 100
fmt.Println(*p3)
}
4)方式4-{}
案例:var person *Person = &Person{}
func main() {
//方式4
//下面的语句,也可以直接给字符赋值
//var person *Person = &Person{"mary", 60}
var person *Person = &Person{}
//因为person 是一个指针,因此标准的访问字段的方法
// (*person).Name = "scott"
//go的设计者为了程序员使用方便,也可以直接写成 person.Name = "scott"
//原因和上面一样,底层会做一些处理
(*person).Name = "scott"
person.Name = "scott~"
(*person).Age = 88
person.Age = 10
fmt.Println(*person)
}
说明:
(1) 第3种和第4种方式返回的是 结构体指针
(2)结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,比如:(*person).Name = "tom"
(3) 但go做了一个简化,也支持 结构体指针.字段名,比如 person.Name = "tom"。更加符合程序员使用的习惯,go编译器底层对 person.Name 做了转化 (*person).Name
结构体内存分配机制:
基本说明:
变量总是存在内存中的,那么结构体变量在内存中究竟是怎么样存在的?
看下面代码,并分析原因:
下面一段代码,会输出什么信息:
var p1 Person
p1.Age = 10
p1.Name = "小明"
var p2 *Person = &p1
fmt.Println((*p2).Age) //p1的Age 10
fmt.Println(p2.Age) //p1的Age 10
p2.Name = "tom~"
fmt.Printf("p2.Name=%v p1.Name=%v
", p2.Name, p1.Name) //tom~
fmt.Printf("p2.Name=%v p1.Name=%v
", (*p2).Name, p1.Name) //tom~
输出的结果:
在内存中的示意图:
看下面的代码,并分析原因
var p1 Person
p1.Age =10
p1.Name = "小明"
var p2 *Person = &p1
fmt.Println(*p2.Age) //能不能这样写?
不对,因为.的运算符优先级比*高,所以先算它以后才会算*。 所以必须加括号(*p2).Age
结构体使用细节:
1)结构体的所有字段在内存中是连续的
案例:
type Rect struct {
leftUp, rightDown Point
}
type Rect2 struct {
leftUp, rightDown *Point
}
func main() {
r1 := Rect{Point{1,2}, Point{3,4}}
//r1有四个整数int,在内存中是连续分布
//打印地址
fmt.Printf("r1.leftUp.x 的地址=%p r1.leftUp.y 的地址=%p r1.rightDown.x 的地址=%p r1.rightDown.y 的地址=%p
",
&r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y)
//r2有两个 *Point类型,这两个*Point类型的本身地址也是连续的
//但是他们指向的地址不一定是连续的
r2 := Rect2{&Point{10,20}, &Point{30,40}}
//打印地址
fmt.Printf("r2.leftUp 本身地址=%p r2.rightDown 本身地址=%p
",
&r2.leftUp, &r2.rightDown)
//他们指向的地址不一定是连续... 这个要看系统在运行时是怎么分配的
fmt.Printf("r2.leftUp 指向地址=%p r2.rightDown 指向地址=%p
",
r2.leftUp, r2.rightDown)
}
在内存中的示意图:
2)结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
type A struct {
Num int
}
type B struct {
Num int
}
func main() {
var a A
var b B
a = A(b) //可以转换,但是有要求,结构体的字段要完全一样(名字,个数和类型)
fmt.Println(a,b)
}
3)结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
type Student struct {
Name string
Age int
}
type Stu Student
func main() {
var stu1 Student
var stu2 Stu
stu2 = stu1 //正确吗? 错误的,可以这样修改stu2 = Stu(stu1)
fmt.Println(stu1,stu2)
}
type integer int
func main() {
var i integer = 10
var j int = 20
j = i //正确吗? 错误的,因为golang认为integer是一种新的数据类型了,不能直接赋值,必须转换 j = int(i)
fmt.Println(i,j)
}
4)struct 的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列号和反序列化。
案例演示:
package main
import (
"fmt"
"encoding/json"
)
type Monster struct {
Name string `json:"name"` // `json:"name"` 就是 struct tag
Age int `json:"age"`
Skill string `json:"skill"`
}
func main() {
//1.创建一个Monster变量
monster := Monster{"牛魔王", 500, "芭蕉扇~"}
//2.将monster变量序列化为 json格式的字串
// json.Marshal 函数中使用反射,这个讲解反射时再详细介绍
jsonStr,err := json.Marshal(monster)
if err != nil {
fmt.Println("json 处理错误", err)
}
fmt.Println("jsonStr", string(jsonStr))
}