乐趣区

关于golang:Golang程序设计数据容器

本文学习 Go 语言数据容器、包含数组、切片和映射。

数组

数组是一个数据汇合,罕用于存储用数字索引的同类型数据。Go 语言的数组调用函数时应用的是值传递,因而形参会拷贝一份实参的值。

在 Go 语言中,申明数组须要同时指定长度和数据类型,数组长度是其类型的一部分,因而 [5]int[1]int是两种类型。

Go 语言能够对数组进行写入、读取、删除、遍历等操作。

package main

import "fmt"

func main() {
    // 申明数组并指明长度,不初始化,因而 a 的 5 个元素为 int 类型的零值(0)var a [5]int
    // 申明数组并指明长度,并初始化 4 个元素,因而 b 的最初 1 个元素为 int 类型零值(0)var b = [5]int{1, 2, 3, 4}
    // 申明数组,不指明长度,编译器会依据值数量推导长度为 4
    var c = [...]int{1, 2, 3, 4}
    // 数组写入
    a[0] = 0
    a[1] = 1
    // 数组读取
    fmt.Printf("a[0]=%d\n", a[0])
    // 数组删除(赋零值)a[0] = 0
    // 数组的遍历
    for index, value := range c {fmt.Printf("c[%d]=%d\n", index, value)
    }
    // 输入 b
    fmt.Printf("b=%v\n", b)
}

切片

应用切片

在 Go 语言中,数组是一个重要的类型,然而应用切片的状况更多。切片是底层数组中的一个间断片段,因而数组反对的个性切片也全副反对,必须程序遍历、通过索引拜访元素等等。

为何应用切片的状况更多呢?次要是因为 Go 语言的数组不反对主动扩容,而且不反对删除元素,更重要的是 Go 语言数组是值类型,切片是援用类型,在向函数传参时切片领有更好的性能。

package main

import "fmt"

func main() {
    // 申明一个大小为 0 的 int 类型切片
    var a = make([]int, 0)
    // 增加三个元素
    a = append(a, 1, 2, 3)
    fmt.Println(a)
    // 遍历元素
    for index, value := range a {fmt.Printf("a[%d]=%d\n", index, value)
    }
    // 申明一个大小为 4 的切片
    var b = make([]int, 4)
    // 将 a 的元素复制到 b
    copy(b, a)
    // 删除指定下标的元素
    a = append(a[:1], a[2:]...)
    fmt.Printf("a=%v\n", a)
    fmt.Printf("b=%v\n", b)
    // 应用值初始化切片
    var c = []int{1, 2, 3, 4}
    fmt.Printf("c=%v\n", c)
    // 只定义,不初始化切片
    var d []int
    d = append(d, 1, 2, 3)
    fmt.Printf("d=%v\n", d)
}

申明切片能够不应用 make 初始化,append 也不会报错。

运行时构造

切片运行时构造如下:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
  1. array 是底层数组
  2. len 是数组大小,能够通过 len 函数获取
  3. cap 是数组容量,能够通过 cap 函数获取

make 函数创立切片有两种写法:

make([]int, 0) // 1
make([]int, 0, 8) // 2
  1. 申明了一个长度为 0 的切片,此时 len 为 0,cap 也为 0
  2. 申明一个长度为 0,容量为 8 的切片,此时 len 为 0,cap 为 8

追加元素

Go 语言提供 append 函数追加元素到切片中,append 会在必要时扩容底层数组。扩容规定如下:

  1. 新容量小于 1024 时,每次扩容 2 倍。例如现有容量为 2,扩容后为 4
  2. 新容量大于 1024 时,每次扩容 1.25 倍。例如现有容量为 1024,扩容后为 1280
package main

import "fmt"

func main() {
    // 间接应用值初始化切片
    var a = []int{1, 2, 3, 4, 5}
    a = append(a, 6, 7)
    var b = []int{9, 10}
    // 追加 b 的全副元素到 a
    a = append(a, b...)
    fmt.Printf("a=%v\n", a)
    fmt.Printf("b=%v\n", b)
}

范畴操作符

切片反对取范畴操作,新切片和原切片共享底层数组,因而对切片的批改会同时影响两个切片。

范畴操作符语法如下:a[begin:end],左闭右开区间。因而 a[1:10]蕴含 a 切片索引为 1~9 的元素。

package main

import "fmt"

func main() {
    // 间接应用值初始化切片
    var a = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    var b = a[1:10]
    fmt.Println(b)
    // 批改新切片元素
    b[0] = 11
    fmt.Println(a)
    fmt.Println(b)
}

能够看到批改 b 索引为 0 的元素为 11 之后,a 切片也同时受到影响。

范畴操作符的切片这一点在编程中要特地留神!

删除元素

利用范畴操作符和 append 函数能够删除指定的切片元素。

package main

import "fmt"

func main() {
    // 间接应用值初始化切片
    var a = []int{1, 2, 3, 4, 5}
    // 删除第 2 个元素
    a = append(a[:1], a[2:]...)
    fmt.Println(a)
    // 删除第 2、3 个元素
    a = []int{1, 2, 3, 4, 5}
    a = append(a[:1], a[3:]...)
    fmt.Println(a)
}

复制元素

通过 copy 函数能够复制切片的全副或局部元素。在复制切片之前,须要申明好指标切片并设置 len。

len必须大于0,否则将不会复制任何元素。

package main

import "fmt"

func main() {
    // 间接应用值初始化切片
    var a = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    var b = make([]int, 0, 8)
    copy(b, a)
    fmt.Println(b)
    var c = make([]int, 8)
    copy(c, a[9:10])
    fmt.Println(c)
}

程序输入如下:

[]
[10 0 0 0 0 0 0 0]

能够看到切片 b 没有任何值,切片 c 胜利复制了 a 的最初一个元素。

映射

映射也叫字典、哈希表,数组和切片是通过数字索引拜访的程序汇合,而映射是通过键来拜访的无序汇合。映射在查找方面十分高效,有着 O(1)的工夫复杂度,是十分罕用的数据结构。

应用映射

映射必须初始化之后能力应用,这一点和切片不同。

package main

import "fmt"

func main() {
    // 应用 make 初始化映射
    var a = make(map[string]int)
    a["zhangsan"] = 18
    a["lisi"] = 28
    fmt.Printf("a=%v\n", a)
    // 应用值初始化映射
    var b = map[string]int{
        "zhangsan": 18,
        "lisi":     28,
    }
    fmt.Printf("b=%v\n", b)
    // 遍历映射
    for key, value := range b {fmt.Printf("%s=%d\n", key, value)
    }
}

上面是未初始化映射的应用

package main

import "fmt"

func main() {var a map[string]int
    a["zhangsan"] = 1
    for k, v := range a {fmt.Printf("%s=%d\n", k, v)
    }
}

该程序会产生运行时谬误:

panic: assignment to entry in nil map
goroutine 1 [running]:
main.main()
/Users/example/go/src/go-microservice-inaction/src/2.1/main.go:7 +0x5d

运行时构造

映射的运行时构造如下:

type hmap struct {
    count      int
    flags      uint8
    B          uint8
    noverflow  uint16
    hash0      uint32
    buckets    unsafe.Pointer
    oldbuckets unsafe.Pointer
    nevacuate  uintptr
    extra      *mapextra
}

局部字段阐明如下:

  1. count 是目前映射的键值对数量
  2. B 是映射的容量,对数。例如 B 为 8,则映射容量为 28=256
  3. buckets 中存储具体的键值对
  4. oldbuckets 在扩容中会应用到
  5. nevacuate 扩容进度指示器

当装载因子超过 6.5 时,映射将产生扩容操作。装载因子计算公式:count/2B。例如以后为为 166,此时装载因子为 166/28=0.6484375,持续插入元素时,装载因子变为 167/28= 0.65234375,此时会触发主动扩容。

每次扩容会减少 1 倍的空间,同时会对已存在的键值对进行渐进式迁徙(一次迁徙一小部分)。

增加元素

Go 语言映射增加元素和其余语言相似,应用 [] 语法即可。

var m = make(map[string]int)
m["name"] = 18

增加元素时运行时会主动解决扩容和键值对迁徙,无需用户程序关怀。

删除元素

要从映射中删除元素,须要应用 delete 函数。

var m = map[string]int{"zhangsan":18,}
delete(m, "zhangsan") 

小结

本章介绍了 Go 语言罕用的数据容器,其中对切片和映射的底层原理进行了简略介绍。Go 语言通过内置切片和映射解决了 C 语言须要手动实现这两种罕用数据结构的问题,进步了开发效率。在下一章将介绍 Go 语言的函数。

退出移动版