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

news/2025/1/18 21:37:31/

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] = 11fmt.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] = 88fmt.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] = 88fmt.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"] = 1a["two"] = 2fmt.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"] = 1val, exist := a["f"]fmt.Println(exist,val)
    }
    //输出:false,0
    
  3. map的删除和清空
    使用go的两个内置函数,delete和clear

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

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

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

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


http://www.ppmy.cn/news/1564240.html

相关文章

elasticsearch线程池配置

在Elasticsearch中&#xff0c;默认的线程池配置如下&#xff1a; search线程池 用途&#xff1a;用于处理搜索请求。 特点&#xff1a; 类型为fixed&#xff0c;即固定大小的线程池。 线程数根据分配给Elasticsearch的处理器数量动态计算&#xff0c;以确保搜索请求能够并行…

matlab中的griddata函数

在Matlab中&#xff0c;griddata函数用于对二维或三维散点数据进行插值。griddata函数支持多种插值方法&#xff0c;其中包括natural方法。以下是关于griddata函数与natural插值方法的关系的详细说明&#xff1a; griddata函数概述 griddata函数主要用于将不规则分布的数据点…

大模型微调介绍-Prompt-Tuning

提示微调入门 NLP四范式 第一范式 基于「传统机器学习模型」的范式&#xff0c;如TF-IDF特征朴素贝叶斯等机器算法. 第二范式 基于「深度学习模型」的范式&#xff0c;如word2vec特征LSTM等深度学习算法&#xff0c;相比于第一范式&#xff0c;模型准确有所提高&#xff0c…

数据结构-栈队列OJ题

文章目录 一、有效的括号二、用队列实现栈三、用栈实现队列四、设计循环队列 一、有效的括号 (链接&#xff1a;ValidParentheses) 这道题用栈这种数据结构解决最好&#xff0c;因为栈有后进先出的性质。简单分析一下这道题&#xff1a;所给字符串不是空的也就是一定至少存在一…

华为手机改ip地址能改定位吗

‌在数字化时代&#xff0c;手机不仅是通讯工具&#xff0c;更是我们日常生活的得力助手。从地图导航到社交媒体&#xff0c;手机定位服务无处不在。然而&#xff0c;有时我们可能出于隐私保护或其他原因&#xff0c;希望更改手机的IP地址&#xff0c;并好奇这是否能同时改变手…

Spring Boot中的自动配置原理是什么

Spring Boot 自动配置原理 Spring Boot 的自动配置机制基于 条件化配置&#xff0c;通过 EnableAutoConfiguration 注解来启用。自动配置的核心原理是 基于类路径和环境条件来推断所需要的配置&#xff0c;Spring Boot 会根据项目中引入的依赖和当前环境来自动装配相关的配置项…

【PCL】Segmentation 模块—— 平面模型分割(Plane model segmentation)

1、简介 PCL&#xff08;Point Cloud Library&#xff09;中的平面模型分割&#xff08;Plane Model Segmentation&#xff09;是一种从点云数据中提取平面结构的方法。它通过识别点云中符合平面模型的点集&#xff0c;将场景中的平面区域分割出来。 1.1 主要步骤 选择模型&…

【2025 Rust学习 --- 18 IO操作和网络】

输入与输出 Rust 标准库中的输入和输出的特性是围绕 3 个特型组织的&#xff0c;即 Read、 BufRead 和 Write。 实现了 Read 的值具有面向字节的输入方法。它们叫作读取器。实现了 BufRead 的值是缓冲读取器。它是 Read的子特型 &#xff0c;外加读取文本行等方法。实现了 Wr…