1.结构体声明: type 结构体名 struct , 字段: field type
type Person struct{
Name string
Age int
}
2.结构体名称首字母小写,则包外部不能使用。大写则包外部能使用。 字段名称首字母大写,包外部才能访问,首字母小写包外部不能访问。
3.字段的数据类型:可以是基本数据类型、数组或引用类型
4.创建一个结构体变量以后,如果没有给字段赋值,都对应的是一个“零值”(默认值),默认值规则如下:
布尔类型是false,数值类型是 0 ,字符串是 "",
数组类型的默认值和它的元素类型相关,
比如 array [3]string 则为["","",""] , array2 [2]int 则为[0,0]
指针,切片,map,channel 的零值是 nil,因为还没有分配内存空间(分配内存空间:指针需要new 切片、map、channel需要make)
5.结构体是值拷贝(不是引用类型)
type Person struct{
Name string
Age int
}
func main() {
a := Person{Name:"Tom",Age: 18}
b := a //值拷贝
b.Name = "Johon"
fmt.Println(a)
fmt.Println(b)
}
6.结构体 变量创建(实例化对象),有四种方式
package main
import "fmt"
type Person struct{
Name string
Age int
}
func main() {
//第一种
var p Person
p.Name = "Roll"
p.Age = 18
fmt.Println(p)
//第二种
p2 := Person{Name: "Jack",Age:20}
p3 := Person{"jack2",21}
fmt.Println(p2,p3)
//第三种 new方式 是指针类型变量
var p4 *Person = new(Person)
(*p4).Name = "Dool"
(*p4).Age = 22
fmt.Println(*p4)
//因为 go官方有做底层优化 (*p4).Name 等价于 p4.Name
p4.Name = "Dool2"
p4.Age = 23
fmt.Println(*p4)
//第四 种方式 指针类型变量
var p5 *Person = &Person{Name: "Alon",Age: 18}
(*p5).Name = "Alon1"
(*p5).Age = 19
var p6 *Person = &Person{}
p6.Name = "Alon2"
p6.Age = 30
fmt.Println(*p5)
fmt.Println(*p6)
}
第三种和第四种:用new()和 &取址方式 创建的结构体变量是指针, 按照表标准的操作方式是(*p).Name, 但是go官方认为这种操作方式过于复杂,所以底层做了简化, 可以直接 p.Name 来操作
7.结构体变量是值拷贝,但是指针类型的结构体变量 指向的是一个内存地址,所以指针类型的结构体变量进行赋值操作是 值引用
package main
import "fmt"
type Person struct{
Name string
Age int
}
func main() {
//第一种
var p Person
p.Name = "Roll"
p.Age = 18
fmt.Println(p)
p1 := &p //指针
fmt.Println(*p1)
p1.Name = "Jack"
p1.Age = 20
fmt.Println(p)
fmt.Println(*p1)
}
结构体变量进行 取址赋值 给另一个变量 ,另一个变量则是指针。 指针变量指向的与原变量是同一个内存空间,所以指针变量操作的字段值改变,则原变量的值也会改变。(另外需要注意的是,指针变量操作符,&是取址操作符,*是取值操作符)
8.结构体的指针变量,在取字段值的时候,写法需要注意
原因很简单:因为 点 “.”运算符的优先级比 “*”的高,所以指针变量需要 (*p).Name
9.结构体变量里的 字段内存地址是连续的(字段类型不同,连续的值不同),同一个结构体但不同的结构体变量的内存地址不一定是连续的。 字段内存地址是连续的是因为方便快速查找值。
package main
import "fmt"
type Person struct{
Name string
Age int
Sex bool
}
func main() {
p := Person{Name: "jack", Age: 18,Sex: false}
p2 := Person{Name: "roll",Age: 20,Sex: true}
fmt.Printf("p.Name内存地址:%p p.Age内存地址:%p p.Sex内存地址:%p \n",&p.Name,&p.Age,&p.Sex)
fmt.Printf("p2.Name内存地址:%p p2.Age内存地址:%p p2.Sex内存地址:%p \n",&p2.Name,&p2.Age,&p2.Sex)
fmt.Printf("p内存地址:%p p2内存地址:%p \n",&p,&p2)
}
10.不同结构体变量之间的强制转换:
转换的要求是严格的: 要求结构体字段名称一致,字段个数一致,字段数据类型一致,如果这三点中有一个不同则不能做强制转换
package main
import "fmt"
type Person struct{
Name string
Age int
}
type Student struct{
Name string
Age int
}
func main() {
var p Person
var s Student
s.Name = "jk"
p = Person(s) //Student类型强制转换成Person类型
fmt.Println(p)
}
11.定义一个结构体类型的结构体(重新定义结构体,相当于给结构体取别名),go会认为是两种数据类型,但是可以互相强制转换。这也说明go对于数据类型是非常严谨的
import "fmt"
type Person struct{
Name string
Age int
}
type A Person //重新定义结构体(相当于取别名)
func main() {
person := Person{"jack",12}
a := A{}
a = A(person) //Person变量转成 A
fmt.Println(a)
}
运行结果:
12.我们实际项目中,数据在网络上传递用的一般是json,我们的结构体也能通过 json.Marshal进行转换成json, 结构体中的字段名称因为需要其他包能访问,所以首字母是大写的,而我们一般网络上进行传递的json字段名称都是一般是小写的,或者是小写加下划线。所以go通过给结构体字段加tag标签,在进行序列化和反序列化时对字段名进行处理,其实现方式用的是反射机制。
package main
import (
"encoding/json"
"fmt"
)
type Person struct{
Name string
Age int
}
type Student struct {
Name string `json:"student_name"`
Age int `json:"student_age"`
}
func main() {
person := Person{"jack",12}
student := Student{"roll",18}
fmt.Println(person,"\n",student)
//转换成json
p,err := json.Marshal(person)
s,err1 := json.Marshal(student)
if err != nil {
fmt.Println("trans error:",err)
}
if err1 != nil {
fmt.Println("trans error:",err)
}
//打印json字符串
fmt.Println(string(p))
fmt.Println(string(s))
}
执行结果:
需要注意的是 tag标签的书写方式,外面包的是 反引号,转的是json类型 名称用双引号包住,同一个字段也可以定义多个tag 用空格 隔开