结构体
结构体
Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体(struct),Go语言中通过结构体来实现面向对象
规范:
- 采用驼峰命名方式,首字母根据访问权限, 来决定使用大写或小写(包括结构体名称、属性、方法)
- 结构体名不应该是动词,应该是名词
- 避免使用 Data、Info 这类无意义的结构体名
- 结构体的声明和初始化应采用多行
结构体定义
1
2
3
4
5
type Person struct {
Name string
Age int
Sex int
}
**注意:结构体定义时结构体字段的可见性规则: ** 结构体中字段大写开头表示可公开访问,小写表示私有
实例化(声明)结构体
var 结构体实例 结构体类型
1
var per Person
创建结构体
工作中一般使用 u:= &User{name: “yangyongjie”,…} 这种方式实例化并初始化
第一种方式:使用键值对初始化 :crossed_swords:
初始化的是结构体类型,直接打印返回的是结构体的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Person struct {
Name string
Age int
Sex int
}
person := Person{
Name: "xiaoyu",
Age: 18,
Sex: 1,
}
fmt.Println(person) // {xiaoyu 18 1}
// or
person := Person{"xiaoyu", 18, 1}
fmt.Println(person) // {xiaoyu 18 1}
第二种方式:使用结构体变量
初始化的是结构体类型,直接打印返回的是结构体的值
1
2
3
4
5
6
7
8
9
10
11
type Person struct {
Name string
Age int
Sex int
}
var person Person
person.Name = "xiaoyu"
person.Age = 18
person.Sex = 1
fmt.Println(person) // {xiaoyu 18 1}
第三种方式:使用new方法
初始化的是指针类型,直接打印返回的是内存地址
1
2
3
4
5
6
7
8
9
10
11
type Person struct {
Name string
Age int
Sex int
}
person := new(Person)
person.Name = "xiaoyu"
person.Age = 18
person.Sex = 1
fmt.Println(person) // &{xiaoyu 18 1}
第四种方式:对结构体指针进行键值对初始化 (常用) :crossed_swords:
初始化的是指针类型,直接打印返回的是内存地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建一个 Person 实例
user := &Person{
Name: "xiaoyu",
Age: 18,
Sex: 1,
}
// 访问结构体成员
fmt.Println("Name:", user.Name) // 访问 Name 字段
fmt.Println("Age:", user.Age) // 访问 Age 字段
fmt.Println("Sex:", user.Sex) // 访问 Sex 字段
// 修改结构体成员
user.Age = 19
fmt.Println("Updated Age:", user.Age) // 更新并打印修改后的 Age
结构体默认值
| 场景 | 是否零值初始化 | 示例 | | ————– | —————- | ————————— | | var p *T | ❌ 否(指针 nil) | var p *Server // p 是 nil | | ` s := &T{} | ✅ 是 | s := &Server{} | | var s T | ✅ 是 | var s Server | | s := T{} | ✅ 是(显式零值) | s := Server{} | | new(T) | ✅ 是 | new(Server) | | &T{} | ✅ 是 | &Server{} | | make([]T, n) | ✅ 是(每个元素) | make([]Server, 5) | | 类型断言失败 | ✅ 是(返回零值) | v, ok := i.(Server)` |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Goods struct {
// 基础字段
ShopID string `json:"shop_id"` // 零值: ""
OuterID int `json:"outer_id"` // 零值: 0
Flag int `json:"flag"` // 零值: 0 (不是 false!)
UpdateTime time.Time `json:"update_time"` // 零值: 0001-01-01 00:00:00 +0000 UTC
// 切片
DescPics []string `json:"desc_pics"` // 零值: nil
AllProps []Prop `json:"all_props"` // 零值: nil
// 指针
CreateTime *time.Time `json:"create_time"` // 零值: nil
GoodsHot *int `json:"goods_hot"` // 零值: nil
PropsPtr *[]Prop `json:"props_ptr"` // 零值: nil
// 嵌套切片
Props []Prop `json:"props"` // 零值: nil
}
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func (c *Collection) Upsert(selector interface{}, update interface{}) (info *ChangeInfo, err error) {
if selector == nil {
selector = bson.D{}
}
op := updateOp{
Collection: c.FullName,
Selector: selector,
Update: update,
Flags: 1,
Upsert: true,
}
var lerr *LastError
for i := 0; i < maxUpsertRetries; i++ {
lerr, err = c.writeOp(&op, true)
if !IsDup(err) {
break
}
}
// 只有在没有错误且有 lastError 时才创建 info
if err == nil && lerr != nil {
info = &ChangeInfo{}
/**
. 默认值初始化
修改后无论是否出错,都会初始化 info = &ChangeInfo{}
ChangeInfo{} 的字段默认值为:Matched=0, Updated=0, Removed=0, UpsertedId=nil
**/
if lerr.UpdatedExisting {
info.Matched = lerr.N
info.Updated = lerr.modified
} else {
info.UpsertedId = lerr.UpsertedId
}
}
return info, err // 如果有错误,info 为 nil
}
赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 预分配切片容量,避免多次内存分配
rsp.List = make([]Geproto.ShopInfo, 0, len(pteRsp.Data.Shops))
// 使用 append
for _, s := range pteRsp.Data.Shops {
rsp.List = append(rsp.List, Geproto.ShopInfo{
HistoryDays: s.HistoryDays,
LastExpirationTime: s.LastExpirationTime,
PlatShopID: strconv.Itoa(s.ShopID),
Status: strconv.Itoa(s.Status),// 直接使用 strconv.Itoa
})
}
for i, s := range pteRsp.Data.Shops {
rsp.List[i] = Geproto.ShopInfo{
HistoryDays: s.HistoryDays,
LastExpirationTime: s.LastExpirationTime,
PlatShopID: strconv.Itoa(s.ShopID),
Status: strconv.Itoa(s.Status),
}
}
rsp.TotalCount = pteRsp.Data.TotalResults
rsp.CommonApiRes = pteRsp.CommonApiRes
访问结构体成员
结构体变量名.成员名, 注: &结构体名.成员名 不能访问
当前结构体可以直接访问其内嵌结构体的内部字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main
import "fmt"
type Animal struct {
Age int
}
type Person struct {
Animal
Name string
}
type Student struct {
Person
ClassName string
}
func main() {
// 初始化方式1
s1 := Student{
Person{
Animal: Animal {
Age: 15,
},
Name:"lisi",
},
"一班",
}
fmt.Println(s1.Age) // 正确输出15
fmt.Println(s1.Person.Name) // 正确输出lisi
// 初始化方式2
var s2 Student
s2.Name = "zs"
s2.Age = 30
s2.ClassName = "二班"
fmt.Println(s2.Age) // 正确输出30
fmt.Println(s2.Person.Name) // 正确输出zs
}
特殊的结构体
匿名结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type Person struct {
name string
age int
}
type course []string
type Student struct {
Person // 匿名字段,struct
course // 内置一个切片类型
classroom string
}
func main() {
// 创建一个学生
s := &Student{Person: Person{"LiLei", 17}, classroom: "二班"}
// 访问该学生字段
fmt.Println(s)
fmt.Println("name= ", s.name)
fmt.Println("name = ", s.Person.name)
fmt.Println("classroom = ", s.classroom)
// 修改学生的课程
s.course = []string{"语文", "美术"}
fmt.Println("course = ", s.course) // [语文 美术]
}
1
2
3
4
name= LiLei
name = LiLei
classroom = 二班
course = [语文 美术]
如果Person和Student中都有同一个字段,那么Go会优先访问当前层。例如二者都有tel字段,那么s.tel将会访问的是Student中的数据。
结构体指针
结构体指针类似于其他指针变量
1
var struct_pointer *struct_name
以上定义的结构体指针可以存储结构体变量的地址
1
struct_pointer = &struct_var_name // 取结构体变量的地址赋给结构体指针
方式1: 使用new
使用new关键字对结构体进行实例化,得到的是结构体的内存地址
1
2
3
4
5
6
var user1 = new(User)
fmt.Println(user1) // &{ 0}
fmt.Println(reflect.TypeOf(user1)) // *main.User 返回的是指针类型
// Go语言中支持对结构体指针直接使用.来访问结构体的成员,user1.name其实在底层是(*user1).name = "hello",这是Go语言帮我们实现的语法糖
user1.name = "hello"
fmt.Println(user1) // &{hello 0}
方式2: 取结构体的地址实例化(推荐)
使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作
1
2
3
4
5
user2 := &User{}
fmt.Println(reflect.TypeOf(user2)) // *main.User
//user2.address 其实在底层是(*user2).name = "nanjing",这是Go语言帮我们实现的语法糖
user2.address = "nanjing"
fmt.Println(user2) // &{ nanjing 0}
树状结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type CommonApiRes struct {
ErrNo int32 `json:"err_no"`
Message string `json:"message"`
Code int `json:"code"`
Msg string `json:"msg"`
SubCode string `json:"sub_code"`
SubMsg string `json:"sub_msg"`
LogId string `json:"log_id"`
}
type PlatRdsCompensateRes struct {
CommonApiRes
}
// 等价
type PlatRdsCompensateRes struct {
ErrNo int32 `json:"err_no"`
Message string `json:"message"`
Code int `json:"code"`
Msg string `json:"msg"`
SubCode string `json:"sub_code"`
SubMsg string `json:"sub_msg"`
LogId string `json:"log_id"`
}
使用指针对结构体自引用,可定义一个树状结构的结构体:
1
2
3
4
5
6
7
8
9
type CommonBaseReq struct {
AppName string `json:"app_name,omitempty" form:"app_name"`
}
type PlatRdsBindShopListReq struct {
libproto.CommonBaseReq
}
访问跳着可以: req := PlatRdsBindShopListReq{} req.AppName
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func main() {
person := &Person{
name: "Job Smith",
children: []*Person{
{
name: "Bob Smith",
children: []*Person{
{
name: "Joy Smith",
},
},
},
{
name: "Bob Smith",
},
},
}
fmt.Println(person.children[0].name) // Bob Smith
}
type Person struct {
name string
children []*Person
}
结构体作为函数参数
结构体变量和结构体指针的理解:
结构体指针指向的是结构体变量的内存地址(使用 整体赋值
*rsp = *pteRsp, 属性赋值rsp .xxx =pteRsp.xxx结构体变量是结构体类型的变量本身的值
1
2
3
4
5
6
7
8
9
10
type Books struct {
title string
author string
subject string
book_id int
}
func printBook(book Books) {
fmt.Printf( "Book title : %s\n", book.title)
}
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main
import (
"fmt"
)
// 定义一个简单的结构体
type Person struct {
Name string
Age int
}
// 使用值传递
func updateAgeByValue(p Person) {
p.Age += 1 // 这个修改不会影响原始的结构体
fmt.Println("在 updateAgeByValue 中:", p)
}
// 使用指针传递
func updateAgeByPointer(p *Person) {
p.Age += 1 // 这个修改会影响原始的结构体
fmt.Println("在 updateAgeByPointer 中:", *p)
}
func main() {
// 创建一个 Person 实例
person := Person{Name: "Alice", Age: 30}
// 值传递
fmt.Println("调用 updateAgeByValue 之前:", person)
updateAgeByValue(person)
fmt.Println("调用 updateAgeByValue 之后:", person)
// 指针传递
fmt.Println("调用 updateAgeByPointer 之前:", person)
updateAgeByPointer(&person) // 传递结构体的地址
fmt.Println("调用 updateAgeByPointer 之后:", person)
}
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type GetOrderListRes struct {
Data struct {
UserMan UserMan `json:"user_man"`
} `json:"data"`
}
var result GetOrderListRes // 创建一个结果结构体的实例
err := instance.TempGetOrderDetail(30, &result.Data.UserMan) // 传递指向该实例的指针
func (mt *dyRdsOrder) TempGetOrderDetail(age int, resPtr interface{}) error {
res := UserMan{}
err := mt.DB.Table(mt.Config.Table).Where("age = ?", age).First(&res).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrNotFound
}
return err
}
//*resPtr = res
if ptr, ok := resPtr.(*UserMan); ok {
*ptr = res // 赋值
} else {
return errors.New("invalid type for resPtr")
}
return nil
}
构造函数
Go语言的结构体没有构造函数, 可以用一个 返回结构体自身类型的指针 函数
1
2
3
4
5
6
7
8
func newUser(name, gender, address string, age int) *User {
return &User{
name: name,
age: age,
gender: gender,
address: address,
}
}
调用构造函数:
1
2
user5 := newUser("yangyongjie", "male", "nanjing", 27)
fmt.Println(user5) // &{yangyongjie male nanjing 27}
方法(接收者)
Go 语言中同时有函数和方法。一个方法就是一个包含了接收者的函数。方法可以将类型和方法封装在一起,实现强耦合。
接收者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集接收者可以理解为当前的对象,即方法所在类型的对象,可以理解为结构体的方法,类似于Java中的this,显式的声明了出来
结构体方法的定义格式如下:
1
2
3
func (接收者变量 接收者类型) 方法名(参数列表) (返回类型) {
方法体
}
什么时候应该使用指针类型接收者:
①:需要修改接收者中的值
②:接收者是拷贝代价比较大的大对象
③:保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者’
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 定义结构体
type User struct {
name string
gender string
address string
age int
}
// 该方法属于User对象中的方法
// 值类型的接收者
func (user User) getName() string {
// name属性即user对象中的属性
return user.name
}
// 指针类型的接收者
func (user *User) setName(name string) {
user.name = name
}
func main() {
user := &User{
name: "yangyongjie",
age: 27,
gender: "male",
address: "nanjing",
}
name := user.getName() // 该方法只能User结构体类型的变量或指针才能调用
fmt.Println(name) // yangyongjie
user1 := &User{}
user1.setName("yyj")
fmt.Println(user1.name) // yyj
}
值类型的接收者和值类型的接收者方法的区别:
- 指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式十分接近于Java语言中的this,和Python语言中的self
- 当方法作用于值类型的接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但是修改操作只是针对副本,无法修改接收者变量本身。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import "fmt"
// 定义结构体
type User struct {
name string
gender string
address string
age int
}
// 值类型的接收者
func (user User) setAddress(address string) {
user.address = address
}
// 指针类型的接收者
func (user *User) setName(name string) {
user.name = name
}
func main() {
user := &User{
name: "yangyongjie",
age: 27,
gender: "male",
address: "nanjing",
}
// 接收值类型的方法,user变量本身值没有被修改
user.setAddress("beijing")
fmt.Println(user.address) // nanjing
// 接收指针类型的方法,user变量本身值没有被修改
user.setName("yyj")
fmt.Println(user.name) // yyj
}
补充示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
person := Person{Name: "Xiaoyu", Age: 18, Sex: 1}
person.Say("hello") // Xiaoyu say hello
}
// 声明
type Person struct {
Name string
Age int
Sex int
}
func (person *Person) Say(words string) {
fmt.Println(person.Name, "say", words)
}
尝试将Say换为say,发现会报错。go就是通过控制方法的大小写来控制其作用域,如果是小写只能内部调用。’
继承
通过嵌套匿名结构体(结构体指针)实现继承
示例:
1
2
3
4
5
6
7
8
9
10
//Animal 动物
type Animal struct {
name string
}
//Dog 狗
type Dog struct {
Feet int8
*Animal //通过嵌套匿名结构体实现继承
}
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func main() {
person := Person{Name: "Xiaoyu", Age: 18, Sex: 1}
person.Kind = "mammalia"
person.Eat() // 好吃
fmt.Println(person) // {mammalia} Xiaoyu 18 1
}
type Animal struct {
Kind string
}
func (person *Animal) Eat() {
fmt.Println("好吃")
}
type Person struct {
Animal // 继承
Name string
Age int
Sex int
}
func (person *Person) Say(words string) {
fmt.Println(person.Name, "say", words)
}
内嵌结构中的命名冲突 如果一个结构体组合了多个结构体,而这些结构体中包含相同的字段名,那么就不能直接为这个字段赋值了,需要先指定是哪个结构体中的字段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type A struct {
a int
}
type B struct {
a int
}
type C struct {
A
B
}
func main() {
c := &C{}
c.A.a = 1 // 指定为A中的a字段
c.B.a = 2 // 指定为B中的a字段
fmt.Println(c) // &{1} {2}}
}
结构体与JSON序列化
使用 json.Marshal 将数据序列化为字节数组。适用于结构体、Map。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
user := &User{
Name: "yangyongjie",
Age: 27,
Gender: "male",
Address: "nanj ing",
}
// 结构体JSON序列化
data, err := json.Marshal(user) // 将结构体序列号为json字节数组
if err != nil {
fmt.Println("json marshal failed")
return
}
fmt.Printf("%s\n", data) // {"Name":"yangyongjie","Gender":"male","Address":"nanjing","Age":27} // 将json字节数组转化为字符串
// JSON反序列化结构体
user1 := &User{}
err = json.Unmarshal(data, user1)
if err != nil {
fmt.Println("json unmarshal failed!")
return
}
fmt.Print(user1) // &{yangyongjie male nanjing 27}
注意:如果结构体中的字段为小写字母开头,将不能被序列化,因为这相当于是一个局部属性
如果想要输出的结果为小写字母开头的key,可以为定义的结构体添加Tag:
1
2
3
4
5
6
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Gender string `json:"gender"`
Address string `json:"Address"`
}
这样的话,就可以输出:
1
{"name":"yangyongjie","gender":"male","address":"nanjing","age":27}