Go初级之五:结构体与方法
Go初级之五:结构体与方法
在Go语言中,结构体(struct) 和 方法(method) 是实现面向对象编程的核心机制。虽然Go不是传统意义上的面向对象语言(没有类、继承等概念),但通过结构体和方法的组合,可以非常优雅地组织代码。
一、结构体(Struct)
1. 什么是结构体?
结构体是一种自定义的数据类型,用于将多个不同类型的数据字段组合在一起,形成一个有意义的整体。
类比:就像“学生”这个概念,包含姓名、年龄、成绩等多个属性。
2. 定义结构体
type Student struct {Name stringAge intScore float64
}
type
:关键字,用于定义新类型。Student
:结构体名称(大写表示对外可见)。{}
内是字段列表,每个字段有名字和类型。
3. 创建结构体实例
方式一:使用 struct{}
字面量
s1 := Student{Name: "张三",Age: 18,Score: 95.5,
}
方式二:按顺序赋值(不推荐,易错)
s2 := Student{"李四", 17, 88.0}
方式三:new 创建指针
s3 := new(Student)
s3.Name = "王五"
s3.Age = 19
s3.Score = 90.0
// 等价于 s3 := &Student{}
4. 访问结构体字段
fmt.Println(s1.Name) // 输出:张三
fmt.Println(s1.Age) // 输出:18
s1.Score = 96.0 // 修改字段
二、方法(Method)
1. 什么是方法?
方法是绑定到某个类型上的函数。在Go中,你可以为结构体(甚至基本类型)定义方法。
2. 定义方法
语法:
func (接收者 变量名 类型) 方法名(参数列表) 返回值 {// 方法体
}
示例:为 Student 定义一个方法
func (s Student) PrintInfo() {fmt.Printf("姓名: %s, 年龄: %d, 成绩: %.2f\n", s.Name, s.Age, s.Score)
}func (s Student) IsPass() bool {return s.Score >= 60
}
调用方法:
s := Student{"张三", 18, 95.5}
s.PrintInfo() // 姓名: 张三, 年龄: 18, 成绩: 95.50
fmt.Println(s.IsPass()) // true
3. 值接收者 vs 指针接收者
❌ 值接收者(不会修改原结构体)
func (s Student) SetName(name string) {s.Name = name // 只修改副本
}
调用后原结构体不变。
✅ 指针接收者(可以修改原结构体)
func (s *Student) SetName(name string) {s.Name = name // 修改原始结构体
}
调用:
s := Student{"张三", 18, 95.5}
s.SetName("李四")
fmt.Println(s.Name) // 输出:李四
✅ 建议:如果方法需要修改结构体,或结构体较大(避免复制开销),使用指针接收者。
三、结构体的高级用法
1. 匿名字段(模拟“继承”)
Go没有继承,但可以通过匿名字段实现类似效果。
type Person struct {Name stringAge int
}type Student struct {Person // 匿名字段Score float64School string
}
使用:
s := Student{Person: Person{Name: "张三", Age: 18},Score: 95.5,School: "清华",
}fmt.Println(s.Name) // 直接访问匿名字段的属性
fmt.Println(s.Age)
fmt.Println(s.Score)
这叫“组合”,不是继承,是Go推崇的设计模式。
2. 结构体标签(Struct Tag)——常用于JSON序列化
type User struct {ID int `json:"id"`Name string `json:"name"`Age int `json:"age"`
}
使用 json.Marshal
:
u := User{ID: 1, Name: "Alice", Age: 25}
data, _ := json.Marshal(u)
fmt.Println(string(data))
// 输出:{"id":1,"name":"Alice","age":25}
常见用途:JSON、数据库ORM(如GORM)、表单验证等。
四、完整示例:学生管理系统
package mainimport "fmt"// 1. 定义结构体
type Student struct {Name stringAge intScore float64
}// 2. 定义方法
func (s Student) Print() {fmt.Printf("学生: %s, 年龄: %d, 成绩: %.2f\n", s.Name, s.Age, s.Score)
}func (s *Student) Promote() {s.Age++fmt.Printf("%s 升了一岁,现在 %d 岁\n", s.Name, s.Age)
}func (s Student) IsExcellent() bool {return s.Score >= 90
}// 3. 主函数
func main() {s := Student{"张三", 18, 95.5}s.Print() // 学生: 张三, 年龄: 18, 成绩: 95.50fmt.Println(s.IsExcellent()) // trues.Promote() // 张三 升了一岁,现在 19 岁
}
五、最佳实践与注意事项
项目 | 建议 |
---|---|
结构体名 | 驼峰命名,首字母大写表示导出 |
字段名 | 同上,小写为包内私有 |
方法接收者 | 小结构用值接收者,大结构或需修改时用指针 |
组合代替继承 | 多用匿名字段组合,少用嵌套 |
使用Tag | JSON、数据库操作时务必使用 |
六、常见面试题
-
值接收者和指针接收者的区别?
- 值接收者:操作副本,不改变原值。
- 指针接收者:操作原值,可修改。
-
Go有继承吗?
- 没有,但可以通过匿名字段实现组合。
-
什么时候用指针接收者?
- 方法会修改结构体。
- 结构体较大(避免复制开销)。
- 保持一致性(同一个类型的方法尽量统一用指针或值)。
✅ 总结
概念 | 说明 |
---|---|
struct | 自定义复合数据类型 |
method | 绑定到类型的函数 |
接收者 | (s Type) 或 (s *Type) |
匿名字段 | 实现组合,“has-a”关系 |
Struct Tag | 元信息,用于JSON、ORM等 |
✅ 下一节预告:Go初级之六:接口(Interface)—— 实现多态与解耦