【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.mzph.cn/news/92843.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pycharm操作git仓库 合并等

菜单 Git CommitPushUpdate ProjectPullFetchMergreRebase 查询 查询分支 查询本地所有分支 # 查询本地分支 git branch# 查询远程分支 git branch -rPycharm查看当前分支 步骤&#xff1a; Git->Branches 哈喽&#xff0c;大家好&#xff0c;我是[有勇气的牛排]&…

红外遥控器 数据格式,按下及松开判断

红外遥控是一种无线、非接触控制技术&#xff0c;具有抗干扰能力强&#xff0c;信息传输可靠&#xff0c;功耗低&#xff0c;成本低&#xff0c;易实现等显著优点&#xff0c;被诸多电子设备特别是家用电器广泛采用&#xff0c;并越来越多的应用到计算机系统中。 同类产品的红…

FPGA project :HDMI

实验目标&#xff1a;驱动HdMI显示十色等宽彩条。 本实验的重点是&#xff1a; 1掌握TMDS通信协议。 2rgb565转rgb888。 3编写HDMI驱动程序。 4学会看流程图编写代码。 值得注意的事情 1注意数据与解析数据的信号&#xff08;比如传入的数据中0或者1的个数&#xff09;&…

IoTDB 在国际数据库性能测试排行榜中位居第一?测试环境复现与流程详解第一弹!...

最近我们得知&#xff0c;Apache IoTDB 多项性能表现位居 benchANT 时序数据库排行榜&#xff08;Time Series: DevOps&#xff09;性能排行第一名&#xff01;&#xff08;榜单地址&#xff1a;https://benchANT.com/ranking/database-ranking&#xff09; benchANT 位于德国&…

毛玻璃动画交互效果

效果展示 页面结构组成 从上述的效果展示页面结构来看&#xff0c;页面布局都是比较简单的&#xff0c;只是元素的动画交互比较麻烦。 第一个动画交互是两个圆相互交错来回运动。第二个动画交互是三角绕着圆进行 360 度旋转。 CSS 知识点 animationanimation-delay绝对定位…

Vue之transition组件

Vue提供了transition组件&#xff0c;使用户可以更便捷地添加过渡动画效果。 transition组件 transition组件也是一个抽象组件&#xff0c;并不会渲染出真实dom。Vue会在其第一个真实子元素上添加过渡效果。 props render 这里将render分为两部分&#xff0c;第一部分界定真…

线性表的链式存储结构——链表

一、顺序表优缺点 优点&#xff1a;我们知道顺序表结构简单&#xff0c;便于随机访问表中任一元素&#xff1b; 缺点&#xff1a;顺序存储结构不利于插入和删除&#xff0c;不利于扩充&#xff0c;也容易造成空间浪费。 二、链表的定义 ①&#xff1a;概念&#xff1a; 用一组任…

JavaScript高阶班之ES6 → ES11(八)

JavaScript高阶班之ES6 → ES11 1、ES6新特性1.1、let 关键字1.2、const关键字1.3、变量的解构赋值1.3.1、数组的解构赋值1.3.2、对象的解构赋值 1.4、模板字符串1.5、简化对象写法1.6、箭头函数1.7、函数参数默认值1.8、rest参数1.9、spread扩展运算符1.9.1、数组合并1.9.2、数…

docker-compose 网络配置- IP 主机名 hosts配置

docker-compose 配置IP、hostname、hosts配置 配置IP version: "3" networks:bd-network: # 声明网络external: true services:kafka: # 服务名称networks:bd-network: # 连接的网络名称ipv4_address: 172.2.0.102 # 配置IP配置 主机名 version: "3&quo…

mybatis-spring集成数据库连接池开启注解式开发

目录 1. 引入依赖包 2. 集成配置文件 2.1 开启注解式开发 2.2 spring引入外部配置文件 2.3 数据库连接池 2.4 spring整合mybatis 2.5 自动代理 3. 注解式开发的几个常用注解 4. spring-test 附录一&#xff1a;spring常用注解 1. 引入依赖包 <!--spring整合mybat…

网络基础(一)

通信&#xff0c;在古代是通过书信与他人互通信息的意思。 今天&#xff0c;“通信”这个词的外沿已经得到了极大扩展&#xff0c;它目前的大意是指双方或多方借助某种媒介实现信息互通的行为。 如果按照当代汉语的方式理解“通信”&#xff0c;那么古代的互遣使节、飞鸽传书…

IDEA git操作技巧大全,持续更新中

作者简介 目录 1.创建新项目 2.推拉代码 3.状态标识 5.cherry pick 6.revert 7.squash 8.版本回退 9.合并冲突 1.创建新项目 首先我们在GitHub上创建一个新的项目&#xff0c;然后将这个空项目拉到本地&#xff0c;在本地搭建起一个maven项目的骨架再推上去&#xff0…

【KingbaseES】银河麒麟V10 ARM64架构_安装人大金仓数据库KingbaseES_V8R6(CentOS8)

&#x1f341; 博主 "开着拖拉机回家"带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,Java基础学习,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341; 希望本文能够给您带来一定的…

焕新古文化传承之路,AI为古彝文识别赋能

目录 1 古彝文与古典保护 2 古文识别的挑战 2.1 西文与汉文OCR 2.2 古彝文识别难点 3 合合信息&#xff1a;古彝文保护新思路 3.1 图像矫正 3.2 图像增强 3.3 语义理解 3.4 工程技巧 4 总结 1 古彝文与古典保护 彝文指的是云南、贵州、四川等地的彝族人使用的文字&am…

十九,镜面IBL--BRDF积分贴图

再回顾下镜面部分的分割求和近似法 现在关注第二部分 最后可化为 也就是说&#xff0c;这两部分积分可以获得F0的系数和F0的偏差。 这两个值可以存储到BRDF积分贴图的RG部分。void main() { vec2 integratedBRDF IntegrateBRDF(TexCoords.x, TexCoords.y); FragColor …

驱动开发:STM32F7控制AD5663模拟量输出

AD5663是ADI公司的一款DAC模块&#xff0c;用以实现两路模拟量信号输出。该芯片通过SPI通信来驱动。下面讲解使用STM32F7主控芯片来控制AD5663模拟量输出的流程。 配置STM32F7 SPI通信管脚 STM32CubeMX生成SPI驱动代码 /* SPI3 init function */ void MX_SPI3_Init(void) {/*…

【数据代理+事件处理+计算属性与监视+绑定样式+条件渲染】

数据代理事件处理计算属性与监视绑定样式条件渲染 1 数据代理1.1 回顾Object.defineProperty方法1.2 数据代理 2 事件处理2.1 绑定监听2.2 事件修饰符2.3 键盘事件 3 计算属性与监视3.1 计算属性3.2 监视属性(侦视属性)3.3 watch对比computed 4 绑定样式4.1 绑定class样式4.2 绑…

区块链3.0时代 基于GoMars构建的新概念TravelFi能否注入新力量?

区块链技术进入3.0时代 后疫情时代&#xff0c;全球数字化进程不断加快&#xff0c;世界范围内的移动通信、互联网技术及各类数字化应用的社会普及率也正在快速提升&#xff0c;疫情推动了互联网经济的增长&#xff0c;也让数字经济的价值开始显现。 数字经济一词的由来至今已经…

代码随想录 Day8 栈(FILO)与队列(FIFO) LeetCode T232 用栈实现队列 LeetCodeT225 用队列实现栈

题目详细思路来自于:代码随想录 (programmercarl.com) 栈和队列都是大家不陌生的数据结构,我们之前的栈和队列一般是用数组或链表来实现的 , 这里我们给出实现方式,用于帮助更好的理解. 1.用链表实现栈 /* 基于链表实现的栈 */ class LinkedListStack { private ListNode sta…

Vue3之Suspense

<Suspense> 是一个内置组件&#xff0c;用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成&#xff0c;并可以在等待时渲染一个加载状态。 我们可以看到官网并不推荐我们使用它&#xff0c;目前仍处于测试中。 他用于加…