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. 接口嵌套

接口可嵌套其他接口,形成新接口(类似多继承):
image-1766157285674

// 基础接口
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"
    }
}