每日一Go-4、Go语言复合数据类型(数组|切片|映射Map)

36 阅读4分钟

Go语言复合数据类型有数组、切片与映射(Map)

1、数组:长度固定的序列数组是一个长度固定且元素类型相同的数据序列,其长度是类型的一部分[n]T。

  • 1.1 关键特性:

    • 长度固定:一旦声明,长度不可改变
    • 值类型:赋值或者传参的时候会赋值整个数组,对副本的修改不会影响原数组
    • 内存连续:元素在内存中连续存储,访问效率高
    • 长度是类型的一部分:[3]int和[5]int是不同的类型
  • 1.2 声明与初始化

// 各种初始化方式
var arr1 [3]int               // 零值初始化: [0, 0, 0]
arr2 := [3]int{1, 2, 3}       // 字面量: [1, 2, 3]
arr3 := [...]int{1, 2, 3, 4}  // 编译器推断长度: [1, 2, 3, 4]
arr4 := [5]int{1:10, 3:30}    // 索引初始化: [0, 10, 0, 30, 0]
  • 1.3 适用场景:
    • 明确知道元素数量且不会改变的情况,如一周的天数、物理常量。
    • 对内存布局有严格要求的场景(利用其连续内存特性)。
    • 作为切片的底层存储

| 处理大数组时需注意性能问题,因为它是值传递。请使用切片或数组指针

2、切片:长度不固定的灵活序列

切片是对底层数组的抽象,提供更灵活、动态的序列视图,类型写为 []T

  • 2.1 关键特性:

    • 动态长度:长度可变,可自动扩容
    • 引用类型:持有对底层数组的引用。多个切片可共享同一底层数组,对一个切片的修改可能影响其他共享底层数组的切片
    • 三元结构:包含指向底层数组的指针、长度(len)和容量(cap)。容量是指切片当前可容纳元素的最大数量
  • 2.2 新建与初始化:

// 多种创建切片方式
    slice1 := make([]int, 5)          // 创建一个长度为5的整型切片
    slice2 := []int{1, 2, 3, 4, 5}  // 使用字面量创建切片
    slice3 := slice2[1:4]             // 从已有切片创建新切片
    fmt.Println("slice1:", slice1)
    fmt.Println("slice2:", slice2)
    fmt.Println("slice3:", slice3)  
    //输出为
    //slice1: [0 0 0 0 0]
    //slice2: [1 2 3 4 5]
    //slice3: [2 3 4]
  • 2.3 常用操作:

添加元素 (append) :超出容量会触发扩容,通常容量翻倍或按照百分比增加(不固定)。扩容后切片会指向新的底层数组。

 slice1 := make([]int, 5) // 创建一个长度为5的整型切片
    // 切片追加元素
    slice1 = append(slice1, 6, 7, 8)
    fmt.Println("After appending, slice1:", slice1)

当程序运行到第三行之前,可以在debug模式下看见slice1的容量cap是5图片 当程序运行到第四行的时候,debug模式下看见slice1的容量cap是10,说明容量翻倍了

图片

切割切片slice[low:high]创建的新切片与原切片共享底层数组,也就是只创建了一个新切片的引用,这个时候对新切片内容的修改,会改变底层数组的数据

复制切片 (copy)实现切片间的深拷贝

slice1 := []int{1, 2, 3, 4, 5}     // 定义并初始化一个整数切片 slice1
    slice2 := make([]int, len(slice1)) // 创建一个与 slice1 长度相同的切片 slice2
    copy(slice2, slice1)               // 使用内置的 copy 函数将 slice1 的内容复制到 slice2
    slice1[0] = 100                // 修改 slice1 的第一个元素
    fmt.Println("slice1:", slice1) // 输出修改后的 slice1
    fmt.Println("slice2:", slice2) // 输出未被修改的 slice2,验证深拷贝效果
    //输出结果
    //slice1: [100 2 3 4 5]
    //slice2: [1 2 3 4 5]
  • 2.4 适用场景:

    • 绝大多数需要动态集合的场景。

    • 函数间传递集合数据

3、映射(Map):键值对集合映射是存储键值对的无序集合,类型写为 map[K]V

  • 3.1 关键特性:

    • 键值对:通过键快速检索值。

    • 引用类型:赋值和传参传递引用。

    • 无序性:遍历顺序不固定。

    • 动态性:可动态添加、删除键值对

  • 3.2 声明与初始化:

    //创建映射
    personMap := make(map[string]Person)
    //添加元素
    personMap["Alice"] = Person{name: "Alice", age: 30}
    personMap["Bob"] = Person{name: "Bob", age: 25}
  • 3.3 常用操作:(增加/修改、删除、查找)
    //增加/修改、删除、查找
    personMap["Charlie"] = Person{name: "Charlie", age: 35} // 增加元素
    personMap["Alice"] = Person{name: "Alice", age: 31}   // 修改元素
    delete(personMap, "Bob")                               // 删除元素  
    person, exists := personMap["Charlie"]                  // 查找元素
    if exists {
        fmt.Println("Found:", person)
    } else {
        fmt.Println("Charlie not found")
    }
    //输出为
    //Found: {Charlie 35}
  • 3.4 适用场景:

    • 键值关联数据,如字典、缓存、配置项

    • 需要快速通过键查找值的场景

4、实践时间

我们做一个投票统计程序

    votes := make(map[string]int)
    //模拟投票
    votes["Alice"] += 1
    votes["Bob"] += 1
    votes["Alice"] += 1
    //输出投票结果
    for candidate, count := range votes {
        fmt.Printf("%s: %d votes\n", candidate, count)
    }
    //输出为
    //Alice: 2 votes
    //Bob: 1 votes

世间没有一蹴而就的成功,每一个小小的坚持,最终都会汇聚成美好人生!