Go语言入门经典:数组与切片详解

Go语言入门经典:数组与切片详解

数组和切片是Go语言中两种重要的数据结构。数组是一种固定长度的集合,而切片则是一种灵活的动态集合。本章将详细讲解数组和切片的定义、初始化、访问元素、动态操作等内容,帮助读者全面掌握这两种数据结构。

1 数组

数组是一种集合,它将一定数量且类型相同的对象放到一起,形成一个整体。数组在定义时就会确定元素的个数,初始化之后无法更改元素数量。

1.1 数组的初始化

数组实例可以使用以下几种格式来初始化:

var x [4]byte
var x = [n]T{...}
x := [n]T{...}

其中,n 表示元素的个数,即数组对象的长度。n 是一个表达式,其计算结果必须是 int 类型的常量,而且不能出现负值。

如果数组在声明时没有进行初始化,就会为每个元素分配一个默认值。例如:

var f [5]uint32{18, 75, 42, 3, 105}

变量 f 初始化后,数组中第一个元素为 18,第二个元素为 75,第三个元素为 42,依此类推。

如果只想为第一个元素赋值,其他元素保留默认值(即 0),那么初始化语句可以进行简化:

var f = [5]uint32{18,}

也可以定义变量后,通过元素索引来逐个赋值。元素索引从 0 开始,即第一个元素的索引为 0,第二个元素的索引为 1,依此类推。

var r [4]float32
r[0] = 1.112
r[1] = 0.000054
r[2] = 370.303
r[3] = -16.75

按照语法规则,数组变量在定义时已确定类型,数组中所有元素都必须是同一类型。因此,下面代码所示的初始化方式会发生错误:

a := [2]uint{1.7, 33}

数组变量 a 的元素类型被声明为 uint(无符号整数),而初始化时第一个元素的值是浮点数值,与数组所定义的类型不符。

不过,如果将数组变量声明为空接口(interface{})类型,那么其中的元素就可以是任意类型的值了。

s := [3]interface{}{"abc", 887, 'H'}

这说明接口的动态类型机制也适用于数组。例如:

type music interface {play()pause()
}type popMusic struct {}
func (x popMusic) play() {fmt.Println("开始播放流行音乐")
}
func (x popMusic) pause() {fmt.Println("暂停播放流行音乐")
}type classicMusic struct {}
func (x classicMusic) play() {fmt.Println("开始播放古典音乐")
}
func (x classicMusic) pause() {fmt.Println("暂停播放古典音乐")
}func main() {// 初始化数组实例var arr = [2]music{popMusic{}, classicMusic{}}// 调用数组实例中各个元素的方法arr[0].play()arr[0].pause()arr[1].play()arr[1].pause()
}

main 函数中,数组变量 arr 的长度为 2,并且指定元素类型为 music 接口。由于接口类型的兼容性,在初始化数组实例时可以使用 popMusic 结构体或者 classicMusic 结构体。

在数组初始化时也可以不指定长度,通过元素个数自动确定数组长度。例如:

r := [...]int32{800, 500, 1600, 2400, 900, 700}

根据所赋值的元素个数,自动推断出数组长度为 6,即 [6]int32

1.2 访问数组元素

通过索引可以随机访问数组元素,索引值必须为 int 类型数值,不能是负值。索引范围为 [0, n-1]n 为数组长度)。

下面示例中,创建了一个包含 5 个元素的数组实例,然后通过索引读取最后两个元素:

var x = [5]rune{'a', 'e', 'i', 'o', 'u'}
// 倒数第二个元素,索引为 3
last1 := x[3]
// 最后一个元素,索引为 4
last2 := x[4]
fmt.Printf("最后两个元素: %c, %c\n", last1, last2)

最后两个元素的索引分别为 34。此示例还可以这样处理:

var x = [5]rune{'a', 'e', 'i', 'o', 'u'}
// 获取数组的长度
n := len(x)
// 最后一个元素的索引为 n-1,倒数第二个的索引为 n-2
last1 := x[n-2]
last2 := x[n-1]
fmt.Printf("最后两个元素: %c, %c\n", last1, last2)

len 是内置函数,其作用是获得数组的长度。随后,最后两个元素的索引可以由 n 的值来确定。

上述示例的运行结果如下:

最后两个元素: o, u

如果想顺序访问数组中的所有元素,可以使用 for 循环。

第一种格式是使用带三个子句的 for 循环,通过一个临时变量来存储索引值。例如:

arr1 := [4]float32{0.11, 0.23, 5.001, 12.63}
for i := 0; i < len(arr1); i++ {fmt.Println(arr1[i])
}

初始化子句将变量 i 的值设置为 0,可访问数组中第一个元素。执行循环的条件子句指定 i 的值应小于数组的长度(即最大值为 n-1),每一轮循环后将变量 i 的值加 1

第二种格式是与 range 子句一起使用。

arr2 := [3]string{"zh-CN", "en-US", "zh-TW"}
for index, value := range arr2 {fmt.Printf("索引: %d, 值: %s\n", index, value)
}

range 子句从数组中取出一个子项,其中包含两个值——元素的索引和元素的值。

1.3 [n]T*[n]T 的区别

[n]T*[n]T 这两种声明格式看起来很像,但它们的含义是完全不同的。

  1. *[n]T:指针类型,存放类型为 [n]T 的实例内存地址。
  2. [n]*T:数组类型,其元素类型为指向 int 数值的指针类型(*int)。

可以通过两个简单的示例来说明。先看第一个示例,定义数组变量 d,元素类型为 float32,数组长度为 3

var d = [3]float32{0.001, 0.002, 0.003}

再定义变量 pd,赋值时通过取地址运算符 & 获取数组实例的内存地址。返回的类型是 *[3]float32

var pd = &d

变量 pd 是指针类型,它的值是数组实例 d 的内存地址。

下面是第二个例子。定义三个变量并初始化,类型都是 int

var a, b, c = 50, 60, 70

接着定义变量 ax,初始化时引用上述三个变量的内存地址。

var ax = [3]*int{&a, &b, &c}

变量 ax 为数组类型,它的元素是 *int 类型。

1.4 多维数组

多维数组指的是维度为二或二以上的数组。Go语言的多维数组更像是“数组的数组”,例如,A数组中包含元素 B,而元素 B 本身也是一个数组。

二维数组的表示形式为:

[m][n]Type

相当于:

[m]([n]Type)

三维数组的表示形式为:

[m][n][o]Type

相当于:

[m]([n]([o]Type))

读取或修改多维数组的元素,也可以通过索引来完成。

a[m][n] = x
y = a[x][y][z]

下面代码分别演示了二维数组和三维数组的使用。

// 二维数组
var a = [2][4]uint8{{12, 13, 14, 15},{16, 17, 18, 19},
}// 输出各元素
fmt.Println("----- 二维数组中的元素 -----")
for i := 0; i < 2; i++ {for j := 0; j < 4; j++ {fmt.Printf("%d ", a[i][j])}fmt.Println() // 换行
}
fmt.Println()// 三维数组
var b = [5][4][3]int32{{{1, 2, 3},{7, 8, 9},{12, 15, 18},{25, 26, 27},},{{-2, -3, -6},{-20, 35, -7},{60, 62, 64},{-100, -101, -102},},{{65, 66, 67},{305, 405, 505},{125, 135, 145},{-6, -17, 810},},{{2200, 130, -96},{-72, 160, 400},{215, -76, -320},{57, 58, 59},},{{8850, 3756, 418},{-600, -520, 307},{2125, 1102, -4720},{-595, -116, 907},},
}fmt.Println("----- 三维数组中的元素 -----")
for i := 0; i < 5; i++ {for j := 0; j < 4; j++ {for k := 0; k < 3; k++ {fmt.Printf("%d ", b[i][j][k])}fmt.Println() // 换行}fmt.Println("\n") // 换行
}

运行代码后,得到结果如下:

----- 二维数组中的元素 -----
12 13 14 15
16 17 18 19----- 三维数组中的元素 -----
1 2 3
7 8 9
12 15 18
25 26 27-2 -3 -6
-20 35 -7
60 62 64
-100 -101 -10265 66 67
305 405 505
125 135 145
-6 -17 8102200 130 -96
-72 160 400
215 -76 -320
57 58 598850 3756 418
-600 -520 307
2125 1102 -4720
-595 -116 907

2 切片

切片(slice)与数组类似,但要比数组灵活,可以在运行阶段动态地添加元素,在实际开发中会用得比较多。

切片类型的底层是通过数组来存储元素的。这个数组实例既可以是代码中已经定义的,也可以由应用程序隐式产生的。

2.1 创建切片实例

以下几种方法都可以创建切片实例:

  1. 从现有的(代码中已定义过的)数组实例中“截取”出新的切片实例。格式如下:
s := a[L:H]

数组实例 a 中被提取的元素索引范围为 L <= index < H。例如:

var x = [5]int32{2, 4, 6, 8, 10}
s := x[2:4]

变量 x 为数组对象,共 5 个元素,切片对象 sx 中提取索引为 23 的元素(即第三、第四个元素),所以 s 中包含的元素为 68

如果将上述代码做以下修改,那么 s2 中就包含 6810 三个元素。

s2 := a[2:5]

索引读取范围为 2 <= index < 5,即被使用的索引为 234

LH 两个值省略,表示使用数组中的所有元素。

s3 := a[:]

从同一个数组实例产生的所有切片实例都会共享数组中的元素,也就是说,当数组中的元素被更改,切片中对应的元素也会同步更新;反过来,如果切片中的元素被更改,数组中对应的元素也会同步更新。以下示例代码将说明这一点。

// 实例化一个数组对象
src := [4]uint32{10, 20, 30, 40}
// 从数组产生两个切片实例
s1 := src[0:2]
s2 := src[1:4]fmt.Println("----- 修改数组前 -----")
fmt.Printf("数组: %v\n", src)
fmt.Printf("切片 1: %v\n", s1)
fmt.Printf("切片 2: %v\n", s2)// 修改数组中的元素
src[0] = 100
src[2] = 300
fmt.Println("\n----- 修改数组后 -----")
fmt.Printf("数组: %v\n", src)
fmt.Printf("切片 1: %v\n", s1)
fmt.Printf("切片 2: %v\n", s2)// 修改切片中的元素
s1[1] = 700
s2[2] = 900
fmt.Println("\n----- 修改切片后 -----")
fmt.Printf("数组: %v\n", src)
fmt.Printf("切片 1: %v\n", s1)
fmt.Printf("切片 2: %v\n", s2)

数组 src 包含 4 个元素,切片 s1 使用了数组中前两个元素(索引是 01);切片 s2 使用了第二、三、四个元素(索引为 123)。这段代码的运行结果如下:

----- 修改数组前 -----
数组: [10 20 30 40]
切片 1: [10 20]
切片 2: [20 30 40]----- 修改数组后 -----
数组: [100 20 300 40]
切片 1: [100 20]
切片 2: [20 300 40]----- 修改切片后 -----
数组: [100 700 300 900]
切片 1: [100 700]
切片 2: [700 300 900]

数组中的第一个元素被修改为 100,切片 s1 的第一个元素也同步更新为 100;同理,数组中第三个元素被修改为 300,切片 s2 的第二个元素也同步更新为 300。对切片实例的修改也会同步到数组实例上,因为切片 s1s2 都是以数组 src 为存储基础的,它们共享数组中的元素。

  1. 直接初始化。格式与数组接近,示例如下:
var (s = []string{"how", "do", "you", "do"}t = []float64{999.0000065, -73.30000082}
)

切片的初始化表达式中不需要指定元素个数(长度),但一对空白中括号([])必须保留。

  1. 使用 make 函数
s := make([]byte, 30)

第一个参数指定要创建实例的类型,此处必须指明是切片类型。因为 make 函数不仅可以创建切片(slice)实例,也可以创建通道(channel)、映射(map)实例。第二个参数指定切片的长度。上述代码中,创建了一个长度为 30 的切片,而且每个元素都会使用 byte 类型的默认值来初始化。

2.2 添加和删除元素

向切片添加元素,可以调用 append 函数。函数原型如下:

func append(slice []Type, elems ...Type) []Type

slice 参数是要追加元素的切片实例,elems 是个数可变的参数,它表示要添加到切片实例中的元素,可以是一个元素,也可以是多个元素。

如果切片所引用的基础数组有足够的容量容纳新添加的元素,那么 append 函数将原来的切片实例返回;如果基础数组的容量不足,append 函数会创建新的数组实例并分配更大的空间,然后把旧数组实例的元素复制到新实例中,并添加新的元素,最后返回由新数组实例所产生的切片实例。

在调用 append 函数前,代码不需要验证切片的容量是否足够,因为 append 函数会自动处理。但是,为了在调用 append 函数后能够获得最新的切片实例,一般会把 append 函数返回的实例重新赋值给切片类型的变量。就像下面这样:

s = append(s, ...)

下面请看一个示例。

先定义一个函数,用来向屏幕输出切片实例的长度与容量。

func printSliceInfo(s []float32) {fmt.Printf("长度:%d, 容量:%d, 元素列表:%v\n", len(s), cap(s), s)
}

len 函数获取的是切片实例的长度,cap 函数获取的是切片实例的容量,长度是指切片中可以被访问的元素个数,而容量是指应用程序为切片的基础数组所分配的空间。为了保证有足够的空间,容量必须大于或等于长度。

初始化一个切片实例,它包含两个元素。然后多次调用 append 函数向切片实例添加元素。

var sf = []float32{0.001, 0.0007}
printSliceInfo(sf)
// 添加一个元素
sf = append(sf, 0.0014)
printSliceInfo(sf)
// 添加两个元素
sf = append(sf, 0.0008, 0.1205)
printSliceInfo(sf)
// 添加三个元素
sf = append(sf, 0.0275, 1.302, 5.0071)
printSliceInfo(sf)

得到的输出结果如下:

长度:2, 容量:2, 元素列表:[0.001 0.0007]
长度:3, 容量:4, 元素列表:[0.001 0.0007 0.0014]
长度:5, 容量:8, 元素列表:[0.001 0.0007 0.0014 0.0008 0.1205]
长度:8, 容量:8, 元素列表:[0.001 0.0007 0.0014 0.0008 0.1205 0.0275 1.302 5.0071]

如果使用 make 函数来创建切片实例,可以为其设置一个默认的容量(初始容量)。当然,随着元素的添加,容量会自动增长。

var s = make([]string, 0, 10)
fmt.Printf("初始化后,长度:%d, 容量:%d\n", len(s), cap(s))
// 添加 50 个元素
for i := 1; i <= 50; i++ {str := fmt.Sprintf("Item %d", i)s = append(s, str)
}
fmt.Printf("添加 50 个元素后,长度:%d, 容量:%d\n", len(s), cap(s))

切片实例 s 初始化的容量为 10,长度为 0,即基础数组分配了可容纳 10 个元素的空间,但其中包含元素个数为 0。如果将 make 函数的调用代码做以下修改,那么创建的切片实例中已包含 3 个元素,这 3 个元素都分配了 string 类型的默认值(空字符串)。

var s = make([]string, 3, 10)

标准库没有提供用于删除切片元素的函数,但是,可以通过截取元素来实现。例如:

var s = []int{1, 2, 3, 4, 5}
fmt.Printf("初始元素列表:%v\n", s)// 截取除最后一个元素外的所有元素
s = s[0 : len(s)-1]
fmt.Printf("删掉最后一个元素后:%v\n", s)

上面代码中,切片实例 s5 个元素,截取时从索引 0 开始,到 len(s)-1,即 [0:4],这样一来,被提取到新切片实例的元素为 1234,删除最后一个元素的目的便实现了。

输出结果如下:

初始元素列表:[1 2 3 4 5]
删掉最后一个元素后:[1 2 3 4]

下面的例子将演示如何删除切片实例中的前两个元素。

var s = []int{1, 2, 3, 4, 5}
fmt.Printf("初始元素列表:%v\n", s)
// 删除前两个元素
s = s[2:]
fmt.Printf("删除前两个元素后:%v\n", s)

[2:] 表示从索引 2(第三个元素)开始截取,直到最后一个元素。这样一来就删除了前两个元素,结果如下:

初始元素列表:[1 2 3 4 5]
删除前两个元素后:[3 4 5]

总结

通过本章的学习,读者应该对Go语言中的数组和切片有了全面的了解。数组提供固定长度的集合,适合需要明确边界的数据结构;而切片则提供了灵活的动态集合,适合需要动态扩展的数据结构。在实际开发中,根据需求选择合适的结构,可以提高代码的效率和可维护性。希望本章的内容能够帮助读者在Go语言的学习和应用中取得更大的进步。

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

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

相关文章

uniapp中如何用iconfont来管理图标集成到我们开发的项目中

我们在开发不管小程序还是APP的过程中都会用到图标这个东西,那么iconfont提供了对应的功能,怎么才能方便的集成到我们的小程序或者APP项目中,目标是方便调用并且方便管理。 首先注册ICONFONT账号 www.iconfont.cn中去注册即可选择我们需要的图标如下 我们搜索我们需要的图…

从实用的角度聊聊Linux下文本编辑器VIM

本文从实用的角度聊聊Vim的常用命令。何为实用&#xff1f;我举个不实用的例子大家就明白了&#xff0c;用vim写代码。;) “vim是从 vi 发展出来的一个文本编辑器。代码补全、编译及错误跳转等方便编程的功能特别丰富&#xff0c;在程序员中被广泛使用&#xff0c;和Emacs并列成…

优化程序命名:提升专业感与用户体验

在软件开发的广阔天地中&#xff0c;程序命名这一环节常常被开发者们忽视。不少程序沿用着简单直白、缺乏雕琢的名字&#xff0c;如同素面朝天的璞玉&#xff0c;虽不影响其核心功能的发挥&#xff0c;但却在无形之中错失了许多提升用户印象与拓展应用场景的机会。今天&#xf…

LeetCode BFS解决最短路问题

广度优先搜索(BFS, Breadth-First Search)是一种用于图和树结构的遍历算法&#xff0c;特别适合解决无权图的最短路径问题。 算法思想&#xff1a; BFS从起始节点开始&#xff0c;按照"广度优先"的原则&#xff0c;逐层向外扩展搜索&#xff1a; 先访问起始节点的…

[物联网iot]对比WIFI、MQTT、TCP、UDP通信协议

第一步&#xff1a;先理解最基础的关系&#xff08;类比快递&#xff09; 假设你要给朋友寄快递&#xff1a; Wi-Fi&#xff1a;相当于“公路和卡车”&#xff0c;负责把包裹从你家运到快递站。 TCP/UDP&#xff1a;相当于“快递公司的运输规则”。 TCP&#xff1a;顺丰快递&…

基于python的电影数据分析及可视化系统

一、项目背景 随着电影行业的快速发展&#xff0c;电影数据日益丰富&#xff0c;如何有效地分析和可视化这些数据成为行业内的一个重要课题。本系统旨在利用Python编程语言&#xff0c;结合数据分析与可视化技术&#xff0c;为电影行业从业者、研究者及爱好者提供一个便捷的电…

Java8 到 Java21 系列之 Lambda 表达式:函数式编程的开端(Java 8)

Java8 到 Java21 系列之 Lambda 表达式&#xff1a;函数式编程的开端&#xff08;Java 8&#xff09; 系列目录 Java8 到 Java21 系列之 Lambda 表达式&#xff1a;函数式编程的开端&#xff08;Java 8&#xff09;Java 8 到 Java 21 系列之 Stream API&#xff1a;数据处理的…

②EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

型号 协议转换通信网关 EtherCAT 转 Modbus TCP 配置说明 网线连接电脑到模块上的 WEB 网页设置网口&#xff0c;电脑所连网口的网段设置成 192.168.1.X&#xff08;X 是除 8 外的任一数值&#xff09;后&#xff0c;打开浏览器&#xff0c;地址栏输入 192.168.1.8 &#xff…

机器视觉--python基础语法

Python基础语法 1. Python标识符 在 Python 里&#xff0c;标识符由字母、数字、下划线组成。 在 Python 中&#xff0c;所有标识符可以包括英文、数字以及下划线(_)&#xff0c;但不能以数字开头。 Python 中的标识符是区分大小写的。 以下划线开头的标识符是有特殊意义的…

算法日常记录

1. 链表 1.1 删除链表的倒数第 N 个结点 问题描述&#xff1a;给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 思路&#xff1a;先让fast跑n步&#xff0c;然后…

14使用按钮实现helloworld(1)

目录 还可以通过按钮的方式来创建 hello world 涉及Qt 中的信号槽机制本质就是给按钮的点击操作,关联上一个处理函数当用户点击的时候 就会执行这个处理函数 connect&#xff08;谁发的信号&#xff0c; 信号类型&#xff0c; 谁来处理这个信息&#xff0c; 怎么处理的&…

【Golang】泛型与类型约束

文章目录 一、环境二、没有泛型的Go三、泛型的优点四、理解泛型&#xff08;一&#xff09;泛型函数&#xff08;Generic function&#xff09;1&#xff09;定义2&#xff09;调用 &#xff08;二&#xff09;类型约束&#xff08;Type constraint&#xff09;1&#xff09;接…

k8s常用总结

1. Kubernetes 架构概览 主节点&#xff08;Master&#xff09;&#xff1a; 负责集群管理&#xff0c;包括 API Server、Controller Manager、Scheduler 和 etcd 存储。 工作节点&#xff08;Node&#xff09;&#xff1a; 运行 Pod 和容器&#xff0c;包含 kubelet、kube-pr…

Android 单例模式全解析:从基础实现到最佳实践

单例模式&#xff08;Singleton Pattern&#xff09;是软件开发中常用的设计模式&#xff0c;其核心是确保一个类在全局范围内只有一个实例&#xff0c;并提供全局访问点。在 Android 开发中&#xff0c;单例模式常用于管理全局资源&#xff08;如网络管理器、数据库助手、配置…

ffmpeg滤镜使用

ffmpeg实现画中画效果 FFmpeg中&#xff0c;可以通过overlay将多个视频流、多个多媒体采集设备、多个视频文件合并到一个界面中&#xff0c;生成画中画的效果 FFmpeg 滤镜 overlay 基本参数 x和y x坐标和Y坐标 eof action 遇到 eof表示时的处理方式&#xff0c;默认为重复。…

OpenAI即将开源!DeepSeek“逼宫”下,AI争夺战将走向何方?

OpenAI 终于要 Open 了。 北京时间 4 月 1 日凌晨&#xff0c;OpenAI 正式宣布&#xff1a;将在未来几个月内开源一款具备推理能力的语言模型&#xff0c;并开放训练权重参数。这是自 2019 年 GPT-2 部分开源以来&#xff0c;OpenAI 首次向公众开放核心模型技术。 【图片来源于…

贪心算法,其优缺点是什么?

什么是贪心算法&#xff1f; 贪心算法(Greedy Algorithm)是一种在每一步选择中都采取在当前状态下最优(局部最优)的选择&#xff0c;从而希望导致全局最优解的算法策略。 它不像动态规划那样考虑所有可能的子问题&#xff0c;而是做出局部最优选择&#xff0c;依赖这些选择来…

python string 类型字符拼接 +=的缺点,以及取代方法

在Python中&#xff0c;使用进行字符串拼接虽然语法简单&#xff0c;但在性能和代码维护方面存在明显缺陷。以下是详细分析及替代方案&#xff1a; 一、的缺点 性能低下 内存分配问题&#xff1a;字符串在Python中不可变&#xff0c;每次操作会创建新字符串对象&#xff0c;导…

web前端开发-JS

web前端开发-JS 什么是JavaScript Web标准也称网页标准,由一系列的标准组成,大部分由W3C(World Wide Web Consortium,万维网联盟)负责制定。三个组成部分: HTML:负责网页的结构(页面元素和内容)。CSS:负责网页的表现(页面元素的外观、位置等页面样式,如:颜色、大小等)。JavaS…

Turtle综合案例实战(绘制复杂图形、小游戏)

在学习了 Turtle 基本的绘图技巧后,我们可以通过结合多个概念和技巧,绘制复杂的图形或实现简单的小游戏。本章将介绍两个实战案例: 绘制复杂图形:结合前面所学的知识,绘制一个精美的多层次复杂图案。简单的游戏:利用 Turtle 实现一个简单的小游戏——蛇形游戏,这是一个经…