Skip to content

Go 数据结构

Go 语言提供了多种内置的数据结构,帮助开发者高效地组织和管理数据。本文将介绍 Go 中常用的数据结构及其使用方法。

数组(Array)

数组是固定长度的同类型元素序列。在 Go 中,数组的长度是类型的一部分,这意味着 [5]int[10]int 是不同的类型。

go
// 声明一个长度为 5 的整数数组
var a [5]int

// 初始化数组
b := [3]int{1, 2, 3}

// 使用 ... 让编译器计算长度
c := [...]int{1, 2, 3, 4, 5} // 长度为 5 的数组

// 访问数组元素
fmt.Println(b[0]) // 输出: 1

// 获取数组长度
fmt.Println(len(c)) // 输出: 5

多维数组

go
// 声明一个 2x3 的二维数组
var matrix [2][3]int

// 初始化二维数组
matrix = [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}

// 访问二维数组元素
fmt.Println(matrix[1][2]) // 输出: 6

切片(Slice)

切片是对数组的一种轻量级的抽象,提供了更灵活的接口来处理序列数据。切片是引用类型,指向底层的数组。

go
// 从数组创建切片
a := [5]int{1, 2, 3, 4, 5}
s := a[1:4] // s 包含 a[1], a[2], a[3],即 [2, 3, 4]

// 直接创建切片
s1 := []int{1, 2, 3}

// 使用 make 创建切片
s2 := make([]int, 5)    // 长度为 5,容量为 5 的切片
s3 := make([]int, 0, 5) // 长度为 0,容量为 5 的切片

// 切片的长度和容量
fmt.Println(len(s2), cap(s2)) // 输出: 5 5
fmt.Println(len(s3), cap(s3)) // 输出: 0 5

切片操作

go
// 追加元素
s := []int{1, 2, 3}
s = append(s, 4, 5)      // s 现在是 [1, 2, 3, 4, 5]
s = append(s, []int{6, 7, 8}...) // s 现在是 [1, 2, 3, 4, 5, 6, 7, 8]

// 复制切片
s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1) // s2 现在是 [1, 2, 3] 的副本

// 删除元素(通过重新切片)
s = []int{1, 2, 3, 4, 5}
// 删除索引为 2 的元素
s = append(s[:2], s[3:]...) // s 现在是 [1, 2, 4, 5]

映射(Map)

映射是一种无序的键值对集合,类似于其他语言中的字典或哈希表。

go
// 声明映射
var m map[string]int

// 初始化映射
m = make(map[string]int)

// 添加键值对
m["apple"] = 1
m["banana"] = 2
m["orange"] = 3

// 简短声明并初始化
m1 := map[string]int{
    "apple":  1,
    "banana": 2,
    "orange": 3,
}

// 访问映射元素
fmt.Println(m["apple"]) // 输出: 1

// 检查键是否存在
value, exists := m["grape"]
if exists {
    fmt.Println("grape 的值是", value)
} else {
    fmt.Println("grape 不存在于映射中")
}

// 删除键值对
delete(m, "banana")

// 获取映射长度
fmt.Println(len(m)) // 输出映射中键值对的数量

结构体(Struct)

结构体是一种用户自定义的类型,可以包含多个不同类型的字段。

go
// 定义结构体
type Person struct {
    Name    string
    Age     int
    Address string
}

// 创建结构体实例
p1 := Person{
    Name:    "张三",
    Age:     30,
    Address: "北京市",
}

// 简短初始化(按字段顺序)
p2 := Person{"李四", 25, "上海市"}

// 访问结构体字段
fmt.Println(p1.Name, p1.Age) // 输出: 张三 30

// 结构体指针
p := &Person{"王五", 35, "广州市"}
fmt.Println(p.Name) // 输出: 王五(Go 自动解引用)

嵌套结构体

go
type Address struct {
    City    string
    Street  string
    ZipCode string
}

type Employee struct {
    Name    string
    Age     int
    Address Address // 嵌套结构体
}

// 创建嵌套结构体
e := Employee{
    Name: "赵六",
    Age:  40,
    Address: Address{
        City:    "深圳市",
        Street:  "科技路",
        ZipCode: "518000",
    },
}

// 访问嵌套字段
fmt.Println(e.Address.City) // 输出: 深圳市

匿名字段(嵌入)

go
type Person struct {
    Name string
    Age  int
}

type Employee struct {
    Person  // 匿名嵌入 Person
    Company string
    Salary  float64
}

// 创建带有匿名字段的结构体
e := Employee{
    Person: Person{
        Name: "张三",
        Age:  30,
    },
    Company: "ABC科技",
    Salary:  10000,
}

// 直接访问嵌入字段的属性
fmt.Println(e.Name, e.Age) // 输出: 张三 30

链表

Go 标准库中没有内置的链表类型,但可以使用 container/list 包:

go
import (
    "container/list"
    "fmt"
)

func main() {
    // 创建新链表
    l := list.New()

    // 在链表尾部添加元素
    l.PushBack("第一个")
    l.PushBack("第二个")
    l.PushBack("第三个")

    // 在链表头部添加元素
    l.PushFront("第零个")

    // 遍历链表
    for e := l.Front(); e != nil; e = e.Next() {
        fmt.Println(e.Value)
    }

    // 获取链表长度
    fmt.Println("链表长度:", l.Len())
}

堆(Heap)

Go 提供了 container/heap 包来实现堆数据结构:

go
import (
    "container/heap"
    "fmt"
)

// 定义一个整数切片类型
type IntHeap []int

// 实现 heap.Interface 所需的方法
func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } // 小顶堆
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *IntHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}

func (h *IntHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

func main() {
    h := &IntHeap{2, 1, 5}
    heap.Init(h)
    heap.Push(h, 3)
    fmt.Printf("最小值: %d\n", (*h)[0])
    for h.Len() > 0 {
        fmt.Printf("%d ", heap.Pop(h))
    }
}

集合(Set)

Go 没有内置的集合类型,但可以使用 map 来模拟集合:

go
// 使用 map[Type]bool 实现集合
set := make(map[string]bool)

// 添加元素
set["apple"] = true
set["banana"] = true
set["orange"] = true

// 检查元素是否存在
if set["apple"] {
    fmt.Println("apple 在集合中")
}

// 删除元素
delete(set, "banana")

// 获取集合大小
fmt.Println("集合大小:", len(set))

// 遍历集合
for item := range set {
    fmt.Println(item)
}

队列和栈

Go 没有内置的队列和栈,但可以使用切片来实现:

栈(使用切片)

go
// 创建栈
stack := []string{}

// 入栈
stack = append(stack, "第一个")
stack = append(stack, "第二个")
stack = append(stack, "第三个")

// 出栈
if len(stack) > 0 {
    n := len(stack) - 1
    item := stack[n]
    stack = stack[:n]
    fmt.Println("出栈元素:", item)
}

// 查看栈顶元素
if len(stack) > 0 {
    fmt.Println("栈顶元素:", stack[len(stack)-1])
}

队列(使用切片)

go
// 创建队列
queue := []string{}

// 入队
queue = append(queue, "第一个")
queue = append(queue, "第二个")
queue = append(queue, "第三个")

// 出队
if len(queue) > 0 {
    item := queue[0]
    queue = queue[1:]
    fmt.Println("出队元素:", item)
}

// 查看队首元素
if len(queue) > 0 {
    fmt.Println("队首元素:", queue[0])
}

总结

Go 语言提供了丰富的数据结构,包括内置的数组、切片、映射和结构体,以及标准库中的链表和堆。这些数据结构为不同的应用场景提供了灵活的解决方案。选择合适的数据结构对于编写高效的 Go 程序至关重要。

在实际开发中,应根据具体需求选择合适的数据结构,考虑因素包括:数据访问模式、内存使用、性能要求等。

基于 MIT 许可证发布