|
|
|
@ -10,7 +10,7 @@
|
|
|
|
|
|
|
|
|
|
数组是类型相同的元素的集合。例如,整数 5, 8, 9, 79, 76 的集合就构成了一个数组。Go不允许在数组中混合使用不同类型的元素(比如整数和字符串)。
|
|
|
|
|
|
|
|
|
|
## 申明
|
|
|
|
|
## 声明
|
|
|
|
|
|
|
|
|
|
数组的类型为` [n]T`,其中 `n` 表示数组中元素的个数,`T` 表示数组中元素的类型。元素的个数 `n`也是数组类型的一部分(我们将在稍后详细讨论)。
|
|
|
|
|
|
|
|
|
@ -51,9 +51,9 @@ func main() {
|
|
|
|
|
|
|
|
|
|
`a[0]`表示数组中的第一个元素。程序的输出为:`[12 78 50]`。
|
|
|
|
|
|
|
|
|
|
(译者注:可以用下标运算符(`[]`)来访问数组中的元素,下标从 0 开始,例如 `a[0]` 表示数组 a 的第一个元素,`a[1] `表示数组 a 的第二元素,以此类推。)
|
|
|
|
|
(译者注:可以用下标运算符(`[]`)来访问数组中的元素,下标从 0 开始,例如 `a[0]` 表示数组 a 的第一个元素,`a[1]` 表示数组 a 的第二个元素,以此类推。)
|
|
|
|
|
|
|
|
|
|
可以利用**速记声明(shorthand declaration)**的方式来创建同样的数组:
|
|
|
|
|
可以利用 **速记声明(shorthand declaration)** 的方式来创建同样的数组:
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
package main
|
|
|
|
@ -87,7 +87,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
上面程序的第 8 行:`a := [3]int{12} `声明了一个长度为 3 的数组,但是只提供了一个初值 12。剩下的两个元素被自动赋值为 0。程序 的输出为:`[12 0 0]`。
|
|
|
|
|
上面程序的第 8 行:`a := [3]int{12}` 声明了一个长度为 3 的数组,但是只提供了一个初值 12。剩下的两个元素被自动赋值为 0。程序 的输出为:`[12 0 0]`。
|
|
|
|
|
|
|
|
|
|
在声明数组时你可以忽略数组的长度并用` ... `代替,让编译器为你自动推导数组的长度。比如下面的程序:
|
|
|
|
|
|
|
|
|
@ -104,7 +104,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
上面已经提到,数组的长度是数组类型的一部分。因此 `[5]int` 和 `[25]int`是两个不同类型的数组。正是因为如此,一个数组不能动态改变长度。不要担心这个限制,因为切片(`slices`)可以弥补这个不足。
|
|
|
|
|
上面已经提到,数组的长度是数组类型的一部分。因此 `[5]int` 和 `[25]int` 是两个不同类型的数组。正是因为如此,一个数组不能动态改变长度。不要担心这个限制,因为切片(`slices`)可以弥补这个不足。
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
package main
|
|
|
|
@ -116,7 +116,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在上面程序的第 6 行,我们试图将一个` [3]int `类型的数组赋值给一个 `[5]int `类型的数组,这是不允许的。编译会报错:`main.go:6: cannot use a (type [3]int) as type [5]int in assignment。`
|
|
|
|
|
在上面程序的第 6 行,我们试图将一个 `[3]int` 类型的数组赋值给一个 `[5]int` 类型的数组,这是不允许的。编译会报错:`main.go:6: cannot use a (type [3]int) as type [5]int in assignment。`
|
|
|
|
|
|
|
|
|
|
## 数组是值类型
|
|
|
|
|
|
|
|
|
@ -189,7 +189,7 @@ func main() {
|
|
|
|
|
|
|
|
|
|
## 使用 range 遍历数组
|
|
|
|
|
|
|
|
|
|
`for `循环可以用来遍历数组中的元素:
|
|
|
|
|
`for` 循环可以用来遍历数组中的元素:
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
package main
|
|
|
|
@ -212,7 +212,7 @@ func main() {
|
|
|
|
|
3 th element of a is 78.00
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Go 提供了一个更简单,更简洁的遍历数组的方法:使用 `range for`。range 返回数组的索引和索引对应的值。让我们用 range for 重写上面的程序(除此之外我们还计算了数组元素的总和)。
|
|
|
|
|
Go 提供了一个更简单,更简洁的遍历数组的方法:使用 range 形式的 for 循环。`range` 返回数组的索引和索引对应的值。让我们用 range for 重写上面的程序(除此之外我们还计算了数组元素的总和)。
|
|
|
|
|
```golang
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
@ -247,7 +247,7 @@ for _, v := range a { //ignores index
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
上面的代码忽略了索引。同样的,也可以忽略值。
|
|
|
|
|
上面的代码忽略了索引。用类似的方法,也可以忽略值。
|
|
|
|
|
|
|
|
|
|
## 多维数组
|
|
|
|
|
|
|
|
|
@ -288,7 +288,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
上面的程序中,第 17 行利用速记声明创建了一个二维数组 a。第 20 行的逗号是必须的,这是因为词法分析器会根据一些简单的规则自动插入分号。如果你想了解更多,请阅读:https://golang.org/doc/effective_go.html#semicolons 。
|
|
|
|
|
上面的程序中,第 17 行利用速记声明创建了一个二维数组 a。第 20 行的逗号是必须的,因为词法分析器会根据一些简单的规则自动插入分号。如果你想了解更多,请阅读:https://golang.org/doc/effective_go.html#semicolons 。
|
|
|
|
|
|
|
|
|
|
在第 23 行声明了另一个二维数组 b,并通过索引的方式给数组 b 中的每一个元素赋值。这是初始化二维数组的另一种方式。
|
|
|
|
|
|
|
|
|
@ -312,7 +312,7 @@ AT&T T-Mobile
|
|
|
|
|
|
|
|
|
|
## 创建切片
|
|
|
|
|
|
|
|
|
|
元素类型为 `T `的切片表示为: `[]T`。
|
|
|
|
|
元素类型为 `T` 的切片表示为: `[]T`。
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
package main
|
|
|
|
@ -328,7 +328,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
通过 `a[start:end] `这样的语法创建了一个从` a[start]` 到 `a[end -1]` 的切片。在上面的程序中,第 9 行` a[1:4]` 创建了一个从 `a[1]` 到 `a[3]` 的切片。因此 b 的值为:`[77 78 79]`。
|
|
|
|
|
通过 `a[start:end]` 这样的语法创建了一个从 `a[start]` 到 `a[end -1]` 的切片。在上面的程序中,第 9 行 `a[1:4]` 创建了一个从 `a[1]` 到 `a[3]` 的切片。因此 b 的值为:`[77 78 79]`。
|
|
|
|
|
|
|
|
|
|
下面是创建切片的另一种方式:
|
|
|
|
|
|
|
|
|
@ -345,7 +345,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在上面的程序中,第 9 行 `c := []int{6, 7, 8} `创建了一个长度为 3 的 int 数组,并返回一个切片给 c。
|
|
|
|
|
在上面的程序中,第 9 行 `c := []int{6, 7, 8}` 创建了一个长度为 3 的 int 数组,并返回一个切片给 c。
|
|
|
|
|
|
|
|
|
|
## 修改切片
|
|
|
|
|
|
|
|
|
@ -397,7 +397,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
可以看到,在第 9 行,` numa[:] `中缺少了开始和结束的索引值,这种情况下开始和结束的索引值默认为 `0` 和` len(numa)`。这里 `nums1` 和 `nums2` 共享了同一个数组。程序的输出为:
|
|
|
|
|
可以看到,在第 9 行,`numa[:]` 中缺少了开始和结束的索引值,这种情况下开始和结束的索引值默认为 `0` 和 `len(numa)`。这里 `nums1` 和 `nums2` 共享了同一个数组。程序的输出为:
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
array before change 1 [78 79 80]
|
|
|
|
@ -429,11 +429,11 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在上面的程序中,创建了一个以 fruitarray 为底层数组,索引从 1 到 3 的切片 `fruitslice`。因此 fruitslice 长度为 2。
|
|
|
|
|
在上面的程序中,创建了一个以 `fruitarray` 为底层数组,索引从 1 到 2 的切片 `fruitslice`,因此 fruitslice 长度为 2。
|
|
|
|
|
|
|
|
|
|
`fruitarray` 的长度是 7。`fruiteslice` 是从 `fruitarray` 的索引 1 开始的。因此 `fruiteslice` 的容量是从 `fruitarray` 的第 1 个元素开始算起的数组中的元素个数,这个值是 6。因此 `fruitslice` 的容量是 6。程序的输出为:`length of slice 2 capacity 6 `
|
|
|
|
|
`fruitarray` 的长度是 7。`fruiteslice` 是从 `fruitarray` 的索引 1 开始的。因此 `fruiteslice` 的容量是从 `fruitarray` 的第 1 个元素开始算起的数组中的元素个数,这个值是 6。因此 `fruitslice` 的容量是 6。程序的输出为:`length of slice 2 capacity 6`
|
|
|
|
|
|
|
|
|
|
切片的长度可以动态的改变(最大为其容量)。任何超出最大容量的操作都会发生运行时错误。
|
|
|
|
|
切片的长度可以动态地改变(最大长度为其容量)。任何超出切片容量的操作都会发生运行错误。
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
package main
|
|
|
|
@ -460,7 +460,7 @@ After re-slicing length is 6 and capacity is 6
|
|
|
|
|
|
|
|
|
|
## 用 make 创建切片
|
|
|
|
|
|
|
|
|
|
内置函数` func make([]T, len, cap) []T` 可以用来创建切片,该函数接受**长度**和**容量**作为参数,返回切片。容量是可选的,默认与长度相同。使用 make 函数将会创建一个数组并返回它的切片。
|
|
|
|
|
内置函数 `func make([]T, len, cap) []T` 可以用来创建切片,该函数接受**长度**和**容量**作为参数,返回切片。容量是可选的,默认与长度相同。使用 make 函数将会创建一个数组并返回它的切片。
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
package main
|
|
|
|
@ -483,7 +483,7 @@ func main() {
|
|
|
|
|
|
|
|
|
|
`x …T` 表示 append 函数可以接受的参数个数是可变的。这种函数叫做变参函数。
|
|
|
|
|
|
|
|
|
|
你可能会问一个问题:如果切片是建立在数组之上的,而数组本身不能改变长度,那么切片是如何动态改变长度的呢?实际发生的情况是,当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组。并将原有数组的元素被拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍(译者注:当超出切片的容量时,append 将会在其内部创建新的数组,该数组的大小是原切片容量的 2 倍。最后 append 返回这个数组的全切片,即从 0 到 length - 1 的切片)。下面的程序使事情变得明朗:
|
|
|
|
|
你可能会问一个问题:如果切片是建立在数组之上的,而数组本身不能改变长度,那么切片是如何动态改变长度的呢?实际发生的情况是,当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组。并将原有数组的元素拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍(译者注:当超出切片的容量时,append 将会在其内部创建新的数组,该数组的大小是原切片容量的 2 倍。最后 append 返回这个数组的全切片,即从 0 到 length - 1 的切片)。下面的程序会帮助你理解这一知识点:
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
package main
|
|
|
|
@ -500,7 +500,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在上面的程序中,cars 的容量开始时为 3。在第 10 行我们追加了一个新的元素给 `cars`,并将 `append(cars, "Toyota") `的返回值重新复制给 cars。现在 cars 的容量翻倍,变为 6。上面的程序输出为:
|
|
|
|
|
在上面的程序中,cars 的容量开始时为 3。在第 10 行我们追加了一个新的元素给 `cars`,并将 `append(cars, "Toyota")` 的返回值重新复制给 cars。现在 cars 的容量翻倍,变为 6。上面的程序输出为:
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
cars: [Ferrari Honda Ford] has old length 3 and capacity 3
|
|
|
|
@ -588,7 +588,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在上面的程序中,第 17 行将切片中的每个元素的值减2。在函数调用之后打印切片的的内容,发现切片内容发生了改变。你可以回想一下,这不同于一个数组,对函数内部的数组所做的更改在函数外不可见。上面的程序输出如下:
|
|
|
|
|
在上面的程序中,第 17 行将切片中的每个元素的值减 2。在函数调用之后打印切片的的内容,发现切片内容发生了改变。你可以回想一下,这不同于一个数组,对函数内部的数组所做的更改在函数外不可见。上面的程序输出如下:
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
array before function call [8 7 6]
|
|
|
|
@ -634,7 +634,7 @@ Go Rust
|
|
|
|
|
|
|
|
|
|
切片保留对底层数组的引用。只要切片存在于内存中,数组就不能被垃圾回收。这在内存管理方便可能是值得关注的。假设我们有一个非常大的数组,而我们只需要处理它的一小部分,为此我们创建这个数组的一个切片,并处理这个切片。这里要注意的事情是,数组仍然存在于内存中,因为切片正在引用它。
|
|
|
|
|
|
|
|
|
|
解决该问题的一个方法是使用 copy 函数 `func copy(dst, src []T) int `来创建该切片的一个拷贝。这样我们就可以使用这个新的切片,原来的数组可以被垃圾回收。
|
|
|
|
|
解决该问题的一个方法是使用 copy 函数 `func copy(dst, src []T) int` 来创建该切片的一个拷贝。这样我们就可以使用这个新的切片,原来的数组可以被垃圾回收。
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
package main
|
|
|
|
@ -656,7 +656,7 @@ func main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在上面程序中,第 9 行 `neededCountries := countries[:len(countries)-2] `创建一个底层数组为 `countries` 并排除最后两个元素的切片。第 11 行将 `neededCountries` 拷贝到 `countriesCpy` 并在下一行返回 `countriesCpy`。现在数组 `countries` 可以被垃圾回收,因为 `neededCountries` 不再被引用。
|
|
|
|
|
在上面程序中,第 9 行 `neededCountries := countries[:len(countries)-2]` 创建一个底层数组为 `countries` 并排除最后两个元素的切片。第 11 行将 `neededCountries` 拷贝到 `countriesCpy` 并在下一行返回 `countriesCpy`。现在数组 `countries` 可以被垃圾回收,因为 `neededCountries` 不再被引用。
|
|
|
|
|
|
|
|
|
|
我(原文作者)已经将我们讨论的所有概念汇总到一个程序中,你可以从 [github](https://github.com/golangbot/arraysandslices) 下载。
|
|
|
|
|
|
|
|
|
@ -666,9 +666,9 @@ func main() {
|
|
|
|
|
[深入解析 Go 中 Slice 底层实现](https://halfrost.com/go_slice/)
|
|
|
|
|
###### 创建(定义)数组
|
|
|
|
|
数组在Go中是值类型,而不是引用(其他语言的数组则是引用类型)
|
|
|
|
|
PS:切片(slice)是一个引用类型
|
|
|
|
|
数组不是统一的类型,大小不同的数组是不可以比较的
|
|
|
|
|
不同数组类型是不可以比较的
|
|
|
|
|
|
|
|
|
|
PS:切片(slice)是一个引用类型。数组不是统一的类型,大小不同的数组是不可以比较的,不同数组类型是不可以比较的
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
var a[2]int
|
|
|
|
|
var b[3]string
|
|
|
|
@ -689,9 +689,9 @@ var d = [...]int{19:90} // 尽可能的满足索引值得数组
|
|
|
|
|
a := [...]int{19:100}
|
|
|
|
|
|
|
|
|
|
// 指向数组的指针
|
|
|
|
|
var p *[20]int = &a //长度为20的int型数组,这里的数组长度`20`必须和a数组长度相等
|
|
|
|
|
var p *[20]int = &a //长度为20的int型数组,这里的数组长度 `20` 必须和a数组长度相等
|
|
|
|
|
fmt.Println(p) //以上表示取这样一个数组的地址
|
|
|
|
|
// 打印结果:&[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 19]
|
|
|
|
|
// 打印结果:&[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 100]
|
|
|
|
|
|
|
|
|
|
//指向指针的数组
|
|
|
|
|
x , y := 1, 2
|
|
|
|
@ -702,7 +702,7 @@ fmt.Println("pp is ",pp) // 打印结果:pp is [0xc420012128 0xc420012130]
|
|
|
|
|
|
|
|
|
|
```golang
|
|
|
|
|
ppp := new([10]int)
|
|
|
|
|
fmt.Println("----ppp-------",ppp); //输出结果:`&[0 0 0 0 0 0 0 0 0 0]`
|
|
|
|
|
fmt.Println("----ppp-------",ppp); //输出结果:`----ppp------- &[0 0 0 0 0 0 0 0 0 0]`
|
|
|
|
|
//以上为指向数组的指针
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@ -712,12 +712,12 @@ fmt.Println("----ppp-------",ppp); //输出结果:`&[0 0 0 0 0 0 0 0 0 0]`
|
|
|
|
|
// 第一种方式
|
|
|
|
|
n := [10]int{}
|
|
|
|
|
n[1] = 10
|
|
|
|
|
fmt.Println("-----1------",n) // 输出:`[0 10 0 0 0 0 0 0 0 0]`
|
|
|
|
|
fmt.Println("-----1------",n) // 输出:`-----1------ [0 10 0 0 0 0 0 0 0 0]`
|
|
|
|
|
|
|
|
|
|
// 第二种方式
|
|
|
|
|
m := new([10]int)
|
|
|
|
|
m[1] = 20
|
|
|
|
|
fmt.Println("-----2------",m) // 输出:`&[0 20 0 0 0 0 0 0 0 0]`
|
|
|
|
|
fmt.Println("-----2------",m) // 输出:`-----2------ &[0 20 0 0 0 0 0 0 0 0]`
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
###### 多维数组
|
|
|
|
@ -804,7 +804,7 @@ func main() {
|
|
|
|
|
s7 := []byte{'a','b','c','d','e','f','g','h','i','j','k'} // 切片底层对应的数组
|
|
|
|
|
|
|
|
|
|
slice_a := s7[2:5]
|
|
|
|
|
fmt.Println(slice_a) // 输出的assica码 值 [99 100 101]
|
|
|
|
|
fmt.Println(slice_a) // 输出的ascii码 值 [99 100 101]
|
|
|
|
|
fmt.Println(string(slice_a)) // 格式化为字符串输出
|
|
|
|
|
fmt.Println(len(slice_a),cap(slice_a))
|
|
|
|
|
|
|
|
|
@ -817,7 +817,7 @@ func main() {
|
|
|
|
|
s8 = append(s8, 12, 48)
|
|
|
|
|
fmt.Printf("%v %p", s8, s8) // 格式化打印值和内存地址:[0 0 0 12 48] 0xc042074030
|
|
|
|
|
|
|
|
|
|
// 追加的元素如果没有超多切片容量,则切片的地址是不变的,否则内存地址会变
|
|
|
|
|
// 追加的元素如果没有超过切片容量,则切片的地址是不变的,否则内存地址会变
|
|
|
|
|
s8 = append(s8, 66, 88)
|
|
|
|
|
fmt.Printf("%v %p\n", s8, s8) // [0 0 0 12 48 66 88] 0xc042048060
|
|
|
|
|
|
|
|
|
@ -828,7 +828,7 @@ func main() {
|
|
|
|
|
fmt.Println(s9) //[33 44 55 4 5 6 7]
|
|
|
|
|
|
|
|
|
|
copy(s10,s9)
|
|
|
|
|
fmt.Println(s10) // [33 44 55]
|
|
|
|
|
fmt.Println(s10) // [1 2 3]
|
|
|
|
|
|
|
|
|
|
copy(s9[2:4],s10[0:2])
|
|
|
|
|
fmt.Println(s9) // [1 2 33 44 5 6 7]
|
|
|
|
|