当前位置: 首页 > news >正文

8.结构体

8.结构体

    Go语言的结构体有点像面向对象编程语言中的"类",但也不完全是。

面向对象是一种对现实世界理解和抽象的一种方法,通过抽象把相关的数据和方法组织为一个整体来看待,从高层次进行系统建模。更贴近事物的自然运行模式。例如:人类就是一个抽象的类,而小明,则代表了一个具体人。在其他语言中,类一般使用关键字class来定义一个类。在Go语言中,没有class关键字,而是使用结构体替换了

8.1 结构体定义

    结构体使用关键字type定义,也可以把结构体当成类型使用。在定义时,必须指定结构的字段名称(属性)和类型,定义的基本语法如下所示:

type name struct {field1 dataTypefield2 dataType...
}

    各字段解释如下所示:

  • type: 用于设置当前自定义的变量为自定义类型
  • name: 结构体名字,满足标识符定义规则即可
  • struct: 声明当前类型为结构体类型
  • fields: 结构体字段名称,也称为结构成员
  • dataType: 字段的数据类型

8.2 结构体初始化

    初始化类似于面向对象语言中的实例,即从抽象类中生成一个具体的对象。示例代码如下所示:

package mainimport "fmt"// 定义结构体
type User struct {id         intname, addr stringheight     float32
}func main() {// 使用var声明,非常常用var u1 User// 加上字段打印fmt.Printf("u1: %+v\n", u1)// 加上打印更多信息fmt.Printf("u1: %#v\n", u1)// 字面量初始化u2 := User{}fmt.Printf("u2: %#v\n", u2)// 字面量初始化,为字段进行赋值u3 := User{name: "Surpass"}fmt.Printf("u3: %#v\n", u3)// 字面量初始化,为字段进行赋值,名称对应,或忽略顺序u4 := User{name:   "Surpass",height: 1.85,id:     9999,addr:   "Shanghai",}fmt.Printf("u4: %#v\n", u4)// 使用new方法u5 := new(User)fmt.Printf("u5: %#v\n", u5)// 获取结构体实例化的内存地址u6 := &User{}fmt.Printf("u5: %#v\n", u6)
}

    代码运行结果如下所示:

u1: {id:0 name: addr: height:0}
u1: main.User{id:0, name:"", addr:"", height:0}
u2: main.User{id:0, name:"", addr:"", height:0}
u3: main.User{id:0, name:"Surpass", addr:"", height:0}
u4: main.User{id:9999, name:"Surpass", addr:"Shanghai", height:1.85}
u5: &main.User{id:0, name:"", addr:"", height:0}
u5: &main.User{id:0, name:"", addr:"", height:0}

    结构体的可见性如下所示:

  • 结构体名称中首字母大写,则跨包可见,否则仅本包内可见
  • 结构体成员中首字母大写,则跨包可见

8.3 结构体访问与修改

    结构体访问,可以使用字段名称访问,修改可以通过字段名称进行修改。示例代码如下所示:

package mainimport "fmt"// 定义结构体
type User struct {id         intname, addr stringheight     float32
}func main() {u := User{name:   "Surpass",height: 1.85,id:     9999,addr:   "Shanghai",}fmt.Printf("u4: %#v\n", u)// 访问结构体字段fmt.Printf("修改前访问User's id is: %d name is: %s  addr is: %s, height is: %f\n", u.id, u.name, u.addr, u.height)// 修改结构体字段u.id = 1000u.height = 1.75u.name = "Surmount"fmt.Printf("修改后访问User's id is: %d name is: %s addr is: %s, height is: %f\n", u.id, u.name, u.addr, u.height)
}

    运行结果如下所示:

u4: main.User{id:9999, name:"Surpass", addr:"Shanghai", height:1.85}
修改前访问User's id is: 9999 name is: Surpass  addr is: Shanghai, height is: 1.850000
修改后访问User's id is: 1000 name is: Surmount addr is: Shanghai, height is: 1.750000

8.4 结构体指针方式的初始化

    在初始化结构体时,可以使用内置方法new& 两种方式,这两种初始化方法都是由指针方式完成的。在访问结构体字段时,使用点,而编译器会自动将其转换为(*structName).field的形式访问。不同的初始化方式在使用上存在一定的差异,但为了统一使用方式,常规初始化的使用方法也能兼容指针方式。指针初始化的真正使用方式如下所示:

package mainimport "fmt"// 定义结构体
type User struct {id         intname, addr stringheight     float32
}func main() {// 使用new方法初始化var u1 *User = new(User)(*u1).name = "Surpass"(*u1).addr = "Shanghai"fmt.Printf("u1's name:%+v,addr:%+v,u1:%#v\n", u1.name, u1.addr, u1)// 通过 & 初始化var u2 *User = &User{}(*u2).name = "Surmount"(*u2).addr = "Wuhai"fmt.Printf("u2's name:%+v,addr:%+v,u2:%#v\n", u2.name, u2.addr, u2)
}

    代码运行结果如下所示:

u1's name:Surpass,addr:Shanghai,u1:&main.User{id:0, name:"Surpass", addr:"Shanghai", height:0}
u2's name:Surmount,addr:Wuhai,u2:&main.User{id:0, name:"Surmount", addr:"Wuhai", height:0}

    根据以上代码总结如下所示:

  • 使用内置方法new和&初始化结构体时,其实例都是指针类型
  • 通过结构体实例u1和u2访问结构体字段时,需要先使用取值操作符从结构体实例存储的内存地址获取结构体,再从结构体获取相应的字段,再进行取值或赋值操作
  • 指针方式的初始化结构也允许直接使用点访问结构体字段,因为编译器会转换为(*structName).field

8.5 结构体标签

    在定义一个结构体,我们还可以为每个字段添加标签(tag),它是一个附属于字段的字符串,用于标识字段的一些属性。例如JSON、ORM框架等用得非常多,基本语法如下所示:

type name struct {field1 dataType `key1:"value1" key2:"value2"`field2 dataType `key1:"value1" key2:"value2"`
}
  • 标签位于字段的数据类型之后,以字符串表示,用反引号包裹
  • 标签内容可以由一个或多个键值对组成,键值之间使用冒号分隔,且不能留有空格
  • 标签内容中的值使用双引号包裹,多个键值对之间使用空格分隔

    先来看看示例代码1:

package mainimport ("encoding/json""fmt"
)// 定义结构体
type User struct {id         intname, addr stringheight     float32
}func main() {u1 := User{id:     9999,name:   "Surpass",addr:   "Shanghai",height: 1.89,}if data, err := json.Marshal(u1); err == nil {fmt.Println(string(data))}
}

    运行结果如下所示:

{}

    以上代码输出结果为空,VSCode出还检查出来问题,如下所示:

struct type 'surpass.net.User' doesn't have any exported fields, nor custom marshaling (SA9005)

    从以下提示信息,可以看出结构体中的字段首字母都是小写,因此无法导出相应的标识符,导致encoding/json无法获取结构体中的字段数据。那解决办法,将首字母改成大写即可,但JSON数据中的key一般是小写,这个时候就可以使用结构体标签,改造后的代码如下所示:

package mainimport ("encoding/json""fmt"
)// 定义结构体
type User struct {Id     int     `json:"id"`Name   string  `json:"name"`Addr   string  `json:"addr"`Height float32 `json:"height"`
}func main() {u1 := User{Id:     9999,Name:   "Surpass",Addr:   "Shanghai",Height: 1.89,}if data, err := json.MarshalIndent(u1, "", "  "); err == nil {fmt.Println(string(data))}
}

    最终运行结果如下所示:

{"id": 9999,"name": "Surpass","addr": "Shanghai","height": 1.89
}

8.6 匿名结构体

    匿名结构体类似于匿名函数,在使用匿名结构体时,需要将其赋值给变量。使用方法如下所示:

package mainimport ("encoding/json""fmt"
)func main() {// 定义匿名结构体var User struct {Id     int     `json:"id"`Name   string  `json:"name"`Addr   string  `json:"addr"`Height float32 `json:"height"`}User.Id = 9999User.Name = "Surpass"User.Addr = "Shanghai"User.Height = 1.89fmt.Printf("User value:%#v\n", User)if data, err := json.MarshalIndent(User, "", "  "); err == nil {fmt.Println(string(data))}// 定义匿名结构体,并赋值u := struct {Id     int     `json:"id"`Name   string  `json:"name"`Addr   string  `json:"addr"`Height float32 `json:"height"`}{Id:     8888,Name:   "Surmount",Addr:   "Wuhan",Height: 1.95,}fmt.Printf("User value:%#v\n", u)if data, err := json.MarshalIndent(u, "", "  "); err == nil {fmt.Println(string(data))}
}

    运行结果如下所示:

User value:struct { Id int "json:\"id\""; Name string "json:\"name\""; Addr string "json:\"addr\""; Height float32 "json:\"height\"" }{Id:9999, Name:"Surpass", Addr:"Shanghai", Height:1.89}
{"id": 9999,"name": "Surpass","addr": "Shanghai","height": 1.89
}
User value:struct { Id int "json:\"id\""; Name string "json:\"name\""; Addr string "json:\"addr\""; Height float32 "json:\"height\"" }{Id:8888, Name:"Surmount", Addr:"Wuhan", Height:1.95}
{"id": 8888,"name": "Surmount","addr": "Wuhan","height": 1.95
}

    根据以上代码运行结果,我们来总结一下结构体与匿名结构体主要区别,如下所示:

0801-结构体与匿名结构区别.png

8.7 匿名字段

    匿名字段是指在结构体中没有明确定义字段名称,只定义了字段的数据类型。在访问时,可以通过字段的数据类型进行访问。示例代码如下所示:

package mainimport ("fmt"
)// 定义匿名字段
type User struct {intstringfloat32bool
}func main() {u1 := User{}fmt.Printf("u1 value: %#v\n", u1)u2 := User{9999, "Surpass", 1.90, false}fmt.Printf("结构体匿名字段int值:%v\n", u2.int)fmt.Printf("u2 value: %#v\n", u2)fmt.Printf("结构体匿名字段string值:%v\n", u2.string)fmt.Printf("结构体匿名字段float32值:%v\n", u2.float32)fmt.Printf("结构体匿名字段bool值:%v\n", u2.bool)u2.bool = truefmt.Printf("结构体匿名字段bool值:%v\n", u2.bool)
}

    运行结果如下所示:

u1 value: main.User{int:0, string:"", float32:0, bool:false}
结构体匿名字段int值:9999
u2 value: main.User{int:9999, string:"Surpass", float32:1.9, bool:false}
结构体匿名字段string值:Surpass
结构体匿名字段float32值:1.9
结构体匿名字段bool值:false
结构体匿名字段bool值:true

如果结构体中存在匿名字段,则同一种数据类型不允许存在多个,如下所示:

type User struct {intstringstring //会报错,不允许存在两个string类型,必须类型不一样才能区分float32bool
}

8.8 结构体嵌套

    在面向对象里面有一个设计原则组合优于继承,而在Go语言中使用嵌套则很好的体现了这一种原则。因为结构体的字段可以设置不同的数据类型,而struct本身也是一种数据类型。因此也可以在一个结构体中使用另一个结构体做为字段,从而形成一种递进的关系,形成嵌套。这种也称之为结构体嵌套,通过这种方式也可以实现面向对象语言中的继承。示例如下所示:

type User struct {Id intName stringAddr stringHeight float32
}type Human struct {UserGender byte
}

    示例代码如下所示:

package mainimport "fmt"type User struct {Id     intName   stringAddr   stringHeight float32
}type Human struct {User   UserGender byteName   string
}func main() {// 嵌套结构体初始化方式一u := User{Id:     9999,Name:   "Surpass",Addr:   "Shanghai",Height: 1.89,}h := Human{User:   u,Gender: 0,Name:   "Human",}fmt.Printf("h value: %#v\n", h)fmt.Printf("h name value: %v,u name value:%v\n", h.Name, h.User.Name)// 嵌套结构体和初始化方式二:h2 := Human{User: User{Id:     8888,Name:   "Surpass",Addr:   "Wuhan",Height: 1.99,},Gender: 1,Name:   "Surmount",}fmt.Printf("h2 value: %#v\n", h2)fmt.Printf("h2 name value: %v,u name value:%v\n", h2.Name, h2.User.Name)
}

    运行结果如下所示:

h value: main.Human{User:main.User{Id:9999, Name:"Surpass", Addr:"Shanghai", Height:1.89}, Gender:0x0, Name:"Human"}
h name value: Human,u name value:Surpass
h2 value: main.Human{User:main.User{Id:8888, Name:"Surpass", Addr:"Wuhan", Height:1.99}, Gender:0x1, Name:"Surmount"}
h2 name value: Surmount,u name value:Surpass

    通过上面代码,总结如下所示:

  • 在结构体Human中嵌套了结构体User。使得Human拥有User的全部字段。若从面向对象的角度来看,Human继承了父类User
  • 如果两个结构拥有相同的字段,在访问字段,需要明确指明访问哪一个结构体的字段

    在结构体嵌套中,也可以通过匿名结构体实现。示例代码代码如下所示:

package mainimport ("fmt"
)type User struct {Id     intName   stringAddr   stringHeight float32
}type Human struct {Name   stringGender byteUserCar struct{ Color, Brand string }
}func main() {h := Human{Name:   "Surmount",Gender: 1,User: User{Id:     8888,Name:   "Surpass",Addr:   "Wuhan",Height: 1.99,},Car: struct {Color stringBrand string}{Color: "Red", Brand: "Audi"},}fmt.Printf("h value: %#v\n", h)fmt.Printf("h name value: %v,u name value:%v color value:%v\n", h.Name, h.User.Name,h.Car.Color)
}

    最终的运行结果如下所示:

h value: main.Human{Name:"Surmount", Gender:0x1, User:main.User{Id:8888, Name:"Surpass", Addr:"Wuhan", Height:1.99}, Car:struct { Color string; Brand string }{Color:"Red", Brand:"Audi"}}
h name value: Surmount,u name value:Surpass color value:Red

在对结构体进行初始化时,如果要按变量名进行赋值,要么都指定,要么全不指定,不可以混用

8.9 构造函数

    Go语言并没有从语言层面为结构体提供构造器,但有时候可以通过一个函数为结构体初始化提供默认属性值,从而更加方便得到一个结构体和实例。习惯上,函数命名以New做为开头。使用构造函数时,可以选择性为结构字段进行赋值,若未赋值,则使用相应的零值。示例代码如下所示:

package mainimport "fmt"type User struct {Id     intName   stringAddr   stringHeight float32
}// 这里NewUserWithNameAndAddr返回值使用了值拷贝,会增加内存开销
func NewUserWithNameAndAddr(name, addr string) User {return User{Name: name, Addr: addr}
}// 一般在返回结构体初始化值,使用指针类型,避免实例的拷贝
func NewUser(id int, name, addr string, height float32) *User {return &User{Id: id, Name: name, Addr: addr, Height: height}
}func main() {u1 := NewUserWithNameAndAddr("Surpass", "Shanghai")u2 := NewUser(9999, "Surpass", "Shanghai", 1.89)fmt.Printf("u1:%#v\n", u1)fmt.Printf("u2:%#v\n", u2)
}

    运行结果如下所示:

u1:main.User{Id:0, Name:"Surpass", Addr:"Shanghai", Height:0}
u2:&main.User{Id:9999, Name:"Surpass", Addr:"Shanghai", Height:1.89}

为了减少内存开销,一般在对结构体定义构造函数时,使用指针类型

8.10 结构体Receiver

    在Go语言中,可以为任意类型包括结构体增加方法,语法形式如下所示:

func (receiver) name(parametes) returnValue{代码块
}
  • receiver: 方法绑定的对象,receiver必须是一个类型T实例或类型T的指针,T不能是指针或接口
  • name: 方法名称
  • parameters:参数列表
  • returnValue: 方法返回值

在Go语言中,函数与方法代表不同的概念,函数是独立的,方法一般是指绑定到结构体的方法,其依赖于结构体

    示例代码如下所示:

package mainimport "fmt"type User struct {Id     intName   stringAddr   stringHeight float32
}// 将 GetUserName 绑定到结构体 User
func (u User) GetUserName() string {return u.Name
}func (u *User) GetUserAddr() string {return u.Addr
}func (u User) SetUserName(name string) {fmt.Printf("非指针Receiver修改前:%+v,%p\n", u, &u)u.Name = namefmt.Printf("非指针Receiver修改后:%+v,%p\n", u, &u)
}func (u *User) SetUserAddr(addr string) {fmt.Printf("指针Receiver修改前:%+v,%p\n", u, u)u.Addr = addrfmt.Printf("指针Receiver修改后:%+v,%p\n", u, u)
}func main() {u := User{Name: "Surmount", Addr: "Wuhan"}fmt.Printf("main函数中:%+v,%p\n", u, &u)u.SetUserName("Surpass")u.SetUserAddr("Shanghai")fmt.Printf("main函数中:%+v,%p\n", u, &u)
}

    运行结果如下所示:

main函数中:{Id:0 Name:Surmount Addr:Wuhan Height:0},0xc000106660
非指针Receiver修改前:{Id:0 Name:Surmount Addr:Wuhan Height:0},0xc0001066c0
非指针Receiver修改后:{Id:0 Name:Surpass Addr:Wuhan Height:0},0xc0001066c0
指针Receiver修改前:&{Id:0 Name:Surmount Addr:Wuhan Height:0},0xc000106660
指针Receiver修改后:&{Id:0 Name:Surmount Addr:Shanghai Height:0},0xc000106660
main函数中:{Id:0 Name:Surmount Addr:Shanghai Height:0},0xc000106660

    从上面示例可以看出,如果是非指针类的Receiver进行调用时,操作是副本,存在值拷贝,而指针类的Receiver进行调用时,操作的是同一个内存的同一个实例。如果是操作大内存的对象时,且操作的是同一个实例时,一定要使用指针类型的Receiver方法

http://www.aitangshan.cn/news/362.html

相关文章:

  • 题解:cses2180 Coin Arrangement
  • imx766在rk3588上的驱动
  • 假期进度报告3
  • nslookup命令
  • ping命令参数
  • P5873 [SEERC 2018] Points and Rectangles 解题报告
  • watch命令
  • About Me
  • wget命令参数
  • 2025.8.10学习日记【PyCharm的入门导览】
  • 电子 Doro 安装步骤
  • ps命令详解
  • 面向对象编程:封装
  • 8 面向对象编程 8.8 接口
  • 2025牛客多校第八场 根号-2进制 个人题解 - CUC
  • vCenter上更新证书后,Citrix Delivery Controller(DDC)提示证书不可用
  • 不定长滑动窗口模板
  • 题解:CF1179D Fedor Runs for President
  • 数论杂记 2025.8.11始
  • 8 面向对象编程 8.5. final 关键字 8.6 抽象类 8.7 抽象类最佳实践-模板设计模式
  • [Atlas200I A2] 安装torch-npu
  • 题解:[Vani有约会] 雨天的尾巴 /【模板】线段树合并
  • 8.11随笔
  • 蒸馏大型语言模型并超越其性能
  • 每日随笔
  • webrtc自定义端口和host
  • 第二十九天
  • 【20250805省选训练】T3-简单树题
  • 让CPU省电的方法
  • IFEO劫持