结构体与方法——面向对象的Go式实现

Go 语言没有 class,但它通过结构体(struct) + 方法(method) + 接口(interface)这套组合,实现了一种非常务实、轻量级的面向对象风格

下面从最基础讲起,一步步说明 Go 是如何“面向对象”的(重点放在结构体与方法的部分)。

1. 结构体 ≈ 数据 + 状态(类似 OOP 的“字段/属性”)

type Person struct {
    Name   string
    Age    int
    Gender string // 小写开头 → 包外不可见(封装)
}
  • 这是最基本的“对象数据载体”
  • Go 不强制要求字段一定要小写(private),但习惯上用小写开头实现封装

2. 方法 = 绑定到结构体的函数(Receiver)

Go 的方法写法非常独特:函数名前加一个“接收者”参数

// 值接收者(最常见,拷贝一份数据)
func (p Person) Greet() string {
    return "你好,我是 " + p.Name
}

// 指针接收者(能修改原对象)
func (p *Person) Birthday() {
    p.Age++   // 真正修改了调用者的 Age
}

使用方式:

func main() {
    alice := Person{Name: "Alice", Age: 25}

    // 值接收者:两种写法都行,Go 会自动解引用
    fmt.Println(alice.Greet())         // 你好,我是 Alice
    fmt.Println((&alice).Greet())      // 也行

    // 指针接收者:通常需要 & 取地址
    alice.Birthday()                   // Age 变成 26
    (&alice).Birthday()                // 也行

    fmt.Println(alice.Age)             // 27
}

3. 值接收者 vs 指针接收者 —— 这是 Go 最容易踩坑的地方

特性值接收者 (p Person)指针接收者 (p *Person)推荐场景
是否拷贝结构体是(拷贝一份)否(只拷贝指针,8字节)
能否修改原对象不能(改的是副本)需要修改状态时必须用指针
方法调用方式值 / 指针 都可以调用只有指针能调用(值调用会编译错)
并发安全性天然安全(操作副本)需要自己加锁小结构体、无状态变化 → 值接收者
性能(大结构体)拷贝开销大几乎无拷贝开销结构体 > 几十字节 → 优先考虑指针
一致性原则(官方建议)同一个类型的所有方法尽量统一用一种接收者

官方经典建议(Effective Go & Go Tour)

“对一个类型的所有方法,要么都用值接收者,要么都用指针接收者,不要混用。”

最常见的现实选择规律(2025–2026 社区共识)

  • 小结构体(< 16–32 字节)、不可变、纯计算 → 值接收者
  • 需要修改状态、包含 mutex、slice/map 等会增长的字段 → 指针接收者(占主流,约 70–80%)
  • 混合类型(例如 String() 方法常用值接收者,其余用指针)→ 可以接受,但尽量避免

4. 完整的“类”风格示例(带构造函数 + 方法)

package main

import "fmt"

// "类":推荐首字母大写(导出)
type Rectangle struct {
    width  float64
    height float64
}

// 构造函数(Go 没有 new 关键字强制要求,习惯用 NewXxx)
func NewRectangle(w, h float64) *Rectangle {
    if w <= 0 || h <= 0 {
        panic("宽度和高度必须 > 0")
    }
    return &Rectangle{width: w, height: h}
}

// 值接收者方法(只读)
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
}

// 实现 Stringer 接口(类似 toString)
func (r Rectangle) String() string {
    return fmt.Sprintf("Rectangle{%.1f × %.1f}", r.width, r.height)
}

func main() {
    rect := NewRectangle(10, 5)
    fmt.Println(rect)              // Rectangle{10.0 × 5.0}
    fmt.Printf("面积: %.2f\n", rect.Area())       // 50.00

    rect.Scale(2)
    fmt.Println(rect)              // Rectangle{20.0 × 10.0}
    fmt.Printf("周长: %.2f\n", rect.Perimeter())   // 60.00
}

5. Go 的 OOP 与传统语言对比(快速记忆表)

特性Java/C#/C++GoGo 的实现方式
classstruct
构造器constructor普通函数(NewXxx)习惯约定
方法member functionreceiver function(t T) 或 (t *T)
继承extends不支持,改用嵌入匿名嵌入结构体
多态override接口(duck typing)隐式实现接口
封装private/protected小写字段/方法包级可见性
this/selfthis / self接收者名字(习惯用 p、r、this)随便起名

总结一句话:

Go 的“面向对象”本质是:用 struct 存数据,用方法(带 receiver)绑行为,用接口做多态,用嵌入做组合/“伪继承”

故意去掉了传统 OOP 最复杂、最容易滥用的部分(类继承、protected、super、构造器链、final 等),换来的是更简单、可预测、高性能的代码。

你现在是用值接收者多一些,还是指针接收者占主流?
或者你在项目里遇到过最纠结的 receiver 选择场景是什么?可以聊聊~

文章已创建 4206

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部