声明切片还是创建切片?

152
在Go语言中,var s []ints := make([]int, 0)有什么区别?我发现两者都可以用,但哪一个更好呢?

第一个创建了一个nil切片,而第二个创建了一个empty切片(这是《Go语言实战》一书中使用的术语)。为避免重复回答,您可以查看https://dev59.com/9V4b5IYBdhLWcg3wbA-B#45997533。 - tgogos
5个回答

161

简单声明

var s []int

不分配内存并且 s 指向 nil

s := make([]int, 0)

分配内存,并使s指向一个包含0个元素的切片。

通常,如果您不知道确切的用例大小,则第一个选项更符合惯用语。


2
我可以对map也这样说吗?var m map[string]int和m:= make(map[string]int)是一样的吗?谢谢。 - joshua
19
你需要创建地图,因为即使是一个空的地图也需要分配一些保留空间。原因在于地图需要进行一些管理工作。 - twotwotwo
14
如果需要返回一个长度为0的切片(而不是 'nil'),则正确的用法是使用 make。 - Jess
9
如果你正在构建一个 API 并且返回一个数组作为响应,使用声明式形式将会在切片没有任何元素的情况下返回 nil,而不是空数组。然而,如果使用 make 创建切片,将会返回一个空数组,这通常是期望的效果。 - robinmitra
10
如在此回答的评论中提到:https://dev59.com/9V4b5IYBdhLWcg3wbA-B#29164565,在尝试进行JSON编组等操作时存在差异。对空切片(var s []int)的编组将产生null,而对空的切片(s := make([]int, 0))的编组将产生预期的[] - asgaines
显示剩余5条评论

111

除了fabriziom答案外,你还可以在 "Go Slices:用法和内部机制" 中看到更多示例,其中提到了[]int的用途:

由于切片的零值(nil)就像一个零长度的切片,因此您可以声明一个切片变量,然后在循环中将其附加到它:

// Filter returns a new slice holding only
// the elements of s that satisfy f()
func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

这意味着,要向切片添加元素时,您不必先分配内存:使用nil切片p int[]作为要添加的切片即可。


为什么您认为它会进行分配?由于容量为零,因此使用或不使用make都不会分配任何内容。 - Arman Ordookhani
1
@ArmanOrdookhani 同意。我只是觉得声明 var p []int 比使用 make 更容易(尽管在 0 容量的情况下,它不会分配任何东西)。就可读性而言,我更喜欢不在这里使用 make - VonC
2
我更倾向于在任何地方使用字面量(例如 p := []int{})。由于我们通常使用 := 语法来声明大多数变量,因此将其应用到所有地方而不是仅针对切片进行例外处理更为自然。除此之外,试图考虑分配通常会促使人们过早地进行优化。 - Arman Ordookhani

25

刚刚发现一个区别。如果你使用

var list []MyObjects

当您将输出编码为JSON时,您会得到null

list := make([]MyObjects, 0)

结果如预期,[]


4
是的,后者在我们想要用[]数组而不是null进行响应时非常有用。 - Nhan Tran

8

一个更完整的例子(在.make()中有一个额外的参数):

slice := make([]int, 2, 5)
fmt.Printf("length:  %d - capacity %d - content:  %d", len(slice), cap(slice), slice)

输出:

length:  2 - capacity 5 - content:  [0 0]

或者使用动态类型的 slice

slice := make([]interface{}, 2, 5)
fmt.Printf("length:  %d - capacity %d - content:  %d", len(slice), cap(slice), slice)

输出:

length:  2 - capacity 5 - content:  [<nil> <nil>]

0
切片可以超出其容量,这意味着需要重新分配内存,这可能是昂贵的(需要一些时间)。关于切片增长的规则如下:
Go切片在大小达到1024之前会翻倍增长,之后每次增长25%。
现在让我们比较两种方法:
1. `var p []int` 2. `p := make([]int, 0, len(s))`
下面我们有两个函数,一个只声明了切片,另一个使用`make`创建了切片,并设置了其容量。
func FilterSliceNil(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

func FilterSliceCapSet(s []int, fn func(int) bool) []int {
    p := make([]int, 0, len(s)) // Set length=0 and capacity to length of s
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

基准测试函数:
package main

import (
    "testing"
)

var slice = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var f = func(i int) bool {
    return i%2 == 0
}

func Benchmark_FilterSliceNil(b *testing.B) {
    for i := 0; i < b.N; i++ {
        FilterSliceNil(slice, f)
    }
}

func Benchmark_FilterSliceCapSet(b *testing.B) {
    for i := 0; i < b.N; i++ {
        FilterSliceCapSet(slice, f)
    }
}

基准测试结果:
$ go test -bench=. -benchtime=10s
goos: linux
goarch: amd64
pkg: example.com/Shadowing
cpu: 13th Gen Intel(R) Core(TM) i7-13700K
Benchmark_FilterSliceNil-24             157664690               76.96 ns/op
Benchmark_FilterSliceCapSet-24          330690351               36.27 ns/op
PASS
ok      example.com/Filter   35.479s

在我的机器上,使用make的版本完成时间只需要一半。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接