探索 Go 语言中的数组和切片:深入理解顺序集合

news2024/9/22 17:28:48

在 Go 语言的丰富数据类型中,数组和切片是处理有序数据集合的强大工具,它们允许开发者以连续的内存块来存储和管理相同类型的多个元素。无论是在处理大量数据时的性能优化,还是在实现算法时对数据结构的需求,数组和切片都扮演着至关重要的角色。

Go 语言中的数组

在Go语言中,数组是一种基本的数据结构,用于存储具有相同类型元素的集合。数组与切片(slice)不同,数组的长度是固定的,一旦声明就不可改变,并且是数组类型定义的一部分,这意味着数组的类型不仅包括元素的类型,还包括其长度。

定义

定义数组时,必须指定元素的类型和数组的长度。例如,[3]bool 表示一个布尔类型的数组,长度为3;[4]int 表示一个整型的数组,长度为4。

var a1 [3]bool
var a2 [4]int

fmt.Printf("a1:%T\na2:%T\n", a1, a2)

打印结果如下:

数组初始化在这里插入图片描述

默认值

定义数组时如果不进行初始化,默认元素就是零值:bool 类型的 false、整型和浮点类型的 0、字符串的空串" "。

var a1 [3]bool 
var a2 [4]int

fmt.Println(a1, a2)

打印结果如下:

在这里插入图片描述

初始化方式 1:在大括号中定义好和长度一致的值

最简单的初始化方式:

var a1 [3]bool 
a1 = [3]bool{true,false,false}
fmt.Println(a1)

打印结果如下:

在这里插入图片描述

初始化方式 2:根据初始值自动判断数组的长度

在中括号中写明长度,当定义的数值个数比长度小时,会用默认值补齐,比如 0、false、“”。

a8 := [10]int{0, 1, 2, 3, 4, 5, 6, 7}  //7后面会用0补齐
fmt.Println(a8)

打印结果: [0 1 2 3 4 5 6 7 0 0]。

[…]的用法:[…]设置数组长度时,会根据初始值自动判断数组的长度。

aa := [...]int{0, 1, 2, 3, 4, 5, 6, 7} //[...]根据初始值自动判断数组的长度
fmt.Println(aa)

打印结果:[0 1 2 3 4 5 6 7]。

初始化方式 3:根据索引初始化

指定索引对应的值,未指定索引的值会用默认值填充,比如 0、false、“”。

a3 := [5]int{0: 1, 4: 2} //根据索引初始化
fmt.Println(a3)

打印结果:[1 0 0 0 2]。

取值

遍历数组

你可以使用一个标准的for循环来遍历数组的每个元素。这种方法需要你手动管理索引变量,并使用它来访问数组中的元素。

for i 循环遍历数组:

citys := [...]string{"北京", "上海", "深圳"} //索引从0到2
// 根据索引遍历
for i := 0; i < len(citys); i++ {
   fmt.Println(citys[i])
}

打印结果如下:

在这里插入图片描述

for range是一种更简洁的遍历数组或切片的语法。for range循环会自动生成索引和元素值,使得遍历过程更加简单。

citys := [...]string{"北京", "上海", "深圳"} //索引从0到2
for i, city := range citys {
   fmt.Printf("key值:%d 城市为:%v\n", i, city)
}

打印结果如下:

在这里插入图片描述

多维数组

定义

我们以二维数组举例,比如定义[[1 2 3][4 5 6]]这样的二维数组,要怎么定义呢?

示例如下:

  1. 下面代码中的第一个长度单位[2],表示二维数组的有几个元素;
  2. 第二个长度单位[3],表示子集数组中有几个元素;
  3. 初始化的时候:变量 = 数组类型{}。
//定义多维数组
var a11 [2][3]int

//初始化多维数组
a11 = [2][3]int{
   [3]int{1, 2, 3},
   [3]int{4, 5, 6}, //注意:最后这个也要加逗号分隔
}

fmt.Println(a11)

打印结果如下:

在这里插入图片描述

取值

多维数组的遍历:

//定义多维数组
var a11 [2][3]int

//初始化多维数组
a11 = [2][3]int{
   [3]int{1, 2, 3},
   [3]int{4, 5, 6}, //注意:最后这个也要加逗号分隔
}

//双重for range遍历取值
for _, v1 := range a11 {
   fmt.Println(v1)
   for _, v2 := range v1 {
      fmt.Println(v2)
   }
}

打印结果如下:

在这里插入图片描述

数组特点:值类型不是引用类型

我们发现把 b1 赋值给 b2,再修改 b2 的值,b1 的值并没有改变,这是数组和切片最大的区别,建议你再对比学习一下切片的知识点。

b1 := [3]int{1, 2, 3}
b2 := b1
b2[0] = 100
fmt.Println(b1,b2)

打印结果如下:

在这里插入图片描述

总结:说明 Go 的数组是值类型,不是引用类型 b2:=b1 的操作,给 b2 开辟了新的内存空间,而不是引用 b1 的内存地址。

数组实战

求数组 cArray[1,3,5,7,8]所有元素之和:

cArray := [...]int{1, 3, 5, 7, 8}
r := 0
for _, i2 := range cArray {
   r += i2
}
fmt.Printf("相加结果为:%v", r)

打印结果:相加结果为:24

求出 cArray 数组中,和为 8 的下标,比如[0 3]和[1 2]:

for i := 0; i < len(cArray); i++ {
   for j := 0; j < i; j++ {
      if cArray[i]+cArray[j] == 8 {
         fmt.Printf("符合的下标为:%v,%v \n", j, i)
      }
   }
}

打印结果如下:

在这里插入图片描述

Go 语言中的切片

切片区别于数组,是引用类型,不是值类型。数组是固定长度的,而切片长度是可变的,我的理解是:切片是对数组一个片段的引用。

定义

切片(slice)是一种灵活的序列类型,它基于数组;与数组不同,切片的长度是可变的,并且不包含在类型定义中。例如,[]int 表示一个存放整数类型元素的切片,[]string 表示一个存放字符串类型元素的切片。

var s1 []int 
var s2 []string 
fmt.Println(s1, s2)
fmt.Println(s1 == nil) //true  为空  没有开辟内存空间
fmt.Println(s2 == nil) //true

打印结果如下:

在这里插入图片描述

解析:这意味着s1s2目前不指向任何实际的内存空间,它们是“空”的切片。在Go语言中,当切片被声明但未初始化时,它们的默认值就是nilnil是一个表示“无”或“空”的特殊值,对于切片来说,它表示切片没有底层数组,也没有长度和容量。因此,尝试访问或修改一个nil切片的元素将会导致运行时错误。

声明并初始化

我们可以在声明的同时初始化:

var s1 = []int{1, 2, 3}
var s2 = []string{"北苑", "长阳", "望京"}
fmt.Println(s1, s2)
fmt.Println(s1 == nil) //false
fmt.Println(s2 == nil) //false

打印结果如下:

在这里插入图片描述

解析:初始化成功,s1s2的值都不等于nil

长度和容量

分别使用len()、cap()获得切片的长度和容量。

fmt.Printf("len(s1):%d cap(s1):%d\n", len(s1), cap(s1))
fmt.Printf("len(s2):%d cap(s2):%d\n", len(s2), cap(s2))

打印结果如下:

在这里插入图片描述

解析:和我们预期的一致,长度和容量都为3。

由数组得到切片

开篇我已经提到数组和切片的关系,这里再进一步讲一下:

  1. 切片的本质是操作数组,只是数组是固定长度的,而切片的长度可变的;
  2. 切片是引用类型,可以理解为引用数组的一个片段;而数组是值类型,把数组A赋值给数组B,会为数组B开辟新的内存空间,修改数组B的值并不会影响数组A;
  3. 而切片作为引用类型,指向同一个内存地址,是会互相影响的。
//定义一个数组
a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s3 := a1[0:4] //基于一个数组切割  [0:4]左包含 右不包含  即为[1,2,3,4]
fmt.Println(s3)

打印结果如下:

在这里插入图片描述

注意:a1[0:4]是一个切片表达式,它基于数组a1创建了一个新的切片,包含了数组中索引从03的元素,上面示例中就是{1, 2, 3, 4}。

更多切割方式举例

切片提供了多种切割方式,这些方式允许你灵活地操作和访问数组或其他切片的部分元素。以下是一些常见的切片切割方式的例子:

a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s4 := a1[2:4] //[3 4]
s5 := a1[:4] //[1 2 3 4]
s6 := a1[2:] //[3 4 5 6 7 8 9]
s7 := a1[:]  //[1 2 3 4 5 6 7 8 9]
fmt.Println(s4)
fmt.Println(s5)
fmt.Println(s6)
fmt.Println(s7)

打印结果如下:

在这里插入图片描述

解析:切片操作s4从索引2开始到索引4(左闭右开),s5默认从索引0开始截取,s6截取到最后一个元素,而s7省略了索引,表示获取全部元素,所有这些切片操作都遵循左包含右不包含的原则。

切片的长度和容量

切片的长度很好理解,就是元素的个数。

切片的容量我们重点理解一下:在切片引用的底层数组中从切片的第一个元素到数组最后一个元素的长度就是切片的容量

我来画个图:

在这里插入图片描述

我们再看下面这个例子就很好理解了:

a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}

s5 := a1[:4] //[1 2 3 4]
s6 := a1[2:] //[3 4 5 6 7 8 9]
s7 := a1[:]  //[1 2 3 4 5 6 7 8 9]

fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5), cap(s5)) //4 9
fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6)) //7 7
fmt.Printf("len(s7):%d cap(s7):%d\n", len(s7), cap(s7)) //9 9

打印结果如下:

在这里插入图片描述

解析:a1是数组长度为9,容量也为9,值是从1~9。

s5/s6/s7都是切割数组a1得到的切片。

s5的长度为4,因为只有1 2 3 4这4个元素,容量为9,因为s5切片是从数组起始位置开始切割的:第一个元素是1,而s5底层数组a1最后一个元素是9,1~9共9个元素,所以s5的容量为9。

s6的长度为7,因为s6的元素是39这7个元素;容量也为7,因为s5的底层数组最后一个元素是9,39共7个元素,所以s6的容量为7。

S7更好理解了,长度和容量都是9,小伙伴们自己理解一下。

切片再切片

我们可以对切片进行再切片操作,比如,针对上面的数据再次切片进行测试:

s8 :=s6[3:]
//s8的值为:6 7 8 9
fmt.Printf("len(s8):%d cap(s8):%d\n", len(s8), cap(s8)) //4 4

打印结果如下:

在这里插入图片描述

解析:我们知道可以对切片进行再次切片就可以,至于长度和容器大家搞明白上面的例子,这个输出结果就是意料之中的了。

切片特点:引用类型不是值类型

我们举个例子来证明切片是引用类型:

//定义数组
a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
//由数组切割成切片s6
s6 := a1[2:] //[3 4 5 6 7 8 9]
//切片再次切片,赋值给s8
s8 :=s6[3:] //[6 7 8 9]
//修改原始数组,把下标为2的值由3改为333
a1[2] = 333
//打印s6,发现s6中的3也变成了333
fmt.Println("s6:", s6) //[333 4 5 6 7 8 9]
//因为s8基于s6切片而成,我们测试一下切片再切片的引用传的
fmt.Println("s8:", s8) //[6 7 8 9]
//我们把原始数组下标为5的值由6改为666
a1[5] = 666
//打印s8切片,得到结果6也变成了666
fmt.Println("s8:", s8) //[666 7 8 9]

打印结果如下:

在这里插入图片描述

解析:由此我们可以明确的知道切片是引用类型,当底层数组改变时,不管是切片,还是切片再切片,值都会改变。因为他们使用的是一个内存块,引用的一个内存地址。

总结

Go语言的数组和切片各有特点:

  1. 数组适合于长度固定且已知的场景,而切片则提供了更大的灵活性,特别是在处理动态大小的数据集合时;
  2. 数组作为值类型,在赋值和函数传递时会复制数据,保证了数据的独立性;而切片作为引用类型,多个切片变量可能指向同一个底层数组,因此在操作时要特别注意对底层数据的影响。

欢迎关注 ❤

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:csdn面试群。

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

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

相关文章

无线领夹麦克风六大行业趋势揭秘:洞察市场风向谨防踩坑!

​近年来&#xff0c;无线领夹麦克风受到很多直播达人、视频创作者的推荐与青睐&#xff0c;作为一款实用便捷的音频设备&#xff0c;各个创作领域都广泛应用。如今无线领夹麦克风市场产品多样且品质各异&#xff0c;有些产品采用劣质材料&#xff0c;甚至存在信号不稳定等问题…

IP代理怎么测试网速:全面指南

在互联网时代&#xff0c;代理IP已经成为了很多人上网的重要工具。不管你是为了保护隐私&#xff0c;还是为了提高访问速度&#xff0c;代理IP都能提供很大的帮助。那么&#xff0c;如何测试代理IP的网速呢&#xff1f;今天我们就来聊聊这个话题。 什么是代理IP&#xff1f; 代…

SQL进阶技巧:如何按任意时段分析时间区间问题? | 分区间讨论【左、中、右】

目录 0 场景描述 1 数据准备 2 问题分析 方法1:分情况讨论,找出重叠区间 方法2:暴力美学法。按区间展开成日期明细表 3 拓展案例 4小结 0 场景描述 现有用户还款计划表 user_repayment ,该表内的一条数据,表示用户在指定日期区间内 [date_start, date_end] ,每天…

从零部件到汽车,Fortinet如何守护车主安全出行每一步

随着汽车产业蓬勃发展&#xff0c; 网络安全威胁也正在“紧盯”汽车全产业链。从制造、销售直至最终使用阶段&#xff0c;均面临着网络安全威胁的挑战&#xff1a;制造环节的软件与生产环境漏洞隐患、销售过程中数据泄露风险&#xff0c;以及使用阶段车载系统脆弱性和用户安全意…

自由能在哪些领域可以大放异彩? ——自由能的多领域应用探索

自由能在哪些领域可以大放异彩&#xff1f; ——自由能的多领域应用探索 【表格】自由能的应用领域 序号应用领域具体描述涉及公式/概念备注1化学领域描述化学反应的自发性&#xff0c;判断反应是否可能进行 Δ G Δ H − T Δ S \Delta G \Delta H - T\Delta S ΔGΔH−T…

#网络编程 笔记

认识网络 网络发展史 ARPnetA--->Internet--->移动互联网--->物联网 TCP 用来检测网络传输中差错的传输控制协议 UDP 用户数据报协议&#xff0c;专门负责对不同网络进行互联的互联网协议 局域网 实现小范围短距离网络通信 广域网 现大范围长距离网络通信…

最佳实践 | SaleSmartly用HelpLook搭建知识库,客服效率提升,服务好全球数万客户

SaleSmartly&#xff0c;作为全渠道私域沟通工具的佼佼者&#xff0c;使用HelpLook开启了全新智能客服服务新体验。通过使用HelpLook搭建了AI知识库和博客中心&#xff0c;SaleSmartly不仅大幅提升了客服效率&#xff0c;还成功优化了品牌形象&#xff0c;服务覆盖全球数万客户…

Onnx使用预训练的 ResNet18 模型对输入图像进行分类,并将分类结果显示在图像上

目录 一、整体功能概述 二、函数分析 2.1 resnet() 函数&#xff1a; 2.2 pre_process(img_path) 函数&#xff1a; 2.3 loadOnnx(img_path) 函数&#xff1a; 三、代码执行流程 一、整体功能概述 这段代码实现了一个图像分类系统&#xff0c;使用预训练的 ResNet18 模型对…

error C2375: “WSAAsyncGetHostByName”: 重定义;不同的链接

error C2375: “WSAAsyncGetHostByName”: 重定义;不同的链接 win11 vs2015 背景:当项目中使用到了开源库,开源库使用WinSock2.h,同时windows项目又有包含Windows.h, 编译时常常会出现一堆编译错误,方法重定义等等。 问题原因: 默认windows.h头文件会包含winsock.h //…

【Linux】快速入门(第一篇)

1. Linux简介 1.操作系统概念 Linux 也是众多操作系统之一&#xff0c;要想知道 Linux 是什么&#xff0c;首先得说一说什么是操作系统。 计算机是一台机器&#xff0c;它按照用户的要求接收信息、存储数据、处理数据&#xff0c;然后再将处理结果输出&#xff08;文字、图片…

.ipynb文件:交互式 Jupyter Notebook

Python 接口文件&#xff08;带有扩展名的文件.pyi&#xff09;&#xff0c;或称为 Python 存根文件&#xff0c;在使用类型提示增强 Python 代码方面发挥着至关重要的作用。 当你遇到名称以 .ipynb、.pyi、.pyc 等结尾的 Python 文件时&#xff0c;你是否会感到困惑&#xff…

Adobe ME软件安装win/mac下载与使用教程

目录 一、Adobe ME软件介绍 1.1 软件概述 1.2 主要功能 1.3 软件优势 二、系统要求 2.1 Windows系统要求 2.2 macOS系统要求 三、安装步骤 3.1 Windows系统安装 3.2 macOS系统安装 四、使用教程 4.1 基本界面介绍 4.2 视频编码与转码 4.3 音频和字幕处理 4.4 高…

快来领取迅雷加速器7天会员,让你的《黑神话·悟空》更新速度嗖嗖嗖!⚡️

嘿&#xff0c;各位《黑神话悟空》的小伙伴们&#xff01;&#x1f606; 最近大家肯定都在Steam上体验这款国产3A大作吧&#xff1f;游戏的画质、玩法是不是让你眼前一亮&#xff1f;&#x1f60d; 但是&#xff01;&#x1f62b; 大家有没有发现&#xff0c;游戏加载和更新时…

谷歌的有害链接是什么?

有害链接&#xff0c;顾名思义&#xff0c;是指那些可能对你网站的Google排名产生负面影响的链接&#xff0c;但&#xff0c;真的存在会对网站造成坏影响的链接吗&#xff1f; 所谓的有害链接&#xff0c;更多是现在很多seo工具所定义出来的&#xff0c;事实上&#xff0c;自从…

豆瓣评分9.4!最适合Python入门后进阶的Python食谱!

Python是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。Python的设计具有很强的可读性&#xff0c;相比其他语言经常使用英文关键字&#xff0c;其他语言的一些标点符号&#xff0c;它具有比其他语言更有特色语法结构。 今天给小伙伴们分享的这份手册&#x…

Python + Playwright(23):处理 iframe (内嵌框架)「详细介绍」

Python Playwright&#xff08;23&#xff09;&#xff1a;处理 iframe 内嵌框架「详细介绍」 简介1. 理解 iframe 的特性2. 处理 iframe 的方法2.1 使用 page.frames 遍历所有的 iframe2.2 通过 page.frames 访问特定 iframe2.2 通过 page.frames 的索引访问 iframe2.3 通过 …

企业如何实现多个分公司组网方案

在现代商业环境中&#xff0c;企业往往需要连接多个分公司以实现高效的资源共享和协同工作。以下是一个全面的多个分公司组网策略供参考。 一、确定网络架构和布局 总部作为核心数据中心&#xff1a;总部应配备高性能的网络设备和完善的安全防护措施&#xff0c;承担数据存储和…

医疗器械管理软件 符合新规 免费升级

盘谷医疗器械管理软件具有对采购、收货、验收、贮存、销售、出库、复核、退货等各经营环节进行实时质量控制的功能&#xff1b;具有权限管理功能&#xff0c;确保各类数据的录入、修改、保存等操作应当符合授权范围、管理制度和操作规程的要求&#xff0c;保证数据真实、准确、…

深入理解ARM64的函数调用标准与栈布局

一、引言 随着计算机技术的飞速发展&#xff0c;人们对计算机的性能要求越来越高&#xff0c;为了突破32位架构的4GB地址空间限制,并实现更好的性能提升。ARM公司推出了一种64位处理器架构&#xff0c;也就是我们今天所要讨论的ARM64。ARM64(也称ARMv8)面世以来&#xff0c;在…

leetcode 3146 两个字符串的排列差

leetcode 3146 两个字符串的排列差 正文题目描述解题思路方法1 Python 处理字符串的思路方法2 正文 题目描述 解题思路 直接 for 循环遍历第一个字符串&#xff0c;在第二个字符串中找出第一个字符串中的对应字符的位置&#xff0c;做差&#xff0c;再取绝对值&#xff0c;最…