不灭的火

加密类型:SHA/AES/RSA下载Go
复合类型:数组(array)、切片(slice)、映射(map)、结构体(struct)、指针(pointer、函数(function)、接口(interface)、通道(channel) Go类型
引用类型:切片(slice)、映射(map)、指针(pointer、函数(function)、通道(channel) Go引用

作者:AlbertWen  添加时间:2025-11-16 10:10:58  修改时间:2025-11-16 12:44:56  分类:02.Go语言编程  编辑

在 Go 语言中,引用类型(reference types)是指在赋值或传递参数时,不会复制底层数据,而是传递对数据的引用(即指针)的类型。主要的引用类型包括以下几种:

  1. 切片(Slice)切片本身是对底层数组的引用,包含指针(指向数组元素)、长度和容量。赋值或传递切片时,复制的是切片结构体(指针、长度、容量),但它们指向同一个底层数组,因此修改切片元素会影响原切片。

// 切片是引用类型
slice1 := []int{1, 2, 3}
slice2 := slice1  // 两个变量引用同一个底层数组
slice2[0] = 100   // 会影响 slice1
fmt.Println(slice1) // [100 2, 3]
  1. 映射(Map)map 是引用类型,创建 map 时返回的是一个指针(内部实现为指向哈希表的指针)。赋值或传递 map 变量时,传递的是指针的副本,操作的是同一个底层数据结构,修改 map 内容会影响所有引用它的变量。

// 映射是引用类型
map1 := map[string]int{"a": 1, "b": 2}
map2 := map1        // 引用同一个底层数据结构
map2["a"] = 100     // 会影响 map1
fmt.Println(map1)   // map[a:100 b:2]
  1. 通道(Channel)channel 本质上是一个指针类型(指向内部的通道结构体),赋值或传递 channel 时,传递的是指针副本,所有引用指向同一个通道实例,操作会影响同一个通道。

// 通道是引用类型
ch1 := make(chan int, 3)
ch2 := ch1          // 引用同一个通道
go func() {
    ch2 <- 42       // 通过 ch2 发送
}()
fmt.Println(<-ch1)  // 通过 ch1 接收,输出 42
  1. 函数(Function)函数在 Go 中是引用类型,函数变量存储的是函数的地址。赋值或传递函数变量时,传递的是地址引用,多个变量可以指向同一个函数。

// 函数也是引用类型
func1 := func(x int) int { return x * 2 }
func2 := func1        // 引用同一个函数
fmt.Println(func2(5)) // 10
  1. 指针(Pointer):指针显式地存储内存地址。虽然是指针,但它本身是值类型(存储地址的值),但常用于实现引用语义。

// 指针也是引用语义
x := 10
p1 := &x            // p1 指向 x
p2 := p1            // p2 也指向同一个地址
*p2 = 20
fmt.Println(x)      // 20
  1. 接口(Interface)接口变量在运行时包含类型信息(动态类型)和数据指针(动态值)。当接口存储引用类型时,传递接口变量会复制接口结构体,但内部的指针仍指向原数据,因此修改会影响原数据(注:若接口存储值类型,则行为不同)。

package main

import "fmt"

// 定义一个结构体类型
type Person struct {
    Name string
    Age  int
}

// 为 Person 实现 String() 方法,满足 fmt.Stringer 接口
func (p Person) String() string {
    return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}

func main() {
    // 创建一个 Person 实例
    p := Person{Name: "Alice", Age: 30}

    // 将 p 赋值给接口变量 i1(接口会存储指向 p 的指针)
    var i1 fmt.Stringer = p   // 值复制,但接口内部持有指针 → 引用语义
    var i2 fmt.Stringer = i1  // i2 和 i1 持有相同的底层数据指针

    // 修改原始结构体(注意:p 是值类型,但接口持有的是 &p)
    // 实际上这里需要注意:接口存储的是值的副本?等等!
    // 关键点来了:Go 接口存储的是(类型,值指针)对
    // 所以即使 p 是值,接口持有的是指向原始值的指针(在赋值时取地址)

    // 正确演示引用行为:使用指针
    p2 := &Person{Name: "Bob", Age: 25}
    var i3 fmt.Stringer = p2
    var i4 fmt.Stringer = i3

    // 修改通过指针指向的数据
    p2.Age = 26

    // i3 和 i4 看到的是同一个底层数据
    fmt.Println("i3:", i3) // i3: Bob (26 years old)
    fmt.Println("i4:", i4) // i4: Bob (26 years old)

    // 再修改一次
    (*p2).Age = 27
    fmt.Println("After modify, i3:", i3) // i3: Bob (27 years old)
    fmt.Println("After modify, i4:", i4) // i4: Bob (27 years old)

    // 证明:接口是引用类型,i3 和 i4 共享同一份数据
}

输出结果:

i3: Bob (26 years old)
i4: Bob (26 years old)
After modify, i3: Bob (27 years old)
After modify, i4: Bob (27 years old)

为什么接口是 引用类型

Go 接口的底层实现是一个结构体:

type eface struct {  // empty interface
    _type *_type
    data  unsafe.Pointer
}
  • data 是一个 指针,指向实际的数据。
  • 多个接口变量赋值时,复制的是这个结构体(包含指针),不复制底层数据
  • 因此,修改底层数据会影响所有持有相同指针的接口变量

关键结论:

Go 接口是引用类型: 它不存储数据副本,而是存储 指向数据的指针,多个接口共享同一份数据。

推荐写法(更清晰地体现引用语义):

var i1 fmt.Stringer = &Person{"Charlie", 30}
var i2 = i1
// 修改底层数据会同步反映到 i1 和 i2

这样更直观地体现“引用”行为。

 

这些类型的特点是:传递或赋值时开销小(仅复制引用),且对其内容的修改会影响所有引用该数据的变量。这与值类型(如 int、struct、array 等)形成对比,值类型在赋值或传递时会复制完整数据。