嘿,各位Gopher(Go语言爱好者)们,今天咱们不聊风花雪月,来聊聊Go语言里如何给数据“组CP”——没错,就是那个让你又爱又恨的结构体(Struct)!
想象一下,你要描述一个程序员小哥。你有一堆信息:名字(字符串)、年龄(整型)、是否秃头(布尔型)……如果把这些变量都零散地定义,你的代码很快就会变成一锅“变量八宝粥”,找起来费劲,管理起来糟心。
这时候,结构体就像一位专业的“红娘”闪亮登场!它说:“别急,我把这些相关的数据打包成一个‘复合类型’,给它们一个名分,让它们成为一个和谐的整体!”
第一章:初识结构体——如何“撮合”数据?
说白了,结构体就是一个自定义的数据类型,它包含了一系列的字段(Field)。每个字段都有自己的类型和名字。
定义结构体,就像写一份“相亲简历”:
// 定义一个名为 Programmer 的结构体类型
type Programmer struct {
Name string
Age int
IsBald bool
Language []string // 会的编程语言,是个切片
}
看,多清晰!Programmer 这个类型内部有名字、年龄、秃头状态和技能列表。type ... struct 就是我们的“简历模板生成器”。
有了模板,接下来就是“发对象”了——创建结构体实例!
有几种方式可以创建,咱们一种一种来看:
方式一:最老实的“填表式”
// 创建一个 Programmer 类型的变量 p1
var p1 Programmer
p1.Name = "码农小明"
p1.Age = 28
p1.IsBald = true
p1.Language = []string{"Go", "Python", "Shell"}
fmt.Printf("码农小明:%+v\n", p1)
// 输出:码农小明:{Name:码农小明 Age:28 IsBald:true Language:[Go Python Shell]}
注意这里的 %+v,它能打印出结构体的字段名和值,非常适合调试!
方式二:一步到位的“简历直投式”
// 在声明时直接初始化所有字段
p2 := Programmer{
Name: "大神老张",
Age: 35,
IsBald: false, // 看,大神不秃!
Language: []string{"Go", "Rust", "Assembly"},
}
fmt.Printf("大神老张:%+v\n", p2)
方式三:不讲武德的“顺序赋值式”
// 按字段定义的顺序直接赋值,可以省略字段名
p3 := Programmer{"菜鸟小丽", 22, false, []string{"Go", "Java"}}
fmt.Printf("菜鸟小丽:%+v\n", p3)
这种方式虽然简洁,但一旦结构体字段顺序发生变化,代码就会出错,所以不推荐使用,除非你在写非常简单的测试代码。
第二章:结构体进阶玩法——“套娃”与“继承”
1. 匿名字段:玩一把“俄罗斯套娃”
有时候,一个结构体可以直接包含另一个结构体,而不给它字段名,这就是匿名字段。这有点像其他语言里的“继承”。
// 定义一个“技能包”结构体
type SkillSet struct {
Backend []string
Frontend []string
DevOps []string
}
// 定义一个“高级程序员”,他直接“拥有”了一个技能包(匿名字段)
type SeniorProgrammer struct {
Programmer // 匿名字段,直接嵌入了 Programmer 的所有字段
SkillSet // 再嵌入一个 SkillSet
Salary float64
}
func main() {
superman := SeniorProgrammer{}
superman.Name = "超级赛亚人" // 可以直接访问 Programmer 的字段!
superman.Age = 40
superman.Salary = 99999.99
superman.Backend = []string{"Go", "C++"} // 直接访问 SkillSet 的字段!
superman.Frontend = []string{"JavaScript"}
fmt.Printf("%+v\n", superman)
}
看,SeniorProgrammer 直接就把 Programmer 和 SkillSet 的字段都“吸收”了,可以直接访问。这极大地促进了代码的复用,让结构体之间的关系变得清晰。
2. 给结构体“绑定技能”——方法
在Go里,没有类的概念。但我们可以为结构体定义方法,也就是属于这个结构体的函数。
// 为 Programmer 结构体定义一个“工作”的方法
func (p Programmer) Work() {
fmt.Printf("我是%s,我正在用%s敲代码...\n", p.Name, p.Language)
}
// 可以为 SeniorProgrammer 也定义一个同名方法,实现“多态”
func (s SeniorProgrammer) Work() {
fmt.Printf("我是大佬%s,我正在设计系统架构,顺便指导别人用%s。\n", s.Name, s.Backend)
}
func main() {
p1 := Programmer{Name: "小明", Language: []string{"Go"}}
s1 := SeniorProgrammer{}
s1.Name = "老张"
s1.Backend = []string{"Go", "Java"}
p1.Work() // 输出:我是小明,我正在用[Go]敲代码...
s1.Work() // 输出:我是大佬老张,我正在设计系统架构,顺便指导别人用[Go Java]。
}
注意方法定义里的 (p Programmer) 或 (s SeniorProgrammer),这叫接收者,它说明了这个方法属于哪个结构体。
第三章:结构体与JSON——当好数据“翻译官”
在现代Web开发中,结构体和JSON的互转是家常便饭。Go通过标签(Tag) 让这个过程变得异常简单。
标签,就是结构体字段的“小纸条”注释。
// 为了序列化成JSON,我们定义一个带标签的结构体
type ProgrammerForJSON struct {
Name string `json:"name"` // 告诉JSON库,这个字段在JSON里叫 "name"
Age int `json:"age"`
IsBald bool `json:"is_bald"`
Language []string `json:"programming_languages"` // JSON里的字段名可以完全不同
}
func main() {
// 实例化一个对象
p := ProgrammerForJSON{
Name: "JSON大师",
Age: 30,
IsBald: true,
Language: []string{"Go", "JSON"},
}
// 序列化:Go结构体 -> JSON字符串
jsonData, err := json.Marshal(p)
if err != nil {
log.Fatal("序列化失败了:", err)
}
fmt.Println(string(jsonData))
// 输出:{"name":"JSON大师","age":30,"is_bald":true,"programming_languages":["Go","JSON"]}
// 反序列化:JSON字符串 -> Go结构体
jsonString := `{"name":"解码侠","age":25,"is_bald":false,"programming_languages":["Python"]}`
var p2 ProgrammerForJSON
err = json.Unmarshal([]byte(jsonString), &p2) // 注意这里要传指针 &p2
if err != nil {
log.Fatal("反序列化失败了:", err)
}
fmt.Printf("反序列化结果:%+v\n", p2)
// 输出:反序列化结果:{Name:解码侠 Age:25 IsBald:false Language:[Python]}
}
看到了吗?通过 json:"xxx" 这样的标签,我们轻松地实现了Go结构体和JSON数据之间的“翻译”。Marshal 是“编码”,Unmarshal 是“解码”,解码时需要传入目标结构体的指针,因为函数内部需要修改它的值。
第四章:完整实战示例——一个微型的程序员管理系统
光说不练假把式,最后我们来个综合的,把上面的知识点都串起来!
package main
import (
"encoding/json"
"fmt"
"log"
)
// 1. 定义核心结构体
type SkillSet struct {
Backend []string `json:"backend"`
Frontend []string `json:"frontend,omitempty"` // omitempty 表示如果为空,则不在JSON中输出此字段
}
type Programmer struct {
ID int `json:"id"`
Name string `json:"name"`
SkillSet SkillSet `json:"skill_set"`
}
// 2. 为Programmer绑定方法
// 介绍自己
func (p Programmer) Introduce() {
fmt.Printf("大家好,我是%d号程序员%s。", p.ID, p.Name)
if len(p.SkillSet.Backend) > 0 {
fmt.Printf(" 我擅长后端语言:%v。", p.SkillSet.Backend)
}
if len(p.SkillSet.Frontend) > 0 {
fmt.Printf(" 我也懂点前端:%v。", p.SkillSet.Frontend)
}
fmt.Println()
}
// 学习新技能
func (p *Programmer) LearnNewSkill(skill string, isBackend bool) {
if isBackend {
p.SkillSet.Backend = append(p.SkillSet.Backend, skill)
} else {
p.SkillSet.Frontend = append(p.SkillSet.Frontend, skill)
}
fmt.Printf("%s 学会了新技能:%s!\n", p.Name, skill)
}
func main() {
fmt.Println("===== 欢迎来到程序员管理系统 =====")
// 3. 创建两个程序员
programmer1 := Programmer{
ID: 1,
Name: "Go语言爱好者",
SkillSet: SkillSet{
Backend: []string{"Go", "Python"},
},
}
programmer2 := Programmer{
ID: 2,
Name: "全栈大神",
SkillSet: SkillSet{
Backend: []string{"Java", "Go"},
Frontend: []string{"JavaScript", "Vue"},
},
}
// 4. 让他们自我介绍
programmer1.Introduce()
programmer2.Introduce()
// 5. 学习新技能 (注意这里接收者是指针,所以能修改原对象)
programmer1.LearnNewSkill("Rust", true)
programmer2.LearnNewSkill("React", false)
// 再次介绍,看看变化
fmt.Println("\n----- 经过一段时间的学习后 -----")
programmer1.Introduce()
programmer2.Introduce()
// 6. 将程序员团队序列化为JSON
team := []Programmer{programmer1, programmer2}
jsonData, err := json.MarshalIndent(team, "", " ") // MarshalIndent 让JSON更美观
if err != nil {
log.Fatal("序列化团队数据失败:", err)
}
fmt.Println("\n----- 团队JSON数据 -----")
fmt.Println(string(jsonData))
}
运行这个程序,你会看到:
- 程序员的创建和初始化。
- 方法的调用(自我介绍)。
- 通过指针接收者方法修改结构体内容(学习新技能)。
- 最终将整个团队的数据漂亮地格式化成JSON输出。
结语
好啦,关于Go语言结构体的深度之旅就到这儿。结构体就像是Go世界里乐高积木,让你能把零散的数据组装成有意义的、强大的对象。从基础定义到高级的嵌入、方法、JSON处理,掌握了它,你就拿到了编写整洁、高效、易维护Go程序的金钥匙。
别再让你数据“散装”了,赶紧用结构体给它们“拉郎配”,组建你的数据天团吧!

被折叠的 条评论
为什么被折叠?



