第十六回:Go 语言结构体 (Struct)

秋香的完美档案
唐伯虎
唐伯虎: 华安我阅人无数,但秋香姐的美是立体的!
姓名、年龄、三围...哦不,是身高,这些信息如果用单独的变量存太乱了。
我要给她建一个专属档案!这就是结构体 (Struct)

📋 档案袋 (结构体定义)

结构体就是把一组相关的数据捆绑在一起,自定义一种新的类型。

type Maid struct {
    Name   string // 姓名
    Age    int    // 芳龄
    Skill  string // 特长
    Salary int    // 月钱
}

📝 填写档案 (初始化)

方式一:键值对 (推荐,清晰明了)

qiuxiang := Maid{
    Name:   "秋香",
    Age:    18,
    Skill:  "背唐诗",
    Salary: 50, // 结尾逗号不能少
}

方式二:值列表 (不推荐,容易乱)

p2 := Maid{"春夏", 20, "扫地", 30} // 必须按顺序写全,容易搞错

方式三:new 函数 (返回指针)

p3 := new(Maid) // p3 是 *Maid 类型 (指针)
p3.Name = "冬梅"  // Go 语法糖:自动处理指针,不需要写 (*p3).Name

🔒 豪门的规矩:可见性 (大小写)

在 Go 语言里,变量名首字母的大小写决定了它的可见性(权限)。

华夫人
华夫人: 听好了!华府的规矩:
1. 首字母大写 (Public):所有人都能看(比如 Name)。
2. 首字母小写 (Private):只有华府内部(当前包)能看,外面的人(其他包)不许看!
type Maid struct {
    Name   string // ✅ 公开:大家都知道她叫秋香
    age    int    // 🔒 私有:女孩子的年龄是秘密 (外部包无法访问)
}

🏷️ 贴标签 (Struct Tags)

唐伯虎要把秋香的档案寄给“皇宫选秀办”(JSON 序列化),需要给字段贴上特殊的标签。

import "encoding/json"

type Maid struct {
    // 后面反引号里的就是 Tag
    Name   string `json:"name"`   // 转成 JSON 时变成小写 "name"
    Age    int    `json:"age"`
    Skill  string `json:"-"`      // "-" 表示忽略这个字段,不告诉皇上
}

func main() {
    q := Maid{Name: "秋香", Age: 18, Skill: "武功"}
    
    // 把结构体变成 JSON 字符串
    data, _ := json.Marshal(q)
    fmt.Println(string(data)) 
    // 输出:{"name":"秋香","age":18}  (Skill 被隐藏了)
}

🏭 工厂模式 (构造函数)

Go 语言没有像 Java 那样的 constructor,但我们可以自己造一个工厂函数,通常以 New 开头。

func NewMaid(name string, age int) *Maid {
    return &Maid{
        Name: name,
        Age:  age,
        // 其他字段可以在这里设置默认值
        Skill: "全能",
        Salary: 100,
    }
}

func main() {
    // 像大少爷一样直接领人,不用自己填表
    q := NewMaid("秋香", 18)
    fmt.Println(q.Name)
}

🎭 临时演员 (匿名结构体)

有时候我们需要一个临时的数据结构,只用一次,不想专门定义一个类型。这就叫匿名结构体

func main() {
    // 定义并初始化一个匿名结构体
    // 场景:临时记录一下皇上的打赏
    reward := struct {
        Gold   int
        Silver int
    }{
        Gold:   100,
        Silver: 50,
    }
    
    fmt.Printf("皇上赏赐:金 %d, 银 %d\n", reward.Gold, reward.Silver)
}

⚖️ 真假秋香 (结构体比较)

如果两个结构体的字段类型都支持比较(如 int, string),那这两个结构体就可以直接用 == 比较。

q1 := Maid{Name: "秋香", Age: 18}
q2 := Maid{Name: "秋香", Age: 18}
q3 := Maid{Name: "石榴", Age: 20}

fmt.Println(q1 == q2) // true (完全一样)
fmt.Println(q1 == q3) // false

🥋 结构体方法 (Method):给骨架注入灵魂

光有属性还不够,秋香姐得会动啊!在 Go 语言里,我们可以给结构体绑定方法。这就像是给角色添加技能。

秋香
秋香: 华安,你看好了,这招叫“三笑留情”!
// (m Maid) 叫接收者 (Receiver),表示这个函数属于 Maid 类型
func (m Maid) Smile() {
    fmt.Printf("%s 微微一笑,很倾城。\n", m.Name)
}

func main() {
    qiuxiang := Maid{Name: "秋香"}
    qiuxiang.Smile() // 调用方法
}

⚠️ 关键:值接收者 vs 指针接收者

这和上一回的“指针”息息相关!

// 想要修改属性,必须用指针接收者 *Maid
func (m *Maid) Rename(newName string) {
    m.Name = newName
}

func main() {
    shiliu := Maid{Name: "石榴姐"}
    
    // 石榴姐想冒充秋香
    shiliu.Rename("秋香") 
    
    fmt.Println("现在她是:", shiliu.Name) // 输出:秋香
}

🎎 结构体嵌套:套娃模式 (组合)

Go 没有“继承”,但有“组合”。我们可以把一个结构体塞进另一个结构体里。

唐伯虎
唐伯虎: 我不仅仅是华安,我还是唐伯虎!我有“书童”的属性,也有“才子”的属性,这就是组合!
type Person struct {
    Name string
}

func (p *Person) SayHello() {
    fmt.Println("你好,我是 " + p.Name)
}

type Maid struct {
    Person // 匿名嵌入,Maid 直接拥有了 Person 的字段和方法
    Job    string
}

func main() {
    // 初始化时要指定内部结构体
    m := Maid{
        Person: Person{Name: "秋香"},
        Job:    "领班",
    }
    
    // 字段提升:可以直接访问 Name,就像 Maid 自己的一样
    fmt.Println(m.Name) // 输出:秋香
    
    // 方法提升:可以直接调用 Person 的方法
    m.SayHello() // 输出:你好,我是 秋香
    
    // 也可以通过内部结构体访问
    fmt.Println(m.Person.Name) 
}

🎯 练功房:秋香的技能

定义一个 Skill 方法,如果是 "秋香",输出 "念诗";如果是 "石榴姐",输出 "画妆"。

package main
import "fmt"

type Maid struct {
    Name string
}

// 填空:定义方法 ShowTalent
func (m ______) ShowTalent() {
    if m.Name == "秋香" {
        fmt.Println("念诗")
    } else {
        fmt.Println("画妆")
    }
}

func main() {
    q := Maid{Name: "秋香"}
    q.ShowTalent()
    
    s := Maid{Name: "石榴姐"}
    s.ShowTalent()
}

任务: 填补接收者定义。

答案: Maid*Maid 都可以,因为不需要修改值。通常只读用值接收者,修改用指针接收者。

func (m Maid) ShowTalent() { ... }