在Go语言的世界里,数组和切片是构建高效、可靠程序的基石。它们提供了一种强大的方式来组织和管理数据集合,使得数据操作既直观又灵活。本文《Go语言进阶,数组与切片》将带领你深入探索这两种数据结构的内部机制,理解它们的本质区别,以及如何有效地使用它们来提升你的Go编程技能。
Array
(数组)Array
简介数组Array
是编程语言中的常见数据类型,几乎所有的主流编程语言都支持数组Array
,Go语言也不例外。
数组 Array
是一片连续的内存区域,存储相同类型的元素,元素的个数固定。 在Go语言中,数组Array
不能进行扩容、在复制和传递时为值复制
Array
声明Go语言中,数组声明主要有三种方式(其他方式一般为以下三种方式的变种)
var arr1 [5]int // 默认方式,定义一个长度为5的int类型数组,元素自动初始化为0
var arr2 = [5]int{1, 2, 3, 4, 5} // 声明并初始化一个长度为5的int类型数组
arr3 := [...]int{1, 2, 3, 4, 5} // 使用...让Go自动计算数组长度
Array
访问arr[0]
将访问数组的第一个元素。arr[1] = 10
将把数组的第二个元素设置为10。for
循环来遍历数组的所有元素。Array
的容量和长度
在Go语言中,数组的长度是固定的,且数组的长度决定了其容量。数组的长度可以通过内置函数len()
来获取:
length := len(arr)
Array
的底层结构和实现原理数组在编译时构建抽象语法树阶段的数据类型为TARRAY
,通过NewArray
函数进行创建,AST节点的Op操作为OARRAYLIT
。
// NewArray 返回一个固定长度的数组类型对象
func NewArray(elem *Type, bound int64) *Type {
// 元素的数量不能小于0
if bound < 0 {
base.Fatalf("NewArray: invalid bound %v", bound)
}
t := newType(TARRAY)
t.extra = &Array{Elem: elem, Bound: bound}
t.SetNotInHeap(elem.NotInHeap())
if elem.HasTParam() {
t.SetHasTParam(true)
}
if elem.HasShape() {
t.SetHasShape(true)
}
return t
}
// 数组的底层结构
type Array struct {
Elem *Type // 元素类型
Bound int64 // 元素的数量; <0 if unknown yet, 根据NewArray的实现可以看出不会小于0
}
Array
的优缺点分析优点:
缺点:
Slice
(切片)Slice
(切片)简介在Go语言中,Slice(切片)是一种非常灵活且强大的数据结构,它是对数组的抽象和扩展。
Slice本身并不存储数据,而是对底层数组的引用,并包含指向数组起始元素的指针、切片长度以及切片的容量等信息。
由于Slice的这种特性,它可以在不改变底层数组的情况下进行动态地增长和缩小,使得在处理可变大小的集合时更加高效和灵活。
Slice
(切片)声明与初始化
下面是slice的常见声明方式
slice1 := []int{1, 2, 3} // 声明并初始化Slice
var slice2 []int // 声明Slice但不分配空间
slice3 := make([]int, 3) // 使用make函数声明Slice
slice4 := make([]int, 3, 5) // 使用make函数声明Slice, 长度为3, 容量为5
Slice
(切片)的底层数据结构Slice
(切片)的底层数据结构包含三个字段
type slice struct {
array unsafe.Pointer
len int
cap int
}
Slice
(切片)的截取和数组一样,切片中的数据仍然是内存中的一片连续区域。
要获取切片某一区域的连续数据,可以通过下标的方式对切片进行截断。要获取切片某一区域的连续数据,可以通过下标的方式对切片进行截断。
// 切片截取的语法
subSlice := oldSlice[start:end]
max
),则新切片的容量将跟随原切片的容量。start
和 end
的值是有效的,即 start
必须小于等于 end
,且两者都必须在原切片的索引范围内。示例:
oldSlice := []int{101, 102, 103, 104, 105}
// 截取从索引1(包含)到索引3(不包含)的元素
newSlice := oldSlice[1:3]
Slice
(切片)值复制与数据引用在Go语言中,slice
(切片)本身是一个值类型,但slice
的值复制实际上是对底层数组的引用和长度、容量的拷贝,而不是对底层数组元素的完全复制。这意味着当你复制一个slice
时,新的slice将引用相同的底层数组,但可以有不同的长度和容量。
oldSlice := []int{1, 2, 3, 4, 5}
// 将原始slice赋值给一个新的slice
newSlice := oldSlice
// 修改新slice的元素
newSlice[0] = 100
// 打印原始slice和新slice
fmt.Println(oldSlice) // 输出: [100 2 3 4 5]
fmt.Println(newSlice) // 输出: [100 2 3 4 5]
Slice
(切片)收缩与扩容在Go语言中,Slice
(切片)收缩可以通过Slice
(切片)的截取来实现。
Go语言内置的append
函数可以添加新的元素到切片的末尾,它可以接受可变长度的元素,并且可以自动扩容。如果原有数组的长度和容量已经相同,那么在扩容后,长度和容量都会相应增加。
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
fmt.Println("原始Slice:", slice, "长度:", len(slice), "容量:", cap(slice))
// 向Slice添加元素,触发扩容
slice = append(slice, 4, 5, 6, 7, 8, 9, 10)
fmt.Println("扩容后的Slice:", slice, "长度:", len(slice), "容量:", cap(slice))
}
输出:
➜ test go run main.go
原始Slice: [1 2 3] 长度: 3 容量: 3
扩容后的Slice: [1 2 3 4 5 6 7 8 9 10] 长度: 10 容量: 10
➜ test
Slice
(切片)扩容的实现原理切片使用append函数添加元素,但不是使用了append函数就需要进行扩容,如下代码向长度为3,容量为4的切片a中添加元素后不需要扩容。
slice := make([]int, 3, 4) // 使用make函数声明Slice, 长度为3, 容量为4
当你向切片中添加元素,而切片的容量不足以容纳更多元素时,Go 会创建一个新的、容量更大的底层数组,并将原有元素复制到新数组中,这个过程即为扩容。
切片扩容的实现原理涉及以下几个步骤:
array
、切片的长度len
和容量cap
。在扩容后,需要更新这个结构体的信息,指向新的底层数组,并更新长度和容量的值。优点:
append
、copy
等,使得对切片的操作非常方便。len
可以获取切片的长度,cap
可以获取切片的容量。缺点:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。