全栈开发知识库

Golang数组、切片及映射(四)

数组

数组就是指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素element,一个数组包含的元素个数被称为数组的长度。需要强调的一点是Go语言中数组的长度固定,无法扩容。

类型[n]T是一个有n个类型为T的值的数组。

定义变量a是一个有十个整数的数组:

var a [10]int // 10个int型的数组,初始值是10个0,数组“零值”状态

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
balance[4] = 50.0 // 将第四个元素的值设为50

q := [...] int {1,2,3} //不声明长度
r := [...] int {99:-1}  //长度为100的数组,只有最后一个是-1,其他都是0
d := [5]int{1, 2, 3} //长度为5的数组,前三个是1,2,3,后面两个是默认的0

Go语言中,可以使用数组下标来访问数组中的元素。数组下标从0开始,len(arr)-1则表示最后一个元素的下标。

只有有效的索引可以被使用,当使用等于或者大于len(arr)的索引时:如果编译器可以检测到,会给出索引超限的提示信息;如果检测不到的话编译会通过而运行时会panic。

  • 数组作为函数参数的时候,它是值传递,实际参数数组的每个元素给形参数组拷贝一份,形参的数组是实参数组的复制品。
func modify(a [5]int) {
    a[0] = 666
    fmt.Println("modify a = ", a)
}

func main() {
    a := [5]int{1, 2, 3, 4, 5}
    // 这里数组传递过去,实际是数组的值得传递
    modify(a)
    fmt.Println("main : a = ", a)
}

执行结果:

modify a =  [666 2 3 4 5]
main : a =  [1 2 3 4 5]

值类型和引用类型

上面把数组传递给函数后,修改数组里面的值,原数组不改变的问题,这里就牵扯到一个值类型和引用类型的问题:

  • 值类型:基本数据类型:int系列、float系列、bool、string、数组、结构体
  • 引用类型:指针、slice切片、map、管道chan、interface等都是引用类型

如果我们要直接修改数组中的值,那就只能是通过指针传递的方式:

func modify(a *[5]int) {
    (*a)[0] = 666
    fmt.Println("modify a = ", *a)
}

func main() {
    a := [5]int{1, 2, 3, 4, 5}
    // 通过指针传递,会直接修改数组中的值
    modify(&a)
    fmt.Println("main : a = ", a)
}

执行结果:

modify a =  [666 2 3 4 5]
main : a =  [666 2 3 4 5]

Go语言中数组是具有固定长度而且拥有零个或者多个相同或相同数据类型元素的序列。由于数组长度固定,所以在Go语言比较少直接使用。而slice长度可增可减, 使用场合比较多。更深入的区别在于:数组在使用的过程中都是值传递,将一个数组赋值给一个新变量或作为方法参数传递时,是将源数组在内存中完全复制了一份, 而不是引用源数组在内存中的地址。为了满足内存空间的复用和数组元素的值的一致性的应用需求,slice出现了,每个slice都是都源数组在内存中的地址的一个引用,源数组可以衍生出多个slice,slice也可以继续衍生其他slice。

切片(slice)

Go语言中数组是值类型,长度不可伸缩;而slice是引用类型,长度可动态增长。

Go语言中,slice表示一个拥有相同类型元素的可变长度序列。slice通常被写为[]T,其中元素的类型都是T;它看上去就像没有长度的数组类型。

slice有三个属性:

  • 指针:指针通常是从指向数组的第一个可以从slice中访问的元素,这个元素不一定是数组的第一个元素
  • 长度:长度指的是slice中的元素个数,它不能超过slice的容量
  • 容量:容量的大小通常大于等于长度,会随着元素个数增多而动态变化

一个slice会指向一个序列的值,并且包含了长度信息。
[]T是一个元素类型为T的切片(slice)。
len(s)返回slice s的长度。 要创建非零长度的空切片,请使用内置make()函数。

package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("s ==", s)

    for i := 0; i < len(s); i++ {
        fmt.Printf("s[%d] == %d\n", i, s[i])
    }
}

打印结果为:

s == [2 3 5 7 11 13]
s[0] == 2
s[1] == 3
s[2] == 5
s[3] == 7
s[4] == 11
s[5] == 13

Go语言的内置函数len和cap用来返回slice的长度和容量。在追加元素时,如果容量cap不足时,cap一般变为原来的2倍来实现扩容。

slice的cap扩容规则:

  • 如果新的cap大小是当前cap的2倍以上,则直接扩容为这个新的cap;
  • 否则循环以下操作:如果当前cap小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新的cap。

切片和数组的转换

func main() {
    a := [...]int{1, 2, 3, 4, 5}
    // 三个参数分别是数组起始角标 结束角标(不包含该角标) 容量
    s := a[0:3:5]
    fmt.Println("s = ", s)
    fmt.Println("s. length : ", len(s))
    fmt.Println("s. cap : ", cap(s))
}

执行结果:

s =  [1 2 3]
s. length :  3
s. cap :  5

切片(slice)的切片

Go切片(Slice)是Go数组的一个抽象。 由于Go数组允许定义类型的变量,可以容纳相同数据类型的几个数据项,但它不提供任何内置的方法来动态增加其大小或获取自己的子数组。切片就没有这样的限制。 它提供了数组所需的许多实用功能,并广泛用于Go编程。

要定义切片,可以将其声明为数组,而不指定大小或使用make函数创建一个。

var numbers []int /* a slice of unspecified size */
/* numbers == []int{0,0,0,0,0}*/
numbers = make([]int,5,5) /* a slice of length 5 and capacity 5*/

切片(slice)可以包含任意的类型,包括另一个slice。

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Create a tic-tac-toe board.
    game := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }

    // The players take turns.
    game[0][0] = "X"
    game[2][2] = "O"
    game[2][0] = "X"
    game[1][0] = "O"
    game[0][2] = "X"

    printBoard(game)
}

func printBoard(s [][]string) {
    for i := 0; i < len(s); i++ {
        fmt.Printf("%s\n", strings.Join(s[i], " "))
    }
}

运行结果是:

X _ X
O _ _
X _ O

因为切片(Slice)是数组上的抽象。 它实际上使用数组作为底层结构体.len()函数返回切片中存在的元素数量, 其中cap()函数返回切片(Slice)的容量(大小),即可容纳多少个元素。 以下是解释切片(Slice)的用法的示例:

package main

import "fmt"

func main() {
   var numbers = make([]int,3,5)

   printSlice(numbers)
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) // len=3 cap=5 slice=[0 0 0]
}

如果缺省情况下声明没有输入切片,则将其初始化为nil。 其长度和容量为零。 按照Go语言规范,任何类型在未初始化时都对应一个零值:布尔类型是false,整型是0,字符串是"",而指针,函数,interface,slice,channel和map的零值都是nil.

package main

import "fmt"

func main() {
   var numbers []int

   printSlice(numbers)

   if(numbers == nil){
      fmt.Printf("slice is nil")
   }
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

切片(Slice)允许指定下界和上界,以使用[lower-bound:upper-bound]获取它的子切片。

如numbers[1:4]

切片(Slice)允许使用append()函数增加切片的容量(大小)。使用copy()函数,将源切片的内容复制到目标切片。以下是示例:

package main

import "fmt"

func main() {
   var numbers []int
   printSlice(numbers)

   /* append allows nil slice */
   numbers = append(numbers, 0)
   printSlice(numbers)

   /* add one element to slice*/
   numbers = append(numbers, 1)
   printSlice(numbers)

   /* add more than one element at a time*/
   numbers = append(numbers, 2,3,4)
   printSlice(numbers)

   /* create a slice numbers1 with double the capacity of earlier slice*/
   numbers1 := make([]int, len(numbers), (cap(numbers))*2)

   /* copy content of numbers to numbers1 */
   copy(numbers1,numbers)
   printSlice(numbers1)   
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

执行结果是:

len=0 cap=0 slice=[]
len=1 cap=2 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=8 slice=[0 1 2 3 4]
len=5 cap=16 slice=[0 1 2 3 4]

函数copy是在两个slice减复制数据,复制长度以len小的为准,两个slice可指向同一底层数据。

data := [...]int[0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := data[8:]  // {8, 9}
s2 := data[:5] // {0, 1, 2, 3, 4}
copy(s2, s1) 

fmt.Println(s2) // {8, 9, 2, 3, 4}
fmt.Println(data) // {8, 9, 2, 3, 4, 5, 6, 7, 8}

因为slice是引用,是指向的同一底层数组,所以会直接把原数据也就行了修改。

切片的合并

append的第一个参数是slice,第二个参数是元素。结合下Go语言函数的不定参数,我们可以用append优雅地实现两个slice的拼接。例如,下面的函数能合并两个slice的全部元素到一个新的slice,并返回新slice的长度len和容量cap:

func main() {
  a := []int{1, 3, 5}
  b := []int{2, 4, 6}
  c := append(a, b...)
  fmt.Print(len(c), cap(c))
}

执行结果:

6 6 [1 3 5 2 4 6]

映射

Go语言中map是一种特殊的数据结构:一种元素对(pair)的无序集合,pair的一个元素是key,对应的另一个元素是value,所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构:给定key,对应的value可以迅速定位。简单的理解就是java中的map集合,用来存储键值对。

map是引用类型,内存用make方法来分配。一旦容量不够,它会自动扩容。 必须要使用make函数来创建映射。

/* declare a variable, by default map will be nil*/
var map_variable map[key_data_type]value_data_type

/* define the map as nil map can not be assigned any value*/
map_variable = make(map[key_data_type]value_data_type)

make(map[KeyType]ValueType, initialCapacity)// 这里可以指定长度,但是如果指定长度是10,map里面却一个数据也没有,这时候len()得出的长度会是0
make(map[KeyType]ValueType)
map[KeyType]ValueType{}
map[KeyType]ValueType{key1 : value1, key2 : value2, ... , keyN : valueN}

示例:

package main

import "fmt"

func main() {
   var countryCapitalMap map[string]string
   /* create a map*/
   countryCapitalMap = make(map[string]string)

   /* insert key-value pairs in the map*/
   countryCapitalMap["France"] = "Paris"
   countryCapitalMap["Italy"] = "Rome"
   countryCapitalMap["Japan"] = "Tokyo"
   countryCapitalMap["India"] = "New Delhi"

   /* print map using keys*/
   for country := range countryCapitalMap {
      fmt.Println("Capital of",country,"is",countryCapitalMap[country])
   }

   /* test if entry is present in the map or not*/
   capital, ok := countryCapitalMap["United States"]
   /* if ok is true, entry is present otherwise entry is absent*/
   if(ok){
      fmt.Println("Capital of United States is", capital)  
   }else {
      fmt.Println("Capital of United States is not present") 
   }

   delete(countryCapitalMap,"France"); // delete()用来根据key来删除对应的数据
}

结果:

Capital of India is New Delhi
Capital of France is Paris
Capital of Italy is Rome
Capital of Japan is Tokyo
Capital of United States is not present

这里需要强调下,根据键值索引某个元素时,也会返回两个值:索引到的值和本次索引是否成功(这里可能会因为索数值越界或者索引键值有误而导致索引失败)。

package main

import(
    "fmt"
)

func main(){
    ages01 := map[string]int{
        "alice":31,
        "bob":13,
    }

    age,ok := ages01["bo"]   //age才是根据键值索引到的值
    if !ok{
        fmt.Printf("索引失败,bo不是map的键值,此时age=%d",age)  //索引失败会返回value的零值,这里是int类型,所以是0
    }   else{
        fmt.Printf("索引成功,age=%d",age)
    }
}

范围(range)

for循环的range格式可以对slice或者map进行迭代循环。

当使用for循环遍历一个slice时,每次迭代range将返回两个值。 第一个是当前下标(序号), 第二个是该下标所对应元素的一个拷贝。

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
}

执行结果:

2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
  • map的遍历

    map的遍历是无序的。

func main() {
    m := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"}
    for key, value := range m {
        fmt.Println("key = ", key, " value = ", value)
    }

    // 删除map中的元素,删除key为1的元素
    delete(m, 1)
}

执行结果:

key =  3  value =  c
key =  4  value =  d
key =  1  value =  a
key =  2  value =  b

map和slice类似,都是引用,所以如果作为函数参数传递后,在函数内操作map后,原map中的数据也会发生改变。


  • 邮箱 :charon.chui@gmail.com
  • Good Luck!
本页由 Markdown 自动渲染 · 共 628 篇笔记