golang切片和数组区别
Go 中基本的集合数据结构是通过数组和切片来表示的。尽管它们可能看起来相似,但它们在实现中具有不同的内涵。简而言之,Go 数组不灵活,并不意味着与动态内存分配相关联。另一方面,切片是建立在这些数组类型之上的抽象,并且是灵活和动态的。这就是为什么在 Go 中更经常使用切片的关键原因。
Go 中创建数组
数组是固定长度的同构数据结构。这仅仅意味着数据项(或数组的元素)具有相同的类型,并且可以是从原始类型(例如int或string)到任何自定义类型的任何内容。数组的长度必须是一个常数——一个正整数值——并且编译器必须在编译之前知道长度以便分配内存。Go 中数组的最大允许内存分配大小为 2Gb。一旦声明,大小是固定的,一旦编译完成就不能扩展或缩小。这定义了它的静态性质。这些项目通过index访问,从0作为第一个元素开始到最后一个元素在索引 = lengthOfArray-1。
通常,您以以下方式在 go 中声明一个数组:var identifier[] type。以下是如何声明 Go 数组的示例:
var arr[10] int
一旦数组被声明为整数类型(或任何其他数值类型,例如float、complex、byte、rune),每个元素都会使用默认值zero进行初始化。字符串类型的默认值为“”(空字符串),布尔类型的默认值为false。对于映射、通道、接口和指针等类型,默认初始化为nil。
Go 数组是可变的。可以在i指示的特定索引处分配值,例如arr[i] = value。引用超出数组大小的任何值会导致panic或导致数组索引超出范围错误。
Go 数字举例
以下是如何在 Go 中声明数组、赋值和提取元素的快速示例:
func main() {
var ai [10]int
var af [10]float64
var as [10]string
ai[5] = 500
af[5] = 500.789
as[5] = "Hi"
for i := 0; i < len(ai); i++ {
fmt.Printf("%d,", ai[i])
}
fmt.Println("]n----------------")
for i := 0; i < len(af); i++ {
fmt.Printf("%f,", af[i])
}
fmt.Println("]n----------------")
for i := 0; i < len(as); i++ {
fmt.Printf("%v,", as[i])
}
weekdays := [...]string{"sun", "mon", "tue", "wed", "thu", "fri", "sat"}
for index := range weekdays {
fmt.Println(weekdays[index])
}
fmt.Println(weekdays[0])
}
与 C 和 C++ 数组是指针类型不同,Go 数组是值类型。因此,我们可以使用new()创建一个数组:
var ac = new([10]complex64)
for i := range ac {
fmt.Println(a[i])
}
我们也可以将一个数组分配给另一个数组;在这种情况下,将在内存中创建数组的不同副本。复制数组中的任何修改都与创建此副本的数组无关。以下是如何使用 Go 代码从另一个数组创建数组:
ac2:=ac; //ac2 是一个不同的数组,其值从 ac 复制而来
这很重要,因为作为参数传递给函数的数组只是数组的副本或按值传递。但是,我们可以更改这一点并通过使用&(与号)运算符引用函数来传递数组。这是一个简单的例子:
func main() {
intArr := [5]int{11, 22, 33, 44, 55}
rev1(intArr)
for i := range intArr {
fmt.Println(intArr[i])
}
rev2(&intArr)
for i := range intArr {
fmt.Println(intArr[i])
}
}
func rev1(arr [5]int) {
size := len(arr)
for i, j := 0, size-1; i < size/2; i++ {
arr[i], arr[j] = arr[j], arr[i]
j--
}
}
func rev2(arr ]*[5]int) {
size := len(arr)
for i, j := 0, size-1; i < size/2; i++ {
arr[i], arr[j] = arr[j], arr[i]
j--
}
}
将大数组传递给函数会很快耗尽内存空间,尤其是如果我们传递它的副本。相反,我们可以将指针传递给数组或使用数组的切片。
在 Go 中初始化一个数组
在 Go 中可以通过多种不同的方式初始化数组。这里有些例子。
一个包含 5 个元素的数组。
var intArray = [5] int {11, 22, 33, 44, 55}
我们可以省略大小如下。在这种情况下,元素的数量决定了编译时数组的大小:
var intArray = [] int {11, 22, 33, 44, 55}
此外,我们可以使用省略号 ( … ) 来决定数组的大小:
var intArray = [...] int {11, 22, 33, 44, 55}
此外,可以像键/值对一样分配数组:
var keyValueArray = [5]string{2: "Coffee", 4: "Tea"}
在这种情况下,索引为2和4的项目被初始化为字符串值;其他设置为默认的空字符串。如果我们不提供大小,则最大键 ( index+1 ) 成为它的长度:
var keyValueArray = []string{2: "Coffee", 3: "Tea"}
Golang 中的切片
切片和数组之间的基本区别在于,切片是对数组连续段的引用。与作为值类型的数组不同,切片是引用类型。切片可以是完整数组或数组的一部分,由开始和结束索引指示。因此,切片也是一个将动态上下文注入底层数组的数组,否则就是静态连续内存分配。
像数组一样,切片也是可索引的。但是,它的长度可以在运行时更改。最小长度为0,最大长度可以是从中获取切片的底层数组的长度。以下是 Go 切片的示例:
var intArray = [10] int {0, 1, 1, 2, 3, 5, 8, 13, 21, 34}
var slice1 [] int = intArray[2:5] // index 2 to 4, size = 3
fmt.Println("Length: ", len(slice1), " Contents: ", slice1, " Capacity: ", cap(slice1))
//输出
//Length: 3 Contents: [1 2 3] Capacity: 8
内置的cap()函数指示切片的长度以及它可以增长多少 –最大长度(intArray) – 起始索引(在上面的示例中为2)。因此,0 <= length(intArray) <= cap(slice1)。
由于切片可以表示同一数组的一部分,因此多个切片可以共享数据项。这对于数组是不可能的。因此,您可以说数组实际上是切片的构建块。
切片声明的典型格式是:var identifier[] type。请注意,不需要声明切片的大小。默认情况下,切片设置为nil,起始长度为0。我们可以使用切片表达式来表示切片的开始和结束。这里有些例子:
var slice1[] int = intArray[0:len(intArray)] // slice consists of entire array
var slice1[] int = intArray[:] // a shorthand to mean entire array
var slice1= &intArray // also means entire array
var slice1= intArray[2:] // same as intArray[2:len(intArray)], 2nd till last
var slice1= intArray[:5] // same as intArray[0:5)], 1st till 5th -1
A slice in Go can be expanded to maximum size as shown in this example:
var slice2 = slice1[:cap(slice1)]
Go 切片作为函数参数
当作为函数参数传递时,切片比数组更有效。当函数被调用时,会创建一个数组切片,并传递一个对它的引用。下面是这个概念的一个快速示例:
package main
import (
"fmt"
)
func main() {
var arr = [10]int{55, 99, 2, 11, 33, 77, 88, 2, 7, 1}
sort(arr[:]) //entire array is passed
for i := 0; i < len(arr); i++ {
fmt.Printf("%d,", arr[i])
}
}
func sort(arr []int) {
size := len(arr)
isSwapped := false
for i := 0; i < size; i++ {
for j := 0; j < size-i-1; j++ { if arr[j] > arr[j+1] {
isSwapped = true
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
if !isSwapped { // no swap in pass, array is sorted
break
}
}
}
请注意,Go 开发人员不应该使用指向切片的指针,因为切片本身是一个引用类型(即指针)。
在 Go 中使用 make() 创建切片
并不总是需要先单独创建一个数组,然后再从中创建一个切片。我们可以使用make()函数将切片与数组一起制作,如下例所示:
var s1 []int = make([]int, 10)
//Or, more conveniently, in this manner:
s2 := make([]int, 10)
这将创建一个大小为10的数组,然后对它进行切片引用。make()函数有两个参数:要创建的数组的类型和项目的数量。我们还可以创建一个引用数组的一部分的切片。make()函数有一个额外的可选参数——cap——表示容量。例如:
s3 := make([]int, 10, 20) // s3 := new([20]int)[10:20]
//This essentially means the same as:
s4 := new([20]int)[10:20]
然而,这并不意味着new和make都做同样的事情,尽管两者都在堆中分配内存。根据 Golang 文档,基本区别是:
-
内置函数(仅)分配和初始化 slice、map 或 chan 类型的对象。和 new 一样,第一个参数是一个类型,而不是一个值。与 new 不同,make 的返回类型与其参数的类型相同,而不是指向它的指针。结果的规格取决于类型。
-
新的内置函数分配内存。第一个参数是一个类型,而不是一个值,返回的值是一个指向该类型新分配的零值的指针。
最后
在本 Go 编程教程中,我们了解了如何在 Go 中使用数组和切片。要明白,归根结底,两者都使用相同的连续内存分配。将 slice 视为指向数组的指针,这使得它可以进行动态操作。数组更像是一种静态类型,但它也是切片的构建块。顾名思义,切片可以引用数组的一部分或整个数组。不用说,当您开始使用 Golang 编写代码和开发应用程序时,由于本开发人员教程中强调的原因,您将使用比数组更多的切片。