十一、数组与切片

一、声明数组并赋值
在声明数组时候必须指定数组长度

var cheeses [2]string
    cheeses[0] = "one"
    cheeses[1] = "two"

二、切片
在go语言中,切片是非常常用的


1、利用数组创建切片
```go
func main() {
    month := [...]string{"1", "2", "3", "4", "5", "6"}
    cheeses := month[0:3]
    cheeses2 := month[1:]
    cheeses3 := month[:5]
    fmt.Println(cheeses)
    fmt.Println(cheeses2)
    fmt.Println(cheeses3)
}
#结果
[1 2 3]
[2 3 4 5 6]
[1 2 3 4 5]
结果可以发现
1、0:3获取123 属于包前不包后
2、1: 包当下至最后
3、:5 包前不包当下
```

2、使用make直接创建切片
myChee := make([]int, 5, 10)
    fmt.Println(myChee)
    fmt.Println(len(myChee))
    fmt.Println(cap(myChee))
    
#结果
[0 0 0 0 0] 因为没有插值所以都是0
5 使用len函数打印出长度是5
10 使用cap函数打印出容量是10

3、长度与容量(难点)

定义:
切片长度:指切片中元素的数量,可以使用len()函数查询其切片的长度。
切片容量:指切片底层数组的长度,可以使用cap()函数查看容量
当定义make([]int, 4, 6),长度为4,容量为6

func main() {
    myChee := make([]int, 4, 6)
    myChee = append(myChee, 1)
    fmt.Println(len(myChee))
    fmt.Println(cap(myChee))
    fmt.Println(myChee)
}
#结果
5
6
[0 0 0 0 1]

在上面案例中,定义了长度为4,容量为6
通过2种案例我们可知,append了1后,是在4个0后增加这个1,并且长度变成了5而容量依然是6,那么如果当我们append 3个1后,容量变成多少了呢?

myChee = append(myChee, 1, 2, 3)

#结果
7
12
[0 0 0 0 1 2 3]

长度变为了7,容量变成了初始6的两倍,变成了6*2=12,而不是一个个增加的
同时如果我们打印出&myChee的地址,会发现如果超过了初始的容量,地址值也会产生改变,没有超过初始容量值则地址不会发生改变

3.1 长度与容量的深入理解

在 Go 中,切片是由数组支持的。这意味着切片的数据以连续的方式存储在数组数据结构中。切片还负责在底层数组已满时添加元素,或在几乎为空时缩减底层数组。

在内部,切片包含指向底层数组的指针,以及长度和容量。长度表示切片包含的元素数量,而容量表示底层数组中的元素数量,从切片中的第一个元素开始计算。让我们通过一些示例来更清楚地了解这些概念。首先,让我们使用给定的长度和容量初始化一个切片:

s := make([]int, 3, 6) // Three-length, six-capacity slice

第一个参数,表示长度,是必须的。但是,第二个参数表示容量是可选的。图1展示了此代码在内存中的结果。

 一个长度为3、容量为6的切片

在这种情况下,make 创建了一个包含六个元素的数组(容量)。但由于长度设置为3,Go 只初始化了前三个元素。另外,因为切片是 []int 类型,所以前三个元素被初始化为 int 类型的零值:0。灰色元素已经分配但尚未使用。

如果我们打印这个切片,会得到长度范围内的元素 [0 0 0]。如果我们将 s[1] 设为1,切片的第二个元素会更新,但不会影响其长度或容量。图2说明了这一点。

图2 — 更新切片的第二个元素:s[1] = 1

然而,访问超出长度范围之外的元素是被禁止的,即使它在内存中已经分配。例如,s[4] = 0 会导致以下 panic:

panic: runtime error: index out of range [4] with length 3

我们如何使用切片剩余的空间呢?通过使用内置函数 append:

s = append(s, 2)

这段代码向现有的 s 切片追加了一个新元素。它使用了第一个灰色元素(已分配但尚未使用)来存储元素2,正如图3所示。

图3 — 向 s 切片追加一个元素

切片的长度从3更新为4,因为现在切片包含了四个元素。现在,如果我们再添加三个元素以至于后台数组不够大,会发生什么?

s = append(s, 3, 4, 5)
fmt.Println(s)

如果我们运行这段代码,会看到切片能够满足我们的请求:

[0 1 0 2 3 4 5]

因为数组是一个固定大小的结构,在第4个元素之前,它能够存储新的元素。当我们想要插入第5个元素时,数组已经满了:Go 内部会创建另一个数组,将所有元素复制过去,然后再插入第5个元素。图4展示了这个过程。

图4 — 因为初始的后台数组已满,Go 创建了另一个数组并复制了所有元素。

现在切片引用了新的后台数组。之前的后台数组会怎样呢?如果它不再被引用,它最终会被垃圾收集器(GC)释放,如果它是在堆上分配的话(我们在错误#95 “不理解堆栈与堆的区别”中讨论了堆内存,并在错误#99 “不理解GC的工作原理”中讨论了GC的工作原理)。

对切片进行切片操作会发生什么?切片是对数组或切片进行的操作,提供了一个左闭右开的范围;第一个索引是包括的,而第二个索引是排除的。以下示例展示了影响,并在图5中显示了内存中的结果:

s1 := make([]int, 3, 6) // Three-length, six-capacity slice
s2 := s1[1:3] // Slicing from indices 1 to 3

图5 — 切片 s1 和 s2 引用相同的后台数组,但长度和容量不同

首先,s1 是一个长度为3、容量为6的切片。当通过对 s1 进行切片创建 s2 时,两个切片都引用同一个后台数组。但是,s2 从不同的索引开始,即索引1。因此,它的长度和容量(长度为2,容量为5)与 s1 不同。如果我们更新 s1[1] 或 s2[0],则更改会作用于同一个数组,因此在两个切片中都是可见的,如图6所示。

图6 — 因为 s1 和 s2 共享同一个数组,更新共同的元素会使两个切片中的更改都可见

现在,如果我们向 s2 添加一个元素会发生什么?以下代码会同时改变 s1 吗?

s2 = append(s2, 2)

共享的后台数组被修改,但只有 s2 的长度发生了变化。图7展示了向 s2 添加元素的结果。

图7 — 向 s2 添加元素

s1 仍然是一个长度为3、容量为6的切片。因此,如果我们打印 s1 和 s2,添加的元素只会在 s2 中可见:

s1=[0 1 0], s2=[1 0 2]

很重要理解这种行为,这样我们在使用 append 时就不会形成错误的假设。

注意: 在这些示例中,后台数组是内部的,不直接对 Go 开发者可见。唯一的例外是从现有数组切片创建切片。

还有最后一件事需要注意:如果我们不断向 s2 中添加元素,直到后台数组满为止,内存状态会是怎样的?让我们再添加三个元素,以便后台数组没有足够的容量:

s2 = append(s2, 3)
s2 = append(s2, 4) // At this stage, the backing is already full
s2 = append(s2, 5)

这段代码导致创建了另一个后台数组。图 8 展示了内存中的结果。

图 8 — 向 s2 添加元素直到后台数组已满

s1 和 s2 现在引用两个不同的数组。由于 s1 仍然是一个三长度、六容量的切片,它仍然有一些可用缓冲区,因此它继续引用最初的数组。而且,新的后台数组是通过从 s2 的第一个索引复制初始数组而生成的。这就是为什么新数组从元素 1 开始,而不是 0。

注意用法

package main

import "fmt"

func main() {
	slice := make([]int, 4, 4)
	slice2 := make([]int, 4, 5)
    // 调用下方定义的test函数
	test(slice)
	test(slice2)

	// 输出两个切片调用test函数后的结果
	fmt.Printf("slice的结果: %v\n", slice)
	fmt.Printf("slice2的结果: %v\n", slice2)

}

func test(s []int) {
	s = append(s, 1)
	for i := 0; i < len(s); i++ {
		s[i] = 100
	}
}

// slice的结果: [0 0 0 0]
// slice2的结果: [100 100 100 100]

结论

总结一下,切片长度 是切片中可用元素的数量,而 切片容量 是后台数组中的元素数量。向一个已满的切片(长度 == 容量)添加元素会导致创建一个新的后台数组,将之前数组中的所有元素复制到新数组中,并更新切片指向新数组。

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值