Menu
CoddyTech

结构体嵌入基础

本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 35 课,共 107 课。

结构体嵌入是 Go 将一种结构体类型包含在另一种结构体中的机制。你无需使用字段名,只需指定类型,Go 就会将嵌入结构体的字段提升到外层结构体中。

这是基本语法:

type Address struct {
    City    string
    Country string
}

type Person struct {
    Name string
    Address  // 嵌入字段,没有字段名
}

将 Address 嵌入 Person 后,你可以直接通过外层结构体访问嵌入的字段:

func main() {
    p := Person{
        Name: "Alice",
        Address: Address{
            City:    "London",
            Country: "UK",
        },
    }
    
    fmt.Println(p.City)     // London:直接访问
    fmt.Println(p.Country)  // UK:直接访问
    fmt.Println(p.Address.City)  // 这样也可以
}

注意,p.City 无需显式写出 p.Address.City 就能正常使用。Go 会自动提升嵌入结构体的字段,使你可以像访问外层结构体的字段一样访问它们。需要时,你仍然可以通过嵌入类型的名称访问这些字段。

这与常规的嵌套结构体不同,访问内部字段时,你必须始终使用字段名。嵌入可以创建更扁平、更方便的 API,同时保留相关数据的逻辑分组。

challenge icon

挑战

简单

让我们构建一个使用结构体嵌入的车辆登记系统,创建清晰、有条理的数据模型。你会将联系信息与车辆详情分开,同时通过嵌入实现对所有字段的直接访问。

你会将代码分成两个文件:

  • vehicle.go:创建两个通过嵌入协同工作的结构体:
    • 一个包含 OwnerName 和 Phone 字段(两者都是字符串)的 ContactInfo 结构体
    • 一个嵌入 ContactInfo(不带字段名),并添加自己的 Make、Model 和 Year 字段的 Vehicle 结构体
    还要为 Vehicle 创建一个名为 Summary 的方法,返回包含所有车辆和联系信息的格式化字符串。
  • main.go:从输入中读取车辆和车主详情,创建一个嵌入了 ContactInfo 的 Vehicle 实例,并演示如何直接访问嵌入字段,以及通过嵌入类型名访问这些字段。最后打印车辆摘要。

输入将按以下格式提供:

  • 第 1 行:车主姓名
  • 第 2 行:电话号码
  • 第 3 行:车辆品牌
  • 第 4 行:车辆型号
  • 第 5 行:年份(整数)

你的 Summary 方法应返回以下格式的字符串:

[Year] [Make] [Model] - Owner: [OwnerName], Phone: [Phone]

在主文件中,通过打印以下内容来演示字段提升:

  1. 直接通过 Vehicle 访问的车主姓名(例如,v.OwnerName)
  2. 通过嵌入类型访问的电话号码(例如,v.ContactInfo.Phone)
  3. 通过你的 Summary 方法生成的完整摘要

例如,给定 Alice Smith、555-1234、Toyota、Camry 和 2023,你的输出应为:

Alice Smith
555-1234
2023 Toyota Camry - Owner: Alice Smith, Phone: 555-1234

注意,这两种访问方式都可行:通过字段提升直接访问,以及通过嵌入类型名显式访问。这让你可以灵活地处理嵌入的数据。

亲手试试

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 读取车主姓名
	ownerName, _ := reader.ReadString('\n')
	ownerName = strings.TrimSpace(ownerName)

	// 读取电话号码
	phone, _ := reader.ReadString('\n')
	phone = strings.TrimSpace(phone)

	// 读取车辆品牌
	make, _ := reader.ReadString('\n')
	make = strings.TrimSpace(make)

	// 读取车辆型号
	model, _ := reader.ReadString('\n')
	model = strings.TrimSpace(model)

	// 读取年份
	yearStr, _ := reader.ReadString('\n')
	yearStr = strings.TrimSpace(yearStr)
	year, _ := strconv.Atoi(yearStr)

	// TODO: 创建一个嵌入 ContactInfo 的 Vehicle 实例
	// 使用上面从输入中读取的值

	// TODO: 打印直接通过 Vehicle 访问的车主姓名(字段提升)
	// 示例:fmt.Println(v.OwnerName)

	// TODO: 打印通过嵌入类型名称访问的电话号码
	// 示例:fmt.Println(v.ContactInfo.Phone)

	// TODO: 使用 Summary 方法打印完整摘要
	// 示例:fmt.Println(v.Summary())

	// 实现答案后,删除这些代码行。
	_ = ownerName
	_ = phone
	_ = make
	_ = model
	_ = year
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器