数组和切片
Q7nl1s admin

数组

数组是属于同一类型的元素的集合。例如,整数 5、8、9、79、76 的集合形成一个数组。不能混合不同类型的值,例如Go 中不允许同时包含字符串和整数的数组。

Declaration

数组属于 type [n]Tn表示数组中的元素个数,T表示每个元素的类型。元素的数量n也是类型的一部分(我们稍后会更详细地讨论这个问题。)

有多种声明数组的方法。让我们逐一讨论。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)


func main() {
var a [3]int //int array with length 3
fmt.Println(a)
}

var a [3]int用于声明一个长度为 3 的整数类型的数组。数组中的所有元素都自动分配相应数组类型的零值。在这种情况下a是一个整数数组,因此的所有元素a都分配给0,即 int 的零值。运行上面的程序会打印:

1
[0 0 0]

数组的索引从0开始到length - 1结束。让我们为上面的数组分配一些值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
)


func main() {
var a [3]int //int array with length 3
a[0] = 12 // array index starts at 0
a[1] = 78
a[2] = 50
fmt.Println(a)
}

a[0] 将值分配给数组的第一个元素。该程序将打印:

1
[12 78 50]

让我们使用简写声明创建相同的数组。

1
2
3
4
5
6
7
8
9
10
package main 

import (
"fmt"
)

func main() {
a := [3]int{12, 78, 50} // short hand declaration to create array
fmt.Println(a)
}

上面的程序将打印相同的输出

1
[12 78 50]

在简写声明期间,不必为数组中的所有元素分配一个值。

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
a := [3]int{12}
fmt.Println(a)
}

在上述程序中的第 81 行。a := [3]int{12}声明了一个长度为 3 的数组,但只提供了一个值12。其余 2 个元素将自动分配0。该程序将打印:

1
[12 0 0]

您甚至可以在声明中忽略数组的长度并将其替换为...并让编译器为您计算长度。这是在以下程序中完成的。

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
a := [...]int{12, 78, 50} // ... makes the compiler determine the length
fmt.Println(a)
}

数组的大小是类型的一部分。因此[5]int[25]int是不同的类型。但也因此无法调整数组的大小。不要担心这个限制,因为slices的存在克服了这个限制。

1
2
3
4
5
6
7
package main

func main() {
a := [3]int{5, 78, 8}
var b [5]int
b = a //not possible since [3]int and [5]int are distinct types
}

在行号。在上面程序的6行中,我们试图将一个 [3]int类型的变量分配给一个不允许的[5]int类型的变量,因此编译器将打印以下错误:

1
./prog.go:6:7: cannot use a (type [3]int) as type [5]int in assignment
数组是值类型

Go 中的数组是值类型而不是引用类型。这意味着当它们被分配给新变量时,原始数组的副本被分配给新变量。如果对新变量进行了更改,它将不会反映在原始数组中。

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
a := [...]string{"USA", "China", "India", "Germany", "France"}
b := a // a copy of a is assigned to b
b[0] = "Singapore"
fmt.Println("a is ", a)
fmt.Println("b is ", b)
}

在上述程序中的第 7 行。副本a分配给b. 在第8行将b的第一个元素改为Singapore。这不会反映在原始数组a中。该程序将打印:

1
2
a is [USA China India Germany France]  
b is [Singapore China India Germany France]

类似地,当数组作为参数传递给函数时,它们是按值传递的,原始数组是不变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func changeLocal(num [5]int) {
num[0] = 55
fmt.Println("inside function ", num)

}
func main() {
num := [...]int{5, 6, 7, 8, 8}
fmt.Println("before passing to function ", num)
changeLocal(num) //num is passed by value
fmt.Println("after passing to function ", num)
}

在上述程序中的第 13 行,数组num实际上是按值传递给函数的changeLocal,因此不会因为函数调用而改变。该程序将打印:

1
2
3
before passing to function  [5 6 7 8 8]  
inside function [55 6 7 8 8]
after passing to function [5 6 7 8 8]
数组的长度

通过将数组作为参数传递给 len函数来查询数组的长度。

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
a := [...]float64{67.7, 89.8, 21, 78}
fmt.Println("length of a is",len(a))

}

上述程序的输出是

1
length of a is 4  
使用 range 迭代数组

for循环可用于迭代数组的元素。

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
a := [...]float64{67.7, 89.8, 21, 78}
for i := 0; i < len(a); i++ { //looping from 0 to the length of the array
fmt.Printf("%d th element of a is %.2f\n", i, a[i])
}
}

上面的程序通过一个for循环来遍历数组的元素,从 index0length of the array - 1。该程序有效并将打印,

1
2
3
4
0 th element of a is 67.70  
1 th element of a is 89.80
2 th element of a is 21.00
3 th element of a is 78.00

Go通过使用for循环的range形式提供了一种更好、更简洁的方法来迭代数组。range返回当前索引位置和该索引处的值。让我们使用范围重写上面的代码。我们还将计算数组中所有元素的总和。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
a := [...]float64{67.7, 89.8, 21, 78}
sum := float64(0)
for i, v := range a {//range returns both the index and value
fmt.Printf("%d the element of a is %.2f\n", i, v)
sum += v
}
fmt.Println("\nsum of all elements of a",sum)
}

上述程序的第8行中的for i, v := range a是for循环的range形式。它将返回当前索引位置和该索引处的值。我们打印这些值并计算数组所有元素的总和a。程序的输出是:

1
2
3
4
5
6
0 the element of a is 67.70  
1 the element of a is 89.80
2 the element of a is 21.00
3 the element of a is 78.00

sum of all elements of a 256.5

如果您只需要该值并想忽略索引,则可以通过将索引替换为_空白标识符来做到这一点。

1
2
for _, v := range a { //ignores index  
}

上面的 for 循环忽略了索引。同样,该值也可以忽略。

多维数组

到目前为止,我们创建的数组都是一维的。可以创建多维数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
)

func printarray(a [3][2]string) {
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}

func main() {
a := [3][2]string{
{"lion", "tiger"},
{"cat", "dog"},
{"pigeon", "peacock"}, //this comma is necessary. The compiler will complain if you omit this comma
}
printarray(a)
var b [3][2]string
b[0][0] = "apple"
b[0][1] = "samsung"
b[1][0] = "microsoft"
b[1][1] = "google"
b[2][0] = "AT&T"
b[2][1] = "T-Mobile"
fmt.Printf("\n")
printarray(b)
}

在上述程序中的第 17 行中,已使用简写语法(short hand syntax)声明了一个二维字符串数组。第20行末尾的逗号是必要的。这是因为词法分析器会根据简单的规则自动插入分号。如果您有兴趣了解更多关于为什么需要分号的信息,请阅读https://golang.org/doc/effective_go.html#semicolons 其实我在前文已经提到过。

另一个二维数组b在第 1 行声明。23 并为每个索引一个一个地添加字符串。这是另一种初始化二维数组的方法。

行号中的printarray功能。7 使用两个 for range 循环来打印二维数组的内容。上面的程序将打印

1
2
3
4
5
6
7
lion tiger  
cat dog
pigeon peacock

apple samsung
microsoft google
AT&T T-Mobile

这就是数组。尽管数组似乎足够灵活,但它们具有固定长度的限制。不可能增加数组的长度。这就是切片出现的地方。事实上,在 Go 中,切片比传统数组更常见。

切片

切片是数组顶部的方便、灵活和强大的包装器。切片本身不拥有任何数据。它们只是对现有数组的引用。

创建切片

具有 T 类型元素的切片表示为[]T

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)

func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4] //creates a slice from a[1] to a[3]
fmt.Println(b)
}

语法a[start:end]创建一个从index start 到index end - 1的数组切片。所以在第9行,语句a[1:4]创建了一个从索引 1 到 3 的数组的切片。因此切片具有值[77 78 79]

让我们看看另一种创建切片的方法。

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
c := []int{6, 7, 8} //creates and array and returns a slice reference
fmt.Println(c)
}

在上述程序中的第 9 行。c := []int{6, 7, 8}创建一个包含 3 个整数的数组并返回一个存储在 c 中的切片引用。

修改切片

切片不拥有自己的任何数据。它只是底层数组的表示。对切片所做的任何修改都将反映在底层数组中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

func main() {
darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
dslice := darr[2:5]
fmt.Println("array before",darr)
for i := range dslice {
dslice[i]++
}
fmt.Println("array after",darr)
}

在上述程序的第 9 行中,我们从数组的索引 2、3、4 处创建dslice。for 循环将这些索引中的值加一。当我们通过 for 循环遍历来打印数组时,我们可以看到对 slice 的更改反映在数组中。程序的输出是:

1
2
array before [57 89 90 82 100 78 67 69 59]  
array after [57 89 91 83 101 78 67 69 59]

当多个切片共享同一个底层数组时,对每个切片所做的更改都会反映在数组中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {
numa := [3]int{78, 79 ,80}
nums1 := numa[:] //creates a slice which contains all elements of the array
nums2 := numa[:]
fmt.Println("array before change 1",numa)
nums1[0] = 100
fmt.Println("array after modification to slice nums1", numa)
nums2[1] = 101
fmt.Println("array after modification to slice nums2", numa)
}

在第9行,numa[:]语句中start和end值都缺失。start 和 end 的默认值分别是0len(numa)nums1nums2两个切片共享同一个数组,程序的输出是:

1
2
3
array before change 1 [78 79 80]  
array after modification to slice nums1 [100 79 80]
array after modification to slice nums2 [100 101 80]

从输出中可以清楚地看出,当多个切片共享同一个数组时。对每个切片所做的修改都会反映在数组中。

切片的 length 和 capacity

切片的长度是切片中元素的数量。切片的容量是底层数组中从创建切片的索引开始到数组结束的元素总数。

让我们编写一些代码来更好地理解这一点。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)

func main() {
fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) //length of fruitslice is 2 and capacity is 6
}

在上面的程序中,fruiteslice是从fruitslice的索引 1 和 2 处创建的。因此fruitslice的长度为 2。

fruitarray的长度是 7。fruitslice是从fruitarray的索引位置1处创建的。因此,fruitslice的容量是fruitarray中从索引1(即从orange开始)开始的元素的个数,该值为6。因此 fruitslice 的容量为6。该程序打印切片的长度为2,容量为6。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
)

func main() {
fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("length of slice %d capacity %d\n", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
fruitslice = fruitslice[:cap(fruitslice)] //re-slicing furitslice till its capacity
fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice))
}

在上述程序的 11 行,fruitslice被重新切片至它的容量末位置。上述程序输出:

1
2
length of slice 2 capacity 6  
After re-slicing length is 6 and capacity is 6
使用 make 创建切片

func make([]T, len, cap) []T 可用于通过传递类型、长度和容量来创建切片。容量参数是可选的,默认为length。make 函数创建一个数组并返回对它的切片引用。

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
i := make([]int, 5, 5)
fmt.Println(i)
}

当使用 make 创建切片时,这些值默认为零。上述程序将输出[0 0 0 0 0].

Appending to a slice

正如我们已经知道的那样,数组被限制为固定长度,并且它们的长度不能增加。切片是动态的,可以使用append函数将新元素附加到切片中。append函数的定义是func append(s []T, x ...T) []T

函数定义中的x …T意味着函数接受参数 x 的可变数量的参数。这些类型的函数称为可变参数函数。

现在有一个问题可能会困扰你————如果切片由数组支持并且数组本身是固定长度的,那么切片为什么是动态长度的。底层发生的事情是,当新元素附加到切片时,会创建一个新数组。现有数组的元素被复制到这个新数组,并返回这个新数组的新切片引用。新切片的容量现在是旧切片的两倍。很酷吧:)。下面的程序会让事情变得清楚。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
)

func main() {
cars := []string{"Ferrari", "Honda", "Ford"}
fmt.Println("cars:", cars, "has old length", len(cars), "and capacity", cap(cars)) //capacity of cars is 3
cars = append(cars, "Toyota")
fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) //capacity of cars is doubled to 6
}

在上述程序中,cars初始容量为3。我们通过第 10 行的append(cars, "Toyota")语句给cars添加了一个新元素并将返回的切片分配给cars。现在汽车的容量增加了一倍,变成了 6 辆。上述程序的输出是

1
2
cars: [Ferrari Honda Ford] has old length 3 and capacity 3  
cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6

切片类型的零值为nilnil切片的长度和容量为 0。nil可以使用 append 函数将值附加到切片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
)

func main() {
var names []string //zero value of a slice is nil
if names == nil {
fmt.Println("slice is nil going to append")
names = append(names, "John", "Sebastian", "Vinay")
fmt.Println("names contents:",names)
}
}

在上面的程序中names是 nil 并且我们已经将 3 个字符串附加到names。 程序的输出是:

1
2
slice is nil going to append  
names contents: [John Sebastian Vinay]

也可以使用...运算符将一个切片附加到另一个切片。你可以在下一节可变参数函数教程中了解有关此运算符的更多信息。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
)

func main() {
veggies := []string{"potatoes","tomatoes","brinjal"}
fruits := []string{"oranges","apples"}
food := append(veggies, fruits...)
fmt.Println("food:",food)
}

在上述程序的第 10 行中我们通过appendfruitsveggies来创建切片 food 。 程序的输出是food: [potatoes tomatoes brinjal oranges apples]

将切片传递给函数

可以认为切片在内部由结构类型表示。看起来是这样的

1
2
3
4
5
type slice struct {  
Length int
Capacity int
ZerothElement *byte
}

切片包含长度、容量和指向数组第零元素的指针。当切片传递给函数时,即使它是按值传递的,指针变量也会引用同一个底层数组。因此,当切片作为参数传递给函数时,函数内部所做的更改在函数外部也可见。让我们编写一个程序来验证它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func subtactOne(numbers []int) {
for i := range numbers {
numbers[i] -= 2
}

}
func main() {
nos := []int{8, 7, 6}
fmt.Println("slice before function call", nos)
subtactOne(nos) //function modifies the slice
fmt.Println("slice after function call", nos) //modifications are visible outside
}

上述程序第 16 行中的函数调用将切片的每个元素减 2。在函数调用之后打印切片时,这些更改是可见的。如果你还记得的话,数组与这不同,其中在函数内部对数组所做的更改在函数外部不可见。上述程序的输出是:

1
2
slice before function call [8 7 6]  
slice after function call [6 5 4]
多维切片

与数组类似,切片可以有多个维度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
)


func main() {
pls := [][]string {
{"C", "C++"},
{"JavaScript"},
{"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}

程序的输出是:

1
2
3
C C++  
JavaScript
Go Rust
内存优化

切片持有对底层数组的引用。只要切片还在内存中,就不能对数组进行垃圾回收。当涉及到内存管理时,这是值得关注的。让我们假设我们有一个非常大的数组,我们只对处理其中的一小部分感兴趣。此后,我们从该数组创建一个切片并开始处理该切片。这里要注意的重要一点是,由于切片引用了数组,所以数组仍将在内存中,而不能被回收。

解决此问题的一种方法是使用copy功能func copy(dst, src []T) int 来制作该切片的副本。这样我们就可以使用新的切片,并且可以对原始数组进行垃圾回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
)

func countries() []string {
countries := []string{"USA", "Singapore", "Germany", "India", "Australia"}
neededCountries := countries[:len(countries)-2]
countriesCpy := make([]string, len(neededCountries))
copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
return countriesCpy
}
func main() {
countriesNeeded := countries()
fmt.Println(countriesNeeded)
}

在上述程序的第 9 行中,neededCountries := countries[:len(countries)-2]创建了一个切片,排除了countries最后 2 个元素。在上述程序第 11 调用copy函数copyneededCountriescountriesCpy,下一行从函数中返回它。现在countries 数组可以被垃圾回收,因为neededCountries不再被引用。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View