- Go语言通过自定义结构的方式来实现新的类型,结构体是类型中带有成员的复合类型。
- Go语言使用结构体和结构体成员来描述真实世界中的实体和实体对应的属性
- Go语言中的类型可以被实例化,使用
new
或&
取地址符所构造的类型实例,实例的类型是类型的指针。 - Go语言没有类
class
的概念,也不支持类的继承等面向对象的特性。 - Go语言的结构体与面向对象中的类都属于复合结构体
- Go语言结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性
- Go语言结构体不仅拥有方法,而且每种自定义类型也可以拥有自己的方法。
Go语言中的结构体是一种用户自定义的类型,结构体允许将可能不同类型的项目分组或组合成单个类型。现实世界中拥有一组属性或字段的实体都可以表示为结构。结构与面向对象编程中类相似,因此结构又称为不支持继承但支持组合的轻量级类。
字段
结构体成员由一系列成员变量构成,这些成员变量又称为字段。
结构体字段特性
- 字段拥有自己的类型和值
- 字段名称必须唯一
- 字段类型可以是结构体,甚至是字段所在结构体的类型。
定义
Go语言结构体即通过自定义的方式来定义新类型,结构体是由零个或多个任意类型的值聚合而成实体,每个类型的值称为结构体成员,结构体是成员类型的复合结构。
结构体定义格式
例如:定义类型名为structname
的类型
type StructName struct {
Field fieldtype
Field fieldtype
...
}
将struct{}
结构体定义为类型名为structname
的类型
- 结构体名称
即自定义类型的名称,用于标识自定义结构体的名称,在同一个包内不能重复。 - 结构体类型
例如:定义包含x和y整形分量的坐标点结构体
type Point struct {
X int
Y int
}
结构体定义时相同类型的字段可一行编写
例如:定义红绿蓝三个分量的颜色结构体,每种颜色分量可使用byte
类型标识。
type RGB struct {
Red, Green, Blue byte
}
实例化
结构体定义是对内存布局的描述,只有当结构体实例化时,才会真正分配内存。因此必须在定义结构体并实例化后才能使用结构体内的字段。
结构体实例化是根据结构体内部定义的格式来创建一份与格式一致的内存区域,结构体实例和实例之间的内存是完全独立的。
- 使用
var
关键字声明的方式实例化结构体
结构体本身只是一种类型,类似整形、字符串等,因此可以使用var
关键字的方式来声明结构体以实现实例化。
var instance T
- instance 表示结构体实例
- T 表示结构体类型
例如:使用结构体表示坐标点结构并实例化
package main
type Point struct {
X, Y int
}
func main() {
var p Point
p.X = 0
p.Y = 0
}
- 结构体实例化后可通过点操作符来访问结构体内的成员变量
- 结构体成员变量赋值方式与普通变量相同。
Go语言中访问结构体指针的成员变量时可以继续使用点操作符,为方便开发者访问结构体指针的成员变量,使用语法糖(Syntactic sugar)将instance.Field
的形式转换为(*instance).Field
。
- 创建指针类型的结构体
Go语言中可使用内置的new
关键字对类型(结构体、整型、浮点型、字符串型)进行实例化,结构体在实例化后会形成指针类型的结构体。
instance := new(T)
- T表示类型,可以是结构体、整型、浮点型、字符串型等。
- instance表示T类型的实例,即实例化后保存到instance变量中,其类型为
*T
(指针)。
例如:定义玩家结构,拥有名字、生命值、魔法值。实例化玩家结构体后对成员变量进行赋值。
package main
type Player struct {
Name string
Health int
Magic int
}
func main(){
p1 := new(Player)
p1.Name = "jc"
p1.Health = 100
p1.Magic = 100
}
- 获取结构体地址实例化
Go语言中对结构体进行&
取地址操作时,相当于对指定类型进行了一个new
的实例化操作。
instance := &T{}
- T表示结构体类型
- instance表示结构体实例,类型为
*T
的指针类型。
例如:使用结构体定义命令行指令,指令包含名称、变量关联、注释。
package main
//定义指令结构体用来表示命令行指令
type Command struct{
Name string //指令名称
//使用整型指针绑定指针,指令的值可以与绑定的值随时保持同步。
Var *int //指令绑定的变量
Comment string //指令注释
}
func main(){
//命令绑定的目标整型变量即版本号
var version int = 1
//实例化结构体
cmd := &Command{}
//初始化成员变量
cmd.Name = "version"
cmd.Var = &version//对结构体取地址以实例化
cmd.Comment = "command version"
}
取地址实例化是结构体实例化最广泛的使用方式,可使用函数封装其结构体初始化过程。
package main
import (
"fmt"
"unsafe"
)
type Command struct{
Name string
Var *int
Comment string
}
func newCommand(name string, varref *int, comment string) *Command{
return &Command{Name:name, Var:varref, Comment:comment}
}
func main(){
version := 1
cmd := newCommand("version", &version, "command version")
fmt.Printf("cmd = %v, type = %T, size = %d, pointer = %p\n", cmd, cmd, unsafe.Sizeof(cmd), cmd)
}
成员变量
结构体实例化时可以直接对成员变量进行初始化,初始化成员变量的方式有两种:
- 以字段键值对的方式初始化,适用于选择性填充字段较多的结构体。
- 以多个值的列表形式初始化,适用于填充字段较少的结构体。
- 使用键值对初始化结构体成员变量
结构体可以使用键值对(key value pair)来初始化字段,每个键(key)对应结构体中的一个字段,键的值则对应字段所需初始化的值。
键值对的填充方式是可选的,当不需要初始化的字段可以不用填充进初始化列表中。
结构体实例化后字段默认值是字段类型的默认值
instance := StructName{Field:Value, Field:Name...}
- StructName 结构体类型名
- Field 表示结构体成员的字段名,结构体类型名的字段初始化列表中,字段名只能出现一次。
- Value 表示结构体成员字段的初始值
- 键值对之间使用分号隔开,多个键值对之间使用逗号分隔。
例如:使用键值对填充结构体字段的方式描述家族树
package main
import "fmt"
type Family struct {
Name string
Child *Family
}
func main(){
relation := &Family{
Name:"Grandpa",
Child:&Family{
Name:"Father",
Child:&Family{
Name:"Son",
},
},
}
fmt.Printf("relation = %v\n", relation)
}
- 使用多个值的列表初始化结构体
Go语言可以在键值对初始化时忽略掉键,即采用多个值的列表来初始化结构体的成员字段。
instance := StructName{Value, Value...}
- 必须初始化结构体中所有成员字段
- 每个初始化填充顺序必须与字段在结构体中声明的顺序保持一致
- 键值对与值列表的初始化形式不能混用
package main
import "fmt"
type Address struct {
Country string
Province string
City string
Area string
}
func main(){
addr := Address{"中国", "湖南", "长沙", "岳麓区"}
fmt.Printf("addr = %v\n", addr)//addr = {中国 湖南 长沙 岳麓区}
}
匿名结构体
匿名结构体指的是没有类型名称且无需通过type
关键字定义即可直接使用的结构体
匿名结构体初始化写法由结构体定义和键值对初始化两部分组成,结构体定义时没有结构体类型名称只有字段和类型定义,键值对初始化部分由可选的多个键值对组成。
instance := struct{
Field Value
Field Value
...
}{
Field:Value,
Field:Value,
...
}
键值对初始化部分是可选的
instance := struct{
Field:Value
Field:Value
...
}
例如
package main
import "fmt"
func print(msg *struct{
code int
message string
}){
fmt.Printf("%T\n", msg)
}
func main() {
msg := &struct{
code int
message string
}{
1,
"success",
}
print(msg)
}
构造函数
Go语言没有提供构造函数相关的特殊机制,用户需要根据自己的需求,将参数使用函数传参到构造体参数的方式,来实现自己的构造函数。
Go语言的类型或结构体中没有构造函数,但可使用结构体初始化的过程来模拟实现构造函数。
常见构造函数的语法规则
- 面向对象编程中类可以添加构造函数,多个构造函数可使用函数重载来实现。
- 面向对象编程中构造函数名一般与类名同名且无返回值
- 构造函数可以拥有一个静态构造函数,用来调用父类的构造函数。
- C++语言中类具有默认的构造函数
...
package main
import "fmt"
type User struct {
Id int
Name string
}
func NewUser(id int, name string) *User{
return &User{Id:id, Name:name}
}
func main() {
user := NewUser(1, "admin")
fmt.Printf("user = %v\n", user)
}
类型嵌套
结构体可以包含一个或多个匿名或内嵌的字段,也就是说字段没有显式的名字,但字段的类型是必不可少的,这种情况下字段的名字也就是类型的名字。
- 匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌的结构体。
- 使用匿名字段内嵌结构体的方式可以和面向对象语言中的继承概念相比较,用来模拟类似继承的行为。
- Go语言中的继承是通过内嵌或组合的方式来实现的,但在Go语言中相比较于继承,组合会更受青睐。
例如:
package main
import "fmt"
type User struct {
int
Name string
}
type Admin struct {
UserName string
Password string
User
}
func main() {
admin := new(Admin)
admin.int = 1
admin.Name = "root"
admin.UserName = "administrator"
admin.Password = "root"
fmt.Printf("admin.int = %d\n", admin.int)//admin.int = 1
fmt.Printf("admin.User type is %f\n", admin.User)//admin.User type is {%!f(int=1) %!f(string=root)}
fmt.Printf("admin.User.Name = %s\n", admin.User.Name)//admin.User.Name = root
}
内嵌结构体
- 结构体本身是一种数据类型,因此可以作为一个匿名字段来使用,因为匿名字段只需要类型而无需字段名。
- 外层结构体可以通过
.
语法直接访问内层结构体中的字段,内嵌结构体甚至可以来自其他包。 - 内层结构体被简单地插入或内嵌到外层结构体中是继承机制的一种实现方式,实现另外一个或一些类型继承部分或全部。
内嵌结构体特性
- 内嵌结构体可以直接访问其成员变量
嵌入结构体的成员可以通过外部结构体的实例来直接访问。若结构体拥有多层嵌入结构体,结构体实力访问任意一级的嵌入结构体成员时只需要给出字段名,无需像传统结构体字段一样,通过一层层结构体字段来访问到最终的字段。 - 内嵌结构体的字段名也就是它的类型名
内嵌结构体字段仍然可以使用详细的字段进行一层层的访问,因为内嵌结构体的字段名也就是它的类型。
需要注意的是,一个结构体只能嵌入一个相同类型的成员,无须担心结构体重名和错误赋值的情况,编译器在发现可能的赋值歧义时会报错。
内嵌结构体初始化
结构体内嵌初始化时,只需要将结构体内嵌的类型作为字段名称,像普通结构体一样进行初始化即可。
例如:汽车是由轮子和引擎组成的,轮子拥有尺寸,引擎拥有功率。
package main
import "fmt"
type Wheel struct {
Size int
}
type Engine struct {
Power int
}
type Car struct {
Wheel
Engine
}
func main() {
car := Car{
Wheel:Wheel{Size:100},
Engine:Engine{Power:200},
}
fmt.Printf("%+v", car)//{Wheel:{Size:100} Engine:{Power:200}}
}
内嵌匿名结构体初始化
为了编码的便利性可以将结构体直接定义在嵌入的结构体内,结构体的定义不会被外部引用到。但当初始化被嵌入的结构体时,必须再次声明结构体才能赋予数据。
package main
import "fmt"
type Wheel struct {
Size int
}
type Car struct {
Engine struct{
Power int
}
Wheel
}
func main() {
car := Car{
Engine:struct{Power int}{Power:100},
Wheel:Wheel{Size:200},
}
fmt.Printf("%+v", car)//{Engine:{Power:100} Wheel:{Size:200}}
}
内嵌结构体成员命名冲突
嵌入结构体内部可能会拥有同名的成员,当成名重名时编译器会智能的提示可能发生歧义或错误。
package main
import "fmt"
type Role struct {
Id int
}
type Profile struct {
Id int
}
type User struct {
Role
Profile
}
func main() {
user := &User{}
user.Role.Id = 1
user.Profile.Id = 2
fmt.Printf("%+v", user)//&{Role:{Id:1} Profile:{Id:2}}
user.Id = 100//ambiguous selector user.Id
}
网友评论