golang切片和数组区别

Go 中基本的集合数据结构是通过数组和切片来表示的。尽管它们可能看起来相似,但它们在实现中具有不同的内涵。简而言之,Go 数组不灵活,并不意味着与动态内存分配相关联。另一方面,切片是建立在这些数组类型之上的抽象,并且是灵活和动态的。这就是为什么在 Go 中更经常使用切片的关键原因。

Go 中创建数组

数组是固定长度的同构数据结构。这仅仅意味着数据项(或数组的元素)具有相同的类型,并且可以是从原始类型(例如intstring)到任何自定义类型的任何内容。数组的长度必须是一个常数——一个正整数值——并且编译器必须在编译之前知道长度以便分配内存。Go 中数组的最大允许内存分配大小为 2Gb。一旦声明,大小是固定的,一旦编译完成就不能扩展或缩小。这定义了它的静态性质。这些项目通过index访问,从0作为第一个元素开始到最后一个元素在索引 = lengthOfArray-1

通常,您以以下方式在 go 中声明一个数组:var identifier[] type。以下是如何声明 Go 数组的示例:

var arr[10] int

一旦数组被声明为整数类型(或任何其他数值类型,例如floatcomplexbyterune),每个元素都会使用默认值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"}

在这种情况下,索引为24的项目被初始化为字符串值;其他设置为默认的空字符串。如果我们不提供大小,则最大键 ( 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]

然而,这并不意味着newmake都做同样的事情,尽管两者都在堆中分配内存。根据 Golang 文档,基本区别是:

  • 内置函数(仅)分配和初始化 slice、map 或 chan 类型的对象。和 new 一样,第一个参数是一个类型,而不是一个值。与 new 不同,make 的返回类型与其参数的类型相同,而不是指向它的指针。结果的规格取决于类型。

  • 新的内置函数分配内存。第一个参数是一个类型,而不是一个值,返回的值是一个指向该类型新分配的零值的指针。

最后

在本 Go 编程教程中,我们了解了如何在 Go 中使用数组和切片。要明白,归根结底,两者都使用相同的连续内存分配。将 slice 视为指向数组的指针,这使得它可以进行动态操作。数组更像是一种静态类型,但它也是切片的构建块。顾名思义,切片可以引用数组的一部分或整个数组。不用说,当您开始使用 Golang 编写代码和开发应用程序时,由于本开发人员教程中强调的原因,您将使用比数组更多的切片。

Golang interface底层数据结构
Linux下清除屏幕的6个命令
标签:

发表我的评论

电子邮件地址不会被公开。 必填项已用*标注

27 + 98 =

ajax-loader