Menu
CoddyTech

反射基础

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

反射允许程序在运行时检查和操作自身结构。Go 的 reflect 包提供了这种能力,让你能够检查类型、读取结构体字段,并动态调用方法。

反射中的两种核心类型是 reflect.Type 和 reflect.Value。你可以使用 reflect.TypeOf() 和 reflect.ValueOf() 获取它们:

import "reflect"

type Person struct {
    Name string
    Age  int
}

p := Person{Name: "Alice", Age: 30}

t := reflect.TypeOf(p)
v := reflect.ValueOf(p)

fmt.Println(t.Name())       // Person
fmt.Println(t.Kind())       // struct
fmt.Println(v.NumField())   // 2

你可以遍历结构体字段,查看它们的名称、类型和值:

for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    value := v.Field(i)
    fmt.Printf("%s: %v\n", field.Name, value)
}
// 输出:
// Name: Alice
// Age: 30

反射功能强大,但也有取舍:它会绕过编译时类型检查,运行速度比直接访问慢,也会让代码更难理解。只有在 JSON 编码、ORM 映射或构建通用工具等无法在编译时确定类型的场景中,才少量使用反射。

challenge icon

挑战

简单

让我们构建一个使用反射检查任意结构体并报告其结构的结构体检查器!这是反射的一种实用用法:创建能处理编译时未知类型的工具。

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

  • inspector.go:创建基于反射的检查工具。

    编写一个函数 InspectStruct(v any) string,使用 reflect 包检查传入的任意结构体。你的函数应返回一份格式化报告,其中包含:

    • 结构体的类型名称
    • 结构体的种类(应为 "struct")
    • 字段数量
    • 每个字段的名称、类型和当前值

    输出格式应为:

    Type: [TypeName]
    Kind: struct
    Fields: [count]
    - [FieldName] ([FieldType]): [Value]
    - [FieldName] ([FieldType]): [Value]
    ...

    使用 reflect.TypeOf() 获取类型信息,并使用 reflect.ValueOf() 访问字段值。使用 NumField() 遍历字段;使用类型上的 Field(i) 获取元数据,并使用值上的 Field(i) 获取实际值。

  • main.go:定义示例结构体并使用检查器。

    读取结构体类型(product 或 employee)及其字段值,然后创建相应的结构体并检查它。

    对于 product:创建一个 Product 结构体,包含 Name(字符串)、Price(float64)和 InStock(布尔值)字段。读取名称、价格和库存状态。

    对于 employee:创建一个 Employee 结构体,包含 ID(整数)、Name(字符串)和 Department(字符串)字段。读取 ID、名称和部门。

    将创建的结构体传给 InspectStruct,并打印结果。

输入内容如下:

  • 第 1 行:结构体类型(product 或 employee)
  • 后续各行:该结构体类型的字段值

例如,给定:

product
Laptop
999.99
true

输出应为:

Type: Product
Kind: struct
Fields: 3
- Name (string): Laptop
- Price (float64): 999.99
- InStock (bool): true

再例如,给定:

employee
42
Alice Johnson
Engineering

输出应为:

Type: Employee
Kind: struct
Fields: 3
- ID (int): 42
- Name (string): Alice Johnson
- Department (string): Engineering

再例如,给定:

product
Headphones
79.50
false

输出应为:

Type: Product
Kind: struct
Fields: 3
- Name (string): Headphones
- Price (float64): 79.5
- InStock (bool): false

注意,你的检查器无需预先了解结构,就能处理不同类型的结构体。这就是反射的强大之处!同一个 InspectStruct 函数可以动态处理 Product 和 Employee 结构体。

亲手试试

package main

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

// Product 结构体,包含 Name、Price 和 InStock 字段
type Product struct {
	Name    string
	Price   float64
	InStock bool
}

// Employee 结构体,包含 ID、Name 和 Department 字段
type Employee struct {
	ID         int
	Name       string
	Department string
}

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

	// 读取结构体类型
	structType, _ := reader.ReadString('\n')
	structType = strings.TrimSpace(structType)

	if structType == "product" {
		// 读取产品字段
		name, _ := reader.ReadString('\n')
		name = strings.TrimSpace(name)

		priceStr, _ := reader.ReadString('\n')
		priceStr = strings.TrimSpace(priceStr)
		price, _ := strconv.ParseFloat(priceStr, 64)

		inStockStr, _ := reader.ReadString('\n')
		inStockStr = strings.TrimSpace(inStockStr)
		inStock, _ := strconv.ParseBool(inStockStr)

		// TODO: 用读取到的值创建一个 Product 结构体
		// TODO: 将产品传给 InspectStruct,并打印结果

	} else if structType == "employee" {
		// 读取员工字段
		idStr, _ := reader.ReadString('\n')
		idStr = strings.TrimSpace(idStr)
		id, _ := strconv.Atoi(idStr)

		name, _ := reader.ReadString('\n')
		name = strings.TrimSpace(name)

		department, _ := reader.ReadString('\n')
		department = strings.TrimSpace(department)

		// TODO: 用读取到的值创建一个 Employee 结构体
		// TODO: 将员工传给 InspectStruct,并打印结果

	}
}
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Go 在线编译器