回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,8 +1,8 @@
## 4.1. 數組
## 4.1. 数组
數組是一由固定度的特定型元素成的序列,一個數組可以由零或多元素成。因爲數組的長度是固定的因此在Go言中很少直接使用數組。和數組對應的類型是Slice切片它是可以增和收縮動態序列slice功能也更但是要理解slice工作原理的需要先理解數組
数组是一由固定度的特定型元素成的序列,一个数组可以由零或多元素成。因为数组的长度是固定的因此在Go言中很少直接使用数组。和数组对应的类型是Slice切片它是可以增和收缩动态序列slice功能也更但是要理解slice工作原理的需要先理解数组
數組的每元素可以通索引下標來訪問,索引下的范圍是從0開始到數組長度減1的位置。置的len函數將返迴數組中元素的個數
数组的每元素可以通索引下标来访问,索引下的范围是从0开始到数组长度减1的位置。置的len函数将返回数组中元素的个数
```Go
var a [3]int // array of 3 integers
@@ -20,7 +20,7 @@ for _, v := range a {
}
```
認情況下,數組的每元素都被初始化元素類型對應的零值,對於數字類型來説就是0。我也可以使用數組字面值法用一組值來初始化數組
认情况下,数组的每元素都被初始化元素类型对应的零值,对于数字类型来说就是0。我也可以使用数组字面值法用一组值来初始化数组
```Go
var q [3]int = [3]int{1, 2, 3}
@@ -28,30 +28,30 @@ var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"
```
數組字面值中,如果在數組的長度位置出的是“...”省略號,則表示數組的長度是根初始化值的個數來計算。因此上面q數組的定可以簡化爲
数组字面值中,如果在数组的长度位置出的是“...”省略号,则表示数组的长度是根初始化值的个数来计算。因此上面q数组的定可以简化为
```Go
q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"
```
數組的長度是數組類型的一個組成部分,因此[3]int和[4]int是兩種不同的數組類型。數組的長度必是常量表式,因爲數組的長度需要在編譯階段確定。
数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必是常量表式,因为数组的长度需要在编译阶段确定。
```Go
q := [3]int{1, 2, 3}
q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int
```
們將會發現,數組、slice、map和結構體字面值的法都很相似。上面的形式是直接提供序初始化值序列,但是也可以指定一索引和對應值列表的方式初始化,就像下面這樣
们将会发现,数组、slice、map和结构体字面值的法都很相似。上面的形式是直接提供序初始化值序列,但是也可以指定一索引和对应值列表的方式初始化,就像下面这样
```Go
type Currency int
const (
USD Currency = iota // 美元
EUR //
GBP // 英
RMB // 人民
EUR //
GBP // 英
RMB // 人民
)
symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}
@@ -59,15 +59,15 @@ symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}
fmt.Println(RMB, symbol[RMB]) // "3 ¥"
```
這種形式的數組字面值形式中,初始化索引的序是無關緊要的,而且用到的索引可以省略,和前面提到的規則一樣,未指定初始值的元素用零值初始化。例如,
这种形式的数组字面值形式中,初始化索引的序是无关紧要的,而且用到的索引可以省略,和前面提到的规则一样,未指定初始值的元素用零值初始化。例如,
```Go
r := [...]int{99: -1}
```
了一含有100元素的數組r後一個元素被初始化-1其它元素都是用0初始化。
了一含有100元素的数组r后一个元素被初始化-1其它元素都是用0初始化。
如果一個數組的元素型是可以相互比的,那麽數組類型也是可以相互比的,這時候我可以直接通==比較運算符來比較兩個數組,隻有當兩個數組的所有元素都是相等的時候數組才是相等的。不相等比較運算符!=遵循同樣的規則
如果一个数组的元素型是可以相互比的,那么数组类型也是可以相互比的,这时候我可以直接通==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。不相等比较运算符!=遵循同样的规则
```Go
a := [2]int{1, 2}
@@ -78,7 +78,7 @@ d := [3]int{1, 2}
fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int
```
爲一個眞實的例子crypto/sha256包的Sum256函數對一個任意的字slice型的數據生成一個對應的消息摘要。消息摘要有256bit大小因此對應[32]byte數組類型。如果兩個消息摘要是相同的,那可以認爲兩個消息本身也是相同(譯註:理上有HASH碰撞的情,但是實際應用可以基本忽略);如果消息摘要不同,那消息本身必然也是不同的。下面的例子用SHA256算法分别生成“x”和“X”兩個信息的摘要:
为一个真实的例子crypto/sha256包的Sum256函数对一个任意的字slice型的数据生成一个对应的消息摘要。消息摘要有256bit大小因此对应[32]byte数组类型。如果两个消息摘要是相同的,那可以认为两个消息本身也是相同(译注:理上有HASH碰撞的情,但是实际应用可以基本忽略);如果消息摘要不同,那消息本身必然也是不同的。下面的例子用SHA256算法分别生成“x”和“X”两个信息的摘要:
<u><i>gopl.io/ch4/sha256</i></u>
```Go
@@ -96,11 +96,11 @@ func main() {
}
```
上面例子中,兩個消息雖然隻有一字符的差,但是生成的消息摘要則幾乎有一半的bit位是不相同的。需要意Printf函的%x副詞參數,它用指定以十六進製的格式打印數組或slice全部的元素%t副詞參數是用打印布爾型數據,%T副詞參數是用於顯示一個值對應的數據類型。
上面例子中,两个消息虽然只有一字符的差,但是生成的消息摘要则几乎有一半的bit位是不相同的。需要意Printf函的%x副词参数,它用指定以十六进制的格式打印数组或slice全部的元素%t副词参数是用打印布尔型数据,%T副词参数是用于显示一个值对应的数据类型。
當調用一個函數的時候,函的每個調用參數將會被賦值給函數內部的參數變量,所以函數參數變量接收的是一個複製的副本,不是原始調用的量。因爲函數參數傳遞的機製導致傳遞大的數組類型將是低效的,併且對數組參數的任何的改都是生在複製的數組上,不能直接脩改調用時原始的數組變量。在這個方面Go語言對待數組的方式和其它很多編程語言不同,其它編程語言可能會隱式地將數組作爲引用或指針對象傳入被調用的函
当调用一个函数的时候,函的每个调用参数将会被赋值给函数内部的参数变量,所以函数参数变量接收的是一个复制的副本,不是原始用的量。因为函数参数传递的机制导致传递大的数组类型将是低效的,并且对数组参数的任何的改都是生在复制的数组上,不能直接修改调用时原始的数组变量。在这个方面Go语言对待数组的方式和其它很多编程语言不同,其它编程语言可能会隐式地将数组作为引用或指针对象传入被用的函
然,我可以式地入一個數組指針,那樣的話函數通過指針對數組的任何改都可以直接反饋到調用者。下面的函數用於給[32]byte型的數組清零:
然,我可以式地入一个数组指针,那样的话函数通过指针对数组的任何改都可以直接反馈到调用者。下面的函数用于给[32]byte型的数组清零:
```Go
func zero(ptr *[32]byte) {
@@ -110,7 +110,7 @@ func zero(ptr *[32]byte) {
}
```
實數組字面值[32]byte{}就可以生成一32字節的數組。而且每個數組的元素都是零值初始化也就是0。因此可以上面的zero函數寫的更簡潔一點
实数组字面值[32]byte{}就可以生成一32字节的数组。而且每个数组的元素都是零值初始化也就是0。因此可以上面的zero函数写的更简洁一点
```Go
func zero(ptr *[32]byte) {
@@ -118,8 +118,8 @@ func zero(ptr *[32]byte) {
}
```
然通過指針來傳遞數組參數是高效的,而且也允在函數內部脩改數組的值,但是數組依然是化的型,因爲數組的類型包含了化的度信息。上面的zero函數併不能接收指向[16]byte類型數組的指,而且也有任何添加或刪除數組元素的方法。由於這些原因除了像SHA256這類需要理特定大小數組的特例外,數組依然很少用作函數參數;相反,我一般使用slice替代數組
然通过指针来传递数组参数是高效的,而且也允在函数内部修改数组的值,但是数组依然是化的型,因为数组的类型包含了化的度信息。上面的zero函数并不能接收指向[16]byte类型数组的指,而且也有任何添加或删除数组元素的方法。由于这些原因除了像SHA256这类需要理特定大小数组的特例外,数组依然很少用作函数参数;相反,我一般使用slice替代数组
**練習 4.1** 編寫一個函數,計算兩個SHA256哈希中不同bit的目。(考2.6.2的PopCount函。)
**练习 4.1** 编写一个函数,计算两个SHA256哈希中不同bit的目。(考2.6.2的PopCount函。)
**練習 4.2** 編寫一個程序,默打印標準輸入的以SHA256哈希,也可以通命令行標準參數選擇SHA384或SHA512哈希算法。
**练习 4.2** 编写一个程序,默打印标准输入的以SHA256哈希,也可以通命令行标准参数选择SHA384或SHA512哈希算法。

View File

@@ -1,6 +1,6 @@
### 4.2.1. append函
### 4.2.1. append函
置的append函數用於向slice追加元素
置的append函数用于向slice追加元素
```Go
var runes []rune
@@ -10,9 +10,9 @@ for _, r := range "Hello, 世界" {
fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"
```
在循中使用append函數構建一由九rune字符成的slice當然對應這個特殊的問題我們可以通Go語言內置的[]rune("Hello, 世界")轉換操作完成。
在循中使用append函数构建一由九rune字符成的slice当然对应这个特殊的问题我们可以通Go语言内置的[]rune("Hello, 世界")转换操作完成。
append函數對於理解slice底是如何工作的非常重要,所以讓我們仔細査看究竟是生了什。下面是第一版本的appendInt函數,專門用於處理[]int型的slice
append函数对于理解slice底是如何工作的非常重要,所以让我们仔细查看究竟是生了什。下面是第一版本的appendInt函数,专门用于处理[]int型的slice
<u><i>gopl.io/ch4/append</i></u>
```Go
@@ -37,13 +37,13 @@ func appendInt(x []int, y int) []int {
}
```
每次調用appendInt函,必須先檢測slice底層數組是否有足的容量保存新添加的元素。如果有足夠空間的話,直接展slice依然在原有的底層數組之上),新添加的y元素複製到新展的空間,併返迴slice。因此入的x和出的z共享相同的底層數組
每次用appendInt函,必须先检测slice底层数组是否有足的容量保存新添加的元素。如果有足够空间的话,直接展slice依然在原有的底层数组之上),新添加的y元素复制到新展的空间,并返回slice。因此入的x和出的z共享相同的底层数组
如果有足的增長空間的話appendInt函數則會先分配一個足夠大的slice用保存新的果,先將輸入的x複製到新的空,然添加y元素。果z和入的x引用的是不同的底層數組
如果有足的增长空间的话appendInt函数则会先分配一个足够大的slice用保存新的果,先将输入的x复制到新的空,然添加y元素。果z和入的x引用的是不同的底层数组
然通過循環複製元素更直接,不過內置的copy函可以方便地將一個slice複製另一相同型的slice。copy函的第一個參數是要複製的目slice第二個參數是源slice和源的位置序和`dst = src`賦值語句是一致的。兩個slice可以共享同一個底層數組甚至有重疊也沒有問題。copy函數將返迴成功複製的元素的個數我們這里沒有用到),等於兩個slice中小的度,所以我不用心覆蓋會超出目slice的范
然通过循环复制元素更直接,不过内置的copy函可以方便地将一个slice复制另一相同型的slice。copy函的第一个参数是要复制的目slice第二个参数是源slice和源的位置序和`dst = src`赋值语句是一致的。两个slice可以共享同一个底层数组甚至有重叠也没有问题。copy函数将返回成功复制的元素的个数我们这里没有用到),等于两个slice中小的度,所以我不用心覆盖会超出目slice的范
了提高存使用效率,新分配的數組一般略大保存x和y所需要的最低大小。通在每次擴展數組時直接將長度翻倍而避免了多次存分配,也保了添加單個元素操的平均時間是一個常數時間。這個程序演示了效果:
了提高存使用效率,新分配的数组一般略大保存x和y所需要的最低大小。通在每次扩展数组时直接将长度翻倍而避免了多次存分配,也保了添加单个元素操的平均时间是一个常数时间。这个程序演示了效果:
```Go
func main() {
@@ -56,7 +56,7 @@ func main() {
}
```
每一次容量的化都會導致重新分配存和copy操作
每一次容量的化都会导致重新分配存和copy操作
```
0 cap=1 [0]
@@ -71,21 +71,21 @@ func main() {
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
```
讓我們仔細査看i=3次的迭代。當時x包含了[0 1 2]三元素但是容量是4因此可以簡單將新的元素添加到末尾,不需要新的存分配。然新的y的度和容量都是4且和x引用着相同的底層數組,如4.2所示。
让我们仔细查看i=3次的迭代。当时x包含了[0 1 2]三元素但是容量是4因此可以简单将新的元素添加到末尾,不需要新的存分配。然新的y的度和容量都是4且和x引用着相同的底层数组,如4.2所示。
![](../images/ch4-02.png)
在下一次迭代i=4現在沒有新的空的空因此appendInt函分配一容量8的底層數組,將x的4元素[0 1 2 3]複製到新空間的開頭,然添加新的元素i新元素的值是4。新的y的度是5容量是8面有3個空閒的位置,三次迭代都不需要分配新的空間。當前迭代中y和x是對應不同底層數組的view。次操作如4.3所示。
在下一次迭代i=4现在没有新的空的空因此appendInt函分配一容量8的底层数组,将x的4元素[0 1 2 3]复制到新空间的开头,然添加新的元素i新元素的值是4。新的y的度是5容量是8面有3个空闲的位置,三次迭代都不需要分配新的空间。当前迭代中y和x是对应不同底层数组的view。次操作如4.3所示。
![](../images/ch4-03.png)
置的append函可能使用比appendInt更複雜的內存擴展策略。因此,通常我們併不知道append調用是否致了存的重新分配,因此我也不能確認新的slice和原始的slice是否引用的是相同的底層數組空間。同,我不能確認在原先的slice上的操作是否會影響到新的slice。因此通常是append返迴的結果直接賦值給輸入的slice量:
置的append函可能使用比appendInt更复杂的内存扩展策略。因此,通常我们并不知道append用是否致了存的重新分配,因此我也不能确认新的slice和原始的slice是否引用的是相同的底层数组空间。同,我不能确认在原先的slice上的操作是否会影响到新的slice。因此通常是append返回的结果直接赋值给输入的slice量:
```Go
runes = append(runes, r)
```
更新slice量不僅對調用append函是必要的,實際上對應任何可能導致長度、容量或底層數組變化的操作都是必要的。要正地使用slice需要記住盡管底層數組的元素是間接訪問但是slice對應結構體本身的指針、長度和容量部分是直接訪問的。要更新些信息需要像上面例子那樣一個顯式的值操作。從這個角度看slice不是一個純粹的引用型,它實際上是一個類似下面結構體的聚合型:
更新slice量不仅对调用append函是必要的,实际上对应任何可能导致长度、容量或底层数组变化的操作都是必要的。要正地使用slice需要记住尽管底层数组的元素是间接访问但是slice对应结构体本身的指针、长度和容量部分是直接访问的。要更新些信息需要像上面例子那样一个显式的值操作。从这个角度看slice不是一个纯粹的引用型,它实际上是一个类似下面结构体的聚合型:
```Go
type IntSlice struct {
@@ -94,7 +94,7 @@ type IntSlice struct {
}
```
的appendInt函每次能向slice追加一元素,但是置的append函數則可以追加多元素,甚至追加一slice。
的appendInt函每次能向slice追加一元素,但是置的append函数则可以追加多元素,甚至追加一slice。
```Go
var x []int
@@ -105,7 +105,7 @@ x = append(x, x...) // append the slice x
fmt.Println(x) // "[1 2 3 4 5 6 1 2 3 4 5 6]"
```
下面的小改,我可以可以到append函數類似的功能。其中在appendInt函數參數中的最的“...”省略表示接收變長的參數爲slice。我們將在5.7節詳細解釋這個特性。
下面的小改,我可以可以到append函数类似的功能。其中在appendInt函数参数中的最的“...”省略表示接收变长的参数为slice。我们将在5.7节详细解释这个特性。
```Go
func appendInt(x []int, y ...int) []int {
@@ -117,4 +117,4 @@ func appendInt(x []int, y ...int) []int {
}
```
了避免重,和前面相同的代碼併沒有顯示。
了避免重,和前面相同的代码并没有显示。

View File

@@ -1,6 +1,6 @@
### 4.2.2. Slice存技巧
### 4.2.2. Slice存技巧
讓我們看看更多的例子,比如镟轉slice、反slice或在slice原有存空間脩改元素。定一字符串列表下面的nonempty函數將在原有slice存空之上返不包含空字符串的列表:
让我们看看更多的例子,比如旋转slice、反slice或在slice原有存空间修改元素。定一字符串列表下面的nonempty函数将在原有slice存空之上返不包含空字符串的列表:
<u><i>gopl.io/ch4/nonempty</i></u>
```Go
@@ -23,7 +23,7 @@ func nonempty(strings []string) []string {
}
```
微妙的地方是,入的slice和出的slice共享一個底層數組。這可以避免分配另一個數組,不過原來的數據將可能被覆,正如下面兩個打印句看到的那
微妙的地方是,入的slice和出的slice共享一个底层数组。这可以避免分配另一个数组,不过原来的数据将可能被覆,正如下面两个打印句看到的那
```Go
data := []string{"one", "", "three"}
@@ -31,9 +31,9 @@ fmt.Printf("%q\n", nonempty(data)) // `["one" "three"]`
fmt.Printf("%q\n", data) // `["one" "three" "three"]`
```
因此我通常會這樣使用nonempty函`data = nonempty(data)`
因此我通常会这样使用nonempty函`data = nonempty(data)`
nonempty函也可以使用append函數實現
nonempty函也可以使用append函数实现
```Go
func nonempty2(strings []string) []string {
@@ -47,27 +47,27 @@ func nonempty2(strings []string) []string {
}
```
無論如何實現,以這種方式重用一slice一般都要求最多爲每個輸入值生一個輸出值,事上很多這類算法都是用來過濾或合序列中相的元素。這種slice用法是比較複雜的技巧,然使用到了slice的一些技巧但是對於某些合是比清晰和有效的。
无论如何实现,以这种方式重用一slice一般都要求最多为每个输入值生一个输出值,事上很多这类算法都是用来过滤或合序列中相的元素。这种slice用法是比较复杂的技巧,然使用到了slice的一些技巧但是对于某些合是比清晰和有效的。
slice可以用來模擬一個stack。最初定的空slice對應一個空的stack可以使用append函數將新的值入stack
slice可以用来模拟一个stack。最初定的空slice对应一个空的stack可以使用append函数将新的值入stack
```Go
stack = append(stack, v) // push v
```
stack的部位置對應slice的最後一個元素:
stack的部位置对应slice的最后一个元素:
```Go
top := stack[len(stack)-1] // top of stack
```
過收縮stack可以彈出棧頂的元素
过收缩stack可以弹出栈顶的元素
```Go
stack = stack[:len(stack)-1] // pop
```
除slice中的某元素保存原有的元素序,可以通過內置的copy函數將後面的子slice向前依次移一位完成:
除slice中的某元素保存原有的元素序,可以通过内置的copy函数将后面的子slice向前依次移一位完成:
```Go
func remove(slice []int, i int) []int {
@@ -81,7 +81,7 @@ func main() {
}
```
如果除元素不用保持原來順序的,我可以簡單的用最後一個元素覆蓋被刪除的元素:
如果除元素不用保持原来顺序的,我可以简单的用最后一个元素覆盖被删除的元素:
```Go
func remove(slice []int, i int) []int {
@@ -95,12 +95,12 @@ func main() {
}
```
**練習 4.3**reverse函,使用數組指針代替slice。
**练习 4.3**reverse函,使用数组指针代替slice。
**練習 4.4** 編寫一個rotate函,通一次循完成镟轉
**练习 4.4** 编写一个rotate函,通一次循完成旋转
**練習 4.5** 寫一個函數在原地完成消除[]string中相鄰重複的字符串的操作。
**练习 4.5** 写一个函数在原地完成消除[]string中相邻重复的字符串的操作。
**練習 4.6** 編寫一個函數,原地將一個UTF-8編碼的[]byte型的slice中相的空格(考unicode.IsSpace成一空格返
**练习 4.6** 编写一个函数,原地将一个UTF-8编码的[]byte型的slice中相的空格(考unicode.IsSpace成一空格返
**練習 4.7** 改reverse函數用於原地反UTF-8編碼的[]byte。是否可以不用分配外的存?
**练习 4.7** 改reverse函数用于原地反UTF-8编码的[]byte。是否可以不用分配外的存?

View File

@@ -1,18 +1,18 @@
## 4.2. Slice
Slice切片代表變長的序列,序列中每元素都有相同的型。一slice型一般作[]T其中T代表slice中元素的slice的法和數組很像,隻是沒有固定度而已。
Slice切片代表变长的序列,序列中每元素都有相同的型。一slice型一般作[]T其中T代表slice中元素的slice的法和数组很像,只是没有固定度而已。
數組和slice之有着密的聯繫。一slice是一個輕量級的數據結構,提供了訪問數組子序列或者全部元素的功能而且slice的底層確實引用一個數組對象。一slice由三部分成:指針、長度和容量。指指向第一slice元素對應的底層數組元素的地址,要意的是slice的第一元素不一定就是數組的第一元素。長度對應slice中元素的目;度不能超容量,容量一般是slice的始位置到底層數據的結尾位置。置的len和cap函分别返slice的度和容量。
数组和slice之有着密的联系。一slice是一个轻量级的数据结构,提供了访问数组子序列或者全部元素的功能而且slice的底层确实引用一个数组对象。一slice由三部分成:指针、长度和容量。指指向第一slice元素对应的底层数组元素的地址,要意的是slice的第一元素不一定就是数组的第一元素。长度对应slice中元素的目;度不能超容量,容量一般是slice的始位置到底层数据的结尾位置。置的len和cap函分别返slice的度和容量。
slice之可以共享底層的數據,併且引用的數組部分區間可能重疊。圖4.1示了表示一年中每月份名字的字符串數組,還有重引用了該數組的兩個slice。數組這樣定義
slice之可以共享底层的数据,并且引用的数组部分区间可能重叠。图4.1示了表示一年中每月份名字的字符串数组,还有重引用了该数组的两个slice。数组这样定义
```Go
months := [...]string{1: "January", /* ... */, 12: "December"}
```
因此一月份是months[1]十二月份是months[12]。通常,數組的第一元素索引0始,但是月份一般是從1開始的,因此我們聲明數組時直接跳第0元素第0元素被自初始化空字符串。
因此一月份是months[1]十二月份是months[12]。通常,数组的第一元素索引0始,但是月份一般是从1开始的,因此我们声明数组时直接跳第0元素第0元素被自初始化空字符串。
slice的切片操作s[i:j]其中0 ≤ i≤ j≤ cap(s),用於創建一新的slice引用s的第i元素始到第j-1元素的子序列。新的slice將隻有j-i元素。如果i位置的索引被省略的話將使用0代替如果j位置的索引被省略的話將使用len(s)代替。因此months[1:13]切片操作引用全部有效的月份和months[1:]操作等months[:]切片操作是引用整個數組。讓我們分别定表示第二季度和北方夏天月份的slice有重部分:
slice的切片操作s[i:j]其中0 ≤ i≤ j≤ cap(s),用于创建一新的slice引用s的第i元素始到第j-1元素的子序列。新的slice将只有j-i元素。如果i位置的索引被省略的话将使用0代替如果j位置的索引被省略的话将使用len(s)代替。因此months[1:13]切片操作引用全部有效的月份和months[1:]操作等months[:]切片操作是引用整个数组。让我们分别定表示第二季度和北方夏天月份的slice有重部分:
![](../images/ch4-01.png)
@@ -23,7 +23,7 @@ fmt.Println(Q2) // ["April" "May" "June"]
fmt.Println(summer) // ["June" "July" "August"]
```
兩個slice都包含了六月份下面的代是一包含相同月份的測試(性能低):
两个slice都包含了六月份下面的代是一包含相同月份的测试(性能低):
```Go
for _, s := range summer {
@@ -35,7 +35,7 @@ for _, s := range summer {
}
```
如果切片操作超出cap(s)的上限將導致一panic但是超出len(s)是意味着展了slice新slice的長度會變大:
如果切片操作超出cap(s)的上限将导致一panic但是超出len(s)是意味着展了slice新slice的长度会变大:
```Go
fmt.Println(summer[:20]) // panic: out of range
@@ -44,9 +44,9 @@ endlessSummer := summer[:5] // extend a slice (within capacity)
fmt.Println(endlessSummer) // "[June July August September October]"
```
另外,字符串的切片操作和[]byte字節類型切片的切片操作是似的。它們都寫作x[m:n]且都是返迴一個原始字節繫列的子序列,底都是共享之前的底層數組,因此切片操作對應常量時間複雜度。x[m:n]切片操作對於字符串生成一新字符串如果x是[]byte的話則生成一新的[]byte。
另外,字符串的切片操作和[]byte字节类型切片的切片操作是似的。它们都写作x[m:n]且都是返回一个原始字节系列的子序列,底都是共享之前的底层数组,因此切片操作对应常量时间复杂度。x[m:n]切片操作对于字符串生成一新字符串如果x是[]byte的话则生成一新的[]byte。
slice值包含指向第一slice元素的指,因此向函數傳遞slice將允許在函數內部脩改底層數組的元素。換句話説複製一個slice隻是對底層的數組創建了一新的slice别名§2.3.2。下面的reverse函在原存空間將[]int型的slice反,而且它可以用任意度的slice。
slice值包含指向第一slice元素的指,因此向函数传递slice将允许在函数内部修改底层数组的元素。换句话说复制一个slice只是对底层的数组创建了一新的slice别名§2.3.2。下面的reverse函在原存空间将[]int型的slice反,而且它可以用任意度的slice。
<u><i>gopl.io/ch4/rev</i></u>
```Go
@@ -58,7 +58,7 @@ func reverse(s []int) {
}
```
里我們反轉數組的應用:
里我们反转数组的应用:
```Go
a := [...]int{0, 1, 2, 3, 4, 5}
@@ -66,7 +66,7 @@ reverse(a[:])
fmt.Println(a) // "[5 4 3 2 1 0]"
```
種將slice元素循向左镟轉n個元素的方法是三次調用reverse反轉函數,第一次是反轉開頭的n元素,然是反剩下的元素,最是反轉整個slice的元素。如果是向右循環镟轉,則將第三個函數調用移到第一個調用位置就可以了。)
种将slice元素循向左旋转n个元素的方法是三次用reverse反转函数,第一次是反转开头的n元素,然是反剩下的元素,最是反转整个slice的元素。如果是向右循环旋转,则将第三个函数调用移到第一个调用位置就可以了。)
```Go
s := []int{0, 1, 2, 3, 4, 5}
@@ -77,9 +77,9 @@ reverse(s)
fmt.Println(s) // "[2 3 4 5 0 1]"
```
意的是slice型的量s和數組類型的量a的初始化法的差。slice和數組的字面值法很似,它都是用花括弧包含一列的初始化元素,但是對於slice併沒有指明序列的度。這會隱式地建一個合適大小的數組,然slice的指指向底層的數組。就像數組字面值一slice的字面值也可以按序指定初始化值序列,或者是通索引和元素值指定,或者的兩種風格的混合法初始化。
意的是slice型的量s和数组类型的量a的初始化法的差。slice和数组的字面值法很似,它都是用花括弧包含一列的初始化元素,但是对于slice并没有指明序列的度。这会隐式地建一个合适大小的数组,然slice的指指向底层的数组。就像数组字面值一slice的字面值也可以按序指定初始化值序列,或者是通索引和元素值指定,或者的两种风格的混合法初始化。
數組不同的是slice之不能比,因此我不能使用==操作符來判斷兩個slice是否含有全部相等元素。不過標準庫提供了高度化的bytes.Equal函數來判斷兩個字節型slice是否相等[]byte但是對於其他型的slice們必須自己展開每個元素行比
数组不同的是slice之不能比,因此我不能使用==操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度化的bytes.Equal函数来判断两个字节型slice是否相等[]byte但是对于其他型的slice们必须自己展开每个元素行比
```Go
func equal(x, y []string) bool {
@@ -95,17 +95,17 @@ func equal(x, y []string) bool {
}
```
上面關於兩個slice的深度相等測試,運行的時間併不比支持==操作的數組或字符串更多,但是何slice不直接支持比較運算符呢?方面有兩個原因。第一原因,一slice的元素是接引用的,一slice甚至可以包含自身。然有很多辦法處理這種情形,但是有一個是簡單有效的。
上面关于两个slice的深度相等测试,运行的时间并不比支持==操作的数组或字符串更多,但是何slice不直接支持比较运算符呢?方面有两个原因。第一原因,一slice的元素是接引用的,一slice甚至可以包含自身。然有很多办法处理这种情形,但是有一个是简单有效的。
第二原因,因slice的元素是接引用的,一固定值的slice在不同的時間可能包含不同的元素,因爲底層數組的元素可能會被脩改。且Go言中map等哈希表之類的數據結構的key隻做簡單的淺拷貝,它要求在整個聲明週期中相等的key必須對相同的元素。對於像指或chan之的引用型,==相等測試可以判斷兩個是否是引用相同的象。一個針對slice的相等測試的==操作符可能是有一定用的,也能臨時解決map型的key問題但是slice和數組不同的相等測試行爲會讓人睏惑。因此安全的做法是直接禁止slice之的比操作。
第二原因,因slice的元素是接引用的,一固定值的slice在不同的时间可能包含不同的元素,因为底层数组的元素可能会被修改。且Go言中map等哈希表之类的数据结构的key只做简单的浅拷贝,它要求在整个声明周期中相等的key必须对相同的元素。对于像指或chan之的引用型,==相等测试可以判断两个是否是引用相同的象。一个针对slice的相等测试的==操作符可能是有一定用的,也能临时解决map型的key问题但是slice和数组不同的相等测试行为会让人困惑。因此安全的做法是直接禁止slice之的比操作。
slice唯一合法的比操作是和nil比,例如:
slice唯一合法的比操作是和nil比,例如:
```Go
if summer == nil { /* ... */ }
```
零值的slice等nil。一nil值的slice併沒有底層數組。一nil值的slice的度和容量都是0但是也有非nil值的slice的度和容量也是0的例如[]int{}或make([]int, 3)[3:]。任意型的nil值一,我可以用[]int(nil)類型轉換表達式來生成一個對應類型slice的nil值。
零值的slice等nil。一nil值的slice并没有底层数组。一nil值的slice的度和容量都是0但是也有非nil值的slice的度和容量也是0的例如[]int{}或make([]int, 3)[3:]。任意型的nil值一,我可以用[]int(nil)类型转换表达式来生成一个对应类型slice的nil值。
```Go
var s []int // len(s) == 0, s == nil
@@ -114,16 +114,16 @@ s = []int(nil) // len(s) == 0, s == nil
s = []int{} // len(s) == 0, s != nil
```
如果你需要測試一個slice是否是空的使用len(s) == 0來判斷,而不應該用s == nil來判斷。除了和nil相等比外,一nil值的slice的行和其它任意0度的slice一例如reverse(nil)也是安全的。除了文檔已經明確説明的地方所有的Go言函數應該以相同的方式待nil值的slice和0度的slice。
如果你需要测试一个slice是否是空的使用len(s) == 0来判断,而不应该用s == nil来判断。除了和nil相等比外,一nil值的slice的行和其它任意0度的slice一例如reverse(nil)也是安全的。除了文档已经明确说明的地方所有的Go言函数应该以相同的方式待nil值的slice和0度的slice。
置的make函數創建一指定元素型、度和容量的slice。容量部分可以省略這種情況下,容量將等於長度。
置的make函数创建一指定元素型、度和容量的slice。容量部分可以省略这种情况下,容量将等于长度。
```Go
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]
```
在底make建了一匿名的數組變量,然後返迴一個slice有通過返迴的slice才能引用底匿名的數組變量。在第一種語句中slice是整個數組的view。在第二個語句中slice引用了底層數組的前len元素,但是容量包含整個的數組。額外的元素是留給未來的增用的。
在底make建了一匿名的数组变量,然后返回一个slice有通过返回的slice才能引用底匿名的数组变量。在第一种语句中slice是整个数组的view。在第二个语句中slice引用了底层数组的前len元素,但是容量包含整个的数组。额外的元素是留给未来的增用的。
{% include "./ch4-02-1.md" %}

View File

@@ -1,16 +1,16 @@
## 4.3. Map
哈希表是一巧妙併且實用的數據結構。它是一個無序的key/value的集合其中所有的key都是不同的後通過給定的key可以在常數時間複雜度內檢索、更新或刪除對應的value。
哈希表是一巧妙并且实用的数据结构。它是一个无序的key/value的集合其中所有的key都是不同的后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。
在Go言中,一map就是一哈希表的引用map型可以寫爲map[K]V其中K和V分别對應key和value。map中所有的key都有相同的所有的value也有着相同的但是key和value之可以是不同的數據類型。其中K對應的key必是支持==比較運算符的數據類所以map可以通過測試key是否相等來判斷是否已存在。然浮點數類型也是支持相等算符比的,但是將浮點數用做key類型則是一個壞的想法,正如第三章提到的,最的情是可能出的NaN和任何浮點數都不相等。對於V對應的value數據類型則沒有任何的限
在Go言中,一map就是一哈希表的引用map型可以写为map[K]V其中K和V分别对应key和value。map中所有的key都有相同的所有的value也有着相同的但是key和value之可以是不同的数据类型。其中K对应的key必是支持==比较运算符的数据类所以map可以通过测试key是否相等来判断是否已存在。然浮点数类型也是支持相等算符比的,但是将浮点数用做key类型则是一个坏的想法,正如第三章提到的,最的情是可能出的NaN和任何浮点数都不相等。对于V对应的value数据类型则没有任何的限
置的make函可以建一map
置的make函可以建一map
```Go
ages := make(map[string]int) // mapping from strings to ints
```
也可以用map字面值的語法創建map時還可以指定一些最初的key/value
也可以用map字面值的语法创建map时还可以指定一些最初的key/value
```Go
ages := map[string]int{
@@ -19,7 +19,7 @@ ages := map[string]int{
}
```
這相當於
这相当于
```Go
ages := make(map[string]int)
@@ -27,48 +27,48 @@ ages["alice"] = 31
ages["charlie"] = 34
```
因此,另一種創建空的map的表式是`map[string]int{}`
因此,另一种创建空的map的表式是`map[string]int{}`
Map中的元素通key對應的下標語法訪問
Map中的元素通key对应的下标语法访问
```Go
ages["alice"] = 32
fmt.Println(ages["alice"]) // "32"
```
使用置的delete函可以除元素:
使用置的delete函可以除元素:
```Go
delete(ages, "alice") // remove element ages["alice"]
```
所有些操作是安全的,卽使這些元素不在map中也沒有關繫;如果一個査找失敗將返迴value類型對應的零值,例如,使map中不存在“bob”下面的代也可以正常工作,因ages["bob"]失敗時將返迴0。
所有些操作是安全的,即使这些元素不在map中也没有关系;如果一个查找失败将返回value类型对应的零值,例如,使map中不存在“bob”下面的代也可以正常工作,因ages["bob"]失败时将返回0。
```Go
ages["bob"] = ages["bob"] + 1 // happy birthday!
```
而且`x += y``x++`簡短賦值語法也可以用在map上所以上面的代可以改
而且`x += y``x++`简短赋值语法也可以用在map上所以上面的代可以改
```Go
ages["bob"] += 1
```
簡單的寫
简单的写
```Go
ages["bob"]++
```
但是map中的元素不是一個變量,因此我不能map的元素行取址操作:
但是map中的元素不是一个变量,因此我不能map的元素行取址操作:
```Go
_ = &ages["bob"] // compile error: cannot take address of map element
```
禁止map元素取址的原因是map可能着元素量的增而重新分配更大的存空間,從而可能致之前的地址效。
禁止map元素取址的原因是map可能着元素量的增而重新分配更大的存空间,从而可能致之前的地址效。
要想遍map中全部的key/value對的話可以使用range格的for循環實現和之前的slice遍歷語法類似。下面的迭代語句將在每次迭代時設置name和age量,它們對應下一個鍵/值
要想遍map中全部的key/value对的话可以使用range格的for循环实现和之前的slice遍历语法类似。下面的迭代语句将在每次迭代时设置name和age量,它们对应下一个键/值
```Go
for name, age := range ages {
@@ -76,7 +76,7 @@ for name, age := range ages {
}
```
Map的迭代序是不定的,且不同的哈希函數實現可能致不同的遍歷順序。在實踐中,遍歷的順序是隨機的,每一次遍歷的順序都不相同。是故意的,每次都使用隨機的遍歷順序可以強製要求程序不會依賴具體的哈希函數實現。如果要按序遍key/value,我們必須顯式地key行排序可以使用sort包的Strings函數對字符串slice行排序。下面是常見的處理方式:
Map的迭代序是不定的,且不同的哈希函数实现可能致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同。是故意的,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。如果要按序遍key/value,我们必须显式地key行排序可以使用sort包的Strings函数对字符串slice行排序。下面是常见的处理方式:
```Go
import "sort"
@@ -91,15 +91,15 @@ for _, name := range names {
}
```
爲我們一開始就知道names的最大小,因此slice分配一個合適的大小將會更有效。下面的代碼創建了一空的slice但是slice的容量好可以放下map中全部的key
为我们一开始就知道names的最大小,因此slice分配一个合适的大小将会更有效。下面的代码创建了一空的slice但是slice的容量好可以放下map中全部的key
```Go
names := make([]string, 0, len(ages))
```
在上面的第一range循中,我們隻關心map中的key所以我忽略了第二個循環變量。在第二個循環中,我們隻關心names中的名字所以我使用“_”空白標識符來忽略第一個循環變也就是迭代slice的索引。
在上面的第一range循中,我们只关心map中的key所以我忽略了第二个循环变量。在第二个循环中,我们只关心names中的名字所以我使用“_”空白标识符来忽略第一个循环变也就是迭代slice的索引。
map型的零值是nil也就是有引用任何哈希表。
map型的零值是nil也就是有引用任何哈希表。
```Go
var ages map[string]int
@@ -107,30 +107,30 @@ fmt.Println(ages == nil) // "true"
fmt.Println(len(ages) == 0) // "true"
```
map上的大部分操作包括找、除、len和range循都可以安全工作在nil值的map上的行和一空的map似。但是向一nil值的map存入元素將導致一panic常:
map上的大部分操作包括找、除、len和range循都可以安全工作在nil值的map上的行和一空的map似。但是向一nil值的map存入元素将导致一panic常:
```Go
ages["carol"] = 21 // panic: assignment to entry in nil map
```
在向map存數據前必須先創建map。
在向map存数据前必须先创建map。
key作索引下標來訪問map將産生一value。如果key在map中是存在的麽將得到key對應的value如果key不存在麽將得到value對應類型的零值,正如我前面看到的ages["bob"]那樣。這個規則很實用,但是有候可能需要知道對應的元素是否的是在map之中。例如如果元素型是一個數字,你可以需要分一個已經存在的0和不存在而返零值的0可以像下面這樣測試
key作索引下标来访问map将产生一value。如果key在map中是存在的么将得到key对应的value如果key不存在么将得到value对应类型的零值,正如我前面看到的ages["bob"]那样。这个规则很实用,但是有候可能需要知道对应的元素是否的是在map之中。例如如果元素型是一个数字,你可以需要分一个已经存在的0和不存在而返零值的0可以像下面这样测试
```Go
age, ok := ages["bob"]
if !ok { /* "bob" is not a key in this map; age == 0. */ }
```
會經常看到將這兩個結合起使用,像這樣
会经常看到将这两个结合起使用,像这样
```Go
if age, ok := ages["bob"]; !ok { /* ... */ }
```
這種場景下map的下標語法將産生兩個值;第二是一個布爾值,用於報告元素是否的存在。布爾變量一般命名ok特别適合馬上用if件判部分。
这种场景下map的下标语法将产生两个值;第二是一个布尔值,用于报告元素是否的存在。布尔变量一般命名ok特别适合马上用if件判部分。
和slice一map之也不能行相等比唯一的例外是和nil行比。要判斷兩個map是否包含相同的key和value們必須通過一個循環實現
和slice一map之也不能行相等比唯一的例外是和nil行比。要判断两个map是否包含相同的key和value们必须通过一个循环实现
```Go
func equal(x, y map[string]int) bool {
@@ -146,14 +146,14 @@ func equal(x, y map[string]int) bool {
}
```
意我是如何用!ok來區分元素缺失和元素不同的。我不能簡單地用xv != y[k]判,那樣會導致在判下面兩個map時産生錯誤的結果:
意我是如何用!ok来区分元素缺失和元素不同的。我不能简单地用xv != y[k]判,那样会导致在判下面两个map时产生错误的结果:
```Go
// True if equal is written incorrectly.
equal(map[string]int{"A": 0}, map[string]int{"B": 42})
```
Go言中併沒有提供一set但是map中的key也是不相同的可以用map實現類似set的功能。爲了説明這一點下面的dedup程序取多行入,但是打印第一次出的行。它是1.3中出的dup程序的變體dedup程序通map表示所有的入行所對應的set集合保已在集合存在的行不被重打印。
Go言中并没有提供一set但是map中的key也是不相同的可以用map实现类似set的功能。为了说明这一点下面的dedup程序取多行入,但是打印第一次出的行。它是1.3中出的dup程序的变体dedup程序通map表示所有的入行所对应的set集合保已在集合存在的行不被重打印。
<u><i>gopl.io/ch4/dedup</i></u>
```Go
@@ -175,11 +175,11 @@ func main() {
}
```
Go程序員將這種忽略value的map作一字符串集合,非所有`map[string]bool`型value都是無關緊要的;有一些可能會同時包含true和false的值。
Go程序员将这种忽略value的map作一字符串集合,非所有`map[string]bool`型value都是无关紧要的;有一些可能会同时包含true和false的值。
候我需要一map或set的key是slice但是map的key必是可比較的類但是slice併不滿足這個條件。不,我可以通過兩個步驟繞過這個限製。第一步,定義一個輔助函kslice轉爲map對應的string型的key確保隻有x和y相等k(x) == k(y)才成立。然後創建一keystring型的map在每次map操作先用k助函數將slice轉化爲string型。
候我需要一map或set的key是slice但是map的key必是可比较的类但是slice并不满足这个条件。不,我可以通过两个步骤绕过这个限制。第一步,定义一个辅助函kslice转为map对应的string型的key确保只有x和y相等k(x) == k(y)才成立。然后创建一keystring型的map在每次map操作先用k助函数将slice转化为string型。
下面的例子演示了如何使用map來記録提交相同的字符串列表的次。它使用了fmt.Sprintf函數將字符串列表轉換爲一個字符串以用map的key%q參數忠實地記録每個字符串元素的信息:
下面的例子演示了如何使用map来记录提交相同的字符串列表的次。它使用了fmt.Sprintf函数将字符串列表转换为一个字符串以用map的key%q参数忠实地记录每个字符串元素的信息:
```Go
var m = make(map[string]int)
@@ -190,9 +190,9 @@ func Add(list []string) { m[k(list)]++ }
func Count(list []string) int { return m[k(list)] }
```
使用同的技可以理任何不可比的key型,而不僅僅是slice型。這種技術對於想使用自定key比較函數的時候也很有用,例如在比字符串的候忽略大小。同時,輔助函k(x)也不一定是字符串型,它可以返任何可比較的類型,例如整數、數組或結構體等。
使用同的技可以理任何不可比的key型,而不仅仅是slice型。这种技术对于想使用自定key比较函数的时候也很有用,例如在比字符串的候忽略大小。同时,辅助函k(x)也不一定是字符串型,它可以返任何可比较的类型,例如整数、数组或结构体等。
是map的另一例子,下面的程序用於統計輸入中每Unicode碼點出現的次數。雖然Unicode全部碼點的數量鉅大,但是出在特定文中的字符種類併沒有多少使用map可以用比自然的方式來跟蹤那些出現過字符的次
是map的另一例子,下面的程序用于统计输入中每Unicode码点出现的次数。虽然Unicode全部码点的数量巨大,但是出在特定文中的字符种类并没有多少使用map可以用比自然的方式来跟踪那些出现过字符的次
<u><i>gopl.io/ch4/charcount</i></u>
```Go
@@ -246,15 +246,15 @@ func main() {
}
```
ReadRune方法行UTF-8解碼併返迴三個值:解的rune字符的值字符UTF-8編碼後的長度,和一個錯誤值。我們可預期的錯誤值隻有對應文件尾的io.EOF。如果入的是效的UTF-8編碼的字符,返迴的將是unicode.ReplacementChar表示效字符,併且編碼長度是1。
ReadRune方法行UTF-8解码并返回三个值:解的rune字符的值字符UTF-8编码后的长度,和一个错误值。我们可预期的错误值只有对应文件尾的io.EOF。如果入的是效的UTF-8编码的字符,返回的将是unicode.ReplacementChar表示效字符,并且编码长度是1。
charcount程序同打印不同UTF-8編碼長度的字符目。map不是一個合適的數據結構;因UTF-8編碼的長度總是從1到utf8.UTFMax最大是4個字節),使用數組將更有效。
charcount程序同打印不同UTF-8编码长度的字符目。map不是一个合适的数据结构;因UTF-8编码的长度总是从1到utf8.UTFMax最大是4个字节),使用数组将更有效。
爲一個實驗,我用charcount程序英文版原稿的字符行了統計。雖然大部分是英但是也有一些非ASCII字符。下面是排名前10的非ASCII字符
为一个实验,我用charcount程序英文版原稿的字符行了统计。虽然大部分是英但是也有一些非ASCII字符。下面是排名前10的非ASCII字符
![](../images/ch4-xx-01.png)
下面是不同UTF-8編碼長度的字符的目:
下面是不同UTF-8编码长度的字符的目:
```
len count
@@ -264,7 +264,7 @@ len count
4 0
```
Map的value型也可以是一聚合型,比如是一map或slice。在下面的代中,graph的key型是一字符串value型map[string]bool代表一字符串集合。概念上graph將一個字符串型的key映射到一組相關的字符串集合,它指向新的graph的key。
Map的value型也可以是一聚合型,比如是一map或slice。在下面的代中,graph的key型是一字符串value型map[string]bool代表一字符串集合。概念上graph将一个字符串型的key映射到一组相关的字符串集合,它指向新的graph的key。
<u><i>gopl.io/ch4/graph</i></u>
```Go
@@ -284,8 +284,8 @@ func hasEdge(from, to string) bool {
}
```
其中addEdge函惰性初始化map是一個慣用方式,也就是在每值首次作key才初始化。addEdge函數顯示了如何map的零值也能正常工作使from到to的不存在graph[from][to]依然可以返迴一個有意義的結果。
其中addEdge函惰性初始化map是一个惯用方式,也就是在每值首次作key才初始化。addEdge函数显示了如何map的零值也能正常工作使from到to的不存在graph[from][to]依然可以返回一个有意义的结果。
**練習 4.8** 改charcount程序使用unicode.IsLetter等相的函數,統計字母、字等Unicode中不同的字符别。
**练习 4.8** 改charcount程序使用unicode.IsLetter等相的函数,统计字母、字等Unicode中不同的字符别。
**練習 4.9** 編寫一個程序wordfreq程序報告輸入文本中每個單詞出現的頻率。在第一次調用Scan前先調用input.Split(bufio.ScanWords)函數,這樣可以按單詞而不是按行入。
**练习 4.9** 编写一个程序wordfreq程序报告输入文本中每个单词出现的频率。在第一次用Scan前先用input.Split(bufio.ScanWords)函数,这样可以按单词而不是按行入。

View File

@@ -1,6 +1,6 @@
### 4.4.1. 結構體面值
### 4.4.1. 结构体面值
結構體值也可以用結構體面值表示,結構體面值可以指定每個成員的值。
结构体值也可以用结构体面值表示,结构体面值可以指定每个成员的值。
```Go
type Point struct{ X, Y int }
@@ -8,17 +8,17 @@ type Point struct{ X, Y int }
p := Point{1, 2}
```
里有兩種形式的結構體面值法,上面的是第一種寫法,要求以結構體成員定義的順序爲每個結構體成員指定一面值。它要求寫代碼和讀代碼的人要記住結構體的每個成員的類型和序,不過結構體成員有細微的調整就可能致上述代不能編譯。因此,上述的法一般在定義結構體的包部使用,或者是在小的結構體中使用,這些結構體的成排列比較規則比如image.Point{x, y}或color.RGBA{red, green, blue, alpha}。
里有两种形式的结构体面值法,上面的是第一种写法,要求以结构体成员定义的顺序为每个结构体成员指定一面值。它要求写代码和读代码的人要记住结构体的每个成员的类型和序,不过结构体成员有细微的整就可能致上述代不能编译。因此,上述的法一般在定义结构体的包部使用,或者是在小的结构体中使用,这些结构体的成排列比较规则比如image.Point{x, y}或color.RGBA{red, green, blue, alpha}。
更常用的是第二種寫法,以成名字和相的值初始化,可以包含部分或全部的成如1.4的Lissajous程序的法:
更常用的是第二种写法,以成名字和相的值初始化,可以包含部分或全部的成如1.4的Lissajous程序的法:
```Go
anim := gif.GIF{LoopCount: nframes}
```
這種形式的結構體面值法中,如果成被忽略的話將默認用零值。因,提供了成的名字,所有成員出現的順序併不重要。
这种形式的结构体面值法中,如果成被忽略的话将默认用零值。因,提供了成的名字,所有成员出现的顺序并不重要。
兩種不同形式的法不能混合使用。而且,你不能企在外部包中用第一種順序賦值的技巧偷偷地初始化結構體中未出的成
两种不同形式的法不能混合使用。而且,你不能企在外部包中用第一种顺序赋值的技巧偷偷地初始化结构体中未出的成
```Go
package p
@@ -30,9 +30,9 @@ var _ = p.T{a: 1, b: 2} // compile error: can't reference a, b
var _ = p.T{1, 2} // compile error: can't reference a, b
```
然上面最一行代碼的編譯錯誤信息中併沒有顯式提到未出的成,但是這樣企圖隱式使用未出成的行也是不允的。
然上面最一行代码的编译错误信息中并没有显式提到未出的成,但是这样企图隐式使用未出成的行也是不允的。
結構體可以作爲函數的參數和返值。例如,這個Scale函數將Point型的值縮放後返迴
结构体可以作为函数的参数和返值。例如,这个Scale函数将Point型的值缩放后返回
```Go
func Scale(p Point, factor int) Point {
@@ -42,7 +42,7 @@ func Scale(p Point, factor int) Point {
fmt.Println(Scale(Point{1, 2}, 5)) // "{5 10}"
```
如果考效率的話,較大的結構體通常用指的方式入和返
如果考效率的话,较大的结构体通常用指的方式入和返
```Go
func Bonus(e *Employee, percent int) int {
@@ -50,7 +50,7 @@ func Bonus(e *Employee, percent int) int {
}
```
如果要在函數內部脩改結構體成員的話,用指針傳入是必的;因在Go言中,所有的函數參數都是值拷貝傳入的,函數參數將不再是函數調用時的原始量。
如果要在函数内部修改结构体成员的话,用指针传入是必的;因在Go言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始量。
```Go
func AwardAnnualRaise(e *Employee) {
@@ -58,17 +58,17 @@ func AwardAnnualRaise(e *Employee) {
}
```
爲結構體通常通過指針處理,可以用下面的寫法來創建併初始化一個結構體變量,併返迴結構體的地址:
为结构体通常通过指针处理,可以用下面的写法来创建并初始化一个结构体变量,并返回结构体的地址:
```Go
pp := &Point{1, 2}
```
它是下面的句是等
它是下面的句是等
```Go
pp := new(Point)
*pp = Point{1, 2}
```
&Point{1, 2}法可以直接在表式中使用,比如一個函數調用。
&Point{1, 2}法可以直接在表式中使用,比如一个函数调用。

View File

@@ -1,6 +1,6 @@
### 4.4.2. 結構體比較
### 4.4.2. 结构体比较
如果結構體的全部成都是可以比的,那麽結構體也是可以比的,那樣的話兩個結構體將可以使用==或!=算符行比。相等比較運算符==將比較兩個結構體的每個成員,因此下面兩個比較的表式是等的:
如果结构体的全部成都是可以比的,那么结构体也是可以比的,那样的话两个结构体将可以使用==或!=算符行比。相等比较运算符==将比较两个结构体的每个成员,因此下面两个比较的表式是等的:
```Go
type Point struct{ X, Y int }
@@ -11,7 +11,7 @@ fmt.Println(p.X == q.X && p.Y == q.Y) // "false"
fmt.Println(p == q) // "false"
```
可比較的結構體類型和其他可比較的類型一,可以用map的key型。
可比较的结构体类型和其他可比较的类型一,可以用map的key型。
```Go
type address struct {

View File

@@ -1,8 +1,8 @@
### 4.4.3. 結構體嵌入和匿名成
### 4.4.3. 结构体嵌入和匿名成
在本中,我們將看到如何使用Go言提供的不同常的結構體嵌入機製讓一個命名的結構體包含另一個結構體類型的匿名成員,這樣就可以通過簡單的點運算符x.f來訪問匿名成員鏈中嵌套的x.d.e.f成
在本中,我们将看到如何使用Go言提供的不同常的结构体嵌入机制让一个命名的结构体包含另一个结构体类型的匿名成员,这样就可以通过简单的点运算符x.f来访问匿名成员链中嵌套的x.d.e.f成
慮一個二維的繪圖程序,提供了一個各種圖形的,例如矩形、橢圓形、星形和形等何形狀。這里是其中兩個的定
虑一个二维的绘图程序,提供了一个各种图形的,例如矩形、椭圆形、星形和形等何形状。这里是其中两个的定
```Go
type Circle struct {
@@ -14,7 +14,7 @@ type Wheel struct {
}
```
Circle代表的圓形類型包含了標準圓心的X和Y坐信息,和一Radius表示的半信息。一Wheel形除了包含Circle型所有的全部成外,增加了Spokes表示徑向輻條的數量。我可以這樣創建一wheel量:
Circle代表的圆形类型包含了标准圆心的X和Y坐信息,和一Radius表示的半信息。一Wheel形除了包含Circle型所有的全部成外,增加了Spokes表示径向辐条的数量。我可以这样创建一wheel量:
```Go
var w Wheel
@@ -24,7 +24,7 @@ w.Radius = 5
w.Spokes = 20
```
隨着庫中幾何形狀數量的增多,我一定會註意到它們之間的相似和重複之處,所以我可能了便於維護而將相同的屬性獨立出
随着库中几何形状数量的增多,我一定会注意到它们之间的相似和重复之处,所以我可能了便于维护而将相同的属性独立出
```Go
type Point struct {
@@ -42,7 +42,7 @@ type Wheel struct {
}
```
這樣改動之後結構體類型變的清晰了,但是這種脩改同時也導致了訪問每個成員變得繁
这样改动之后结构体类型变的清晰了,但是这种修改同时也导致了访问每个成员变得繁
```Go
var w Wheel
@@ -52,7 +52,7 @@ w.Circle.Radius = 5
w.Spokes = 20
```
Go言有一特性讓我們隻聲明一個成員對應的數據類型而不指名成的名字;這類成員就叫匿名成。匿名成員的數據類型必是命名的型或指向一命名的型的指。下面的代Circle和Wheel各自都有一匿名成。我可以Point型被嵌入到了Circle結構體,同Circle型被嵌入到了Wheel結構體
Go言有一特性让我们只声明一个成员对应的数据类型而不指名成的名字;这类成员就叫匿名成。匿名成员的数据类型必是命名的型或指向一命名的型的指。下面的代Circle和Wheel各自都有一匿名成。我可以Point型被嵌入到了Circle结构体,同Circle型被嵌入到了Wheel结构体
```Go
type Circle struct {
@@ -66,7 +66,7 @@ type Wheel struct {
}
```
得意匿名嵌入的特性,我可以直接訪問葉子屬性而不需要出完整的路
得意匿名嵌入的特性,我可以直接访问叶子属性而不需要出完整的路
```Go
var w Wheel
@@ -76,16 +76,16 @@ w.Radius = 5 // equivalent to w.Circle.Radius = 5
w.Spokes = 20
```
在右邊的註釋中給出的式形式訪問這些葉子成員的語法依然有效,因此匿名成員併不是眞的無法訪問了。其中匿名成Circle和Point都有自己的名字——就是命名的型名字——但是些名字在操作符中是可的。我們在訪問子成員的時候可以忽略任何匿名成部分。
在右边的注释中给出的式形式访问这些叶子成员的语法依然有效,因此匿名成员并不是真的无法访问了。其中匿名成Circle和Point都有自己的名字——就是命名的型名字——但是些名字在操作符中是可的。我们在访问子成员的时候可以忽略任何匿名成部分。
不幸的是,結構體字面值併沒有簡短表示匿名成員的語法, 因此下面的句都不能編譯通過
不幸的是,结构体字面值并没有简短表示匿名成员的语法, 因此下面的句都不能编译通过
```Go
w = Wheel{8, 8, 5, 20} // compile error: unknown fields
w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields
```
結構體字面值必遵循形狀類型聲明時的結構,所以我們隻能用下面的兩種語法,它彼此是等的:
结构体字面值必遵循形状类型声明时的结构,所以我们只能用下面的两种语法,它彼此是等的:
<u><i>gopl.io/ch4/embed</i></u>
```Go
@@ -110,16 +110,16 @@ fmt.Printf("%#v\n", w)
// Wheel{Circle:Circle{Point:Point{X:42, Y:8}, Radius:5}, Spokes:20}
```
需要意的是Printf函中%v參數包含的#副它表示用和Go語言類似的法打印值。對於結構體類型來説,將包含每個成員的名字。
需要意的是Printf函中%v参数包含的#副它表示用和Go语言类似的法打印值。对于结构体类型来说,将包含每个成员的名字。
匿名成也有一個隱式的名字,因此不能同包含兩個類型相同的匿名成員,這會導致名字突。同,因爲成員的名字是由其類型隱式地定的,所有匿名成也有可性的規則約束。在上面的例子中Point和Circle匿名成都是出的。使它們不導出(比如改成小字母開頭的point和circle依然可以用短形式訪問匿名成嵌套的成
匿名成也有一个隐式的名字,因此不能同包含两个类型相同的匿名成员,这会导致名字突。同,因为成员的名字是由其类型隐式地定的,所有匿名成也有可性的规则约束。在上面的例子中Point和Circle匿名成都是出的。使它们不导出(比如改成小字母开头的point和circle依然可以用短形式访问匿名成嵌套的成
```Go
w.X = 8 // equivalent to w.circle.point.X = 8
```
但是在包外部,因circle和point沒有導出不能訪問它們的成,因此短的匿名成員訪問語法也是禁止的。
但是在包外部,因circle和point没有导出不能访问它们的成,因此短的匿名成员访问语法也是禁止的。
到目前止,我看到匿名成特性隻是對訪問嵌套成員的點運算符提供了短的法糖。稍,我們將會看到匿名成員併不要求是結構體類型;其任何命名的型都可以作爲結構體的匿名成。但是爲什麽要嵌入一個沒有任何子成員類型的匿名成員類型呢?
到目前止,我看到匿名成特性只是对访问嵌套成员的点运算符提供了短的法糖。稍,我们将会看到匿名成员并不要求是结构体类型;其任何命名的型都可以作为结构体的匿名成。但是为什么要嵌入一个没有任何子成员类型的匿名成员类型呢?
答案是匿名型的方法集。短的點運算符法可以用於選擇匿名成嵌套的成,也可以用於訪問它們的方法。實際上,外層的結構體不僅僅是獲得了匿名成員類型的所有成,而且也得了該類型導出的全部的方法。這個機製可以用於將一個有簡單行爲的對象組合成有複雜行爲的對象。合是Go言中面向對象編程的核心,我們將在6.3節中專門討論
答案是匿名型的方法集。短的点运算符法可以用于选择匿名成嵌套的成,也可以用于访问它们的方法。实际上,外层的结构体不仅仅是获得了匿名成员类型的所有成,而且也得了该类型导出的全部的方法。这个机制可以用于将一个有简单行为的对象组合成有复杂行为的对象。合是Go言中面向对象编程的核心,我们将在6.3节中专门讨论

View File

@@ -1,8 +1,8 @@
## 4.4. 結構體
## 4.4. 结构体
結構體是一聚合的數據類型,是由零或多任意型的值聚合成的實體。每個值稱爲結構體的成。用結構體的經典案例理公司的工信息,每個員工信息包含一唯一的員工編號、員工的名字、家庭住址、出生日期、工作位、薪、上級領導等等。所有的些信息都需要定到一個實體中,可以作爲一個整體單元被複製,作爲函數的參數或返值,或者是被存儲到數組中,等等。
结构体是一聚合的数据类型,是由零或多任意型的值聚合成的实体。每个值称为结构体的成。用结构体的经典案例理公司的工信息,每个员工信息包含一唯一的员工编号、员工的名字、家庭住址、出生日期、工作位、薪、上级领导等等。所有的些信息都需要定到一个实体中,可以作为一个整体单元被复制,作为函数的参数或返值,或者是被存储到数组中,等等。
下面兩個語句聲明了一叫Employee的命名的結構體類型,併且聲明了一Employee型的量dilbert
下面两个语句声明了一叫Employee的命名的结构体类型,并且声明了一Employee型的量dilbert
```Go
type Employee struct {
@@ -18,33 +18,33 @@ type Employee struct {
var dilbert Employee
```
dilbert結構體變量的成可以通過點操作符訪問比如dilbert.Name和dilbert.DoB。因dilbert是一個變量,它所有的成也同樣是變量,我可以直接對每個成員賦值:
dilbert结构体变量的成可以通过点操作符访问比如dilbert.Name和dilbert.DoB。因dilbert是一个变量,它所有的成也同样是变量,我可以直接对每个成员赋值:
```Go
dilbert.Salary -= 5000 // demoted, for writing too few lines of code
```
或者是對成員取地址,然後通過指針訪問
或者是对成员取地址,然后通过指针访问
```Go
position := &dilbert.Position
*position = "Senior " + *position // promoted, for outsourcing to Elbonia
```
操作符也可以和指向結構體的指一起工作:
操作符也可以和指向结构体的指一起工作:
```Go
var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"
```
當於下面
当于下面
```Go
(*employeeOfTheMonth).Position += " (proactive team player)"
```
下面的EmployeeByID函數將根據給定的工ID返迴對應的員工信息結構體的指。我可以使用操作符來訪問它里面的成
下面的EmployeeByID函数将根据给定的工ID返回对应的员工信息结构体的指。我可以使用操作符来访问它里面的成
```Go
func EmployeeByID(id int) *Employee { /* ... */ }
@@ -55,9 +55,9 @@ id := dilbert.ID
EmployeeByID(id).Salary = 0 // fired for... no real reason
```
面的句通EmployeeByID返迴的結構體指針更新了Employee結構體的成。如果EmployeeByID函的返迴值從`*Employee`針類型改Employee值型,那更新語句將不能編譯通過,因爲在賦值語句的左邊併不確定是一個變量(譯註:調用函數返迴的是值,不是一可取地址的量)。
面的句通EmployeeByID返回的结构体指针更新了Employee结构体的成。如果EmployeeByID函的返回值从`*Employee`针类型改Employee值型,那更新语句将不能编译通过,因为在赋值语句的左边并不确定是一个变量(译注:调用函数返回的是值,不是一可取地址的量)。
通常一行對應一個結構體成員,成的名字在前型在,不如果相的成員類型如果相同的可以被合到一行就像下面的Name和Address成員那樣
通常一行对应一个结构体成员,成的名字在前型在,不如果相的成员类型如果相同的可以被合到一行就像下面的Name和Address成员那样
```Go
type Employee struct {
@@ -70,13 +70,13 @@ type Employee struct {
}
```
結構體成員的輸入順序也有重要的意。我也可以Position成員合併(因也是字符串型),或者是交Name和Address出的先後順序,那樣的話就是定了不同的結構體類型。通常,我們隻是將相關的成員寫到一起。
结构体成员的输入顺序也有重要的意。我也可以Position成员合并(因也是字符串型),或者是交Name和Address出的先后顺序,那样的话就是定了不同的结构体类型。通常,我们只是将相关的成员写到一起。
如果結構體成員名字是以大字母開頭的,那麽該成員就是出的;是Go語言導出規則決定的。一個結構體可能同包含出和未出的成
如果结构体成员名字是以大字母开头的,那么该成员就是出的;是Go语言导出规则决定的。一个结构体可能同包含出和未出的成
結構體類型往往是冗的,因它的每個成員可能都占一行。然我每次都可以重寫整個結構體成員,但是重複會令人厭煩。因此,完整的結構體寫法通常隻在類型聲明語句的地方出就像Employee類型聲明語句那
结构体类型往往是冗的,因它的每个成员可能都占一行。然我每次都可以重写整个结构体成员,但是重复会令人厌烦。因此,完整的结构体写法通常只在类型声明语句的地方出就像Employee类型声明语句那
命名S的結構體類型將不能再包含S型的成:因爲一個聚合的值不能包含它自身。(該限製同樣適應於數組但是S型的結構體可以包含`*S`針類型的成員,這可以讓我們創建遞歸的數據結構,比如表和樹結構等。在下面的代中,我使用一二叉樹來實現一個插入排序:
命名S的结构体类型将不能再包含S型的成:因为一个聚合的值不能包含它自身。(该限制同样适应于数组但是S型的结构体可以包含`*S`针类型的成员,这可以让我们创建递归的数据结构,比如表和树结构等。在下面的代中,我使用一二叉树来实现一个插入排序:
<u><i>gopl.io/ch4/treesort</i></u>
```Go
@@ -121,9 +121,9 @@ func add(t *tree, value int) *tree {
}
```
結構體類型的零值是每個成員都對是零值。通常會將零值作最合理的默值。例如,對於bytes.Buffer型,結構體初始值就是一個隨時可用的空存,有在第9章將會講到的sync.Mutex的零值也是有效的未鎖定狀態。有時候這種零值可用的特性是自然得的,但是也有些型需要一些外的工作。
结构体类型的零值是每个成员都对是零值。通常会将零值作最合理的默值。例如,对于bytes.Buffer型,结构体初始值就是一个随时可用的空存,有在第9章将会讲到的sync.Mutex的零值也是有效的未锁定状态。有时候这种零值可用的特性是自然得的,但是也有些型需要一些外的工作。
如果結構體沒有任何成員的話就是空結構體,寫作struct{}。它的大小0也不包含任何信息但是有候依然是有值的。有些Go言程序用map帶模擬set數據結構時,用它代替map中布爾類型的value隻是強調key的重要性但是因爲節約的空有限,而且法比較複雜,所有我通常避免避免這樣的用法。
如果结构体没有任何成员的话就是空结构体,写作struct{}。它的大小0也不包含任何信息但是有候依然是有值的。有些Go言程序用map带模拟set数据结构时,用它代替map中布尔类型的value只是强调key的重要性但是因为节约的空有限,而且法比较复杂,所有我通常避免避免这样的用法。
```Go
seen := make(map[string]struct{}) // set of strings

View File

@@ -1,14 +1,14 @@
## 4.5. JSON
JavaScript象表示法JSON是一種用於發送和接收結構化信息的標準協議。在似的協議JSON不是唯一的一個標準協議。 XML§7.14、ASN.1和Google的Protocol Buffers都是似的協議,併且有各自的特色,但是由於簡潔性、可性和流行程度等原因JSON是用最泛的一
JavaScript象表示法JSON是一种用于发送和接收结构化信息的标准协议。在似的协议JSON不是唯一的一个标准协议。 XML§7.14、ASN.1和Google的Protocol Buffers都是似的协议,并且有各自的特色,但是由于简洁性、可性和流行程度等原因JSON是用最广泛的一
Go語言對於這些標準格式的編碼和解都有良好的支持,由標準庫中的encoding/json、encoding/xml、encoding/asn1等包提供支持譯註Protocol Buffers的支持由 github.com/golang/protobuf 包提供),併且這類包都有着相似的API接口。本,我們將對重要的encoding/json包的用法做概述。
Go语言对于这些标准格式的编码和解都有良好的支持,由标准库中的encoding/json、encoding/xml、encoding/asn1等包提供支持译注Protocol Buffers的支持由 github.com/golang/protobuf 包提供),并且这类包都有着相似的API接口。本,我们将对重要的encoding/json包的用法做概述。
JSON是JavaScript中各種類型的值——字符串、字、布值和象——Unicode本文編碼。它可以用有效可的方式表示第三章的基礎數據類型和本章的數組、slice、結構體和map等聚合數據類型。
JSON是JavaScript中各种类型的值——字符串、字、布值和象——Unicode本文编码。它可以用有效可的方式表示第三章的基础数据类型和本章的数组、slice、结构体和map等聚合数据类型。
基本的JSON型有字(十進製或科學記數法)、布true或false、字符串其中字符串是以雙引號包含的Unicode字符序列支持和Go語言類似的反斜槓轉義特性,不JSON使用的是\Uhhhh轉義數字來表示一UTF-16編碼(譯註UTF-16和UTF-8一是一種變長的編碼有些Unicode碼點較大的字符需要用4個字節表示而且UTF-16有大端和小端的問題而不是Go言的rune型。
基本的JSON型有字(十进制或科学记数法)、布true或false、字符串其中字符串是以双引号包含的Unicode字符序列支持和Go语言类似的反斜杠转义特性,不JSON使用的是\Uhhhh转义数字来表示一UTF-16编码(译注UTF-16和UTF-8一是一种变长的编码有些Unicode码点较大的字符需要用4个字节表示而且UTF-16有大端和小端的问题而不是Go言的rune型。
些基礎類型可以通JSON的數組和對象類型進行遞歸組合。一JSON數組是一有序的值序列,在一方括號中併以逗分隔;一JSON數組可以用於編碼Go言的數組和slice。一JSON象是一字符串到值的映射,成以列的name:value形式,用花括包含以逗分隔JSON的對象類型可以用於編碼Go言的mapkey型是字符串)和結構體。例如:
些基础类型可以通JSON的数组和对象类型进行递归组合。一JSON数组是一有序的值序列,在一方括号中并以逗分隔;一JSON数组可以用于编码Go言的数组和slice。一JSON象是一字符串到值的映射,成以列的name:value形式,用花括包含以逗分隔JSON的对象类型可以用于编码Go言的mapkey型是字符串)和结构体。例如:
```
boolean true
@@ -20,7 +20,7 @@ object {"year": 1980,
"medals": ["gold", "silver", "bronze"]}
```
慮一個應用程序,程序負責收集各種電影評論併提供反功能。它的Movie數據類型和一典型的表示影的值列表如下所示。(在結構體聲明中Year和Color成員後面的字符串面值是結構體成員Tag我們稍後會解釋它的作用。)
虑一个应用程序,程序负责收集各种电影评论并提供反功能。它的Movie数据类型和一典型的表示影的值列表如下所示。(在结构体声明中Year和Color成员后面的字符串面值是结构体成员Tag我们稍后会解释它的作用。)
<u><i>gopl.io/ch4/movie</i></u>
```Go
@@ -42,7 +42,7 @@ var movies = []Movie{
}
```
這樣的數據結構特别合JSON格式且在兩種之間相互轉換也很容易。將一個Go言中似movies的結構體slice轉爲JSON的程叫編組marshaling編組通過調用json.Marshal函完成:
这样的数据结构特别合JSON格式且在两种之间相互转换也很容易。将一个Go言中似movies的结构体slice转为JSON的程叫编组marshaling编组通过调用json.Marshal函完成:
```Go
data, err := json.Marshal(movies)
@@ -52,7 +52,7 @@ if err != nil {
fmt.Printf("%s\n", data)
```
Marshal函數返還一個編碼後的字slice包含很的字符串,併且沒有空白縮進;我們將它摺行以便於顯示:
Marshal函数返还一个编码后的字slice包含很的字符串,并且没有空白缩进;我们将它折行以便于显示:
```
[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr
@@ -61,7 +61,7 @@ tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"
Actors":["Steve McQueen","Jacqueline Bisset"]}]
```
這種緊湊的表示形式然包含了全部的信息,但是很難閲讀。爲了生成便於閲讀的格式,另一json.MarshalIndent函數將産生整齊縮進的輸出。該函數有兩個額外的字符串參數用於表示每一行出的前和每一個層級的縮進
这种紧凑的表示形式然包含了全部的信息,但是很难阅读。为了生成便于阅读的格式,另一json.MarshalIndent函数将产生整齐缩进的输出。该函数有两个额外的字符串参数用于表示每一行出的前和每一个层级的缩进
```Go
data, err := json.MarshalIndent(movies, "", " ")
@@ -71,7 +71,7 @@ if err != nil {
fmt.Printf("%s\n", data)
```
上面的代碼將産生這樣的輸出(譯註:在最後一個成員或元素後面併沒有逗分隔符):
上面的代码将产生这样的输出(译注:在最后一个成员或元素后面并没有逗分隔符):
```Json
[
@@ -103,18 +103,18 @@ fmt.Printf("%s\n", data)
]
```
編碼時,默使用Go語言結構體的成名字作JSON的象(通reflect反射技,我們將在12.6節討論)。隻有導出的結構體成員才會被編碼,這也就是我們爲什麽選擇用大字母開頭的成員名稱
编码时,默使用Go语言结构体的成名字作JSON的象(通reflect反射技,我们将在12.6节讨论)。只有导出的结构体成员才会被编码,这也就是我们为什么选择用大字母开头的成员名称
心的者可能已經註意到其中Year名字的成員在編碼後變成了released有Color成員編碼後變成了小字母開頭的color。是因爲構體成員Tag所致的。一個構體成員Tag是和在編譯階段關聯到該成員的元信息字符串:
心的者可能已经注意到其中Year名字的成员在编码后变成了released有Color成员编码后变成了小字母开头的color。是因为构体成员Tag所致的。一个构体成员Tag是和在编译阶段关联到该成员的元信息字符串:
```
Year int `json:"released"`
Color bool `json:"color,omitempty"`
```
結構體的成Tag可以是任意的字符串面值但是通常是一列用空格分隔的key:"value"鍵值對序列;因值中含義雙引號字符,因此成Tag一般用原生字符串面值的形式書寫。json開頭鍵名對應的值用於控製encoding/json包的編碼和解的行爲,併且encoding/...下面其它的包也遵循這個約定。成Tag中json對應值的第一部分用指定JSON象的名字,比如Go言中的TotalCount成員對應到JSON中的total_count象。Color成的Tag還帶了一個額外的omitempty選項,表示Go語言結構體成員爲空或零值不生成JSON象(里false零值。果然Casablanca是一黑白影,併沒有輸出Color成
结构体的成Tag可以是任意的字符串面值但是通常是一列用空格分隔的key:"value"键值对序列;因值中含义双引号字符,因此成Tag一般用原生字符串面值的形式书写。json开头键名对应的值用于控制encoding/json包的编码和解的行为,并且encoding/...下面其它的包也遵循这个约定。成Tag中json对应值的第一部分用指定JSON象的名字,比如Go言中的TotalCount成员对应到JSON中的total_count象。Color成的Tag还带了一个额外的omitempty选项,表示Go语言结构体成员为空或零值不生成JSON象(里false零值。果然Casablanca是一黑白影,并没有输出Color成
編碼的逆操作是解對應將JSON數據解碼爲Go言的數據結構Go言中一般叫unmarshalingjson.Unmarshal函完成。下面的代碼將JSON格式的電影數據解碼爲一個結構體slice結構體中隻有Title成。通過定義合適的Go語言數據結構,我可以選擇性地解JSON中感趣的成員。當Unmarshal函數調用返slice將被隻含有Title信息值填充其它JSON成員將被忽略。
编码的逆操作是解对应将JSON数据解码为Go言的数据结构Go言中一般叫unmarshalingjson.Unmarshal函完成。下面的代码将JSON格式的电影数据解码为一个结构体slice结构体中只有Title成。通过定义合适的Go语言数据结构,我可以选择性地解JSON中感趣的成员。当Unmarshal函数调用返slice将被只含有Title信息值填充其它JSON成员将被忽略。
```Go
var titles []struct{ Title string }
@@ -124,7 +124,7 @@ if err := json.Unmarshal(data, &titles); err != nil {
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"
```
多web服都提供JSON接口HTTP接口送JSON格式請求併返迴JSON格式的信息。爲了説明這一點,我們通過Github的issue査詢服務來演示似的用法。首先,我要定義合適的類型和常量:
多web服都提供JSON接口HTTP接口送JSON格式请求并返回JSON格式的信息。为了说明这一点,我们通过Github的issue查询服务来演示似的用法。首先,我要定义合适的类型和常量:
<u><i>gopl.io/ch4/github</i></u>
```Go
@@ -157,9 +157,9 @@ type User struct {
}
```
和前面一樣,卽使對應的JSON象名是小字母,每個結構體的成名也是聲明爲大小字母開頭的。因有些JSON成名字和Go結構體成員名字不相同因此需要Go語言結構體成員Tag指定對應的JSON名字。同,在解碼的時候也需要做同樣的處GitHub服務返迴的信息比我們定義的要多很多。
和前面一样,即使对应的JSON象名是小字母,每个结构体的成名也是声明为大小字母开头的。因有些JSON成名字和Go结构体成员名字不相同因此需要Go语言结构体成员Tag指定对应的JSON名字。同,在解码的时候也需要做同样的处GitHub服务返回的信息比我们定义的要多很多。
SearchIssues函數發出一HTTP求,然後解碼返迴的JSON格式的果。因爲用戶提供的査詢條件可能包含`?``&`的特殊字符,了避免URL造成突,我用url.QueryEscape來對査詢中的特殊字符進行轉義操作。
SearchIssues函数发出一HTTP求,然后解码返回的JSON格式的果。因为用户提供的查询条件可能包含`?``&`的特殊字符,了避免URL造成突,我用url.QueryEscape来对查询中的特殊字符进行转义操作。
<u><i>gopl.io/ch4/github</i></u>
```Go
@@ -198,9 +198,9 @@ func SearchIssues(terms []string) (*IssuesSearchResult, error) {
}
```
在早些的例子中,我使用了json.Unmarshal函數來將JSON格式的字符串解碼爲字節slice。但是這個例子中,我使用了基流式的解器json.Decoder它可以從一個輸入流解JSON數據,盡管這不是必的。如您所料,有一個針對輸出流的json.Encoder編碼對象。
在早些的例子中,我使用了json.Unmarshal函数来将JSON格式的字符串解码为字节slice。但是这个例子中,我使用了基流式的解器json.Decoder它可以从一个输入流解JSON数据,尽管这不是必的。如您所料,有一个针对输出流的json.Encoder编码对象。
們調用Decode方法填充量。里有多方法可以格式化結構。下面是最簡單的一,以一固定度打印每issue但是在下一節我們將看到如果利用模闆來輸出複雜的格式。
们调用Decode方法填充量。里有多方法可以格式化结构。下面是最简单的一,以一固定度打印每issue但是在下一节我们将看到如果利用模板来输出复杂的格式。
<u><i>gopl.io/ch4/issues</i></u>
```Go
@@ -228,7 +228,7 @@ func main() {
}
```
命令行參數指定檢索條件。下面的命令是査詢Go語言項目中和JSON解碼相關的問題,還有査詢返迴的結果:
命令行参数指定检索条件。下面的命令是查询Go语言项目中和JSON解码相关的问题,还有查询返回的结果:
```
$ go build gopl.io/ch4/issues
@@ -249,12 +249,12 @@ $ ./issues repo:golang/go is:open json decoder
#4237 gjemiller encoding/base64: URLEncoding padding is optional
```
GitHub的Web服接口 https://developer.github.com/v3/ 包含了更多的特性。
GitHub的Web服接口 https://developer.github.com/v3/ 包含了更多的特性。
**練習 4.10** 改issues程序據問題的時間進行分,比如不到一月的、不到一年的、超一年。
**练习 4.10** 改issues程序据问题的时间进行分,比如不到一月的、不到一年的、超一年。
**練習 4.11** 編寫一個工具,允許用戶在命令行建、取、更新和關閉GitHub上的issue必要的候自動打開用戶默認的編輯器用於輸入文本信息。
**练习 4.11** 编写一个工具,允许用户在命令行建、取、更新和关闭GitHub上的issue必要的候自动打开用户默认的编辑器用于输入文本信息。
**練習 4.12** 流行的web漫畵服務xkcd也提供了JSON接口。例如 https://xkcd.com/571/info.0.json 請求將返迴一個很多人喜的571編號的詳細描述。下載每個鏈接(隻下載一次)然後創建一個離線索引。編寫一個xkcd工具使用這些離線索引,打印和命令行入的檢索詞相匹配的漫的URL。
**练习 4.12** 流行的web漫画服务xkcd也提供了JSON接口。例如 https://xkcd.com/571/info.0.json 请求将返回一个很多人喜的571编号的详细描述。下载每个链接(只下载一次)然后创建一个离线索引。编写一个xkcd工具使用这些离线索引,打印和命令行入的检索词相匹配的漫的URL。
**練習 4.13** 使用開放電影數據庫的JSON服接口,允許你檢索和下 https://omdbapi.com/ 上影的名字和對應的海報圖像。編寫一個poster工具命令行入的影名字,下載對應的海
**练习 4.13** 使用开放电影数据库的JSON服接口,允许你检索和下 https://omdbapi.com/ 上影的名字和对应的海报图像。编写一个poster工具命令行入的影名字,下载对应的海

View File

@@ -1,8 +1,8 @@
## 4.6. 文本和HTML模
## 4.6. 文本和HTML模
前面的例子,是最簡單的格式化使用Printf是完全足的。但是有時候會需要複雜的打印格式,這時候一般需要格式化代碼分離出來以便更安全地改。這寫功能是由text/template和html/template等模包提供的,它提供了一個將變量值填充到一文本或HTML格式的模闆的機製
前面的例子,是最简单的格式化使用Printf是完全足的。但是有时候会需要复杂的打印格式,这时候一般需要格式化代码分离出来以便更安全地改。这写功能是由text/template和html/template等模包提供的,它提供了一个将变量值填充到一文本或HTML格式的模板的机制
個模闆是一字符串或一文件,里面包含了一或多個由雙花括包含的`{{action}}`象。大部分的字符串是按面值打印,但是對於actions部分將觸發其它的行。每actions都包含了一用模闆語言書寫的表式,一action雖然簡短但是可以輸出複雜的打印值,模闆語言包含通過選擇結構體的成員、調用函或方法、表式控流if-else句和range循環語句,有其它例化模闆等諸多特性。下面是一個簡單的模字符串:
个模板是一字符串或一文件,里面包含了一或多个由双花括包含的`{{action}}`象。大部分的字符串是按面值打印,但是对于actions部分将触发其它的行。每actions都包含了一用模板语言书写的表式,一action虽然简短但是可以输出复杂的打印值,模板语言包含通过选择结构体的成员、调用函或方法、表式控流if-else句和range循环语句,有其它例化模板等诸多特性。下面是一个简单的模字符串:
{% raw %}
@@ -21,11 +21,11 @@ Age: {{.CreatedAt | daysAgo}} days
{% raw %}
這個模闆先打印匹配到的issue總數,然打印每issue的編號、創建用戶、標題還有存在的時間。對於每一action都有一個當前值的概念,對應點操作符,作“.”。前值“.”最初被初始化爲調用模是的參數,在前例子中對應github.IssuesSearchResult型的量。模`{{.TotalCount}}`對應action將展開爲結構體中TotalCount成以默的方式打印的值。模`{{range .Items}}``{{end}}`對應一個循環action因此它直接的容可能被展多次,循每次迭代的前值對應當前的Items元素的值。
这个模板先打印匹配到的issue总数,然打印每issue的编号、创建用户、标题还有存在的时间。对于每一action都有一个当前值的概念,对应点操作符,作“.”。前值“.”最初被初始化为调用模是的参数,在前例子中对应github.IssuesSearchResult型的量。模`{{.TotalCount}}`对应action将展开为结构体中TotalCount成以默的方式打印的值。模`{{range .Items}}``{{end}}`对应一个循环action因此它直接的容可能被展多次,循每次迭代的前值对应当前的Items元素的值。
{% endraw %}
在一action中`|`操作符表示前一個表達式的果作爲後一個函數的輸入,類似於UNIX中管道的概念。在Title一行的action中第二操作是一printf函,是一個基於fmt.Sprintf實現的內置函,所有模都可以直接使用。對於Age部分第二個動作是一叫daysAgo的函,通time.Since函數將CreatedAt成員轉換爲過去的時間長度:
在一action中`|`操作符表示前一个表达式的果作为后一个函数的输入,类似于UNIX中管道的概念。在Title一行的action中第二操作是一printf函,是一个基于fmt.Sprintf实现的内置函,所有模都可以直接使用。对于Age部分第二个动作是一叫daysAgo的函,通time.Since函数将CreatedAt成员转换为过去的时间长度:
```Go
func daysAgo(t time.Time) int {
@@ -33,9 +33,9 @@ func daysAgo(t time.Time) int {
}
```
需要意的是CreatedAt的參數類型是time.Time不是字符串。以同的方式,我可以通過定義一些方法來控製字符串的格式化§2.5),一個類型同可以定自己的JSON編碼和解碼行爲。time.Time類型對應的JSON值是一個標準時間格式的字符串。
需要意的是CreatedAt的参数类型是time.Time不是字符串。以同的方式,我可以通过定义一些方法来控制字符串的格式化§2.5),一个类型同可以定自己的JSON编码和解码行为。time.Time类型对应的JSON值是一个标准时间格式的字符串。
生成模闆的輸出需要兩個處理步。第一步是要分析模闆併轉爲內部表示,然後基於指定的輸入執行模。分析模部分一般需要行一次。下面的代碼創建併分析上面定的模templ。意方法調用鏈的順template.New先創建併返迴一個模闆Funcs方法daysAgo等自定義函數註冊到模中,併返迴模闆;最後調用Parse函分析模
生成模板的输出需要两个处理步。第一步是要分析模板并转为内部表示,然后基于指定的输入执行模。分析模部分一般需要行一次。下面的代码创建并分析上面定的模templ。意方法调用链的顺template.New先创建并返回一个模板Funcs方法daysAgo等自定义函数注册到模中,并返回模板;最后调用Parse函分析模
```Go
report, err := template.New("report").
@@ -46,9 +46,9 @@ if err != nil {
}
```
爲模闆通常在編譯時就測試好了,如果模解析失敗將是一致命的錯誤。template.Must助函可以簡化這個致命錯誤的處理:它接受一個模闆和一error型的參數,檢測error是否nil如果不是nil則發出panic常),然後返迴傳入的模。我們將在5.9節再討論這個話題
为模板通常在编译时就测试好了,如果模解析失败将是一致命的错误。template.Must助函可以简化这个致命错误的处理:它接受一个模板和一error型的参数,检测error是否nil如果不是nil则发出panic常),然后返回传入的模。我们将在5.9节再讨论这个话题
一旦模闆已經創建、註冊了daysAgo函數、併通過分析和檢測,我就可以使用github.IssuesSearchResult作爲輸入源、os.Stdout作爲輸出源來執行模
一旦模板已经创建、注册了daysAgo函数、并通过分析和检测,我就可以使用github.IssuesSearchResult作为输入源、os.Stdout作为输出源来执行模
```Go
var report = template.Must(template.New("issuelist").
@@ -66,7 +66,7 @@ func main() {
}
```
程序出一個純文本告:
程序出一个纯文本告:
```
$ go build gopl.io/ch4/issuesreport
@@ -86,9 +86,9 @@ Age: 695 days
...
```
現在讓我們轉到html/template模包。它使用和text/template包相同的API和模闆語言,但是增加了一個將字符串自動轉義特性,可以避免入字符串和HTML、JavaScript、CSS或URL語法産生衝突的問題。這個特性可以避免一些期存在的安全問題,比如通生成HTML入攻,通過構造一含有意代碼的問題標題,這些都可能讓模闆輸出錯誤的輸出,從而讓他們控製頁面。
现在让我们转到html/template模包。它使用和text/template包相同的API和模板语言,但是增加了一个将字符串自动转义特性,可以避免入字符串和HTML、JavaScript、CSS或URL语法产生冲突的问题。这个特性可以避免一些期存在的安全问题,比如通生成HTML入攻,通过构造一含有意代码的问题标题,这些都可能让模板输出错误的输出,从而让他们控制页面。
下面的模以HTML格式出issue列表。意import句的不同:
下面的模以HTML格式出issue列表。意import句的不同:
{% raw %}
@@ -119,26 +119,26 @@ var issueList = template.Must(template.New("issuelist").Parse(`
{% endraw %}
下面的命令在新的模闆上執行一稍微不同的査詢
下面的命令在新的模板上执行一稍微不同的查询
```
$ go build gopl.io/ch4/issueshtml
$ ./issueshtml repo:golang/go commenter:gopherbot json encoder >issues.html
```
4.4示了在web瀏覽器中的效果。每issue包含到Github對應頁面的接。
4.4示了在web浏览器中的效果。每issue包含到Github对应页面的接。
![](../images/ch4-04.png)
4.4中issue有包含會對HTML格式産生衝突的特殊字符,但是我們馬上將看到標題中含有`&``<`字符的issue。下面的命令選擇了兩個這樣的issue
4.4中issue有包含会对HTML格式产生冲突的特殊字符,但是我们马上将看到标题中含有`&``<`字符的issue。下面的命令选择了两个这样的issue
```
$ ./issueshtml repo:golang/go 3133 10535 >issues2.html
```
4.5示了該査詢的結果。html/template包已經自動將特殊字符轉義,因此我依然可以看到正的字面值。如果我使用text/template包的這2個issue將會産生錯誤,其中“&amp;lt;”四字符將會被當作小字符“<理,同&lt;link&gt;”字符串將會被當作一個鏈接元素理,它們都會導致HTML文檔結構的改變,從而導致有未知的風險
4.5示了该查询的结果。html/template包已经自动将特殊字符转义,因此我依然可以看到正的字面值。如果我使用text/template包的这2个issue将会产生错误,其中“&amp;lt;”四字符将会被当作小字符“<理,同&lt;link&gt;”字符串将会被当作一个链接元素理,它们都会导致HTML文档结构的改变,从而导致有未知的风险
也可以通過對信任的HTML字符串使用template.HTML類型來抑製這種自動轉義的行爲。還有很多采用型命名的字符串型分别對應信任的JavaScript、CSS和URL。下面的程序演示了兩個使用不同型的相同字符串生的不同A是一普通字符串B是一信任的template.HTML字符串型。
也可以通过对信任的HTML字符串使用template.HTML类型来抑制这种自动转义的行为。还有很多采用型命名的字符串型分别对应信任的JavaScript、CSS和URL。下面的程序演示了两个使用不同型的相同字符串生的不同A是一普通字符串B是一信任的template.HTML字符串型。
![](../images/ch4-05.png)
@@ -163,15 +163,15 @@ func main() {
{% endraw %}
4.6示了出現在瀏覽器中的模闆輸出。我看到A的黑體標記被轉義失效了但是B有。
4.6示了出现在浏览器中的模板输出。我看到A的黑体标记被转义失效了但是B有。
![](../images/ch4-06.png)
們這里隻講述了模闆繫統中最基本的特性。一如往,如果想了解更多的信息,自己看包文
们这里只讲述了模板系统中最基本的特性。一如往,如果想了解更多的信息,自己看包文
```
$ go doc text/template
$ go doc html/template
```
**練習 4.14** 建一web服器,査詢一次GitHub生成BUG告、里程碑和對應的用信息。
**练习 4.14** 建一web服器,查询一次GitHub生成BUG告、里程碑和对应的用信息。

View File

@@ -1,6 +1,6 @@
# 第四章 複合數據類
# 第四章 复合数据类
在第三章我們討論了基本數據類型,它可以用於構建程序中數據結構是Go言的世界的原子。在本章,我們將討論複合數據類型,它是以不同的方式合基本型可以造出來的複合數據類型。我主要討論四種類型——數組、slice、map和結構體——同在本章的最,我們將演示如何使用結構體來解碼和編碼到對應JSON格式的數據併且通過結合使用模闆來生成HTML面。
在第三章我们讨论了基本数据类型,它可以用于构建程序中数据结构是Go言的世界的原子。在本章,我们将讨论复合数据类型,它是以不同的方式合基本型可以造出来的复合数据类型。我主要讨论四种类型——数组、slice、map和结构体——同在本章的最,我们将演示如何使用结构体来解码和编码到对应JSON格式的数据并且通过结合使用模板来生成HTML面。
數組和結構體是聚合型;它的值由多元素或成字段的值成。數組是由同的元素成——每個數組元素都是完全相同的型——結構體則是由異構的元素成的。數組和結構體都是有固定存大小的數據結構。相比之下slice和map則是動態的數據結構,它們將根據需要動態增長
数组和结构体是聚合型;它的值由多元素或成字段的值成。数组是由同的元素成——每个数组元素都是完全相同的型——结构体则是由异构的元素成的。数组和结构体都是有固定存大小的数据结构。相比之下slice和map则是动态的数据结构,它们将根据需要动态增长