自学内容网 自学内容网

Go语言之路————数组、切片、map

Go语言之路————数组、切片、map

前言

  • 我是一名多年Java开发人员,因为工作需要现在要学习go语言,Go语言之路是一个系列,记录着我从0开始接触Go,到后面能正常完成工作上的业务开发的过程,如果你也是个小白或者转Go语言的,希望我这篇文章对你有所帮助。
  • 有关go其他基础的内容的文章大家可以查看我的主页,接下来主要就是把这个系列更完,更完之后我会在每篇文章中挂上连接,方便大家跳转和复习。

在java中,大家最常用的就是list和map,而在go中,最常用的就是切片和map,下面我就一一介绍一下它们的用法。

一、数组

  1. 用var定义一个数组,var后面跟数组名字,后面中括号代表数组容量(容量一定要是常量),最后面代表数组值的数据类型

    var array [5]int
    
  2. 初始化数组
    其实在第一步中,我们用var声明的时候,已经相当于初始化了数组,这样声明出来的数组,里面数据全是0,容量为5
    我们还可以用值等的方式去初始化:

    func main() {
    array := [5]int{1, 2, 3, 4, 5}
    fmt.Println(array)
    }
    //输出:[1 2 3 4 5]
    
  3. 获取数组长度 、容量
    获取长度,我们需要用到len函数,容量需要用到cap函数,不过数组的长度和容量相等的,容量对切片才有意义。

    fmt.Println(len(array))
    fmt.Println(cap(array))
    //输出:5
    
  4. 根据下标操作元素

    func main() {
    array := [5]int{1, 2, 3, 4, 5}
    fmt.Println(array[1])
    array[1] = 11
    fmt.Println(array[1])
    }
    
  5. 遍历
    数组的遍历,和下面切片的遍历,都可以用上篇文章我们讲的for range来实现

    func main() {
    array := [5]int{1, 2, 3, 4, 5}
    for _, value := range array {
    fmt.Println(value)
    }
    }
    
  6. 数组的切割
    数组可以根据下标去切割,区间为左闭右开,切割后,就变成了切片

    func main() {
    array := [5]int{1, 2, 3, 4, 5}
    array1 := array[1:3]
    fmt.Println(array1)
    }
    //输出:[2 3]
    

    上面示例中,经过我们切割后的array1,就是一个切片,我们来打印一下数据类型:

    fmt.Printf("%T", array)
    fmt.Printf("%T", array1)
    //输出:[5]int[]int
    

    [5]int是一个数组,[]int是一个切片
    要注意,这时候的切片array1和数组array指向的是同一片内存,我们修改切片中的内容,也会修改到数组中的值,看代码:

    func main() {
    array := [5]int{1, 2, 3, 4, 5}
    slice := array[1:3]
    fmt.Println(array[1])
    slice[0] = 88
    fmt.Println(array[1])
    }
    //输出:
    2
    88
    

    如果我们要对切片做更改怎么办,可以用slices的Clone函数,我们拷贝一个新的切片出来,避免修改到同一个内存中的数据

    func main() {
    array := [5]int{1, 2, 3, 4, 5}
    array1 := array[1:3]
    clone := slices.Clone(array1)
    clone[0] = 88
    fmt.Println(array[1])
    }
    //输出:2
    

数组是值类型,并且不能扩容

二、切片

定义:和数组几乎一模一样,但是又有着很明显的区别,那就是切片可以动态扩展,而数组当你定义好长度后,就不能动态去扩展了。而且数组是值类型,切片是引用类型,切片的底层实现依旧是数组,可以简单理解为是指向底层数组的指针。切片我们在实际开发中用的最多,所以我们就详细来讲一下,用法和数组基本一致,会操作切片了,也就会操作数组了。

  1. 定义一个切片,有如下4几种方法

    var slice[]int // 值
    slice := []int{1, 2, 3} // 值
    slice := make([]int, 0, 0) // 值
    slice := new([]int) // 指针
    

    通过上面数组的第六点和现在的代码,我们可以看到切片和数组定义几乎一模一样,只是没有在中括号中去声明数组的长度

  2. make函数
    通常情况下,我们用make函数来定义一个切片,下面要讲的map也是,都用make函数,make函数接受3个参数,第一个参数代表定义的数据类型,第二个参数代表长度,第三个参数代表的是容量

    func main() {
    slice := make([]int, 0, 0)   // 值
    slice2 := make([]int, 4, 40) // 值
    fmt.Println(len(slice), cap(slice))
    fmt.Println(len(slice2), cap(slice2))
    }
    //输出:
    0 0
    4 40
    

    怎么去理解长度和容量呢,长度就是代表着切片当前的数据长度,而容量代表着切片允许最大的数据长度,一旦超过容量,就要进行扩容,就像水库蓄水一样。

  3. new关键词
    既然这里提到了,就简单说一下,new关键词跟java不一样,不是创建一个新的对象出来,而是创建一个指针,后面会详细说指针。

  4. append函数向尾部添加元素
    切片的下标读取、修改、for循环遍历等和数组完全一样,这里就不写重复代码了,重点提一下他们的区别,如何动态扩容:append函数
    append向末尾添加新元素,并且返回一个切片

    func main() {
    slice := make([]int, 0, 0) // 值
    slice = append(slice, 1)
    fmt.Println(slice, len(slice), cap(slice))
    }
    //输出:[1] 1 1
    

    我们即使定义一个长度和容量都为0的切片,用append添加一个元素后,会动态扩容为1的切片。这里再简单提一下返回的新切片的扩容策略:在 1.18 版本更新之后,slice 的扩容策略变为了: 当原 slice 容量(oldcap)小于 256 的时候,新 slice(newcap)容量为原来的 2 倍;原 slice 容量超过 256,新 slice 容量 newcap = oldcap+(oldcap+3*256)/4,这个大家了解一下就行了,一般只有面试才能用到。
    因为这个扩容机制,所以才会出现长度和容量不一致的情况,这都是正常的内部算法。

  5. append插入元素
    从头部插入元素

    func main() {
    slice := []int{1, 2, 3, 4, 5}
    slice = append([]int{-1, 0}, slice...)
    fmt.Println(slice)
    }
    

    从中间插入元素,比如我们是从下标为3开始插入

    func main() {
    slice := []int{1, 2, 3, 4, 5}
    slice = append(slice[:3+1], append([]int{999, 999}, slice[3+1:]...)...)
    fmt.Println(slice)
    }
    

    尾部插入
    尾部插入就是我们上面已经使用过的,直接调用就好,可以支持一次性插入多个

    func main() {
    slice := []int{1, 2, 3, 4, 5}
    slice = append(slice, 6, 7, 8)
    fmt.Println(slice)
    }
    

    我们如果仔细去阅读一下头部插入和中间插入的代码,无非就是定义一个新的切片,然后组装老的切片,以实现插入的效果。

  6. 删除元素
    定义以下切片

    slice := []int{1, 2, 3, 4, 5}
    
    • 删除头部和尾部,直接用上面讲到的数据切割的知识点就行了。

      //删除头部2个元素
      slice = slice[2:]
      //删除尾部2个元素
      slice = slice[:3]
      //输出分别为:[3 4 5] 和[1 2 3]
      
    • 从中间指定下标 i 位置开始删除 n 个元素

      这个就要借助append函数,本质上还是和插入一样,新切片的组装,这点就是要吐槽的一点,没有官方的api封装,这个使用起来很是麻烦。

      slice = append(slice[:i], slice[i+n:]...)
      
  7. 拷贝
    从一个切片拷贝到另一个切片,切记目标切片的长度一定要大于等于源切片

    func main() {
    source := []int{1, 2, 3, 4, 5}
    target := make([]int, 5, 10)
    copy(target, source)
    fmt.Println(target)
    }
    

    就像这个例子,我们定义target的时候,它的长度一定要大于等于5,才能拷贝全,如果我们定义长度为4,那么拷贝后就是[1,2,3,4],5拷贝不进来。

  8. 遍历
    上面讲数组说过了,主要常用两种方式,fori和for range,直接看代码:

    source := []int{1, 2, 3, 4, 5}
    for i := 0; i < len(source); i++ {
    fmt.Println(source[i])
    }
    for _, v := range source {
    fmt.Println(v)
    }
    
  9. 清空数组或者切片
    clear函数

    source := []int{1, 2, 3, 4, 5}
    clear(source)
    
  10. 二维数组或者切片
    这里简单提一下,其实和java差不多,只是要结合go来使用
    分别定义一个二维的数组和二维切片

    func main() {
    aa := [5][5]int{}
    fmt.Println(aa)
    bb := make([][]int, 5)
    fmt.Println(bb)
    }
    //输出:
    [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
    [[] [] [] [] []]
    

    通过这里大家也看到了,数组长度是固定的,所以定义出来就是好的,但是切片不一样,所以切片的多维我们要循环单独定义

    func main() {
    bb := make([][]int, 5)
    for i := range bb {
    bb[i] = make([]int, 5)
    }
    fmt.Println(bb)
    }
    //输出:
    [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
    

三、map

go中map的术语叫做映射,它和java的map基本一致,都是k,v结构,只是用法有细微的差别,我给你简单举几个例,相信你很快就能掌握它

  1. 我们先来用var和make函数定义一个映射

    //定义一个key是string类型,value是string类型的一个映射
    var a map[string]string
    //定义一个key是string类型,value是int类型的一个映射
    a := make(map[string]int, 5)
    

    可以看到,map的语法:map[keyType]valueType{},然后后面跟一个初始容量,默认是0

  2. map的添加和读取
    map的添加和读取都是通过key索引来做的,就跟数组一样,非常简单

    func main() {
    a := make(map[string]int, 5)
    a["one"] = 1
    a["two"] = 2
    fmt.Println(a)
    i := a["one"]
    fmt.Println(i)
    }
    
    //输出:
    map[one:1 two:2]
    1
    

    map的访问是有返回的,可以用来判断是否存在exist,如果不存在就返回false和默认值,int默认值是0

    func main() {
    a := make(map[string]int, 5)
    a["one"] = 1
    val, exist := a["f"]
    fmt.Println(exist,val)
    }
    //输出:false,0
    
  3. map的删除和清空
    使用go的两个内置函数,delete和clear

    func main() {
    a := make(map[string]int, 5)
    a["one"] = 1
    a["two"] = 2
    delete(a,"one")
    clear(a)
    }
    
  4. map的遍历
    也是我们的老朋友,for range,range迭代两个参数,一个k一个v

    func main() {
    a := make(map[string]int, 5)
    a["one"] = 1
    a["two"] = 2
    for k, v := range a {
    fmt.Println(k, v)
    }
    }
    

map不是并发安全的,go中有sync.Map来做并发安全的map,类似于java的ConcurrentHashmap

好了,以上就是本节全部内容了,下一篇,我们将开始了解go中的指针和结构体的知识点。


原文地址:https://blog.csdn.net/StreamlineWq/article/details/145162285

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!