数组和切⽚ - Go语言从入门到实战
数组的声明
package main
import "fmt"
func main() {
var a [3]int //声明并初始化为默认零值
a[0] = 1
fmt.Println("a:", a) // 输出: a: [1 0 0]
b := [3]int{1, 2, 3} //声明同时初始化
fmt.Println("b:", b) // 输出: b: [1 2 3]
c := [2][2]int{{1, 2}, {3, 4}} //多维数组初始化
fmt.Println("c:", c) // 输出: c: [[1 2] [3 4]]
}
数组元素遍历
与其他主要编程语⾔的差异
func TestTravelArray(t *testing.T) {
a := [...]int{1, 2, 3, 4, 5} // 这个数组有5个元素
for idx, elem := range a {
fmt.Println(idx, elem) // 打印索引和元素值
}
}
与其他一些编程语言(如C或Java)不同,Go语言的数组索引是从0开始的。在上面的示例中,第一个元素的索引是0,第二个元素的索引是1,依此类推。
Go语言中的数组是值类型,当你将数组作为参数传递给函数时,会创建一个数组的副本。如果你想在函数内部修改原始数组的元素,需要传递数组的指针。切片
则不同,它们是引用类型,传递切片时会共享底层数组。
数组截取、切片
在Go语言中,可以使用切片(slice)来实现数组的截取。切片是对数组的一个连续片段的引用,可以通过指定开始索引和结束索引来定义。
代码示例:
//a[开始索引(包含), 结束索引(不包含)]
a := [...]int{1, 2, 3, 4, 5}
// a[1:2] 截取从索引1到索引2(不包含)的元素,结果为 [2]
slice1 := a[1:2]
fmt.Println(slice1) // 输出: [2]
// a[1:3] 截取从索引1到索引3(不包含)的元素,结果为 [2, 3]
slice2 := a[1:3]
fmt.Println(slice2) // 输出: [2 3]
// a[1:len(a)] 截取从索引1到数组末尾的元素,结果为 [2, 3, 4, 5]
slice3 := a[1:len(a)]
fmt.Println(slice3) // 输出: [2 3 4 5]
// a[1:] 截取从索引1到数组末尾的元素,结果为 [2, 3, 4, 5]
slice4 := a[1:]
fmt.Println(slice4) // 输出: [2 3 4 5]
// a[:3] 截取从数组开始到索引3(不包含)的元素,结果为 [1, 2]
slice5 := a[:3]
fmt.Println(slice5) // 输出: [1 2]
切片内部结构
type slice struct {
array unsafe.Pointer // 底层数组的指针,指向切片所引用的数组的首个元素的地址。
len int // 切片的长度,即切片当前包含的元素个数。
cap int // 切片的容量,即从切片的起始位置到底层数组的末尾的元素个数。容量表示了可以在不重新分配内存的情况下,切片可以容纳的元素数量。
}
切⽚声明
var s0 []int //声明了一个名为s0的切片,该切片的元素类型为int。s0是一个空切片,长度为0,容量为0。
s0 = append(s0, 1) //使用append函数将整数1添加到s0切片的末尾。由于s0的容量为0,因此会自动分配内存空间以容纳新元素。s0的长度为1,容量大于1。
s := []int{} //声明并初始化了一个名为s的切片,该切片的元素类型为int。s是一个空切片,长度为0,容量为0。
s1 := []int{1, 2, 3} //声明并初始化了一个名为s1的切片,该切片的元素类型为int,并指定了初始元素为1、2和3。此时,s1的长度为3,容量大于或等于3。
s2 := make([]int, 2, 4) //使用make函数创建了一个名为s2的切片,该切片的元素类型为int,长度为2,容量为4。s2的前两个元素会被初始化为整数类型的默认零值(即0),而后续的元素则未初始化且不可访问。
/*
[]type, len, cap
其中len个元素会被初始化为默认零值,未初始化元素不可以访问
*/
切⽚共享存储结构
图片看起来很绕,可以用代码来解释,展示了在Go语言中切片如何共享底层数组的内存。在这个示例中,有一个名为year的字符串切片,它包含了12个月份的名称。然后,通过切片操作,创建了两个新的切片Q2和summer,它们分别引用了year切片的不同片段。
func TestSliceShareMemory(t *testing.T) {
year := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
"Oct", "Nov", "Dec"}
Q2 := year[3:6]
//Q2 := year[3:6]:创建了一个名为Q2的切片,它引用了year切片从索引3到索引5的元素(不包括索引6)。
t.Log(Q2, len(Q2), cap(Q2))
//输出Q2的内容、长度和容量。此时,Q2的内容为["Apr", "May", "Jun"],长度为3,容量为9(与year切片的容量相同)。
summer := year[5:8]
//创建了一个名为summer的切片,它引用了year切片从索引5到索引7的元素(不包括索引8)。
t.Log(summer, len(summer), cap(summer))
//输出summer的内容、长度和容量。此时,summer的内容为["Jun", "Jul", "Aug"],长度为3,容量为9(与year切片的容量相同)。
summer[0] = "Unknow"
//将summer切片的第一个元素修改为"Unknow"。由于summer和Q2切片共享底层数组的内存,这个修改也会影响到Q2切片对应位置的元素。
t.Log(Q2)
//输出修改后的Q2切片的内容。此时,Q2的内容变为["Unknow", "May", "Jun"]。
t.Log(year)
//输出修改后的year切片的内容。此时,year切片的内容也受到了影响,变为"Jan", "Feb", "Mar", "Apr", "Unknow", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"。
}
数组 vs. 切⽚
- 容量是否可伸缩:
- 数组:数组在声明时需要指定元素个数,一旦创建后,其长度和容量都是固定的,无法改变。这意味着数组的大小是固定的,无法根据需要进行扩展或缩小。
- 切片:切片是对底层数组的一个连续片段的引用,它具有动态大小的特性。切片的长度可以根据需要进行扩展或缩小,而容量也可以根据需要进行增长。这是通过
append
函数实现的,当切片的长度超过容量时,append
函数会自动分配新的底层数组,并将原始数据复制到新的数组中。
- 是否可以进行比较:
- 数组:在Go语言中,数组是值类型,这意味着它们可以进行直接比较。两个数组只有在长度相等且对应位置的元素都相等时才被认为是相等的。
- 切片:切片是引用类型,它们不可以进行直接比较。切片的比较需要逐个元素进行比较,或者可以使用
reflect.DeepEqual
函数来进行深度比较。这是因为切片包含了一个指向底层数组的指针,而指针的比较并不是直接比较底层数据的内容。
reflect.DeepEqual
函数会递归地比较两个值的结构和具体的元素。对于切片、映射和结构体等复杂数据类型,它会逐个比较它们的元素或字段。
package main
import (
"fmt"
"reflect"
)
func main() {
slice1 := []int{1, 2, 3}
slice2 := []int{1, 2, 3}
slice3 := []int{4, 5, 6}
fmt.Println(reflect.DeepEqual(slice1, slice2)) // 输出: true
fmt.Println(reflect.DeepEqual(slice1, slice3)) // 输出: false
}
学习Go语言主要是多练,多找些代码段写写,不懂可以私聊咨询。
欢迎关注云尔Websites CSDN博客
欢迎进入Golang交流社区