当前位置: 首页 > article >正文

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

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

  • 前言
  • 一、数组
  • 二、切片
  • 三、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中的指针和结构体的知识点。


http://www.kler.cn/a/509442.html

相关文章:

  • hydra破解密码
  • 初学stm32 --- CAN
  • 【视觉惯性SLAM:十七、ORB-SLAM3 中的跟踪流程】
  • 微透镜阵列精准全检,白光干涉3D自动量测方案提效70%
  • PHP智慧小区物业管理小程序
  • HTML中如何保留字符串的空白符和换行符号的效果
  • PL/SQL语言的文件操作
  • macOS 安装JDK17
  • 【HarmonyOS-开发指南】
  • 使用opencv.js 的时候报错 Uncaught 1022911432
  • JAVA:在IDEA引入本地jar包的方法(不读取maven目录jar包)
  • 【AcWing】蓝桥杯辅导课-递归与递推
  • 了解EJB两种主要类型:BMP与CMP
  • 【Unity】使用Canvas Group改变UI的透明度
  • QrCode.js 技术文档:二维码生成与导出工具类
  • LocalSend - 免费、开源、跨平台的局域网文件传输工具
  • leetcode206-反转链表
  • 数字化转型的新引擎:开源AI智能名片2+1链动模式S2B2C商城小程序源码的应用与实践
  • 年后找工作需要注意的事项
  • java每日精进1.17(用户登录,登出,Token相关)
  • Scala语言的编程范式
  • HTML格式
  • PCL 快速均匀采样【2025最新版】
  • 【C++基础】enum,union,uint8_t,static
  • Java集合 - LinkedHashMap
  • Java复习第二天