PRF: #9609 完成译文校对

This commit is contained in:
pityonline 2018-08-19 08:56:46 +08:00
parent 67ee576249
commit 2733df5589
No known key found for this signature in database
GPG Key ID: 89C97C8DF02C633E

View File

@ -1,14 +1,14 @@
Slices from the ground up
从零开始学习切片
======
这篇文章最初的灵感来源于我与一个使用切片作栈的同事的一次聊天。那次聊天,话题最后拓展到了 Go 语言中的切片是如何工作的。我认为把这些知识记录下来会帮到别人
这篇文章受到了我与同事讨论使用<ruby>切片<rt>slice</rt></ruby>作为<ruby><rt>stack</rt></ruby>的一次聊天的启发。后来话题聊到了 Go 语言中的切片是如何工作的。我认为这些信息对别人也有用,所以就把它记录了下来
### 数组
任何关于 Go 语言的切片的讨论都要从另一个数据结构,也就是 Go 语言的数组开始。Go 语言的数组有两个特性:
任何关于 Go 语言切片的讨论都要从另一个数据结构也就是<ruby>数组<rt>array</rt></ruby>开始。Go 的数组有两个特性:
1. 数组的长度是固定的;`[5]int` 是由 5 个 `unt` 构成的数组,和 `[3]int` 不同。
2. 数组是值类型。考虑如下示例:
1. 数组的长度是固定的;`[5]int` 是由 5 个 `int` 构成的数组,和 `[3]int` 不同。
2. 数组是值类型。看下面这个示例:
```
package main
@ -23,16 +23,16 @@ Slices from the ground up
}
```
语句 `b := a` 定义了一个新的变量 `b`类型是 `[5]int`,然后把 `a` 中的内容 _复制_`b` 中。改变 `b` 中的值`a` 中的内容没有影响,因为 `a``b` 是相互独立的值。[1][1]
语句 `b := a` 定义了一个类型是 `[5]int` 的新变量 `b`,然后把 `a` 中的内容 _复制_`b` 中。改变 `b``a` 中的内容没有影响,因为 `a``b` 是相互独立的值。[1][1]
### 切片
Go 语言的切片和数组的主要有如下两个区别:
1. 切片没有一个固定的长度。切片的长度不是它类型定义的一部分,而是由切片内部自己维护的。我们可以使用内置的 `len` 函数知道他的长度。
2. 将一个切片赋值给另一个切片时 _不会_ 切片进行复制操作。这是因为切片没有直接保存它的内部数据,而是保留了一个指向 _底层数组_ [3][3]的指针。数据都保留在底层数组里。
1. 切片没有一个固定的长度。切片的长度不是它类型定义的一部分,而是由切片内部自己维护的。我们可以使用内置的 `len` 函数知道它的长度。[2][2]
2. 将一个切片赋值给另一个切片时 _不会_ 切片进行复制操作。这是因为切片没有直接保存它的内部数据,而是保留了一个指向 _底层数组_ [3][3] 的指针。数据都保留在底层数组里。
基于第二个特性,两个切片可以享有共同的底层数组。考虑如下示例:
基于第二个特性,两个切片可以享有共同的底层数组。看下面的示例:
1. 对切片取切片
@ -49,7 +49,7 @@ Go 语言的切片和数组的主要有如下两个区别:
}
```
在这个例子里,`a` 和 `b` 享有共同的底层数组 —— 尽管 `b` 的起始值在数组里的偏移不同,两者的长度也不同。通过 `b` 修改底层数组的值也会导致 `a` 里的值的改变。
在这个例子里,`a` 和 `b` 享有共同的底层数组 —— 尽管 `b` 在数组里的起始偏移不同,两者的长度也不同。通过 `b` 修改底层数组的值也会导致 `a` 里的值的改变。
2. 将切片传进函数
@ -73,7 +73,7 @@ Go 语言的切片和数组的主要有如下两个区别:
在这个例子里,`a` 作为形参 `s` 的实参传进了 `negate` 函数,这个函数遍历 `s` 内的元素并改变其符号。尽管 `nagate` 没有返回值,且没有接触到 `main` 函数里的 `a`。但是当将之传进 `negate` 函数内时,`a` 里面的值却被改变了。
大多数程序员都能直观地了解 Go 语言切片的底层数组是如何工作的,因为它与其语言中类似数组的工作方式类似。比如下面就是使用 Python 重写的这一小节的第一个示例:
大多数程序员都能直观地了解 Go 语言切片的底层数组是如何工作的,因为它与其语言中类似数组的工作方式类似。比如下面就是使用 Python 重写的这一小节的第一个示例:
```
Python 2.7.10 (default, Feb 7 2017, 00:08:15)
@ -103,7 +103,7 @@ irb(main):004:0> a
### 切片头
让切片得以同时拥有值和指针的特性的魔法来源于切片实际上是一个结构体类型。这个结构体通常叫做 _切片头_,这里是[反射包内的相关定义][20]。且片头的定义大致如下:
切片同时拥有值和指针特性的神奇之处在于理解切片实际上是一个<ruby>结构体<rt>struct</rt></ruby>类型。这个结构体通常叫做 _切片头_,这里是[<ruby>反射<rt>reflect</rt></ruby>包内的相关定义][20]。切片头的定义大致如下:
![](https://dave.cheney.net/wp-content/uploads/2018/07/slice.001-300x257.png)
@ -117,9 +117,9 @@ type slice struct {
}
```
个头很重要,因为和 [`map` 以及 `chan` 这两个类型不同][21],切片是值类型,当被赋值或者被作为函数的参数时候会被复制过去。
这很重要,因为和 [`map` 以及 `chan` 这两个类型不同][21],切片是值类型,当被赋值或者被作为参数传入函数时候会被复制过去。
程序员们都能理解 `square` 的形参 `v``main` 中声明的 `v` 的是相互独立的,我们一次为例。
程序员们都能理解 `square` 的形参 `v``main` 中声明的 `v` 的是相互独立的。请看下面的例子:
```
package main
@ -155,13 +155,13 @@ func main() {
}
```
Go 语言的切片是作为值传递的这一点很是不寻常。当你在 Go 语言内定义一个结构体时90% 的时间里传递的都是这个结构体的指针。[5][9] 切片的传递方式真的很不寻常,我能想到的唯一与之相同的例子只有 `time.Time`
Go 的切片是作为值传递的这一点很是不寻常。当你在 Go 内定义一个结构体时90% 的时间里传递的都是这个结构体的指针。[5][9]切片的传递方式真的很不寻常,我能想到的唯一与之相同的例子只有 `time.Time`
切片作为值传递而不是作为指针传递这一会让很多想要理解切片的工作原理的 Go 程序员感到困惑,这是可以理解的。你只需要记住,当你对切片进行赋值,取切片,传参或者作为返回值等操作时,你是在复制结构体内的三个位域:指针,长度,以及容量。
切片作为值传递而不是作为指针传递这一特殊行为会让很多想要理解切片的工作原理的 Go 程序员感到困惑,这是可以理解的。你只需要记住,当你对切片进行赋值,取切片,传参或者作为返回值等操作时,你是在复制切片头结构的三个字段:指向底层数组的指针,长度,以及容量。
### 总结
我们来用我们引出这一话题的切片作为栈的例子来总结下本文的内容:
我们来用引出这一话题的切片作为栈的例子来总结下本文的内容:
```
package main
@ -182,7 +182,7 @@ func main() {
}
```
`main` 函数的最开始我们把一个 `nil` 切片以及 `level` 0传给了函数 `f`。在函数 `f` 里我们把当前的 `level` 添加到切片的后面,之后增加 `level` 的值并进行递归。一旦 `level` 大于 5函数返回打印出当前的 `level` 以及们复制到的 `s` 的内容。
`main` 函数的最开始我们把一个 `nil` 切片以及 `level` 的值 0 传给了函数 `f`。在函数 `f` 里我们把当前的 `level` 添加到切片的后面,之后增加 `level` 的值并进行递归。一旦 `level` 大于 5函数返回打印出当前的 `level` 以及们复制到的 `s` 的内容。
```
level: 5 slice: [0 1 2 3 4 5]
@ -193,9 +193,9 @@ level: 1 slice: [0 1]
level: 0 slice: [0]
```
你可以注意到在每一个 `level``s` 的值没有被别的 `f` 的调用影响,尽管当计算更高`level` 时作为 `append` 的副产品,调用栈内的四个 `f` 函数创建了四个底层数组,但是没有影响到当前各自的切片。
你可以注意到在每一个 `level``s` 的值没有被别的 `f` 的调用影响,尽管当计算更高的 `level` 时作为 `append` 的副产品,调用栈内的四个 `f` 函数创建了四个底层数组,但是没有影响到当前各自的切片。
### 了解更多
### 扩展阅读
如果你想要了解更多 Go 语言内切片运行的原理,我建议看看 Go 博客里的这些文章:
@ -204,12 +204,12 @@ level: 0 slice: [0]
### 注释
1. 这不是数组才有的特性,在 Go 语言里 _一切_ 赋值都是复制过去的,
2. 你可以在对数组使用 `len` 函数,但是得到的结果是多少人尽皆知。[][14]
3. 也叫做后台数组,以及更不严谨的说法是后台切片。[][15]
4. Go 语言里我们倾向于说值类型以及指针类型,因为 C++ 的引用会使使用引用类型这个词产生误会。但是在这里我说引用类型是没有问题的。[][16]
5. 如果你的结构体有[定义在其上的方法或者实现了什么接口][17],那么这个比率可以飙升到接近 100%。[][18]
6. 证明留做习题。
1. 这不是数组才有的特性,在 Go 语言里_一切_ 赋值都是复制过去的。[][13]
2. 你可以在对数组使用 `len` 函数,但是得到的结果是多少人尽皆知。[][14]
3. 有时也叫做<ruby>后台数组<rt>backing array</rt></ruby>,以及更不严谨的说法是后台切片。[][15]
4. Go 语言里我们倾向于说值类型以及指针类型,因为 C++ 的<ruby>引用<rt>reference</rt></ruby>类型这个词产生误会。但在这里我认为调用数组作为引用类型是没有问题的。[][16]
5. 如果你的结构体有[定义在其上的方法或者用于满足某个接口][17],那么你传入结构体指针的比率可以飙升到接近 100%。[][18]
6. 证明留做习题。[][19]
### 相关文章:
@ -224,7 +224,7 @@ via: https://dave.cheney.net/2018/07/12/slices-from-the-ground-up
作者:[Dave Cheney][a]
译者:[name1e5s](https://github.com/name1e5s)
校对:[校对者ID](https://github.com/校对者ID)
校对:[pityonline](https://github.com/pityonline)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出