← 返回目录

第三章:函数与结构体

函数、方法、接口与结构体组合

1. 函数

Go 的函数是一等公民,支持多返回值、命名返回值、可变参数和闭包。

基本定义

// 基本函数
func add(a int, b int) int {
    return a + b
}

// 相同类型参数可合并
func add2(a, b int) int {
    return a + b
}

// 多返回值
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// 命名返回值(自动声明,return 时自动返回)
func rectInfo(width, height float64) (area, perimeter float64) {
    area = width * height
    perimeter = 2 * (width + height)
    return // 等同于 return area, perimeter
}

可变参数

// 可变参数(类似 Java 的 varargs)
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// 调用
sum(1, 2, 3)          // 6
sum([]int{1, 2, 3}...) // 展开切片

函数作为值与闭包

package main

import "fmt"

func main() {
    // 函数作为值
    op := func(a, b int) int { return a + b }
    fmt.Println(op(3, 4)) // 7

    // 函数作为参数
    result := apply(5, 3, func(a, b int) int {
        return a * b
    })
    fmt.Println(result) // 15

    // 闭包:捕获外部变量
    counter := makeCounter()
    fmt.Println(counter()) // 1
    fmt.Println(counter()) // 2
    fmt.Println(counter()) // 3
}

func apply(a, b int, fn func(int, int) int) int {
    return fn(a, b)
}

func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

Go 的特色:多返回值是语言核心特性,不是语法糖。惯用方式是将 error 作为最后一个返回值。命名返回值在短函数中提高可读性。

2. 结构体

Go 没有类(class),使用结构体(struct)+ 方法来组织数据和行为。

package main

import "fmt"

// 定义结构体
type User struct {
    Name  string
    Email string
    Age   int
}

// 构造函数惯例:NewXxx
func NewUser(name, email string, age int) *User {
    return &User{
        Name:  name,
        Email: email,
        Age:   age,
    }
}

// 结构体嵌入(组合 > 继承)
type Admin struct {
    User      // 匿名嵌入,自动提升字段和方法
    Level int
}

// 带标签的结构体(用于 JSON、数据库映射)
type Article struct {
    ID        int    `json:"id" db:"id"`
    Title     string `json:"title" db:"title"`
    Content   string `json:"content" db:"content"`
    Published bool   `json:"published" db:"published"`
}

func main() {
    // 创建结构体
    u1 := User{Name: "Alice", Email: "alice@example.com", Age: 30}
    u2 := User{"Bob", "bob@example.com", 25} // 按顺序
    u3 := NewUser("Charlie", "charlie@example.com", 28)

    // 访问字段
    fmt.Println(u1.Name, u1.Age)

    // 修改字段(值类型,注意指针)
    u3.Age = 29

    // 嵌入结构体
    admin := Admin{
        User:  User{Name: "Super", Email: "admin@example.com", Age: 35},
        Level: 1,
    }
    fmt.Println(admin.Name)  // 直接访问嵌入字段
    fmt.Println(admin.Level)

    fmt.Println(u1, u2, u3)
}

🔄 OOP 对比

概念 Java/Python Go
class struct + 方法
继承 extends 嵌入(组合)
接口 implements(显式) 隐式实现
构造器 constructor/__init__ NewXxx 工厂函数

3. 方法

方法是绑定到特定类型上的函数。接收者可以是值类型或指针类型。

package main

import (
    "fmt"
    "math"
)

type Circle struct {
    Radius float64
}

// 值接收者:不修改原始值,适合只读操作
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// 指针接收者:可以修改原始值,避免复制大结构体
func (c *Circle) Scale(factor float64) {
    c.Radius *= factor
}

func (c Circle) String() string {
    return fmt.Sprintf("Circle(r=%.2f)", c.Radius)
}

func main() {
    c := Circle{Radius: 5}
    fmt.Printf("面积: %.2f\n", c.Area())         // 78.54
    fmt.Printf("周长: %.2f\n", c.Perimeter())     // 31.42

    c.Scale(2) // 指针接收者,会修改 c
    fmt.Printf("缩放后面积: %.2f\n", c.Area())    // 314.16

    fmt.Println(c) // Circle(r=10.00)
}

值接收者 vs 指针接收者:

  • 值接收者 (c Circle):操作副本,不修改原始值,适合小型不可变结构体
  • 指针接收者 (c *Circle):操作原始值,可以修改字段,避免大结构体复制开销
  • • 经验法则:如果任何方法用了指针接收者,那所有方法都应该用指针接收者

4. 接口

Go 的接口是隐式实现的——只要类型实现了接口的所有方法,就自动满足该接口。不需要 implements 关键字。

package main

import (
    "fmt"
    "math"
)

// 定义接口
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Stringer 接口(类似 Java 的 toString)
type Stringer interface {
    String() string
}

// Circle 隐式实现了 Shape
type Circle struct{ Radius float64 }
func (c Circle) Area() float64      { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }

// Rectangle 也隐式实现了 Shape
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) }

// 接口作为参数
func printShapeInfo(s Shape) {
    fmt.Printf("面积: %.2f, 周长: %.2f\n", s.Area(), s.Perimeter())
}

// 空接口 any(Go 1.18+ 的别名)
func printAnything(v any) {
    fmt.Printf("类型: %T, 值: %v\n", v, v)
}

func main() {
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 3, Height: 4},
    }
    for _, s := range shapes {
        printShapeInfo(s)
    }

    // 类型断言
    var s Shape = Circle{Radius: 3}
    if c, ok := s.(Circle); ok {
        fmt.Println("是圆形,半径:", c.Radius)
    }

    // 类型开关
    checkType := func(v any) {
        switch t := v.(type) {
        case int:
            fmt.Println("整数:", t)
        case string:
            fmt.Println("字符串:", t)
        case Circle:
            fmt.Println("圆形:", t.Radius)
        default:
            fmt.Printf("未知类型: %T\n", t)
        }
    }

    checkType(42)
    checkType("hello")
    checkType(Circle{Radius: 7})
}

常用标准库接口

// io.Reader - 读取数据的通用抽象
type Reader interface {
    Read(p []byte) (n int, err error)
}

// io.Writer - 写入数据的通用抽象
type Writer interface {
    Write(p []byte) (n int, err error)
}

// fmt.Stringer - 自定义字符串表示
type Stringer interface {
    String() string
}

// error - Go 的错误接口
type error interface {
    Error() string
}

// sort.Interface - 可排序集合
type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

隐式接口的优势:接口由使用方定义,而非实现方。这意味着你可以为第三方类型定义接口,无需修改其源码。这是 Go 接口设计的精髓。

5. 泛型(Go 1.18+)

Go 1.18 引入了泛型,通过类型参数实现类型安全的通用代码。

package main

import (
    "fmt"
    "cmp"
)

// 泛型函数:类型参数 [T]
func Max[T cmp.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// 自定义约束
type Number interface {
    ~int | ~int64 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// 泛型结构体
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    last := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return last, true
}

func (s *Stack[T]) Len() int {
    return len(s.items)
}

// 泛型 Map 函数
func Map[T any, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func main() {
    fmt.Println(Max(3, 7))       // 7
    fmt.Println(Max("a", "z"))   // z

    fmt.Println(Sum([]int{1, 2, 3, 4, 5}))       // 15
    fmt.Println(Sum([]float64{1.1, 2.2, 3.3}))   // 6.6

    // 泛型栈
    s := &Stack[string]{}
    s.Push("hello")
    s.Push("world")
    val, _ := s.Pop()
    fmt.Println(val) // world

    // Map 转换
    nums := []int{1, 2, 3, 4}
    doubled := Map(nums, func(n int) int { return n * 2 })
    fmt.Println(doubled) // [2 4 6 8]

    strs := Map(nums, func(n int) string {
        return fmt.Sprintf("#%d", n)
    })
    fmt.Println(strs) // [#1 #2 #3 #4]
}

🔄 泛型对比

语言 泛型语法
Go func Max[T cmp.Ordered](a, b T) T
Java <T extends Comparable> T max(T a, T b)
TypeScript function max<T>(a: T, b: T): T
Rust fn max<T: Ord>(a: T, b: T) -> T

Go 用方括号 [] 而非尖括号 <>~int 表示底层类型是 int 的所有类型。

6. 本章要点

📌 函数

支持多返回值、命名返回值、可变参数。函数是一等公民,可作为值传递,闭包捕获外部变量。

📌 结构体

替代类,通过嵌入实现组合。NewXxx 工厂函数替代构造器,标签用于序列化映射。

📌 方法

值接收者操作副本,指针接收者操作原始值。如果需要修改结构体,必须用指针接收者。

📌 接口

隐式实现,无需 implements。小接口设计(如 io.Reader),any 是空接口别名。

📌 泛型

Go 1.18+ 支持类型参数,用 [] 声明。约束接口限定类型范围,~ 匹配底层类型。

📌 组合优于继承

Go 不支持类继承,通过结构体嵌入和接口组合实现代码复用,更灵活、更松耦合。