Golang 结构体、方法和接口
一、结构体(Struct)
结构体是 Go 语言中实现面向对象特性(如继承、组合)的重要方式,用于封装数据。
1. 定义
结构体通过 type 关键字定义,包含多个字段,字段可设置可见性和元信息(标签)。
type User struct {
Name string `json:"name"` // 首字母大写,包外可见;标签用于元信息(如 JSON 序列化)
age int `json:"age"` // 首字母小写,仅包内可见
}
(1)字段导出规则
- 首字母大写:可被包外访问;
- 首字母小写:仅包内可见;
- 同一包内,字段可见性不受首字母大小写影响。
(2)结构体标签(Tag)
- 用反引号
`定义,用于存储字段的元信息(如 JSON 序列化时的键名); - 标签内容需符合特定格式(如
json:"name"),供反射机制解析。
(3)避免循环嵌套
结构体不能直接包含自身类型,但可包含自身指针(用于实现链表、树等数据结构):
type Node struct {
value int
Next *Node // 允许指向自身类型的指针
}
2. 初始化
结构体成员默认值为零值,未使用的成员可不初始化,支持两种初始化方式:
(1)键值对初始化
指定字段名赋值,无需顺序一致,可省略部分字段:
u1 := User{Name: "Alice"} // age 为默认零值 0
(2)列表初始化
需按字段声明顺序赋值,且必须初始化所有字段,不能与键值对方式混用:
u2 := User{"Bob", 20} // 顺序必须与结构体定义一致
3. 访问与指针
(1)访问成员
通过小数点 . 访问结构体成员:
fmt.Println(u1.Name) // 访问包外可见字段
(2)结构体指针
-
与普通指针用法一致,可通过
new关键字创建:u3 := new(User) // u3 是 *User 类型 u3.Name = "Charlie" -
指针访问成员时,Go 会自动解引用,无需显式
*操作。
4. 作为函数参数
(1)值传递
传递结构体副本,函数内修改不影响原结构体:
func updateName(u User, newName string) {
u.Name = newName // 仅修改副本
}
(2)指针传递
传递结构体指针,函数内修改会影响原结构体,适合大型结构体(减少内存复制):
func updateAge(u *User, newAge int) {
u.age = newAge // 修改原结构体
}
5. 高级用法
(1)匿名结构体
临时使用的结构体,无需命名,其字段可被外层结构体直接访问(提升字段):
package main
import "fmt"
type person struct {
name string
age int
}
type student struct {
person // 匿名结构体
grade int
}
func main() {
var s student
s.name = "tom" // 提升字段
s.age = 22 // 提升字段
s.grade = 6
fmt.Println(s) // {{tom 22} 6}
}
(2)结构体嵌套
通过嵌套模拟继承,内层结构体的字段和方法可被外层结构体使用:
type Animal struct {
name string
}
type Dog struct {
Animal // 嵌套 Animal
}
func main() {
d := Dog{Animal{name: "Buddy"}}
fmt.Println(d.name) // 访问嵌套结构体的字段
}
(3)空结构体 struct{}
不占用内存空间,常用于:
-
作为 map 占位符(仅需键,无需值):
m := make(map[string]struct{}, 10) // 节省内存 -
无数据的信道(channel,用于协程通知):
ch := make(chan struct{}) // 仅用于信号传递,例如来通知子协程(goroutine)执行任务,或只用来控制协程并发度 -
使用空结构体作为方法的 receiver(无状态)。
(4)内存对齐
与 C++ 类似,字段顺序影响内存占用,合理调整顺序可优化空间(如将相同类型字段放在一起)。
二、方法(Method)
方法是与特定类型(如结构体)绑定的函数,通过接收者(receiver)关联,可模拟类的方法。
1. 语法
type MyType struct{}
// 值接收者:接收结构体副本
func (t MyType) method1() { /* 实现 */ }
// 指针接收者:接收结构体指针
func (t *MyType) method2() { /* 实现 */ }
2. 接收者类型
(1)值接收者(T Type)
- 方法内操作的是接收者的副本,不会修改原对象;
- 适合不需要修改原对象的场景。
(2)指针接收者(T *Type)
- 方法内操作的是原对象,修改会影响外部;
- Go 会自动解引用指针,调用时无需显式取地址;
- 若需修改结构体字段,必须使用指针接收者。
3. 与函数的对比
| 特性 | 方法 | 函数 |
|---|---|---|
| 关联对象 | 需指定接收者(绑定类型) | 独立,无接收者 |
| 命名冲突 | 不同接收者可重名 | 同一包内不可重名 |
| 调用方式 | 通过接收者调用(如 t.m()) |
直接调用(如 f()) |
4. 注意事项
- 接收者类型不限于结构体,还可是类型别名、切片、映射、信道等;
- 结构体与其方法可在同一包的不同文件中定义,但必须属于同一包;
- 同一类型的方法名必须唯一(无方法重载);
- 方法本质是特殊函数,通过接收者建立与类型的关联。
三、接口(Interface)
接口是一种类型,定义了一组方法签名,任何类型实现了这些方法即实现了该接口,用于解耦和实现多态。
1. 语法
// 定义接口(包含方法签名)
type USB interface {
read()
write()
}
// 结构体实现接口(必须实现所有方法)
type Computer struct{}
func (c Computer) read() {
fmt.Println("Computer read...")
}
func (c Computer) write() {
fmt.Println("Computer write...")
}
2. 接口与类型的关系
(1)一个类型可实现多个接口
type Player interface { playMusic() }
type Video interface { playVideo() }
type Mobile struct{}
func (m Mobile) playMusic() { /* 实现 */ } // 实现 Player
func (m Mobile) playVideo() { /* 实现 */ } // 实现 Video
(2)多个类型可实现同一接口(多态)
type Pet interface { eat() }
type Dog struct{}
func (d Dog) eat() { fmt.Println("Dog eat") }
type Cat struct{}
func (c Cat) eat() { fmt.Println("Cat eat") }
// 同一接口接收不同实现
func feed(p Pet) { p.eat() }
func main() {
feed(Dog{}) // 输出 "Dog eat"
feed(Cat{}) // 输出 "Cat eat"
}
3. 接口嵌套
接口可嵌套其他接口,形成新接口(类似多继承):

// 基础接口
type Flyer interface { fly() }
type Swimmer interface { swim() }
// 嵌套接口(需实现所有嵌套接口的方法)
type FlyFish interface {
Flyer
Swimmer
}
// 实现 FlyFish 接口
type Fish struct{}
func (f Fish) fly() { fmt.Println("Fly...") }
func (f Fish) swim() { fmt.Println("Swim...") }
4. 接口的目的
主要用于解耦,实现开闭原则(OCP):对扩展开放(新增实现类),对修改关闭(无需修改接口使用者)。
5. 空接口 interface{}
不包含任何方法,所有类型都实现了空接口,可存储任意类型值:
(1)典型应用
-
fmt 包下的 Print 系列函数
type any = interface{} func Fprintln(w io.Writer, a ...any) (n int, err error) func Println(a ...any) (n int, err error) -
存储多种类型数据的容器:
ORM 中,搭配断言和反射解析空接口中存储的数据。package main import "fmt" type Person struct { name string age int } // 空接口 type A interface{} type B1 struct { name string } type B2 struct { age int } func main() { // 1.空接口可以存储任意类型的数值 var a1 A = B1{name: "b1"} var a2 A = B2{age: 10} var a3 A = 100 var a4 A = "hello" fmt.Printf("a1: %v\n", a1) // a1: {b1} fmt.Printf("a2: %v\n", a2) // a2: {10} fmt.Printf("a3: %v\n", a3) // a3: 100 fmt.Printf("a4: %v\n", a4) // a4: hello // 2. map1 := make(map[string]interface{}) map1["name"] = "tom" map1["age"] = 24 map1["friend"] = Person{"jim", 22} fmt.Printf("map1: %v\n", map1) // map1: map[age:24 friend:{jim 22} name:tom] // 3. slice1 := make([]interface{}, 0, 10) slice1 = append(slice1, a1, a2, a3, a4, "world", 200, map1) fmt.Printf("slice1: %v\n", slice1) // slice1: [{b1} {10} 100 hello world 200 map[age:24 friend:{jim 22} name:tom]] fmt.Println("-------------") printAny(slice1) } /* 0: {b1} 1: {10} 2: 100 3: hello 4: world 5: 200 6: map[age:24 friend:{jim 22} name:tom] */ func printAny(slice []interface{}) { for i := 0; i < len(slice); i++ { fmt.Printf("%d: %v\n", i, slice[i]) } }
(2)接口操作
1). 接口断言
用于获取空接口存储的真实类型:
-
安全断言(返回值 + 布尔值):
<目标类型的值>, <布尔参数:= <表达式>.(目标类型)var a interface{} = "hello" if s, ok := a.(string); ok { fmt.Println(s) // 输出 "hello" } -
非安全断言(类型不匹配时 panic):
<目标类型的值:= <表达式>.(目标类型)s := a.(string) // 若 a 不是 string 类型,触发 panic -
类型分支(批量判断类型):
switch v := a.(type) { case string: fmt.Println("string:", v) case int: fmt.Println("int:", v) default: fmt.Println("unknown") }
2). 接口比较与赋值
通过反射原理,可以进行接口的比较。
- 判断类型是否一致:
reflect.TypeOf(a).Kind() == reflect.TypeOf(b).Kind() - 判断值是否相等:
reflect.DeepEqual(a, b interface{}) - 赋值:
reflect.ValueOf(a).Elem().Set(reflect.ValueOf(b))(将一个 interface{} 赋值给另一个 interface{})
四、模拟面向对象(OOP)特性
Go 无原生 OOP 概念,可通过结构体嵌套和方法绑定模拟类、继承、构造函数等。
1. 模拟类与继承
(1)属性与方法
结构体模拟类的属性,绑定的方法模拟类的方法:
type Animal struct {
name string // 属性
}
// 方法(模拟类的行为)
func (a Animal) eat() {
fmt.Printf("%s eat\n", a.name)
}
(2)继承
通过结构体嵌套实现,子类可访问父类的属性和方法,若方法名重复,子类方法覆盖父类:
type Dog struct {
Animal // 嵌套父类
}
func (d Dog) bark() { // 子类特有方法
fmt.Printf("%s bark\n", d.name)
}
func main() {
d := Dog{Animal{name: "Buddy"}}
d.eat() // 调用父类方法:"Buddy eat"
d.bark() // 调用子类方法:"Buddy bark"
}
2. 模拟构造函数
通过函数返回结构体指针,实现初始化逻辑(如参数校验):
type Person struct {
name string
age int
}
// 构造函数
func NewPerson(name string, age int) (*Person, error) {
if name == "" {
return nil, fmt.Errorf("name 不能为空")
}
if age < 0 {
return nil, fmt.Errorf("年龄不能为负数")
}
return &Person{name: name, age: age}, nil
}
func main() {
p, err := NewPerson("Alice", 30)
if err == nil {
fmt.Println(p.name) // 输出 "Alice"
}
}