欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

4. 数组、切片和映射(Go Tutorial)

程序员文章站 2024-03-23 13:50:40
...

数组、切片和映射(Go Tutorial)

4.1 数组的内部实现和基础功能

4.1.1 内部实现

在 Go 语言里,数据是一个长度固定的数据类型,用于存储一段具有相同的类型的元素的连续块。

数据存储的类型可以是内置类型,如 int 和 string 等,也可以是自定义类型。

由于数组的每个元素类型相同,又是连续分配,所以可以以固定速度索引数组中的任意数据,速度非常快。

4.1.2 声明和初始化

  • 声明一个数组并初始化为零值
// 声明一个包含 5 个元素的整形数组
var array [5]int
  • 使用数组字面量声明数组
// 声明一个包含5个元素的整形数组并使用具体值初始化每个元素
array := [5]int{10, 20, 30, 40, 50}
// `...` 表示容量由初始化值的数量决定
array := [...]int{10, 20, 30, 40}
  • 声明数据并指定特定元素的值
// 声明一个有 5 个元素的数组,用具体值初始化索引为 1 和 2 的元素,其余元素保存零值
array := [5]int{1: 10, 2:20}

4.1.3 使用数组

  • 访问数组元素
array := [5]int{10, 20, 30, 40, 50}
// 修改数组中索引为 2 的元素的值
array[2] = 35
  • 访问指针数组的元素
// 声明包含 5 个元素的指向整形的数组
array := [5]*int{0: new(int), 1: new(int)}
// 为索引 0 和 1 的元素赋值
*array[0] = 10
*array[1] = 20
  • 把一个指针数组赋值给另一个
var array1 [3]*string
array2 := [3]*string{new(string), new(string), new(string)}
*array2[0] = "a"
*array2[1] = "b"
*array3[2] = "c"
array1 = array2

数组变量的类型包括长度和每个元素的类型,这有这两部分都相同的数组,才能互相赋值,否则编译器会报错

4.1.4 多维数组

  • 声明二维数组
// 声明一个二维数组,两个维度分别为存储 4 个元素和 2 个元素
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整形数组
array := [4][2]int{{10, 11}, {12, 13}, {14, 15}, {16, 17}}
// 声明并初始化外层数组中索引为 1 和 3 的元素
array := [4][2]int{1: {20, 21}, 3: {30, 31}}
// 声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1: (0: 20), 3: (1: 31)}
  • 访问二维数组
var array [2][2]int
array[0][0] = 10
array[0][1] = 20
array[1][0] = 30
arrat[1][1] = 40

4.1.5 在函数之间传递数组

在函数之间传递数组是一个开销很大的操作。因为在函数之间传递数组时,总是以值的方式传递的。

  • 使用指针在函数间传递大数组
var array [le6]int
foo(&array)
func foo(array *[le6]int) {
  // ....
}

虽然将数组的地址传给函数,不会带来很大的内存开销,但是由于传递的是指针,如果改变指针指向的值,会改变共享的内存。

4.2 切片的内部实现和基础功能

4.2.1 内部实现

切片是一种便于使用和管理的数据集合,其是围绕动态数组的概念构建的,可以按需自动增长和缩小。

切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法。

切片有 3 个字段的数据节后,这些数据结构包含 Go 语言需要操作底层数组的元数据。

这 3 个字段分别是指向底层数组的指针、切片的长度、切片的容量。

4.2.2 创建和初始化

  1. make 和 切片字面量

    • 使用长度声明一个字符串切片
// 创建一个字符串切片
// 其长度和容量都是 5 个元素
slice := make([]string, 5)

如果只指定长度,那么容量和长度相等

  • 使用长度和容量声明整形切片
// 创建一个整形切片
// 其长度为 4,容量为 7
slice := make([]int, 4, 7)

不允许创建容量小于长度的切片

  • 通过字面量来声明切片
slice := []string{"red", "blue", "yellow"}
slice := []int{10, 20, 30}
  • 使用切片字面量,可以设置初始化长度和容量
// 创建字符串切片
// 使用空字符串初始化第 100 个元素
slice := []string{99: ""}
  • 声明数组和切片的不同
// 声明数组
array := [3]int{10, 20, 30}
// 声明切片
slice := []int{10, 20, 30}

如果在 [] 运算符里指定了一个值,那么创建的就是数组而不是切片

  1. nil 和空切片

    • 创建 nil 切片
// 创建 nil 整形切片
var slice []int

需要描述一个不存在的切片时,nil 切片会很好用

  • 创建空切片
slice := make([]int, 0)
slice := []int{}

在表示空集合时,空切片很有用

4.2.3 使用切片

4.2.3.1 赋值和切片

对切片元素赋值与数组元素的赋值一样

  • 使用切片字面量来声明切片
slice := []int{10, 20, 30}
slice[1] = 21
  • 使用切片创建切片
slice := []int{10, 20, 30}
newSlice := slice[1:2]

上面两个切片共享一段底层数组,但通过不同的切片会看到底层数组的不同部分

  • 计算长度和容量

对于底层数组容量是 k 的切片 slice[i:j] 来说

长度:j - i

容量:k - i

  • 修改切片内容可能导致的结果
slice := []int{10, 20, 30}
newSlice := slice[1:2]
newSlice[0] = 21
// output:21
fmt.Pringln(slice[1])

4.2.3.2 切片增长

相对数组而言,使用切片的一个好处是,可以按需增长切片的容量。

Go 语言内置的 append 函数会处理增加长度时的所有操作细节。append 调用返回时,会返回一个包含修改结果的新切片。

函数 append 总是会增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决与被操作的切片是否有可用的容量。

  • 使用 append 向切片增加元素
slice := []int{10, 20, 30}
newSlice := slice[0 : 1]
newSlice = append(newSlice, 21)
// [10 21]
fmt.Println(newSlice)
// [10 21 30]
fmt.Println(slice)

因为 newSlice 在底层数组还有容量可以,所以和 slice 共用的是同一个底层数组,对 newSlice 的修改就是修改共用的底层数组

  • 使用 append 同时增加长度和容量
slice := []int{10, 20, 30, 40}
newSlice := append(slice, 50)
newSlice[0] = 11
// [10 20 30 40]
fmt.Println(slice)
// [11 20 30 40 50]
fmt.Println(newSlice)

如果切片的底层数组没有足够的可以容量,append 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。

在上面的例子中,newSlice 与 slice 不再共用同一个底层数组。

append 函数会智能的处理底层数组的容量增长:当切片的容量小于 1000 个元素时,总是成倍的增长容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是每次增长 25% 的容量。

4.2.3.3 创建切片的 3 个索引

在创建切片时,还可以使用第三个索引选项,第三个索引选项可以用来控制新切片的容量,其目的并不是要增加容量,而是要限制容量,这为底层数组提供了一定的保护,可以更好的控制追加操作。

  • 使用 3 个索引创建切片
source := []string{"apple", "orange", "banana", "grape", "plum"}
slice := source[2 : 3 : 4]

对于 slice[i : j : k],其长度为 j - i,容量为 k - i。

当设置容量大于已有容量时编译器会报错

  • 设置长度和容量一致
source := []string{"apple", "orange", "banana", "grape", "plum"}
slice := source[1:3:3]
slice = append(slice, "kiwi", "hello")
// [orange banana kiwi hello]
fmt.Println(slice)

如果在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个 append 函数操作创建新的底层数组,与原有的底层数组分离。

  • 将一个切片追加到另一个切片
s1 := []int{1, 2}
s2 := []int{2, 4}
// [1 2 2 4]
fmt.Printf("%v\n", append(s1, s2...))

4.2.3.4 迭代切片

Go 语言里可以使用关键字 range,它可以配合关键字 for 来迭代切片里的元素

  • 使用 range 迭代切片
slice := []int{10, 20, 30}
for _, value := range slice {
  fmt.Printf("value: %d\n", value)
}
for index, value := range slice {
  fmt.Printf("index: %d, value: %d\n", index, value)
}
  • 使用传统的 for 循环迭代切片
slice := []int{10, 20}
for index := 2; index < len(slice); index++ {
  fmt.Printf("index: %d, value: %d\n", index, slice[index])
}

内置函数 len 和 cap,可以用于处理数组、切片和通道。对于切片,len 返回长度,cap 返回容量

4.2.4 多维切片

  • 声明多维切片
slice := [][]int{{10}, {20, 30}}
slice[0] = append(slice[0], 20)
// [[10 20] [20 30]]
fmt.Println(slice)

4.2.5 在函数间传递切片

由于与切片关联的数据包含在底层数组里,不属于切片本身,所以将切片复制到任意函数的时候,对底层数组大小都不会有影响。复制时只会赋值切片本身,所以在函数间传递会非常快速、简单。

package main

import (
    "fmt"
)

func test(slice []int)  {
    var newSlice []int
    newSlice = slice
    newSlice = slice[0:len(slice):len(slice)]
    newSlice = append(newSlice, 40)
    newSlice[0] = 10
    fmt.Println(newSlice)
}

func main() {
    slice := []int{1, 2, 3, 4, 5}
    test(slice[0:3])
    fmt.Println(slice)
    // [10 2 3 40] 
    // [1 2 3 4 5]
}

4.3 映射的内部实现和基础功能

映射是一种存储一系列无序键值对的数据结构

4.3.1 内部实现

只需记住一件事:映射是一个存储键值对的无序集合

4.3.2 创建和初始化

  • 使用 make 声明映射
dict := make(map[string]int)
dict := map[string]string{"name": "li", "gender": "male"}
  • 声明一个存储字符串切片的映射
// 创建一个映射,使用字符串切片作为值
dict := map[int][]string{}

4.3.3 使用映射

func main() {
    // 创建一个空映射并赋值
    colors := map[string]string{}
    colors["red"] = "RED"
    fmt.Println(colors)
    // 对 nil 映射赋值时,会报错
    var colors2 map[string]string
    colors2["red"] = "RED"
    fmt.Println(colors2)
}

从映射取值时有两个选择。

  • 第一个选择是,可以同时获得值,以及一个表示这个是否存在的标志
value, exists := colors["blue"]
// 这个键存在吗
if exists {
  fmt.Println(value)
}
  • 另一个选择是,只返回键对应的值,然后判断这个值是否是零值来判断键是否存在
value := colors["blue"]
if value != "" {
  fmt.Println(value)
}

使用 range 关键字迭代映射

func main() {
    colors := map[string]string{"name": "lilei", "gender": "male", "country": "china"}
    for key, value := range colors {
        fmt.Printf("%s is %s\n", key, value)
    }
}

从映射中删除一项

func main() {
    colors := map[string]string{"name": "liLei", "gender": "male", "country": "china"}
    delete(colors, "name")
    for key, value := range colors {
        fmt.Printf("%s is %s\n", key, value)
    }
}

4.3.4 在函数间传递映射

将映射传递给函数成本很小,并且不会赋值底层的数据结构

package main

import (
    "fmt"
)

func removeColors(colors map[string]string, key string) map[string]string {
    delete(colors, key)
    return colors
}

func iterator(colors map[string]string)  {
    for key, value := range colors {
        fmt.Printf("%s is %s\n", key, value)
    }
}

func main() {
    colors := map[string]string{"name": "liLei", "gender": "male", "country": "china"}
    iterator(removeColors(colors, "name"))
    iterator(colors)
    // output is:
    // gender is male
    // country is china
    // gender is male
    // country is china
}
相关标签: go

上一篇: C++的inline

下一篇: c++内联函数