【Golang】数组 切片

【Golang】数组 && 切片

1、数组

  • 基本概念

    数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成

    因为数组的长度是固定的,所以在Go语言中很少直接使用数组

  • 数组初始化

    //1、默认数组中的值是类型的默认值
    var array [3]int
    //2、使用 {}将数组中的每个元素初始化
    var array [3]int = [3]int {1, 2, 3}
    //or
    array := [3]int {1, 2, 3} //推荐这种写法,书写高效方便
    //3、初始化数组中指定下标的数据
    array := [3]int {1:100} //数组内容是[0,100,0]
    //4、根据{}里面的元素数量推断数组大小
    array := [...]int {1, 2, 3, 4}
    
  • 数组使用

    var array := [10]int {8:100, 1:10}
    //1、通过下标访问
    fmt.Printf("array[0] = %d", array[0])//0
    fmt.Printf("array[1] = %d", array[1])//10
    //2、简单for循环
    for i := 0; i < len(array); i++ {fmt.Printf("array[%d] = %d \n", i, array[i])
    }
    //3、for  range 遍历
    for k,v := range array {fmt.Printf("array[%d] = %d \n", k, v)
    }
    

    注意:数组的长度是初始化的时候(编译时期)就确定好了,整个生命周期内不可改变

  • 数组比较

    只有两个数组类型相同(包括数组的长度,数组中元素的类型>)的情况下,我们才可以直接通过较运算符(==!=)来判断两个数组是否相等

    只有当两个数组的所有元素都是相等的时候数组才是相等的

    不能比较两个类型不同的数组,否则程序将无法完成编译

    a := [2]int{1, 2}
    b := [...]int{1, 2}
    c := [2]int{1, 3}
    fmt.Println(a == b, a == c, b == c) // "true false false"
    d := [3]int{1, 2}
    fmt.Println(a == d) // 编译错误:无法比较 [2]int == [3]int
    

2、多维数组

  • 概念理解

    多维数组本质上还是一个一维数组,只不过这个一维数组中的每个元素也是一个数组

    N维数组本质上是一个一维数组,这个一维数组的每个元素是N-1维数组,以此类推,直到访问到最底一层,能够直接处理数组中的元素

  • 以二维数组为例总结使用

    二维数组是最简单的多维数组,二维数组本质上是由多个一维数组组成的

    / 声明一个二维整型数组,两个维度的长度分别是 42
    var array [4][2]int
    // 使用数组字面量来声明并初始化一个二维整型数组
    array = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
    // 声明并初始化数组中索引为 1 和 3 的元素
    array = [4][2]int{1: {20, 21}, 3: {40, 41}}
    // 声明并初始化数组中指定的元素
    array = [4][2]int{1: {0: 20}, 3: {1: 41}}
    

    二维数组的使用

    array := [2][2]int {{10, 30}, {-1. 90}}
    //1、使用下标访问
    fmt.Println(array[1][0]) // -1
    //2、使用 for range 遍历
    for index,value := range array{for k, v := range value {fmt.Printf("array[%d][%d] = %d \n", index, k, v)}
    }
    

    只要类型一致,就可以将多维数组互相赋值

    如下所示,多维数组的类型包括每一维度的长度以及存储在元素中数据的类型

    // 声明两个二维整型数组 [2]int [2]int
    var array1 [2][2]int  
    var array2 [2][2]int
    // 为array2的每个元素赋值
    array2[0][0] = 10
    array2[0][1] = 20
    array2[1][0] = 30
    array2[1][1] = 40
    // 将 array2 的值复制给 array1
    array1 = array2
    

    数组中每个元素都是一个值,所以可以独立复制某个维度

    // 将 array1 的索引为 1 的维度复制到一个同类型的新数组里
    var array3 [2]int = array1[1]
    // 将数组中指定的整型值复制到新的整型变量里
    var value int = array1[1][0]
    

3、切片

  • 基本概念

    切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器

    与数组不同的是,无法通过切片类型来确定其值的长度

    每个切片值都会将数组作为其底层数据结构,我们也把这样的数组称为切片的底层数组

    切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型

    这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内(左闭右开的区间)

    Go语言中切片的内部结构包含地址大小容量,切片一般用于快速地操作一块数据集合

  • 切片存在的形式

    从连续内存区域生成切片

    var a  = [3]int{1, 2, 3}
    //a[1:2] 生成了一个新的切片
    slice := a[1:2]
    fmt.Println(a, slice) // [1, 2, 3]  [2]
    

    注意事项:

    从数组或切片生成新的切片拥有如下特性:

    • 取出的元素数量为:结束位置 - 开始位置;
    • 取出元素不包含结束位置对应的索引;
    • 当缺省开始位置时,表示从连续区域开头到结束位置(a[:2])
    • 当缺省结束位置时,表示从开始位置到整个连续区域末尾(a[0:])
    • 两者同时缺省时,与数组本身等效(a[:])
    • 两者同时为 0 时,等效于空切片,一般用于切片复位(a[0:0])

    直接申明新的切片

    /*语法结构:name 表示切片的变量名,Type 表示切片对应的元素类型。var name []Type
    */// 声明字符串切片
    var strList []string
    // 声明整型切片
    var numList []int
    // 声明一个空切片
    var numListEmpty = []int{}
    // 输出3个切片
    fmt.Println(strList, numList, numListEmpty)
    // 输出3个切片大小
    fmt.Println(len(strList), len(numList), len(numListEmpty))
    // 切片判定空的结果
    fmt.Println(strList == nil)
    fmt.Println(numList == nil)
    fmt.Println(numListEmpty == nil)
    

    切片是动态结构,只能与 nil 判定相等,不能互相判定相等。声明新的切片后,可以使用 append() 函数向切片中添加元素。

    var strList []string
    // 追加一个元素
    strList = append(strList,"golang")
    fmt.Println(strList)
    

    使用make函数构造切片

    /*
    语法结构make([]Type, size, cap)Type 切片的元素类型size 为这个类型分配多少个元素cap  预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。
    */a := make([]int, 2)
    b := make([]int, 2, 10)
    fmt.Println(a, b)
    //容量不会影响当前的元素个数,因此 a 和 b 取 len 都是 2
    //但如果我们给a 追加一个 a的长度就会变为3
    fmt.Println(len(a), len(b))
    

    注意事项:

    使用 make() 函数生成的切片一定发生了内存分配操作>

    但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作

    //小试牛刀
    //问:下面的代码有什么问题吗?如果没有问题,请回答输出的结果是什么??
    var numbers4 = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    myslice := numbers4[4:6]fmt.Printf("myslice为 %d, 其长度为: %d\n", myslice, len(myslice))myslice = myslice[:cap(myslice)]fmt.Printf("myslice的第四个元素为: %d", myslice[3])
    

    在这里插入图片描述

4、切片的复制

Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。

/*
语法说明copy( destSlice, srcSlice []T) intsrcSlice  数据来源切片destSlice 复制的目标(也就是将 srcSlice 复制到 destSlice)目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,返回值    实际发生复制的元素个数
*/slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 复制slice2的3个元素到slice1的前3个位置

切片的引用和复制操作对切片元素的影响

重点理解:copy底层是新开辟了空间,二者之间是独立的, 引用是公用同一块空间,一方修改会影响另一方

package main
import "fmt"
func main() {// 设置元素数量为1000const elementCount = 1000srcData := make([]int, elementCount)for i := 0; i < elementCount; i++ {srcData[i] = i}// 引用切片数据 切片不会因为等号操作进行元素的复制refData := srcDatacopyData := make([]int, elementCount)// 将数据复制到新的切片空间中copy(copyData, srcData)srcData[0] = 999// 打印引用切片的第一个元素 引用数据的第一个元素将会发生变化fmt.Println(refData[0])//999// 打印复制切片的第一个和最后一个元素 由于数据是复制的,因此不会发生变化。fmt.Println(copyData[0], copyData[elementCount-1])copy(copyData, srcData[4:6])for i := 0; i < 5; i++ {fmt.Printf("%d ", copyData[i])// [5, 6, 2, 3, 4]}
}

5、map

  • 基本概念

    map 是一种无序的键值对的集合

    map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值

    map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,map 是无序的,我们无法决定它的返回顺序,因为 map 是使用 hash 表来实现的

    map 是引用类型

  • map的定义方式

/*[keytype] 和 valuetype 之间允许有空格。var mapname map[keytype]valuetypemapname 为 map 的变量名keytype 为键类型valuetype 是键对应的值类型在声明的时候不需要知道 map 的长度,因为 map 是可以动态增长的未初始化的 map 的值是 nil,使用函数 len() 可以获取 map 中 键值对的数目
*/
//另一种定义方式 
make(map[keytype]valuetype)
make(map[keytype]valuetype, cap)

map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度或者最大限制,但是也可以选择标明 map 的初始容量 capacity

当 map 增长到容量上限的时候,如果再增加新的 key-value,map 的大小会自动加 1,所以出于性能的考虑,对于大的 map 或者会快速扩张的 map,即使只是大概知道容量,也最好先标明

既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?

答案是:使用切片

例如,当我们要处理 unix 机器上的所有进程,以父进程ID作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value。

通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅的解决这个问题,示例代码如下所示

mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
  • map的使用

    遍历map------使用for range的方式

    scene := make(map[string]int)
    scene["cat"] = 66
    scene["dog"] = 4
    scene["pig"] = 960
    for k, v := range scene {fmt.Println(k, v)
    }
    

    删除map中的某个元素

     使用 delete(map, 键)

    scene := make(map[string]int)
    // 准备map数据
    scene["cat"] = 66
    scene["dog"] = 4
    scene["pig"] = 960
    delete(scene, "dog")
    for k, v := range scene {fmt.Println(k, v)
    }
    
    • 线程安全的map

      上面介绍的map不是线程安全的,并发情况下读写 map 时会出现问题,代码如下:

      // 创建一个int到int的映射
      m := make(map[int]int)
      // 开启一段并发代码
      go func() {// 不停地对map进行写入for {m[1] = 1}
      }()
      // 开启一段并发代码
      go func() {// 不停地对map进行读取for {_ = m[1]}
      }()
      // 无限循环, 让并发程序在后台执行
      for {
      }
      

      运行代码会报错:fatal error: concurrent map read and map write

      错误信息显示,并发的 map 读和 map 写,也就是说使用了两个并发函数不断地对 map 进行读和写而发生了竞态问题,map 内部会对这种并发操作进行检查并提前发现

      需要并发读写时,一般的做法是加锁,但这样性能并不高,Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构

      sync.Map 有以下特性:

      • 无须初始化,直接声明即可。
      • sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
      • 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。

      使用示例如下:

      package main
      import ("fmt""sync"
      )
      func main() {//sync.Map 不能使用 make 创建var scene sync.Map// 将键值对保存到sync.Map//sync.Map 将键和值以 interface{} 类型进行保存。scene.Store("greece", 97)scene.Store("london", 100)scene.Store("egypt", 200)// 从sync.Map中根据键取值fmt.Println(scene.Load("london"))// 根据键删除对应的键值对scene.Delete("london")// 遍历所有sync.Map中的键值对//遍历需要提供一个匿名函数,参数为 k、v,类型为 interface{},每次 Range() 在遍历一个元素时,都会调用这个匿名函数把结果返回。scene.Range(func(k, v interface{}) bool {fmt.Println("iterate:", k, v)return true})
      }
      

6、nil

在Go语言中,布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil

但是go语言中的nil和其他语言的null是不同的,具体表现在:

  • nil 标识符是不能比较的
package main
import ("fmt"
)
func main() {//invalid operation: nil == nil (operator == not defined on nil)fmt.Println(nil==nil)
}
  • nil 不是关键字或保留字
//但不提倡这样做
var nil = errors.New("my god")
  • nil 没有默认类型
package main
import ("fmt"
)
func main() {//error :use of untyped nilfmt.Printf("%T", nil)print(nil)
}
  • 不同类型 nil 的指针是一样的
package main
import ("fmt"
)
func main() {var arr []intvar num *intfmt.Printf("%p\n", arr)fmt.Printf("%p", num)
}
  • nil 是 map、slice、pointer、channel、func、interface 的零值
package main
import ("fmt"
)
func main() {var m map[int]stringvar ptr *intvar c chan intvar sl []intvar f func()var i interface{}fmt.Printf("%##v\n", m) //map[int]string(nil)fmt.Printf("%##v\n", ptr) //(*int)(nil)fmt.Printf("%##v\n", c)  //(chan int)(nil)fmt.Printf("%##v\n", sl) //[]int(nil)fmt.Printf("%##v\n", f)  //(func())(nil)fmt.Printf("%##v\n", i)  //<nil>
}
  • 不同类型的 nil 值占用的内存大小可能是不一样的, 具体的大小取决于编译器和架构
package main
import ("fmt""unsafe"
)
func main() {var p *struct{}fmt.Println( unsafe.Sizeof( p ) ) // 8var s []intfmt.Println( unsafe.Sizeof( s ) ) // 24var m map[int]boolfmt.Println( unsafe.Sizeof( m ) ) // 8var c chan stringfmt.Println( unsafe.Sizeof( c ) ) // 8var f func()fmt.Println( unsafe.Sizeof( f ) ) // 8var i interface{}fmt.Println( unsafe.Sizeof( i ) ) // 16
}

7、new && make

make 关键字的主要作用是创建 slice、map 和 Channel 等内置的数据结构

new 的主要作用是为类型申请一片内存空间,并返回指向这片内存的指针

  1. make 分配空间后,会进行初始化,new分配的空间被清零
  2. new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;
  3. new 可以分配任意类型的数据;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/147066.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

华为智能企业上网行为管理安全解决方案(2)

本文承接&#xff1a; https://blog.csdn.net/qq_37633855/article/details/133339254?spm1001.2014.3001.5501 重点讲解华为智能企业上网行为管理安全解决方案的部署流程。 华为智能企业上网行为管理安全解决方案&#xff08;2&#xff09; 课程地址方案部署整体流程组网规划…

ARM-day2

1、1到100累加 .text .global _start_start:MOV r0, #1ADD r1,r0, #1fun:ADD r0,r0,r1ADD r1,r1, #1cmp r1, #0x65moveq PC,LRbl funstop:b stop.end2、思维导图

Spring Boot的自动装配中的@ConditionalOnBean条件装配注解在Spring启动过程中,是如何保证处理顺序靠后的

前言 为什么Spring Boot条件注解那么多&#xff0c;而标题中是ConditionalOnBean呢&#xff1f; 因为&#xff0c;相比之下我们用的比较多的条件装配注解也就是ConditionalOnClass、ConditionalOnBean了&#xff0c;而ConditionalOnClass对顺序并不敏感&#xff08;说白了就是判…

uniapp app 导出excel 表格

直接复制运行 <template><view><button click"tableToExcel">导出一个表来看</button><view>{{ successTip }}</view></view> </template><script>export default {data() {return {successTip: }},metho…

2023年【道路运输企业安全生产管理人员】最新解析及道路运输企业安全生产管理人员考试技巧

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 道路运输企业安全生产管理人员最新解析参考答案及道路运输企业安全生产管理人员考试试题解析是安全生产模拟考试一点通题库老师及道路运输企业安全生产管理人员操作证已考过的学员汇总&#xff0c;相对有效帮助道路运…

JavaSE | 初识Java(五) | 方法的使用

方法就是一个代码片段&#xff0c; 类似于 C 语言中的 " 函数 "。 方法可以是我们代码逻辑更清晰&#xff0c;并且可以服用方法使代码更简洁 方法语法格式 // 方法定义 修饰符 返回值类型 方法名称([参数类型 形参 ...]){ 方法体代码; [return 返回值]; } 实例&…

数据结构:KMP算法的原理图解和代码解析

文章目录 应用场景算法方案算法原理完整代码 本篇总结的是关于串中的KMP算法解析 应用场景 现给定两个串&#xff0c;现在要看较短的一个串是不是较长的串的子串&#xff0c;如果是就输出子串后面的内容&#xff0c;如果不是则输出Not Found 能匹配到&#xff1a; 长串&…

【办公自动化】在Excel中按条件筛选数据并存入新的表(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Java环境配置无效

Java环境配置无效 老是使用1.8版本&#xff0c;象牛皮癣。 查找java来源 where java 打开C:\Windows\System32 删掉java.exe javaaw.exe javaaws.exe 正常

机器人过程自动化(RPA)入门 3. 顺序、流程图和控制流程

到目前为止&#xff0c;我们已经了解了RPA是什么&#xff0c;并且我们已经看到了通过记录任务的活动并运行它来训练UiPath机器人是多么简单。使用记录器的UiPath可以很容易地自动化日常任务。在我们开始自动化复杂的任务之前&#xff0c;让我们学习如何控制从一个到另一个的活动…

python复习

1.python属于解释型语言&#xff0c;解释器逐行解释每一句代码&#xff0c;然后执行 编译型语言需要由编译器生成最终可执行文件再执行 2. #单行注释""" 多行注释 """ 注释快捷键ctrl/ 3.变量是在计算机语言中能储存计算结果或表示某个数据…

探索腾讯企业邮箱替代方案:选择适合你的新邮件服务

腾讯企业邮箱作为一款广受欢迎的企业级电子邮件服务&#xff0c;已经在国内市场占据了相当大的份额。然而&#xff0c;随着全球市场竞争的加剧&#xff0c;腾讯企业邮箱也面临着海外市场的挑战。本文将探讨腾讯企业邮箱出海的劣势&#xff0c;并推荐一些替代品牌&#xff0c;以…

从其它环境转移到Nacos的方法-NacosSync

理解 NacosSync 组件启动 NacosSync 服务通过一个简单的例子&#xff0c;演示如何将注册到 Zookeeper 的 Dubbo 客户端迁移到 Nacos。 介绍 NacosSync是一个支持多种注册中心的同步组件,基于Spring boot开发框架,数据层采用Spring Data JPA,遵循了标准的JPA访问规范,支持多种…

Neural Networks for Fingerprint Recognition

Neural Computation ( IF 3.278 ) 摘要&#xff1a; 在采集指纹图像数据库后&#xff0c;设计了一种用于指纹识别的神经网络算法。当给出一对指纹图像时&#xff0c;算法输出两个图像来自同一手指的概率估计值。在一个实验中&#xff0c;神经网络使用几百对图像进行训练&…

基于SSM的微博系统网站的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

十四天学会C++之第一天(入门和基本语法)

C的起源和历史 C诞生于20世纪80年代初&#xff0c;它的创造者是计算机科学家Bjarne Stroustrup。当时&#xff0c;Stroustrup在贝尔实验室工作&#xff0c;他希望为C语言添加一些功能&#xff0c;以便更好地支持系统开发。这个愿望促使他创建了C。 C的名字来源于它的基因&…

BIT.8_Linux 多线程

lesson35: 一、 1.OS调度的基本单位&#xff08;0&#xff1a;13&#xff1a;5&#xff09; 2.进程XXXX&#xff08;0&#xff1a;14&#xff1a;15&#xff09; a.进程的内核数据结构包含哪几个部分&#xff1f;&#xff08;n个&#xff09;&#xff08;0&#xff1a;15&a…

24Hibench

1. Hibench 官网 ​ HiBench is a big data benchmark suite that helps evaluate different big data frameworks in terms of speed, throughput and system resource utilizations. It contains a set of Hadoop, Spark and streaming workloads, including Sort, WordCou…

中断向量控制器(NVIC)

1. 什么是中断 在处理器中&#xff0c;中断是一个过程&#xff0c;即CPU在正常执行程序的过程中&#xff0c;遇到外部/内部的紧急事件需要处理&#xff0c;暂时中止当前程序的执行&#xff0c;转而去为处理紧急的事件&#xff0c;待处理完毕后再返回被打断的程序处继续往下执行…

博客无限滚动加载(html、css、js)实现

介绍 这是一个简单实现了类似博客瀑布流加载功能的页面&#xff0c;使用html、css、js实现。简单易懂&#xff0c;值得学习借鉴。&#x1f44d; 演示地址&#xff1a;https://i_dog.gitee.io/easy-web-projects/infinite_scroll_blog/index.html 代码 index.html <!DOCT…