diff --git a/README.md b/README.md index 99d9eeb0fe591eed4e19083b7fcc844a362c4580..18b79c581797dddf97a2c728c5522e2961b165d6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # 图书《Practical Go Lessons》中文翻译 +由于作者自己在翻译中文版,因此没有授权给我们翻译。此仓库作废! + ## 介绍 前段时间发现了一本好书:[Practical Go Lessons](https://www.practical-go-lessons.com),挺多人希望翻译成中文,于是有了这个项目。 diff --git a/chap-20-programming-a-computer/.keep b/chap-20-programming-a-computer/.keep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chap-20-programming-a-computer/20-chapter.md b/chap-20-programming-a-computer/20-chapter.md new file mode 100644 index 0000000000000000000000000000000000000000..83ab42c6873e5624b5362b5af8516cf3b9c590c8 --- /dev/null +++ b/chap-20-programming-a-computer/20-chapter.md @@ -0,0 +1,502 @@ +# 第 20 章: 数组 + +![](imgs/array.e70c221f.jpg) + +## 1 本章将学习什么? + +- 什么是数组? +- 如何创建数组。 +- 如何创建多维数组。 +- 如何迭代一个数组。 +- 如何在数组中查找元素。 + +## 2 涵盖2个技术概念 + +- 数组 +- 元素类型 +- 长度 +- 容量 +- 数组的维度 + +## 3 定义 + +- 数组是相同类型元素的集合。 + +- 数组内的元素类型称为元素类型。 + +- 该集合在**编译**时具有**固定数量的已知元素**。 + +## 4 使用示例 +假设要存储月份名称。您可以使用字符串数组。编译时已知月数(12) + +## 5 数组创建 + +```go +// array/creation/main.go +package main + +import "fmt" + +func main() { + var myArray [2]int + myArray[0] = 156 + myArray[1] = 147 + fmt.Println(myArray) + // output : [156 147] +} +``` +前面的程序在第一行定义了一个名为`myArray`的变量,它是一个整数数组`(int)`,长度为2。 + +换句话说,这是两个为`int`类型元素的集合。 + +然后,我们设置第一个元素与表达`myArray[0]`。0表示myArray第一个元素的索引。这些元素从 0 索引。 + +之后,我们将值 147 赋值给数组的第二个元素。然后,我们打印数组。 +![](imgs/array_index_length.e1699603.png) +数组: 索引和长度[fig:Array-index-length] +### 5.1 更简洁的方式 +使用前面的方法来定义数组是正确的,但写法不是很方便。我们使用三行来定义数组。有一个更容易的方法:使用**数组字面**。 +```go +myArray := [2]int{156,147} +fmt.Println(myArray) +// output : [156 147] +``` +在以前的代码行中,我们定义了 2 个整数数组,并通过使用数组字面`[n]T}`直接填充它 + +您还可以让编译器使用语法推断出我们数组的大小`[...]T}`。例如: +```go +myArray := [...]int{156, 147} +``` +### 5.2 总结 +```go +// long way +var a [2]int +a[0] = 156 +a[1] = 147 +fmt.Println(a) + +// more concise +b := [2]string{"FR", "US"} +fmt.Println(b) + +// size computed by the compiler +c := [...]float64{13.2, 37.2} +fmt.Println(c) + +// values not set (yet) +d := [2]int{} +fmt.Println(d) +``` +## 6 零值 +当您定义一个数组时,您不必知道要放入该数组的所有值。 + +您可以创建一个空的数组,并在程序执行期间填充它。**但请记住,Go中没有未定义的元素。** + +当您定义空数组时,Go 将用元素类型的零值填充它: + +```go +// array/zero-value/main.go +package main + +import "log" + +func main() { + var myA [3]int + log.Printf("%v", myA) +} +``` +会输出: +```go +2021/01/27 17:56:21 [0 0 0] +``` +我们在这里定义了一个长度为 3 的整数数组。因为我们没有在括号内指定任何值。所以用整数的零fwhg去填充它,即"0"。请注意,如果您定义一个字符串数组而不指定值,则行为是相同的: + +```go +myEmptyArray2 := [2]string{} +fmt.Println(myEmptyArray2) +// output : [] +``` +Go 将把一个空字符串放入集合的第一个元素`myEmptyArray2[0]`。并在集合的第二个元素`myEmptyArray2[1]`。 +## 7 内置功能 +### 7.1 len: 长度 +`len`是一个 Go 内置功能,将返回变量 v 的**长度**。数组的长度是其内部的元素数。让我们用v表示T型数组,则长度为: +```go +len(v) +``` +### 7.2 cap:容量 +`内置功能的上限`返回与`len`功能相同。它在数组中没有用处,但在操作切片时会非常方便。 +## 8 访问组元素 +数组是存储数据的一种方便又高效的方式,其长度在编译时已知。但是,如果我们存储一些东西,它是为了后面读取。当您想要读取数组的特定元素时,您可以直接通过其索引访问它: +```go +// array/access-element/main.go +package main + +import "fmt" + +func main() { + b := [2]string{"FR", "US"} + firstElement := b[0] + secondElement := b[1] + fmt.Println(firstElement, secondElement) +} +``` +在此示例中,我们使用符号`b[1]`访问数组`b`的第二个元素。 +### 8.1 超出索引长度 +您必须确保您提供的索引(此处为 1)已在数组中定义。如果您想获得数组的第 101 个元素,您将无法编译您的程序。您正在尝试访问不存在的东西的价值。这是编译器给出的错误: + +``` +invalid array index 100 (out of bounds for 2-element array) +``` + +## 9 迭代数组 +### 9.1 带范围子句的 For 循环[subsec:For-loop-with-range-array] +for 循环允许您迭代扫描数组的所有元素: +```go +myArray := [2]int{156, 147} +for index, element := range myArray { + fmt.Printf("element at index %d is %d\n", index, element) +} +// output : +//element at index 0 is 156 +//element at index 1 is 147 +``` +`for`在与关键字组合循环`range`用于迭代数组的元素。 +您将使用以下语法在每次迭代时获取当前索引。`i`和元素`e`。 +#### 9.1.0.1 忽略索引(或值) +有时您只需关注值,而不是索引。您可以使用以下语法忽略索引: +```go +for _, element := range myArray { + fmt.Println(element) +} +``` +`_`是空白标识符。由于这个标识符,程序会忽略索引。请注意,您可以对 value 执行相同的操作: +```go +for index, _ := range myArray { + fmt.Println(index) +} +``` +这种用例很少见。 +## 9.2 For 循环 +如果你想从头迭代开始,前面的方法很有趣。但是如果你想从一个特定的索引开始并在另一个索引处停止怎么办。假设我有一个包含五个元素的表,我想从索引 2 处的元素开始并在索引 4 处的元素(第五个元素)处停止? + +我们可以将 for 循环与一个用作计数器的变量一起使用。 +```go +myArray2 := [2]int{156, 147} +for i := 0; i < len(myArray2); i++ { + fmt.Printf("element at index %d is %d\n", i, myArray2[i]) +} +// output : +//element at index 0 is 156 +//element at index 1 is 147 +``` +这是语法定义: + +要从索引 0 到最后一个索引遍历数组,请使用以下语法: +```go +for i := 0; i < len(a); i++ { + fmt.Println(i, a[i]) +} +``` +以降序迭代(从最后一个元素到第一个元素): + +```go +for i := len(a) - 1; i >= 0; i-- { + fmt.Println(i, a[i]) +} +``` +## 10 如何使用for循环在数组中查找元素? +要在数组中查找元素,您别无选择检查其中的每个元素: + +```go +// getIndex will find an element (needle) inside an array (haystack) +// if not found the function returns -1 +func getIndex(haystack [10]int, needle int) int { + for index, element := range haystack { + if element == needle { + return index + } + } + return -1 +} +``` +这个功能并不完善。假设您搜索的元素位于数组的末尾。在找到索引之前,您的函数将遍历每个数组的元素。 + +另一个缺点:此函数特定于 10 个元素的数组! + +## 11 比较 +您可以比较两个具有以下特征的数组: + +完全相同`类型`的元素(例如,整数) + +`长度`完全一样。 + +唯一被授权用于数组的比较运算符是`==`(相等) 和`!=`(不相等) : +```go +a := [2]int{1, 2} +b := [2]int{1, 2} +if a == b { + print("equal") +} else { + print("not equal") +} + +// output : equal +``` +## 12 如何将数组传递给函数? +- 这是一个简单的错误:将数组传递给函数将复制数组。 + +- 当你想在函数中修改数组时传递一个指向数组的指针 + +- 注意力!如果您的数组很大,制作它的副本会影响程序的性能。 +#### 12.0.0.1演示 +这是一个示例包: + +```go +// array/demo/demo.go +package demo + +const NewValue = "changedValue" + +func UpdateArray1(array [2]string) { + array[0] = NewValue +} +``` +该函数`UpdateArray1`采用类型为 的参数。该函数将修改索引 0 处的值。让我们测试一下:`[2]string` +```go +// array/demo/demo_test.go +package demo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUpdateArray1(t *testing.T) { + testArray := [2]string{"Value1", "Value2"} + UpdateArray1(testArray) + assert.Equal(t, NewValue, testArray[0]) +} +``` +在这个单元测试中,我们创建了一个长度为 2 的新数组`testArray`。 + +我们启动功能`UpdateArray1`。在函数调用之后,我们验证索引 0 处的元素是否等于常量`NewValue`。让我们运行这个测试: +```go +$ go test ./... +--- FAIL: TestUpdateArray1 (0.00s) + demo_test.go:11: + Error Trace: demo_test.go:11 + Error: Not equal: + expected: "changedValue" + actual : "Value1" + + Diff: + --- Expected + +++ Actual + @@ -1 +1 @@ + -changedValue + +Value1 + Test: TestUpdateArray1 +FAIL +FAIL maximilien-andile.com/array/copy/demo 0.016s +FAIL +``` +函数 UpdateArray1 将接收一个副本`testArray`。该函数修改副本,而不是原始数组。 + +#### 12.0.0.2解决方案:指针 +要修改原始数组,该函数应采用类型为 的元素。这是一个指针类型。它表示该函数可以采用任何指向类型值的指针(称为基类型)。指针是指向内存空间的地址。`*[2]string[2]string` +```go +// array/demo/demo.go +package demo + +const NewValue = "changedValue" + +func UpdateArray2(array *[2]string) { + array[0] = NewValue +} +``` +这是现在接受指针类型的函数的第二个版本。让我们测试一下这个函数是否正常工作: + +```go +// array/demo/demo_test.go +package demo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUpdateArray2(t *testing.T) { + testArray := [2]string{"Value1", "Value2"} + UpdateArray2(&testArray) + assert.Equal(t, NewValue, testArray[0]) +} +``` +我们创建一个新的字符串数组`testArray`。我们初始化两个数组值。在函数调用之后,我们测试索引 0 处的元素是否等于常量`NewValue`。让我们运行测试: +```go +$ go test ./... +ok maximilien-andile.com/array/copy/demo 0.015s +``` +## 13 如何复制数组? +这个单元测试的结果是什么? + +```go +// array/demo/demo_test.go +func TestArrayCopy(t *testing.T) { + testArray := [2]string{"Value1", "Value2"} + newCopy := testArray + testArray[1] = "updated" + assert.Equal(t, "updated", newCopy[1]) +} +``` +这个测试没有通过。`newCopy[1]`的值等于`"Value 2"`。为什么呢?那是因为当我们创建变量`newCopy`时,数组`testArray`将被复制。在内存中,我们将有两个单独的数组。当您修改`testArray`时,您不会修改`newCopy`。 + +## 14 如何取数组的地址? +在下面的测试中,我们不复制数组,而是创建一个类型为(指向 2 个字符串数组的指针)的新变量:`*[2]string` +```go +// array/demo/demo_test.go +func TestArrayReference(t *testing.T) { + testArray := [2]string{"Value1", "Value2"} + reference := &testArray + testArray[1] = "updated" + assert.Equal(t, "updated", reference[1]) +} +``` +`reference`是指向原始数组的指针`testArray`。当我们在内部编写时,Go 将获取存储到。`reference[1]testArray[1]` + +## 15 二维数组 +“维度”这个词可能会让那些讨厌数学的人产生焦虑。你不应该害怕;背后的概念非常简单。为了理解它,我建议将它可视化。在图2 中,您可以看到一个一维数组(长度为 3,元素类型为 int)。 +![](imgs/one_dimension_array.0fc6c244.png) +一维数组[fig:Array-of-one] + +在前面的示例中,我们将整数存储到数组中。当您将字符串、整数、浮点数、布尔值、字节、复数存储到数组中时,该数组的维数为 1。 + +当数组的值是另一个数组时,我们就有了一个多维数组。您可以在图3 中看到一个二维数组。 + +![](imgs/2_dimension_array.523cc981.png) +二维数组[fig:2-dimensions-array] + +这种数组的类型是: + +```go +[3][2]int +``` +该数组由 3 个值组成。这三个值的类型为。这是一个数组变成一个数组。[2]int + +要访问此类数组中的特定元素,您可以使用以下语法: +```go +value := a[2][1] +``` +`myArr[2]`将获取索引 2 处的值。索引 2 处的值是一个数组。将获取此数组中索引 1 处的元素。`[1]` +![](imgs/target_element_2d_array.e112fd00.png) +二维数组[fig:2-dimensions-array-1] + +#### 15.0.0.1 示例用法:酒店客人列表 +在我们酒店,工作人员需要具备获取客人名单的能力。我们的酒店由 10 间客房组成,每间客房最多可入住 2 位客人。为了表示这个客人列表,我们可以使用一个二维数组。 +![](imgs/guest_list_2-dim_array.71f9abf9.png) +使用示例二维数组[fig:Usage-example-2-dim-array] + +第一个数组的索引是房间号。有十个房间,所以这个数组的长度是10。在每个元素里面,我们放了另一个由两个字符串()组成的数组。这是将生成此列表的代码:`[2]string` +```go +// array/two-dimensional/guestList/guestList.go +package guestList + +import ( + "github.com/Pallinder/go-randomdata" +) + +func generate() [10][2]string { + guestList := [10][2]string{} + for index, _ := range guestList { + guestList[index] = roomGuests(index) + } + return guestList +} + +func roomGuests(roomId int) [2]string { + guests := [2]string{} + guests[0] = randomdata.SillyName() + guests[1] = randomdata.SillyName() + return guests +} +``` +在函数中,`generate`我们将初始化我们的二维数组。然后我们用 for-range 循环迭代它。对于每个元素,我们放置一个类型为(由两个字符串组成的长度为 2 的数组)的元素。该数组包含两个客人姓名。用于生成有趣的名称。在实际系统中,我们将在数据库中获取这些名称。`[2]stringrandomdata.SillyName()` +如果我们打印这个数组,我们会得到以下结果: +```go +[[Cockatoorogue Liftersolstice] [Footclever Chargernickel] [Chestsatin Scarivy] [Jesterbush Cloaksteel] [Robincanyon Boltchip] [Bonefrill Shiftshy] [Skinnerflannel Looncalico] [Loonnova Spikemesquite] [Hideforest Yakstitch] [Cloakcoconut Minnowsnapdragon]] +``` +要访问睡在房间 7 的第二位客人的姓名,我们可以使用语法:`guestList[7][1]`。 +## 16 多维数组 +在 Go 中可以生成具有两个以上维度的数组: +```go +// array/two-dimensional/main.go +package main + +import "fmt" + +func main() { + threeD := [2][2][2]string{} + threeD[0][0][0] = "element 0,0,0" + threeD[0][1][0] = "element 0,1,0" + threeD[0][0][1] = "element 0,1,1" + fmt.Println(threeD) +} +``` +该数组`3d`具有三个维度。在图6 中,您可以看到它的形状。 +![](imgs/3d_array.b5678a70.png) +3维数组[图:3维数组] +#### 16.0.0.1 **注意事项** +具有两个以上维度的数组很难可视化。如果你想在你的代码中引入这种类型的数组,也许你应该考虑另一种解决方案。您的代码将更难阅读,更难理解和审查。 +## 17 效率考虑 +- 使用索引访问数组上的数据是一种**非常快速**的操作。 + +- 查找元素在数组中可能会很慢,因为您必须对其进行迭代。如果您对存储在数组中的元素的顺序一无所知,您可能需要遍历所有数组。在这种情况下,您可以使用地图(请参阅专用章节)。 +## 18 限制 +数组是有效的,但它们的主要限制是它们的固定大小。 + +在实际情况下,您要存储的数据很少在编译时固定大小。因此 Go 的创造者创造了切片的概念。我们将在专门的章节中看到如何操作它们。 + +## 19 自测 +### 19.1 问题 + 1.数组是各种类型元素的集合。对或错? + + 2.数组作为参数传递给函数;函数可以修改数组吗? + + 3.数组的长度是多少? + + 4.长度和容量(对于数组)之间的关系是什么? +### 19.2 答案 +1,数组是各种类型元素的集合。对或错? + 1.错误的 + 2.数组的元素应该具有相同的类型。 + +2,数组作为参数传递给函数;函数可以修改数组吗? + + 1.不 + + 2.但是,如果您将指向数组的指针传递给函数,则它可以修改它。 + +3.数组的长度是多少? + + 1.可以存储的最大元素数 + +4.长度和容量(对于数组)之间的关系是什么? + + 1.对于数组长度 = 容量。 + ## 20 关键要点 + - 数组是**相同类型**元素的集合 + + - 数组的大小称为**长度**。 + + - 数组的长度在**编译时已知**的。 + + - 换句话说,数组一旦创建就不能增长 + + - 要遍历数组,您可以使用 for 循环(带或不带 range 子句) + + - 当数组传递给函数时,它被**复制**,函数不能修改它 + + - 当您将**指向**数组的**指针**传递给函数时,您使该函数能够**修改**该数组。 + + - 没有内置函数来查找数组中的元素。 diff --git a/chap-20-programming-a-computer/imgs/.keep b/chap-20-programming-a-computer/imgs/.keep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chap-20-programming-a-computer/imgs/2_dimension_array.523cc981.png b/chap-20-programming-a-computer/imgs/2_dimension_array.523cc981.png new file mode 100644 index 0000000000000000000000000000000000000000..c2daf54c64eaedc63f017905a412e5fe6afdb23e Binary files /dev/null and b/chap-20-programming-a-computer/imgs/2_dimension_array.523cc981.png differ diff --git a/chap-20-programming-a-computer/imgs/3d_array.b5678a70.png b/chap-20-programming-a-computer/imgs/3d_array.b5678a70.png new file mode 100644 index 0000000000000000000000000000000000000000..b6abd3341678b169c38c239b1715e71cba832573 Binary files /dev/null and b/chap-20-programming-a-computer/imgs/3d_array.b5678a70.png differ diff --git a/chap-20-programming-a-computer/imgs/array.e70c221f.jpg b/chap-20-programming-a-computer/imgs/array.e70c221f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3b11d5c9b666f4b3cb980f32725b91d19f6af38b Binary files /dev/null and b/chap-20-programming-a-computer/imgs/array.e70c221f.jpg differ diff --git a/chap-20-programming-a-computer/imgs/array_index_length.e1699603.png b/chap-20-programming-a-computer/imgs/array_index_length.e1699603.png new file mode 100644 index 0000000000000000000000000000000000000000..d0fae215dec9a5dc838b4427c07abb4bb176bac3 Binary files /dev/null and b/chap-20-programming-a-computer/imgs/array_index_length.e1699603.png differ diff --git a/chap-20-programming-a-computer/imgs/guest_list_2-dim_array.71f9abf9.png b/chap-20-programming-a-computer/imgs/guest_list_2-dim_array.71f9abf9.png new file mode 100644 index 0000000000000000000000000000000000000000..2da57ce7b4f4ea318512db36e224feee7d9f613b Binary files /dev/null and b/chap-20-programming-a-computer/imgs/guest_list_2-dim_array.71f9abf9.png differ diff --git a/chap-20-programming-a-computer/imgs/one_dimension_array.0fc6c244.png b/chap-20-programming-a-computer/imgs/one_dimension_array.0fc6c244.png new file mode 100644 index 0000000000000000000000000000000000000000..f0fe373cacc5d6c552d3b28ebe1c94be72dda137 Binary files /dev/null and b/chap-20-programming-a-computer/imgs/one_dimension_array.0fc6c244.png differ diff --git a/chap-20-programming-a-computer/imgs/target_element_2d_array.e112fd00.png b/chap-20-programming-a-computer/imgs/target_element_2d_array.e112fd00.png new file mode 100644 index 0000000000000000000000000000000000000000..c6c07fc863d4909c79b46a726b7ee8aa48a93afd Binary files /dev/null and b/chap-20-programming-a-computer/imgs/target_element_2d_array.e112fd00.png differ