Golang
Go语言struct结构体详解
11-04 09:38声明结构体
type 标识符 strcut{
字段 类型
field1 type
field2 type
...
}
结构体快速入门案
//定义一个结构体
type User struct{
Name string
Age int
Gender string
}
func main(){
//创建一个User变量,此时内存中已经分配空间,默认使用各类型的零值
var user User
fmt.Println("user=", user) //user=(0),string默认值为空串,int默认值为0,所以只打印一个0
user.Name = "张飞" //赋值
user.Age = 30
user.Gender = "男"
fmt.Println("user=", user)
//使用类型推导,直接赋值
user2 := User{Name: "Alice", Age: 30, Gender: "女"}
fmt.Println("user2=", user2)
//使用类型推导,直接赋值,并省略字段
user3 := User{"Tom", 20, "男"}
fmt.Println("user2=", user2)
}
通过上面案例可以看出,结构体和结构体变量的区别和联系:
1、结构体是自定义的数据类型,代表一类事物
2、结构体变量(实例)是具体的、实际的,代表一个具体变量
结构体在内存中的布局

结构体字段基本介绍
从概念或叫法上看:结构体字段 = 结构体属性 = 结构体field
字段是结构体的一个组成部分,一般是基本数据类型、数组,也可以是引用类型
结构体字段注意事项和细节说明
1、声明字段的语法类似声明变量,比如:字段名 字段类型
2、字段的类型可以为:基本类型、数组或引用类型
3、在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),比如:布尔类型是false,数值是0,字符串是"",数组类型的默认值和它的元素类型相关(如score [3]int,零值为[0,0,0]),指针、slice、map的零值都是nil,即还没有分配空间(虽然输出不一样,实际都为nil)
//如果字段类型是指针、slice、map,它们的零值都是nil,即还没有分配空间(虽然输出不一样,实际都为nil)
type Person struct {
Name string
Age int
Email string
Hobby map[string]string
Score [3]float64
Slice []int
Ptr *int
}
//使用时,指针、slice、map的零值都是nil
person := Person{}
if person.Hobby == nil { fmt.Println("Map使用了零值nil") }
if person.Slice == nil { fmt.Println("Slice使用了零值nil") }
if person.ptr == nil { fmt.Println("指针使用了零值nil") }
4、不同结构体变量的字段是独立的,互不影响,一个结构体变量的字段发生变化,不影响另一个,结构体是值类型,比如:

注意:在Go语言中,结构体是值类型,直接指向数据空间(并没有先指向一个地址,再通过地址指向数据空间)
var stu1 = Stu{"Jack", 18} //结构体:str1 => 结构体数据空间[xxx, xxx]
var stu2 = &Stu{"Tom", 18} //结构体指针:str2 => 地址 => 结构体数据空间[xxx, xxx]
struct类型的内存分配机制

Golang严格区分大小写
结构体名称首字母如果是大写的,证明这个结构体可以被其它包使用
如果字段名字首字母是大写的,证明这个字段可以被其它包使用,小写的表示私有,只能在本包使用
创建结构体常用的方式
type User struct {
Name string
Age int
}
func main() {
//方式1 - 直接声明
var u1 User
u1.Name = "Tom"
u1.Age = 18
fmt.Println("u1 =", u1)
//方式2 - {}
var u2 = User{} //或者 u2 := User{}
u2.Name = "Tom"
u2.Age = 18
fmt.Println("u2 =", u2)
//方式3
var u3 = User{Name:"Tom", Age:18}
fmt.Println("u3 =", u3)
//方式4:省略了字段名,但需要每次把所有字段都声明了,缺失字段会报错
var u4 User = User{"Tom", 18}
fmt.Println("u4 =", u4)
//方式5:比上面的方式4省去变量类型
var u5 = User{"Tom", 18}
fmt.Println("u5 =", u5)
//方式6
u6 := User{"Tom", 18}
fmt.Println("u6 =", u6)
//方式7 - *指针
var u7 *User = new(User) //new(结构体)会得到一个结构体指针
//因为u7是一个指针,因此标准的给字段赋值方式
(*u7).Name = "Tom"
//有种简化写法,可以去掉指针变量前面的*,Go的设计者认为这样做,更符合程序员的使用习惯
//Go的编译器底层会对 u7.Age = 18 进行处理,给 u7 加上取值运算,转化为 (*u7).Age = 18
//所以,(*u7).Age 等价于 u7.Age,前者为标准写法,后者为简化写法
u7.Age = 18
//因为是指针,所以取出时会带一个&符号,告诉你这是指针,其它都一样
//如果要拿掉地址符号&,在前面加一个取值符号*,这样在输出时,就把结构体本身的数据空间拿到了
fmt.Println("u7 =", *u7)
//方式8 - &指针
var u8 *User = &User{} //得到一个结构体指针
(*u8).Age = 18 //标准写法
//结构体指针访问字段的标准方式应该是:(*结构体指针).字段名 ,比如 (*person).Name = "tom" 3) ,但 go 做了一个简化,也支持 “结构体指针.字段名”,比如:person.Name = "tom"。
u8.Name = "Tom" //简化写法
fmt.Println("u8 =", *u8)
//方式9 - &指针
//可以直接给字段赋值
var u9 *User = &User{"Tom", 18} //u9 => 地址 => 结构体数据空间[xxx, xxx]
//因为是指针,所以取出时会带一个&符号,告诉你这是指针,其它都一样
//如果要拿掉地址符号&,在前面加一个取值符号*,这样在输出时,就把结构体本身的数据空间拿到了
fmt.Println("u9 =", *u9)
}
结构体中使用切片,两种方式
type Person struct{
Str string
Slice []int
}
func main(){
//方式一:直接赋值会报错,因为仅初始化了,并没有指向一个空间,需要先make
p1 := Person{}
p1.Slice = make([]int, 3)
p1.Slice[0] = 100
//方式二:省去slice的make,直接赋值
var p2 = Person{}
p2.Slice = []int{1, 2, 3}
}
结构体中使用map,两种方式
type Person struct{
Name string
Hobby map[string]string
}
func main(){
//方式一:先make,再赋值
var p3 Person
p3.Hobby = make(map[string]string)
p3.Hobby["Bob"] = "bob"
//方式二:直接赋值
var p4 Person
p4.Hobby = map[string]string{"Alice": "alice", "Bob": "bob"}
}
结构体的注意事项和使用细节
1、结构体的所有字段在内存中是连续的,比如下面的分析图:


2、结构体是用户单独定义的类型,和其它类型(结构体)进行转换时,需要有完全相同的字段(名字、个数、类型)

3、当我们定义好一个结构体后,再次使用type进行重新定义,Golang认为这是新的数据类型,新旧两种类型的变量之间可以相互强转

正确的使用方法:使用类型强转

再举个例子,比如把int类型重新定义为integer后,Go认为这是两种类型

正确的做法是:

4、struct的每个字段后面,可以加上一个tag,这个tag可以通过反射机制获取,常见的使用场景是序列化和反序列化(json)
下面是没有使用tag时的案例,struct转json后,字段首字母依然大写:

下是使用tag后,结构体的字段在转json时,可以被自定义(任意名称):
