结构体
定义结构体
package main
import "fmt"
// 定义结构体
type Person struct {
Name string
Age int
Email string
}
func main() {
// 创建结构体实例
// 方式1:按字段顺序
p1 := Person{"张三", 25, "zhangsan@example.com"}
// 方式2:按字段名(推荐)
p2 := Person{
Name: "李四",
Age: 30,
Email: "lisi@example.com",
}
// 方式3:使用 new 关键字
p3 := new(Person)
p3.Name = "王五"
p3.Age = 28
p3.Email = "wangwu@example.com"
fmt.Println("p1:", p1)
fmt.Println("p2:", p2)
fmt.Println("p3:", p3)
}
访问结构体字段
package main
import "fmt"
type Person struct {
Name string
Age int
Email string
}
func main() {
p := Person{Name: "张三", Age: 25}
// 访问字段
fmt.Println("姓名:", p.Name)
fmt.Println("年龄:", p.Age)
// 修改字段
p.Age = 26
fmt.Println("新年龄:", p.Age)
// 使用指针
ptr := &p
ptr.Name = "李四"
fmt.Println("新姓名:", p.Name)
}
结构体嵌套
package main
import "fmt"
// 地址结构体
type Address struct {
City string
Street string
ZipCode string
}
// 用户结构体
type User struct {
Name string
Age int
Address // 嵌套结构体
}
func main() {
user := User{
Name: "张三",
Age: 25,
Address: Address{
City: "北京",
Street: "长安街",
ZipCode: "100000",
},
}
fmt.Println("用户:", user.Name)
fmt.Println("城市:", user.Address.City)
fmt.Println("街道:", user.Address.Street)
}
匿名嵌套(提升字段)
package main
import "fmt"
type Point struct {
X, Y int
}
type Circle struct {
Point // 匿名嵌套
Radius int
}
func main() {
c := Circle{
Point: Point{X: 10, Y: 20},
Radius: 5,
}
// 可以直接访问提升的字段
fmt.Println("X:", c.X)
fmt.Println("Y:", c.Y)
fmt.Println("半径:", c.Radius)
// 也可以通过嵌套类型访问
fmt.Println("Point.X:", c.Point.X)
}
结构体方法
package main
import "fmt"
type Rectangle struct {
Width float64
Height float64
}
// 值接收者方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 值接收者方法(不修改原结构体)
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// 指针接收者方法(可以修改原结构体)
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func main() {
rect := Rectangle{Width: 3, Height: 4}
fmt.Printf("面积: %.2f\n", rect.Area())
fmt.Printf("周长: %.2f\n", rect.Perimeter())
rect.Scale(2)
fmt.Printf("缩放后面积: %.2f\n", rect.Area())
}
接口
package main
import "fmt"
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14159 * c.Radius
}
func printShapeInfo(s Shape) {
fmt.Printf("面积: %.2f, 周长: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
rect := Rectangle{Width: 3, Height: 4}
circle := Circle{Radius: 5}
printShapeInfo(rect)
printShapeInfo(circle)
}
空接口
package main
import "fmt"
// 空接口可以接受任何类型
func printAnything(value interface{}) {
fmt.Println("值:", value)
// 类型断言
if str, ok := value.(string); ok {
fmt.Println("是字符串,长度:", len(str))
} else if num, ok := value.(int); ok {
fmt.Println("是整数,值:", num)
}
}
// 类型 switch
func checkType(value interface{}) {
switch v := value.(type) {
case string:
fmt.Println("字符串:", v)
case int:
fmt.Println("整数:", v)
case bool:
fmt.Println("布尔值:", v)
default:
fmt.Println("未知类型")
}
}
func main() {
printAnything("Hello")
printAnything(42)
printAnything(3.14)
checkType("Go")
checkType(100)
checkType(true)
}
Interface 的内部实现原理
1. Interface 的内部结构:
Go 的接口在运行时使用 eface(空接口)和 iface(非空接口)两种结构:
// 空接口 (interface{}) 的内部结构
type eface struct {
_type *_type // 指向类型信息的指针
data unsafe.Pointer // 指向数据的指针
}
// 非空接口的内部结构
type iface struct {
tab *itab // 指向方法表的指针
data unsafe.Pointer // 指向数据的指针
}
// 方法表结构
type itab struct {
inter *interfacetype // 接口类型信息
_type *_type // 具体类型信息
hash uint32 // 类型哈希值
_ [4]byte // 填充
fun [1]uintptr // 方法指针数组(可变长)
}
2. 类型信息结构:
type _type struct {
size uintptr // 类型大小
ptrdata uintptr // 包含指针的字节数
hash uint32 // 类型哈希值
tflag tflag // 类型标志
align uint8 // 对齐
fieldalign uint8 // 字段对齐
kind uint8 // 类型种类
alg *typeAlg // 算法表(hash、equal)
gcdata *byte // GC 数据
str nameOff // 类型名称
ptrToThis typeOff // 指向此类型的指针类型
}
3. 接口赋值过程:
package main
import "fmt"
type Shape interface {
Area() float64
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func main() {
rect := Rectangle{Width: 3, Height: 4}
// 赋值给接口时的内部过程:
// 1. 创建 itab 结构
// 2. 填充接口类型信息 (inter = Shape)
// 3. 填充具体类型信息 (_type = Rectangle)
// 4. 填充方法指针 (fun[0] = Rectangle.Area)
// 5. 设置 data 指向 rect
var s Shape = rect
// 调用方法时的内部过程:
// 1. 通过 s.tab.fun[0] 找到 Rectangle.Area
// 2. 将 s.data 作为接收者传递
fmt.Println("面积:", s.Area())
}
4. 类型断言原理:
package main
import "fmt"
type Writer interface {
Write([]byte) (int, error)
}
type File struct {
name string
}
func (f *File) Write(data []byte) (int, error) {
fmt.Println("写入文件:", f.name)
return len(data), nil
}
func main() {
var w Writer = &File{"test.txt"}
// 类型断言的内部过程:
// 1. 检查 w.tab._type 是否等于 *File
// 2. 如果相等,返回 w.data
// 3. 如果不相等,检查 *File 是否实现了 Writer
if file, ok := w.(*File); ok {
fmt.Println("类型断言成功:", file.name)
}
}
5. 接口比较:
package main
import "fmt"
func main() {
// 空接口比较
var a, b interface{} = 1, 1
fmt.Println("a == b:", a == b) // true
// 非空接口比较
type I interface {
M()
}
type T struct { x int }
func (t T) M() {}
var i1, i2 I = T{1}, T{1}
fmt.Println("i1 == i2:", i1 == i2) // true
// 比较原理:
// 1. 比较 tab 指针(类型是否相同)
// 2. 比较 data 指向的值
}
6. 动态分派(Dynamic Dispatch):
package main
import "fmt"
type Writer interface {
Write([]byte) (int, error)
}
type Buffer struct {
data []byte
}
func (b *Buffer) Write(p []byte) (int, error) {
b.data = append(b.data, p...)
return len(p), nil
}
type File struct {
name string
}
func (f *File) Write(p []byte) (int, error) {
fmt.Printf("写入到文件 %s: %s\n", f.name, p)
return len(p), nil
}
// 动态分派:运行时决定调用哪个方法
func writeData(w Writer, data []byte) {
w.Write(data)
}
func main() {
var w Writer
// 动态选择实现
w = &Buffer{}
w.Write([]byte("hello"))
w = &File{"test.txt"}
w.Write([]byte("world"))
}
7. 接口转换(Interface Conversion):
package main
import "fmt"
type Reader interface {
Read() string
}
type Writer interface {
Write(string)
}
type ReadWriter interface {
Reader
Writer
}
type MyStruct struct {
data string
}
func (m *MyStruct) Read() string {
return m.data
}
func (m *MyStruct) Write(s string) {
m.data = s
}
func main() {
m := &MyStruct{data: "hello"}
// 接口转换:从具体类型到接口
var rw ReadWriter = m
// 接口转换:从大接口到小接口
var r Reader = rw
fmt.Println("读取:", r.Read())
// 接口转换:从小接口到大接口(需要类型断言)
if rw2, ok := r.(ReadWriter); ok {
rw2.Write("world")
}
}
8. nil 接口的特殊性:
package main
import "fmt"
type Error interface {
Error() string
}
type MyError struct {
msg string
}
func (e *MyError) Error() string {
return e.msg
}
func main() {
// 1. 完全的 nil 接口
var err1 interface{} = nil
fmt.Println("err1 == nil:", err1 == nil) // true
// 2. 包含 nil 指针的接口(不是 nil)
var err2 Error = (*MyError)(nil)
fmt.Println("err2 == nil:", err2 == nil) // false
fmt.Println("err2 != nil 但 err2.(*MyError) == nil:", err2.(*MyError) == nil)
// 3. nil 接口调用方法会 panic
var err3 interface{} = nil
// err3.(Error).Error() // panic
// 4. 安全检查
if e, ok := err2.(Error); ok {
fmt.Println("err2.Error():", e.Error())
}
}
9. 接口与指针(值接收者 vs 指针接收者):
package main
import "fmt"
type Counter interface {
Increment()
Get() int
}
type ValueCounter struct {
value int
}
// 值接收者
func (v ValueCounter) Increment() {
v.value++ // 不会修改原值
}
func (v ValueCounter) Get() int {
return v.value
}
type PointerCounter struct {
value int
}
// 指针接收者
func (p *PointerCounter) Increment() {
p.value++ // 会修改原值
}
func (p *PointerCounter) Get() int {
return p.value
}
func main() {
// 值接收者:值和指针都可以赋值给接口
v1 := ValueCounter{value: 0}
var c1 Counter = v1
c1.Increment()
fmt.Println("v1.value:", v1.value) // 0(未修改)
v2 := &ValueCounter{value: 0}
var c2 Counter = v2
c2.Increment()
fmt.Println("v2.value:", v2.value) // 0(未修改)
// 指针接收者:只有指针可以赋值给接口
p := &PointerCounter{value: 0}
var c3 Counter = p
c3.Increment()
fmt.Println("p.value:", p.value) // 1(已修改)
// var c4 Counter = PointerCounter{value: 0} // 编译错误
}
10. 接口性能优化:
- 避免频繁类型断言:缓存类型断言结果
- 使用小接口:接口方法越少,性能越好
- 避免接口嵌套:减少间接调用层级
- 减少接口转换:避免不必要的接口类型转换
- 使用具体类型:在性能关键路径上使用具体类型而非接口
Embed(嵌入)库使用
1. 结构体嵌入(Struct Embedding):
package main
import "fmt"
// 基础结构体
type Animal struct {
Name string
Age int
}
func (a Animal) Speak() {
fmt.Printf("%s 发出声音\n", a.Name)
}
func (a Animal) Eat() {
fmt.Printf("%s 在吃东西\n", a.Name)
}
// 嵌入 Animal
type Dog struct {
Animal // 嵌入结构体
Breed string
}
// Dog 特有的方法
func (d Dog) Bark() {
fmt.Printf("%s 汪汪叫!\n", d.Name)
}
type Cat struct {
Animal // 嵌入结构体
Color string
}
func (c Cat) Meow() {
fmt.Printf("%s 喵喵叫!\n", c.Name)
}
func main() {
dog := Dog{
Animal: Animal{Name: "旺财", Age: 3},
Breed: "金毛",
}
// 可以直接调用嵌入结构体的方法
dog.Speak() // 旺财 发出声音
dog.Eat() // 旺财 在吃东西
dog.Bark() // 旺财 汪汪叫!
// 可以直接访问嵌入结构体的字段
fmt.Printf("%s 今年 %d 岁\n", dog.Name, dog.Age)
cat := Cat{
Animal: Animal{Name: "咪咪", Age: 2},
Color: "白色",
}
cat.Speak()
cat.Meow()
}
2. 接口嵌入(Interface Embedding):
package main
import "fmt"
// 基础接口
type Reader interface {
Read([]byte) (int, error)
}
type Writer interface {
Write([]byte) (int, error)
}
// 嵌入接口
type ReadWriter interface {
Reader // 嵌入 Reader 接口
Writer // 嵌入 Writer 接口
Close() error // 额外的方法
}
type File struct {
name string
}
func (f *File) Read(p []byte) (int, error) {
fmt.Println("读取文件:", f.name)
return len(p), nil
}
func (f *File) Write(p []byte) (int, error) {
fmt.Println("写入文件:", f.name)
return len(p), nil
}
func (f *File) Close() error {
fmt.Println("关闭文件:", f.name)
return nil
}
func main() {
file := &File{"data.txt"}
// File 实现了 ReadWriter 接口
var rw ReadWriter = file
rw.Read(nil)
rw.Write(nil)
rw.Close()
}
3. 多重嵌入:
package main
import "fmt"
type Base1 struct {
Field1 string
}
func (b Base1) Method1() {
fmt.Println("Base1.Method1")
}
type Base2 struct {
Field2 int
}
func (b Base2) Method2() {
fmt.Println("Base2.Method2")
}
type Derived struct {
Base1 // 嵌入 Base1
Base2 // 嵌入 Base2
Field3 bool
}
func (d Derived) Method3() {
fmt.Println("Derived.Method3")
}
func main() {
d := Derived{
Base1: Base1{Field1: "value1"},
Base2: Base2{Field2: 42},
Field3: true,
}
d.Method1() // 来自 Base1
d.Method2() // 来自 Base2
d.Method3() // 来自 Derived
fmt.Println(d.Field1, d.Field2, d.Field3)
}
4. 嵌入与覆盖:
package main
import "fmt"
type Base struct {
Name string
}
func (b Base) Greet() {
fmt.Println("Hello from Base:", b.Name)
}
type Derived struct {
Base
Name string // 覆盖 Base.Name
}
// 覆盖 Base.Greet
func (d Derived) Greet() {
fmt.Println("Hello from Derived:", d.Name)
// 调用被覆盖的方法
d.Base.Greet()
}
func main() {
d := Derived{
Base: Base{Name: "BaseName"},
Name: "DerivedName",
}
fmt.Println("d.Name:", d.Name) // DerivedName
fmt.Println("d.Base.Name:", d.Base.Name) // BaseName
d.Greet() // 调用 Derived.Greet
}
5. 嵌入的注意事项:
- 命名冲突:如果嵌入的多个结构体有相同的字段名,需要明确指定
- 方法覆盖:外层结构体的方法会覆盖嵌入结构体的同名方法
- 零值问题:嵌入的结构体字段会被初始化为零值
- 序列化:嵌入结构体的字段会被平铺到外层结构体
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type Person struct {
Name string `json:"name"`
Address // 嵌入 Address
}
func main() {
p := Person{
Name: "张三",
Address: Address{
City: "北京",
Country: "中国",
},
}
// JSON 序列化时,Address 的字段会被平铺
data, _ := json.Marshal(p)
fmt.Println(string(data))
// 输出: {"name":"张三","city":"北京","country":"中国"}
}
6. 嵌入与组合(Composition):
package main
import "fmt"
// 基础组件
type Engine struct {
Power int
}
func (e *Engine) Start() {
fmt.Printf("引擎启动,功率: %d\n", e.Power)
}
type Wheels struct {
Count int
}
func (w *Wheels) Rotate() {
fmt.Printf("%d 个轮子转动\n", w.Count)
}
// 通过嵌入组合成汽车
type Car struct {
Engine
Wheels
Brand string
}
func (c Car) Drive() {
c.Start()
c.Rotate()
fmt.Printf("%s 汽车行驶中\n", c.Brand)
}
func main() {
car := Car{
Engine: Engine{Power: 200},
Wheels: Wheels{Count: 4},
Brand: "特斯拉",
}
car.Drive()
}
7. 嵌入与接口(Interface Embedding):
package main
import "fmt"
// 基础接口
type Reader interface {
Read([]byte) (int, error)
}
type Writer interface {
Write([]byte) (int, error)
}
type Closer interface {
Close() error
}
// 组合多个接口
type ReadWriteCloser interface {
Reader
Writer
Closer
}
// 嵌入接口实现
type MyFile struct {
name string
data []byte
}
func (f *MyFile) Read(p []byte) (int, error) {
fmt.Println("读取文件:", f.name)
return len(p), nil
}
func (f *MyFile) Write(p []byte) (int, error) {
f.data = append(f.data, p...)
fmt.Println("写入文件:", f.name)
return len(p), nil
}
func (f *MyFile) Close() error {
fmt.Println("关闭文件:", f.name)
return nil
}
func main() {
file := &MyFile{name: "test.txt"}
// MyFile 自动实现了 ReadWriteCloser 接口
var rwc ReadWriteCloser = file
rwc.Write([]byte("hello"))
rwc.Read(nil)
rwc.Close()
}
8. 嵌入与并发(Concurrency):
package main
import (
"fmt"
"sync"
"time"
)
// 基础缓存结构
type Cache struct {
data map[string]string
}
func (c *Cache) Get(key string) (string, bool) {
val, ok := c.data[key]
return val, ok
}
func (c *Cache) Set(key, value string) {
c.data[key] = value
}
// 嵌入并添加并发安全
type SafeCache struct {
Cache
mu sync.RWMutex
}
func (sc *SafeCache) Get(key string) (string, bool) {
sc.mu.RLock()
defer sc.mu.RUnlock()
return sc.Cache.Get(key)
}
func (sc *SafeCache) Set(key, value string) {
sc.mu.Lock()
defer sc.mu.Unlock()
sc.Cache.Set(key, value)
}
func main() {
cache := &SafeCache{
Cache: Cache{data: make(map[string]string)},
}
// 并发测试
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
key := fmt.Sprintf("key%d", n)
value := fmt.Sprintf("value%d", n)
cache.Set(key, value)
fmt.Printf("设置: %s = %s\n", key, value)
}(i)
}
wg.Wait()
// 读取数据
for i := 0; i < 10; i++ {
key := fmt.Sprintf("key%d", i)
if val, ok := cache.Get(key); ok {
fmt.Printf("读取: %s = %s\n", key, val)
}
}
}
9. 嵌入与测试(Testing):
package main
import "fmt"
// 数据库接口
type Database interface {
Query(sql string) ([]string, error)
Execute(sql string) error
}
// 真实数据库实现
type RealDB struct {
connectionString string
}
func (db *RealDB) Query(sql string) ([]string, error) {
fmt.Printf("执行查询: %s\n", sql)
return []string{"result1", "result2"}, nil
}
func (db *RealDB) Execute(sql string) error {
fmt.Printf("执行SQL: %s\n", sql)
return nil
}
// 测试用的模拟数据库
type MockDB struct {
queries []string
results [][]string
}
func (m *MockDB) Query(sql string) ([]string, error) {
m.queries = append(m.queries, sql)
return m.results[len(m.results)-1], nil
}
func (m *MockDB) Execute(sql string) error {
m.queries = append(m.queries, sql)
return nil
}
func (m *MockDB) GetQueries() []string {
return m.queries
}
// 使用嵌入的服务
type UserService struct {
Database
}
func (s *UserService) GetUsers() ([]string, error) {
return s.Query("SELECT * FROM users")
}
func (s *UserService) CreateUser(name string) error {
sql := fmt.Sprintf("INSERT INTO users (name) VALUES ('%s')", name)
return s.Execute(sql)
}
func main() {
// 使用真实数据库
realDB := &RealDB{connectionString: "localhost:3306"}
realService := &UserService{Database: realDB}
realService.CreateUser("张三")
users, _ := realService.GetUsers()
fmt.Println("用户列表:", users)
// 使用模拟数据库(测试场景)
mockDB := &MockDB{
results: [][]string{{"user1", "user2"}},
}
mockService := &UserService{Database: mockDB}
mockService.CreateUser("李四")
mockUsers, _ := mockService.GetUsers()
fmt.Println("模拟用户列表:", mockUsers)
fmt.Println("执行的查询:", mockDB.GetQueries())
}
10. 嵌入与中间件(Middleware):
package main
import "fmt"
// 基础处理器
type Handler interface {
Handle(string) string
}
type BaseHandler struct{}
func (h BaseHandler) Handle(request string) string {
return "处理: " + request
}
// 日志中间件
type LoggingMiddleware struct {
Handler
}
func (l *LoggingMiddleware) Handle(request string) string {
fmt.Printf("日志: 收到请求 - %s\n", request)
result := l.Handler.Handle(request)
fmt.Printf("日志: 返回结果 - %s\n", result)
return result
}
// 认证中间件
type AuthMiddleware struct {
Handler
token string
}
func (a *AuthMiddleware) Handle(request string) string {
fmt.Printf("认证: 验证令牌\n")
return a.Handler.Handle(request)
}
func main() {
// 组合中间件
base := BaseHandler{}
withLogging := &LoggingMiddleware{Handler: base}
withAuth := &AuthMiddleware{Handler: withLogging, token: "secret"}
result := withAuth.Handle("GET /users")
fmt.Println("最终结果:", result)
}
11. 嵌入的最佳实践:
- 优先使用组合而非继承:Go 不支持继承,嵌入是实现代码复用的最佳方式
- 明确嵌入的意图:嵌入应该是"is-a"关系,而不是"has-a"关系
- 避免过度嵌入:过多的嵌套会使代码难以理解和维护
- 文档化嵌入行为:明确说明嵌入的字段和方法的使用方式
- 考虑接口嵌入:接口嵌入可以创建更灵活的组合方式
标准库 embed 使用
1. 基本使用:
Go 1.16 引入了 embed 包,允许在编译时将静态文件嵌入到二进制文件中。
package main
import (
"embed"
"fmt"
)
//go:embed hello.txt
var content string
func main() {
fmt.Println(content)
}
2. 嵌入单个文件:
package main
import (
_ "embed"
"fmt"
)
//go:embed config.yaml
var config string
//go:embed version.txt
var version string
func main() {
fmt.Println("配置文件内容:")
fmt.Println(config)
fmt.Println("版本信息:")
fmt.Println(version)
}
3. 嵌入多个文件:
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/*.html
var templates embed.FS
func main() {
// 读取嵌入的文件
content, err := templates.ReadFile("templates/index.html")
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println(string(content))
// 遍历嵌入的文件
fs.WalkDir(templates, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Printf("文件: %s\n", path)
return nil
})
}
4. 嵌入整个目录:
package main
import (
"embed"
"fmt"
"io/fs"
"net/http"
)
//go:embed static/*
var staticFiles embed.FS
func main() {
// 创建文件服务器
staticFS, err := fs.Sub(staticFiles, "static")
if err != nil {
fmt.Println("错误:", err)
return
}
http.Handle("/static/", http.FileServer(http.FS(staticFS)))
fmt.Println("服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
5. 嵌入多个模式:
package main
import (
"embed"
"fmt"
)
//go:embed config/*.yaml
//go:embed templates/*.html
//go:embed static/*
var files embed.FS
func main() {
// 读取不同类型的文件
config, _ := files.ReadFile("config/app.yaml")
template, _ := files.ReadFile("templates/index.html")
fmt.Println("配置:", string(config))
fmt.Println("模板:", string(template))
}
6. 嵌入为字节数组:
package main
import (
"embed"
"fmt"
)
//go:embed image.png
var imageData []byte
func main() {
fmt.Printf("图片大小: %d 字节\n", len(imageData))
}
7. Web 服务器示例:
package main
import (
"embed"
"fmt"
"io/fs"
"net/http"
)
//go:embed templates/*
var templates embed.FS
//go:embed static/*
var static embed.FS
func homeHandler(w http.ResponseWriter, r *http.Request) {
content, err := templates.ReadFile("templates/index.html")
if err != nil {
http.Error(w, "文件未找到", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/html")
w.Write(content)
}
func main() {
// 设置静态文件服务
staticFS, _ := fs.Sub(static, "static")
http.Handle("/static/", http.FileServer(http.FS(staticFS)))
// 设置首页
http.HandleFunc("/", homeHandler)
fmt.Println("服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
8. 嵌入的注意事项:
- 指令位置:
//go:embed指令必须紧邻变量声明,中间不能有空行 - 变量类型:可以是
string、[]byte或embed.FS - 路径限制:只能嵌入当前包所在目录及其子目录的文件
- 文件大小:嵌入大文件会增加二进制文件的大小
- 开发模式:在开发时,文件路径是相对于源文件的
9. embed 最佳实践:
- 用于静态资源:HTML 模板、CSS、JavaScript、图片等
- 配置文件:默认配置、示例配置等
- 单文件应用:创建无需外部依赖的可执行文件
- 版本控制:将嵌入的文件纳入版本控制
- 测试数据:将测试数据嵌入到测试代码中
结构体标签
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"-"` // 不进行 JSON 序列化
}
func main() {
user := User{
ID: 1,
Name: "张三",
Email: "zhangsan@example.com",
Password: "secret",
}
// JSON 序列化
jsonData, _ := json.Marshal(user)
fmt.Println("JSON:", string(jsonData))
// 反射获取标签
t := reflect.TypeOf(user)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("字段: %s, 标签: %s\n", field.Name, field.Tag.Get("json"))
}
}