问题描述
我理解 Go 中数组和切片的区别.但我不明白的是为什么拥有数组是有帮助的.为什么数组类型定义指定长度和元素类型会有帮助?为什么我们使用的每个数组"不能都是切片?
I understand the difference between arrays and slices in Go. But what I don't understand is why it is helpful to have arrays at all. Why is it helpful that an array type definition specifies a length and an element type? Why can't every "array" that we use be a slice?
推荐答案
数组还有更多内容 不仅仅是固定长度:它们是可比,而且它们是值(不是引用或指针类型).
There is more to arrays than just the fixed length: they are comparable, and they are values (not reference or pointer types).
在某些情况下,数组相对于切片有无数的优势,所有这些都足以证明数组(连同切片)的存在是合理的.让我们看看他们.(我什至没有将数组视为切片的构建块.)
There are countless advantages of arrays over slices in certain situations, all of which together more than justify the existence of arrays (along with slices). Let's see them. (I'm not even counting arrays being the building blocks of slices.)
1. 具有可比性意味着您可以将数组用作映射中的键,但不能使用切片.是的,您现在可以说为什么不让切片具有可比性,这样仅凭这一点就不能证明两者的存在是合理的.切片上的相等性没有很好地定义.常见问题解答:为什么地图不允许切片作为键?
1. Being comparable means you can use arrays as keys in maps, but not slices. Yes, you could say now that why not make slices comparable then, so that this alone wouldn't justify the existence of both. Equality is not well defined on slices. FAQ: Why don't maps allow slices as keys?
它们没有实现相等,因为在这些类型上没有很好地定义相等;有多个考虑涉及浅与深比较、指针与值比较、如何处理递归类型等.
2. 数组还可以为您提供更高的编译时安全性,因为可以在编译时检查索引边界(数组长度必须评估为非负数constant 可由 int
) 类型的值表示:
2. Arrays can also give you higher compile-time safety, as the index bounds can be checked at compile time (array length must evaluate to a non-negative constant representable by a value of type int
):
s := make([]int, 3)
s[3] = 3 // "Only" a runtime panic: runtime error: index out of range
a := [3]int{}
a[3] = 3 // Compile-time error: invalid array index 3 (out of bounds for 3-element array)
3.此外,传递或分配数组值将隐式复制整个数组,因此它将与原始值分离".如果你传递一个切片,它仍然会复制一个切片,但只是切片 header,但切片值(标题)将指向同一个后备数组.这可能是也可能不是您想要的.如果您想从原始"切片中分离"切片,则必须明确复制内容,例如使用内置的 copy()
函数创建一个新切片.
3. Also passing around or assigning array values will implicitly make a copy of the entire array, so it will be "detached" from the original value. If you pass a slice, it will still make a copy but just of the slice header, but the slice value (the header) will point to the same backing array. This may or may not be what you want. If you want to "detach" a slice from the "original" one, you have to explicitly copy the content e.g. with the builtin copy()
function to a new slice.
a := [2]int{1, 2}
b := a
b[0] = 10 // This only affects b, a will remain {1, 2}
sa := []int{1, 2}
sb := sa
sb[0] = 10 // Affects both sb and sa
4.同样由于数组长度是数组类型的一部分,不同长度的数组是不同的类型.一方面,这可能是麻烦事"(例如,您编写的函数采用 [4]int
类型的参数,您不能使用该函数来获取和处理数组[5]int
类型),但这也可能是一个优势:这可以用于显式指定预期的数组长度.例如.你想写一个接受 IPv4 地址的函数,它可以用 [4]byte
类型建模.现在,您可以在编译时保证传递给函数的值恰好为 4 个字节,不多也不少(无论如何,这都是无效的 IPv4 地址).
4. Also since the array length is part of the array type, arrays with different length are distinct types. On one hand this may be a "pain in the ass" (e.g. you write a function which takes a parameter of type [4]int
, you can't use that function to take and process an array of type [5]int
), but this may also be an advantage: this may be used to explicitly specify the length of the array that is expected. E.g. you want to write a function which takes an IPv4 address, it can be modeled with the type [4]byte
. Now you have a compile-time guarantee that the value passed to your function will have exactly 4 bytes, no more and no less (which would be an invalid IPv4 address anyway).
5. 与前面相关,数组长度也可以用于文档目的.类型 [4]byte
正确地记录了 IPv4 有 4 个字节.[3]byte
类型的 rgb
变量告诉每个颜色分量有 1 个字节.在某些情况下,它甚至被取出并可用,单独记录;例如在 crypto/md5
包中:md5.Sum()
返回 [Size 类型的值]byte
其中 md5.Size
是一个常量,16
:MD5 校验和的长度.
5. Related to the previous, the array length may also serve a documentation purpose. A type [4]byte
properly documents that IPv4 has 4 bytes. An rgb
variable of type [3]byte
tells there are 1 byte for each color components. In some cases it is even taken out and is available, documented separately; for example in the crypto/md5
package: md5.Sum()
returns a value of type [Size]byte
where md5.Size
is a constant being 16
: the length of an MD5 checksum.
6.它们在规划结构类型的内存布局时也非常有用,请在此处查看 JimB 的回答,以及 这个答案更详细和现实生活中的例子.
6. They are also very useful when planning memory layout of struct types, see JimB's answer here, and this answer in greater detail and real-life example.
7.此外,由于切片是标题并且它们(几乎)总是按原样(没有指针)传递,语言规范对切片指针的限制比指向的指针更严格数组.例如,规范提供了多个用于操作数组指针的简写,而在切片的情况下同样会产生编译时错误(因为很少使用指向切片的指针,如果你仍然想要/必须这样做,你必须明确处理它;在这个答案中阅读更多内容.
7. Also since slices are headers and they are (almost) always passed around as-is (without pointers), the language spec is more restrictive regarding pointers to slices than pointers to arrays. For example the spec provides multiple shorthands for operating with pointers to arrays, while the same gives compile-time error in case of slices (because it's rare to use pointers to slices, if you still want / have to do it, you have to be explicit about handling it; read more in this answer).
这样的例子是:
将
p
指针切片到数组:p[low:high]
是(*p)[low:high].如果
p
是指向切片的指针,则这是编译时错误 (spec: Slice表达式).
Slicing a
p
pointer to array:p[low:high]
is a shorthand for(*p)[low:high]
. Ifp
is a pointer to slice, this is compile-time error (spec: Slice expressions).
索引p
指针到数组:p[i]
是(*p)[i]
的简写.如果 p
是指向切片的指针,这是一个编译时错误(spec: Index表达式).
Indexing a p
pointer to array: p[i]
is a shorthand for (*p)[i]
. If p
is pointer to a slice, this is a compile time error (spec: Index expressions).
示例:
pa := &[2]int{1, 2}
fmt.Println(pa[1:1]) // OK
fmt.Println(pa[1]) // OK
ps := &[]int{3, 4}
println(ps[1:1]) // Error: cannot slice ps (type *[]int)
println(ps[1]) // Error: invalid operation: ps[1] (type *[]int does not support indexing)
8. 访问(单个)数组元素比访问切片元素更高效;对于切片,运行时必须通过隐式指针取消引用.另外"表达式len(s)
和cap(s)
是常量,如果s
的类型是数组或指向一个数组".
8. Accessing (single) array elements is more efficient than accessing slice elements; as in case of slices the runtime has to go through an implicit pointer dereference. Also "the expressions len(s)
and cap(s)
are constants if the type of s
is an array or pointer to an array".
可能令人惊讶,但您甚至可以这样写:
May be suprising, but you can even write:
type IP [4]byte
const x = len(IP{}) // x will be 4
它是有效的,即使 IP{}
不是 常量表达式 所以例如const i = IP{}
将是一个编译时错误!在此之后,以下内容也有效也就不足为奇了:
It's valid, and is evaluated and compile-time even though IP{}
is not a constant expression so e.g. const i = IP{}
would be a compile-time error! After this, it's not even surprising that the following also works:
const x2 = len((*IP)(nil)) // x2 will also be 4
查看可以使用数组/比切片更有意义的相关问题:
See related questions where an array can be used / makes more sense than a slice:
为什么 Go 切片不能像在 Go 映射中用作键一样,就像数组可以用作键一样?
这只是出于好奇: 一片可以包含自身,而数组不能.(实际上,此属性使比较更容易,因为您不必处理递归数据结构).
And this is just for curiosity: a slice can contain itself while an array can't. (Actually this property makes comparison easier as you don't have to deal with recursive data structures).
必读博客:
这篇关于为什么在 Go 中有数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!