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

来源:互联网 发布:淘宝上传视频要收费吗 编辑:程序博客网 时间:2024/05/29 14:10

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

  • 1 数组的内部实现和基础功能
    • 11 内部实现
    • 12 声明和初始化
    • 13 使用数组
    • 14 多维数组
    • 15 在函数之间传递数组
  • 2 切片的内部实现和基础功能
    • 21 内部实现
    • 22 创建和初始化
    • 23 使用切片
      • 231 赋值和切片
      • 232 切片增长
      • 233 创建切片的 3 个索引
      • 234 迭代切片
    • 24 多维切片
    • 25 在函数间传递切片
  • 3 映射的内部实现和基础功能
    • 31 内部实现
    • 32 创建和初始化
    • 33 使用映射
    • 34 在函数间传递映射

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]*stringarray2 := [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]intarray[0][0] = 10array[0][1] = 20array[1][0] = 30arrat[1][1] = 40

4.1.5 在函数之间传递数组

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

  • 使用指针在函数间传递大数组
var array [le6]intfoo(&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,容量为 7slice := 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:21fmt.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 mainimport (    "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 mainimport (    "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}
原创粉丝点击