作者简介:
高科,先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C++,go等语言开发经验,mysql,mongo,redis等数据库,设计模式和网络库开发经验,对战棋类,回合制,moba类页游,手游有丰富的架构设计和开发经验。 (谢谢你的关注)
-------------------------------------------------------------------------------------------------------------------------------
在Go语言中,没有类的概念,但可以使用struct来定义自定义类型。struct是一种可以包含多个不同类型字段的复合数据类型。可以将它看作是一个数据结构,其中每个字段都有自己的类型和值。
本文相当于go的数据类型的一部分,在分享struct之前我有必要解释下类型别名和自定义类型的区别:
类型别名和自定义类型
自定义类型
在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型,Go语言中可以使用type关键字来定义自定义类型。
自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:
type RANKTYPE int32const (RANKTYPE_GLOBAL_DECORATE RANKTYPE = 0 //全服装扮排名RANKTYPE_PRIVANCE_DECORATE RANKTYPE = 1 //省级装扮排名RANKTYPE_FRIEND_DECORATE RANKTYPE = 2 //好友的装扮排名RANKTYPE_SEASON_SCORE RANKTYPE = 3 //赛季积分排行榜RANKTYPE_LOCAL_SCORE RANKTYPE = 4 //本地积分排行RANKTYPE_BIGSEASON_SCORE RANKTYPE = 5 //赛季积分世界排行榜RANKTYPE_ACTIVITY_SCORE RANKTYPE = 6 //活动积分排行RANKTYPE_CROSSSEASON_SCORE RANKTYPE = 7 //跨服美食积分排行 暂时不用,RANKTYPE_SCENCEPROCESS_SCORE RANKTYPE = 8 //场景进度跨服排行RANKTYPE_DECORATE_SCORE RANKTYPE = 9 //场景进度跨服排行RANKTYPE_HANDBOOK_SCORE RANKTYPE = 10 //场景图鉴跨服排行RANKTYPE_COSTUMDESIGNVOTE_SCORE RANKTYPE = 11 //装扮设计投票排行RANKTYPE_PANDFISH_SCORE RANKTYPE = 12 //钓鱼 池塘鱼的排行记录RANKTYPE_SCTREAMFISH_SCORE RANKTYPE = 13 //钓鱼 溪流鱼的排行记录RANKTYPE_MARINEFISH_SCORE RANKTYPE = 14 // 钓鱼 海洋鱼的排行记录RANKTYPE_SCOREFIISHING_SCORE RANKTYPE = 15 //钓鱼积分的排行记录
)
通过type关键字的定义,RANKTYPE就是一种新的类型,它具有int的特性。
类型别名
类型别名是Go1.9版本添加的新功能。
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
type TypeAlias = Type
我们之前见过的rune和byte就是类型别名,他们的定义如下:
type byte = uint8type rune = int32
类型定义和类型别名的区别
类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。
package mainimport "fmt"
//类型定义
type NewInt int//类型别名
type MyInt = intfunc main() {var a NewIntvar b MyIntfmt.Printf("type of a:%T\n", a) fmt.Printf("type of b:%T\n", b)
}
输出:
结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。
struct结构体
结构体的定义
使用type和struct关键字来定义结构体,具体代码格式如下:
type 类型名 struct {字段名 字段类型字段名 字段类型…}
其中:
1.类型名:标识自定义结构体的名称,在同一个包内不能重复。2.字段名:表示结构体字段名。结构体中的字段名必须唯一。3.字段类型:表示结构体字段的具体类型。
举个例子,我们定义一个游戏玩家的Player结构体,代码如下:
type Player struct {playerid stringname stringlevel intexp intlogintime int64registertime int64coin int32gem int32}
我们可以把具有相同数据类型的(比如name和playerid)的所有键都在一行上分组和定义:
type Player struct {playerid,name stringlevel ,exp int logintime,registertime int64coin ,gem int32}
这样我们就拥有了一个Player的自定义类型,它有playerid,name等字段。
结构体的tags
结构体的tags
是附加到字段中的元数据的小片段,为struct
使用该结构的其他 Go 代码提供指令。
例如:这里是名为的自定义类型,Employee
可以注释为
type Employee struct {FirstName string `json: "first_name"` LastName string `json: "last_name"`EmployeeID string `json: "employee_id"`Salary float64 `json: "salary"`
}
然后,Go 代码能够检查这些结构并提取分配给它请求的特定键的值。如果没有其他代码检查结构标记,则结构标记不会影响代码的操作。
如果我们正在读取YAML
或JSON
文件,那么我们可以注释struct
这样的内容
type Employee struct {FirstName string `yaml: "first_name"` LastName string `yaml: "last_name"`EmployeeID string `yaml: "employee_id"`Salary float64 `yaml: "salary"`
}
type Manager struct {ManagerFirstName string `json: "manager_first_name"` ManagerLastName string `json: "manager_last_name"`ManagerEmployeeID string `json: "manager_employee_id"`ManagerSalary float64 `json: "manager_salary"`
}
下面的代码读取文件YAML
并将文件中的值分配YAML
给变量
var mgr Manager
f, err := os.Open("manager_list.json")if err != nil {log.Fatalf("os.Open() failed with '%s'\n", err)}defer func(f *os.File) {err := f.Close()if err != nil {
}}(f)
mrgObj := yaml.NewDecoder(f)err = mrgObj.Decode(&mgr)if err != nil {log.Fatalf("dec.Decode() failed with '%s'\n", err)}
fmt.Println("%s %s employ_id is %s", mgr.FirstName, mgr.LastName, mgr.EmployeeID)
标准库中的JSON 编码器使用结构标记作为注释,向编码器指示您希望如何命名JSON输出中的字段。这些JSON编码和解码机制可以在encoding/json
包中找到。
现在假设您有一个空的 JSON 字段,您想要消除它,那么您可以使用它omitempty
,如果JSON对象没有该键的值,则不会填充和跳过它
type Manager struct {ManagerFirstName string `json: "manager_first_name"` ManagerLastName string `json: "manager_last_name"`ManagerEmployeeID string `json: "manager_employee_id"`ManagerSalary float64 `json: "manager_salary,omitempty"`
}
如果你想忽略某些字段,那么你可以使用-
in tags
,它将被忽略
type Manager struct {ManagerFirstName string `json: "manager_first_name"` ManagerLastName string `json: "manager_last_name"`ManagerEmployeeID string `json: "manager_employee_id"`ManagerSalary float64 `json: "-"`
}
如果您想更深入地访问,tags
那么您可以使用允许运行时反射的反射包
使用它tag
可以让您更轻松地导航存储数据及其表示形式。您可以使用go-playground/validator,它提供了更多有关tags
.有些能力就像
- 字段之间的比较
- 领域之间的调节
- 管理字段之间的依赖关系等等……
例如:下面的示例展示了如何使用go-playground/validator来验证字段,而无需编写任何额外的代码。
type Manager struct {ManagerFirstName string `json:"manager_first_name" validate:"required"` ManagerLastName string `json:"manager_last_name" validate:"required_if=ManagerFirstName"`ManagerEmployeeID string `json:"manager_employee_id" validate:"required, gte=1000,lt=10000"`ManagerSalary float64 `json: "manager_salary,omitempty"`
}
在上面的例子中我能够validate
遵循
ManagerFirstName
是required
字段ManagerLastName
isrequired_if
字段ManagerFirstName
已提供ManagerEmployeeID
是required
字段并且不能小于1000
因此,我们可以使用go-playground/validator,而不是为某些基本和条件验证编写数据验证代码,因为它们具有相同的内置逻辑,完全基于tags
只有当结构体实例化时,才会真正地分配
结构体实例化
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。
var 结构体实例 结构体类型
基本实例化
我们通过var p1 Player的方式来实例化一个Player结构体,并通过.操作来访问或者修改其成员变量
package mainimport ("fmt"
)
type Player struct {Playerid string `json: "playerid"` Name string `json: "name"` Level int `json: "level"` Exp int `json: "exp"` Logintime int64 `json: "logintime"` Registertime int64 `json: "registertime"` Coin int32 `json: "coin"` Gem int32 `json: "gem"`
} func main() {var p1 Playerp1.Playerid = "12222222"p1.Name = "高科"p1.Level = 100fmt.Printf("p1=%v\n", p1) //p1={12222222 高科 100 0 0 0 0 0}fmt.Printf("p1=%#v\n", p1) //p1=main.Player{Playerid:"12222222", Name:"高科", Level:100, Exp:0, Logintime:0, Registertime:0, Coin:0, Gem:0}
}
或者你可以直接在实例化的同时进行初始化操作,所以下面的初始化方式都可以:
var p1 = Player{playerid:"12222222",Name:"高科",Level: 100, Exp:20, Logintime: 1711296000, Registertime:1701296000, Coin:100000, Gem:999999}
p2 := Player{playerid:"12222223",Name:"高科2",Level: 100, Exp:20, Logintime: 1711296000, Registertime:1701296000, Coin:100000, Gem:999999}
匿名结构体
在定义一些临时数据结构等场景下还可以使用匿名结构体。
package mainimport ("fmt"
)
func main() {var p1 struct{playerid string `json: "playerid"` Name string `json: "name"` Level int `json: "level"` Exp int `json: "exp"` Logintime int64 `json: "logintime"` Registertime int64 `json: "registertime"` Coin int32 `json: "coin"` Gem int32 `json: "gem"` } p1.playerid = "12222222"p1.Name = "高科"p1.Level = 100fmt.Printf("p1=%v\n", p1) //p1={12222222 高科 100 0 0 0 0 0}fmt.Printf("p1=%#v\n", p1) //p1=main.Player{Playerid:"12222222", Name:"高科", Level:100, Exp:0, Logintime:0, Registertime:0, Coin:0, Gem:0}
}
创建指针类型结构体
我们还可以通过使用new关键字或者&对结构体进行实例化,得到的是结构体的地址。 格式如下:
var 变量名 new(struct类型)
package mainimport ("fmt"
)
type Player struct {playerid string `json: "playerid"` Name string `json: "name"` Level int `json: "level"` Exp int `json: "exp"` Logintime int64 `json: "logintime"` Registertime int64 `json: "registertime"` Coin int32 `json: "coin"` Gem int32 `json: "gem"`
} func main() {var p1 = new(Player) var p2 = &Player{} fmt.Printf("p1=%v ,p2=%v \n", p1,p2) //p1=&{ 0 0 0 0 0 0} fmt.Printf("p1=%#v ,p2=%#v \n", p1,p2) //p1=&main.Player{playerid:"", Name:"", Level:0, Exp:0, Logintime:0, Registertime:0, Coin:0, Gem:0}
}