天天看点

Go学习笔记(9)Go容器类型——数组&切片&map写在前面数组切片Map

文章目录

  • 写在前面
  • 数组
  • 切片
  • Map

写在前面

    前面的文章介绍了Go的一些基本类型,本文开始涉及Go的一些容器类型,它们都是可以包含多个元素的数据结构,如数组、切片、map

数组

    数组是具有相同类型且长度固定的一组元素集合,定义的格式:

var 数组名 [数组长度]数组元素类型

,下面声明一个长度为5的int型数组

arr

  • 数组元素可以是任意的原始类型,例如整型、字符串等,一个数组内所有元素的类型必须是相同的
  • 数组的长度必须是一个常量表达式,且是一个非负整数,特别注意数组的长度也是数组类型的一部分,因此不同长度的数组是不同的类型。数组的长度在声明时就要给出。获取数组长度用内置函数

    len(arr)

  • 当声明数组时所有的元素都会被初始化为默认的类型零值,我们也可以在声明数组的同时进行初始化
    var arr = [5]int{1, 2, 3, 4, 5}
    a := [3]int{1, 2, 3}
               
    还可以在初始化时仅初始化指定元素
  • 如果声明数组时不想直接写长度,可以用

    ...

    代替,编译器会自动生成满足最低长度要求的数组
  • 通过

    for

    循环来遍历操作数组
    var arr [5]int
    for i := 0; i < len(arr); i++ {
    	arr[i] = i * 2
    }
    for i, v := range arr {
    	fmt.Println("index: ", i, "value: ", v)
    }
               
  • 数组是一种值类型(不像C/C++那样是指向首元素的指针),可以通过

    new

    来创建数组,不过它返回的是指向该数组的指针
    d := new([4]int)
    fmt.Println(d)            //&[0,0,0,0]
    fmt.Println(*d)           //[0,0,0,0]
               
    注意区分指向数组的指针和指针数组
    e := [5]int{1, 2, 3, 4, 5}
    var p *[5]int = &e              //p为指向数组e的指针 &[1,2,3,4,5]
    x, y := 1, 2
    z := [2]*int{&x,&y}            //z是一个指针数组
               
  • 多维数组
    h := [2][3]int{
    	{1, 1, 1},
    	{2, 2, 2}
    }
               
  • 数组可以使用

    ==

    !=

    来比较
    f := [2]int{1, 2}
    g := [2]int{1, 2}
    fmt.Println(f == g)                 //true
               

切片

    切片(slice)是对某个数组的一段连续片段的引用(该数组我们称为相关数组,该片段可以是整个数组,也可以是数组中的某一段),因此切片是一个引用类型。切片底层实现是数组,它与数组的关系如下图:

Go学习笔记(9)Go容器类型——数组&amp;切片&amp;map写在前面数组切片Map

    多个slice可以指向同一个底层相关数组,此时其中一个值改变会影响全部的值

  • 切片是一个可变长的数组,它的长度可以动态修改,可以用

    len()

    来获取切片的长度。切片还有另外一个属性容量,表示切片可以达到的最大长度,可以用

    cap()

    来获取。切片的容量等于切片的长度+相关数组中在切片之后剩下的的长度。所以切片的长度小于等于切片的容量。
  • 切片的容量是预先分配的,如果在运行的过程中,切片的长度超过了原来分配的容量,则会重新分配一个空间更大的数组,然后将值都拷贝过去
  • 切片的声明格式:

    var 切片名 []元素类型

    ,这里不需要指定长度,如果是数组则必须指定长度或者用

    ...

    替代
  • 切片可以通过底层相关数组切取,也可以用

    make

    直接创建,还可以通过已有的切片来生成
    • 通过数组生成
      arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
      s1 := arr[2:5]           //切取数组arr索引从2~4这一段(即[3,4,5]),注意索引值不包括5
      s2 := arr[:]               //切片为整个arr数组
      s3 := arr[5:]             //切取从索引5开始到末尾,即[6,7,8,9,10]
                 
    • 通过

      make

      创建

      格式:

      make([]type, len, cap)

      ,其中

      cap

      可以省略,省略则默认和

      len

      相同
      s4 := make([]int, 3, 20)
      s5 := make([]int, 3)
                 
    • 通过切片生成,这种方式有时也被称之未称未切片重组reslice
      sa := []int{1, 2, 3, 4, 5}
      sb := sa[1:4]                    //[2,3,4],len:3,cap:4
      sc := sb[1:4]                    //[3,4,5],len:3,cap:3
                 
          需要注意的是,新的切片的索引不能超过原切片的容量,否则会引发编译错误,而不是重新分配数组
  • 在切片上追加元素(append)

        如果要在切片上添加新的元素,可以使用

    append

    函数,用法为

    append(被追加元素的slice, 追加的元素...)

    s1 := make([]int, 3, 6)
    s1 = append(s1, 1, 2, 3)     //[0,0,0,1,2,3],还是返回原来的slice (没有超过原来cap)
    s1 = append(s1, 1, 2, 3)     //[0,0,0,1,2,3,1,2,3],返回的是一个新的slice(已经超过了cap,重新分配底层数组)
               
        注意,如果追加后的长度未超过原slice的容量,则返回原始的slice,如果超过了,则重新分配空间更大的数组并拷贝原始数据
  • 拷贝slice

        可以使用

    copy

    函数将一个切片拷贝到另外一个切片中,用法

    copy(目标slice, 被拷贝的slice)

    sc1 := []int{1, 2, 3, 4, 5}
    sc2 := []int{6, 7, 8}
    copy(sc1, sc2)
    fmt.Println(sc1)                 //[6 7 8 4 5]
    copy(sc2,sc1)
    fmt.Println(sc2)                //[6,7,8]
    copy(sc2[0:2], sc1[3:5])       //指定具体位置
    fmt.Println(sc2)               //[4 5 8]
               

Map

    Map是Go里面的键值对集合,由key-value对组成,给定key,可以快速定位到对应的value。也被称为字典、哈希表等

  • map中的key可以是任意能够用

    ==

    或者

    !=

    操作符比较的类型,比如

    string

    int

    float

    等,不能是函数、map、切片;value可以是任意类型
  • map的声明方式

    var map变量名 map[key类型]vlaue类型

    var m map[int]string               //声明
    m = map[int]string{1:"a", 2:"b"}  //初始化
               
        特别注意,map必须要初始化才能使用,即如果用上面这种方式,必须要有初始化的语句

    map1 = map[keyType]ValueType{}

    ,否则将报错。当然如果觉得太繁杂,可以使用下面的

    make

    语句来替代,更加简洁(主要是因为使用前需要先分配好内存空间给map,使用初始化语句或者

    make

    语句才能实现空间的分配)
  • map也属于引用类型,声明的时候不需要知道长度,可以动态增加,可以使用

    make

    来创建
    var m1 map[int]string = make(map[int]string)
    m2 := make(map[int]string)           //简要写法
    m1[1] = "ok"                         //插入 (1 : ok)的key-value
    m2[1] = "good"
               
        

    make

    创建还可以指定容量,

    make(map[key类型]value类型,cap)

    cap

    为容量,可以省略。超出容量会自动扩容,但为了性能还是尽量提供一个大概的初始值。
  • 获取指定key的value

    a:=m[1]

    (获取key为1的value)
  • 插入一个k-v对(或对指定key进行修改)

    m[1]="ok"

  • 判断键值对是否存在

        上面获取指定key的值

    map[key]

    还有另外一种用法:

    value,isPresent = map[key]

    ,即返回两个值,第二个值

    isPresent

    是布尔类型,如果该key存在,该值为

    true

    ,且

    value

    为该key对应的值;如果key不存在,则

    isPresent

    false

    ,且

    value

    为空值。
    if value, ok := map1[key1];ok{
    	fmt.Println(value)
    }
               
  • 删除指定key的键值对

    delete(map1, key1)

  • 使用

    for-range

    对map进行遍历
    map1 := make(map[int]string)
    map1[1] = "a"
    map1[2] = "b"
    map1[3] = "c"
    for key, value := range map1 {
    	fmt.Println(key,value)
    }
               
        如果指向获取key或者value,可以这么使用
    for key := range map1 {
    	fmt.Println(key)
    }
    for _, vlaue := range map1 {
    	fmt.Println(value)
    }
               
        需要注意的是,

    for

    中获得的key和value值都是副本,直接对这两个值进行修改并不会对原来的map有影响。需要用

    map[key]

    才能真正改变map中的值
  • map类型的切片:注意需要使用两次

    make

    ,第一次分配切片,第二次分配切片中每个map元素
    sm := make([]map[int]string, 5)
    for i := range sm {
    	sm[i] = make(map[int]string,1)
    	sm[i][1]="ok"
    }
               
  • 嵌套map的使用:即map中value也是map类型,这种情况同样需要注意每一个嵌套的map也需要进行

    make

    之后才能使用(初始化分配空间)
    var m2 map[int]map[int]string
    m2 = make(map[int]map[int]string)
    m2[1] = make(map[int]string)     //value中嵌套的map也需要进行初始化
    m2[1][1] = "ok1"
               

继续阅读