Go 面向对象(上):结构体与方法
上一篇聊了 Go 基础语法,这篇进入面向对象。Go 没有
class,但它的结构体+方法组合能实现几乎所有 OOP 能力——只是思维方式要转弯。
Q1:Go 的结构体和 Java/C# 的 class 有什么区别?
A: 核心区别就一张表:
| 特性 | Java Class | Go Struct |
|---|---|---|
| 定义 | class Person { } |
type Person struct { } |
| 继承 | extends / implements |
没有继承,用组合替代 |
| 构造函数 | 有构造方法 | 没有,用工厂函数模拟 |
| this 指针 | 隐式传递 | 接收者显式声明 |
| 可见性 | public / private | 首字母大写 = 导出,小写 = 包内私有 |
1 | // 定义结构体 |
注意那个 &——Go 建议返回指针,避免值拷贝。
Q2:方法的接收者到底该用值还是指针?这个我踩过坑!
A: 这是 Go 新手第一高频坑。直接看代码:
1 | type Counter struct { |
选择规则很简单:
| 场景 | 用什么 | 理由 |
|---|---|---|
| 需要修改结构体字段 | *T 指针接收者 |
必须的,否则改的是副本 |
| 结构体较大(>64字节) | *T |
避免拷贝开销 |
| 一致性原则 | 如果某方法用了 *T,其余方法也统一用 *T |
混用会让人困惑 |
| 小的不变对象(如 time.Time) | T 值接收者 |
安全、语义清晰 |
实战口诀:不确定就用指针接收者,90% 场景都不会错。
Q3:Go 的”构造函数”怎么写?有几种模式?
A: 三种常见模式,从简单到高级:
模式一:简单工厂函数
1 | func NewUser(name string) *User { |
最常用,够用了。
模式二:带选项模式的工厂(推荐)
1 | // 选项函数类型 |
为什么推荐选项模式? 参数多了之后不会出现 NewUser("", 0, "", true, false) 这种鬼东西。
模式三:必须校验的场景
1 | func NewDatabase(url string) (*sql.DB, error) { |
返回 (T, error) 是 Go 的惯用法。
Q4:结构体能有 JSON/数据库标签吗?实际项目怎么用的?
A: 可以,而且这是日常开发最高频的操作:
1 | type Article struct { |
几个关键标签含义:
| 标签 | 含义 | 示例 |
|---|---|---|
json:"field" |
JSON 序列化字段名 | snake_case → camelCase 映射 |
omitempty |
零值时省略不输出 | Content "" → JSON 里不出现 |
json:"-" |
序列化时忽略此字段 | 密码、内部状态等 |
db:"field" |
ORM 映射(GORM/xorm 等) | 数据库列名 |
1 | a := Article{ID: 1, Title: "Hello", Content: "", IsDeleted: true} |
Q5:匿名嵌入是什么?和继承有什么区别?
A: Go 没有继承,但有 嵌入(Embedding),效果类似:
1 | type Animal struct { |
但和真正的继承有本质区别:
1 | // Dog 可以覆盖 Speak() |
关键区别总结:
| 继承(Java) | 嵌入(Go) | |
|---|---|---|
| is-a 关系 | Cat is a Animal | Dog has an Animal |
| 类型兼容 | 父类引用指向子类 | 不兼容,是不同类型 |
| 方法覆盖 | @Override | 同名方法直接覆盖 |
| 多态 | 支持 | 不支持 |
Q6:结构体比较能用 == 吗?
A: 看情况:
1 | // ✅ 可以比较:所有字段都是可比较类型 |
需要比较复杂结构体怎么办?
1 | import "reflect" |
不过 reflect.DeepEqual 性能不高,热路径上别滥用。
Q7:空结构体 struct{}{} 有什么用?
A: 这是个很巧妙的技巧,主要用于:
1. Set 集合(只关心 key,不需要 value)
1 | // 用户去重 |
struct{} 大小为 0 字节,比用 map[string]bool 或 map[string]int 省内存。
2. Channel 信号(纯通知)
1 | done := make(chan struct{}) |
3. Method Set 占位
某些接口只需要方法存在而不关心数据时。
Q8:结构体的内存对齐是什么?为什么要关注?
A: Go 编译器会对结构体字段做 内存对齐,影响大小:
1 | type Bad struct { // 24 bytes |
规则:按字段大小从大到小排列,减少填充(padding)。
在 slg-go 这种高并发服务中,如果玩家状态结构体被大量实例化,对齐优化能显著降低内存使用。
下期预告
下一篇聊 接口(interface)与鸭子类型——Go 最具标志性的设计之一。为什么说”接口是隐式的”既是优点也是陷阱?error 接口的底层原理是什么?
TODO: 如果你手头有正在写的 Go 项目,可以试试把其中某个 struct 重构一下——比如给它的方法加个选项模式工厂,或者调整字段顺序看看 sizeof 变化。