【Golang】数组 切片

news2024/11/26 16:33:57

【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) int
	srcSlice  数据来源切片
	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() {
    // 设置元素数量为1000
    const elementCount = 1000
    srcData := make([]int, elementCount)
    for i := 0; i < elementCount; i++ {
        srcData[i] = i
    }
    // 引用切片数据 切片不会因为等号操作进行元素的复制
    refData := srcData

    copyData := 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]valuetype
	mapname 为 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 nil
    fmt.Printf("%T", nil)
    print(nil)
}
  • 不同类型 nil 的指针是一样的
package main
import (
    "fmt"
)
func main() {
    var arr []int
    var num *int
    fmt.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]string
    var ptr *int
    var c chan int
    var sl []int
    var 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 ) ) // 8
    var s []int
    fmt.Println( unsafe.Sizeof( s ) ) // 24
    var m map[int]bool
    fmt.Println( unsafe.Sizeof( m ) ) // 8
    var c chan string
    fmt.Println( unsafe.Sizeof( c ) ) // 8
    var f func()
    fmt.Println( unsafe.Sizeof( f ) ) // 8
    var 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.coloradmin.cn/o/1056297.html

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

相关文章

buuctf-[RoarCTF 2019]Easy Java

第一次遇到java类的题目 打开环境&#xff0c;很像sql 点击help 以为是文件包含&#xff0c;&#xff0c;但是不对 这里需要了解JAVA WEB目录结构 WEB-INF&#xff1a;Java的web应用安全目录&#xff1b; 此外如果想在页面访问WEB-INF应用里面的文件&#xff0c;必须要通过w…

localStorage实现历史记录搜索功能

&#x1f4dd;个人主页&#xff1a;爱吃炫迈 &#x1f48c;系列专栏&#xff1a;JavaScript &#x1f9d1;‍&#x1f4bb;座右铭&#xff1a;道阻且长&#xff0c;行则将至&#x1f497; 文章目录 为什么使用localStorage如何使用localStorage实现历史记录搜索功能&#xff08…

矢量图形编辑软件illustrator 2023 mac特点介绍

illustrator 2023 mac是一款矢量图形编辑软件&#xff0c;用于创建和编辑排版、图标、标志、插图和其他类型的矢量图形。 illustrator mac软件特点 矢量图形&#xff1a;illustrator创建的图形是矢量图形&#xff0c;可以无限放大而不失真&#xff0c;这与像素图形编辑软件&am…

[Linux]线程互斥

[Linux]线程互斥 文章目录 [Linux]线程互斥线程并发访问问题线程互斥控制--加锁pthread_mutex_init函数pthread_mutex_destroy函数pthread_mutex_lock函数pthread_mutex_unlock函数锁相关函数使用示例使用锁的细节加锁解锁的实现原理 线程安全概念常见的线程不安全的情况常见的…

蓝桥杯每日一题2023.10.2

时间显示 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 输入为毫秒&#xff0c;故我们可以先将毫秒转化为秒&#xff0c;由于只需要输出时分&#xff0c;我们只需要将天数去除即可&#xff0c;可以在这里多训练一次天数判断 #include<bits/stdc.h> using namespace std…

检索qpython文件夹下.py

需求口令 检索 /storage/emulated/0/qpython 文件夹下的.py文件 编号原文件名&#xff1a;复制到/storage/emulated/0/qpython/py文件/ 没有文件夹就创建 检索qpython文件夹下.py&#xff0c;复制到py文件单独路径 根据这个提问清单和步骤&#xff0c;我们需要完成以下任务&…

Audio2Face的工作原理

预加载一个3D数字人物模型(Digital Mark),该模型可以通过音频驱动进行面部动画。 用户上传音频文件作为输入。 将音频输入馈送到预训练的深度神经网络中。 Audio2Face加载预制的3d人头mesh 3D数字人物面部模型由大量顶点组成,每个顶点都有xyz坐标。 深度神经网络输入音频特征,…

C++基础语法(继承)

终于&#xff0c;经过一路的过关斩将&#xff0c;我们来到了继承面前。还记得在最初学习类于对象时&#xff0c;那个对封装概念一直模糊不清的自己&#xff0c;还记得被模板&#xff0c;被迭代器折磨的日日夜夜吗&#xff1f;这一路你挺过来了&#xff0c;你失去了一些东西&…

zkLogin构建者的最佳实践和业务思考

随着zkLogin在Sui主网上线&#xff0c;构建者可以开始为其应用程序提供丝滑的帐户创建服务。与任何新技术集成一样&#xff0c;构建者需要考虑许多重要的问题&#xff0c;以降低风险并成功优化。 本文概述了其中一些考虑因素&#xff0c;并突出了zkLogin文档中提供的明确指导。…

Linux-centos系统安装MySql5.7

1.配置yum仓库 1.1配置yum仓库 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 1.2 安装Mysql yum库 rpm -Uvh http://repo.mysql.com//mysql57-community-release-el7-7.noarch.rpm 2.使用yum安装Msql 说明&#xff1a;下载大约5分钟左右 yum -y install mysq…

基于Java的教学评价管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统功能结构图系统ER图具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划…

分页前后端完整代码

一、后端代码 User实体类要继承PageVo package com.like.entity;import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.like.common.PageVo; import…

web:[SUCTF 2019]EasySQL

题目 点进页面&#xff0c;页面显示为 没有其他的提示信息&#xff0c;查看源代码看看是否有提示 什么都没有&#xff0c;由题目名为easysql&#xff0c;能想到这道题与sql注入有关 先输入1查看回显 再输入其他数字或字母看看回显 都是无显示 先验证是否存在sql注入 构造pay…

python爬取csdn个人首页中的所有封面

#爬取csdn个人首页中的所有封面 import requests import json import reurlhttps://blog.csdn.net/community/home-api/v1/get-business-list? headers{User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safar…

Scala第十二章节

Scala第十二章节 1.读取数据 2.写入数据 3.案例&#xff1a;学生成绩表 scala总目录 文档资料下载

Origin分段显示柱状图

注意这里生成的是柱状图&#xff0c;而不是直方图。因此用到的是plot -> column/bar/pie -> stacked column&#xff1a; 而不是Statistical->histogram。 先上最终的作图效果&#xff1a; 单个柱的柱状图 第一步先填充数据&#xff0c;如图左所示&#xff0c;然后选…

P1540 [NOIP2010 提高组] 机器翻译(模拟)

[NOIP2010 提高组] 机器翻译 题目背景 小晨的电脑上安装了一个机器翻译软件&#xff0c;他经常用这个软件来翻译英语文章。 题目描述 这个翻译软件的原理很简单&#xff0c;它只是从头到尾&#xff0c;依次将每个英文单词用对应的中文含义来替换。对于每个英文单词&#xf…

Java类与对象

文章目录 引出概念快速入门对象内存布局属性概念创建对象访问属性对象分配机制成员方法&#x1f996;注意事项和细节 引出 ●看一个养猫猫问题 张老太养了两只猫猫: 一只名字叫小白, 今年3岁, 白色. 还有一只叫小花, 今年100岁, 花色. 请编写一个程序, 当用户输入小猫的名字时…

Qt自定义菜单

Qt开发过程中&#xff0c;弹出菜单时我们一般使用QMenu,但是QMenu都是一条项固定的格式&#xff0c;如查想要自己的设计界面就没法使用默认的Action项了&#xff0c;因此我们得用自定义的QMenu。 本篇介绍使用自定义的QMenu设计出UI。我们使用QWidget QWidgetAction来实现。Q…

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

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