反射基础
本课属于 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 映射或构建通用工具等无法在编译时确定类型的场景中,才少量使用反射。
挑战
简单让我们构建一个使用反射检查任意结构体并报告其结构的结构体检查器!这是反射的一种实用用法:创建能处理编译时未知类型的工具。
你将把代码分成两个文件:
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,并打印结果
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器