第4章,部分字词修订。

This commit is contained in:
zhliner 2017-08-24 22:27:15 +08:00
parent 725acf091c
commit 9c0723ff6a
9 changed files with 19 additions and 19 deletions

View File

@ -1,6 +1,6 @@
## 4.1. 数组 ## 4.1. 数组
数组是一个由固定长度的特定类型元素组成的序列一个数组可以由零个或多个元素组成。因为数组的长度是固定的因此在Go语言中很少直接使用数组。和数组对应的类型是Slice切片它是可以增长和收缩动态序列slice功能也更灵活但是要理解slice工作原理的话需要先理解数组。 数组是一个由固定长度的特定类型元素组成的序列一个数组可以由零个或多个元素组成。因为数组的长度是固定的因此在Go语言中很少直接使用数组。和数组对应的类型是Slice切片它是可以增长和收缩动态序列slice功能也更灵活但是要理解slice工作原理的话需要先理解数组。
数组的每个元素可以通过索引下标来访问索引下标的范围是从0开始到数组长度减1的位置。内置的len函数将返回数组中元素的个数。 数组的每个元素可以通过索引下标来访问索引下标的范围是从0开始到数组长度减1的位置。内置的len函数将返回数组中元素的个数。

View File

@ -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]" 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 ```Go
func appendInt(x []int, y ...int) []int { func appendInt(x []int, y ...int) []int {

View File

@ -77,7 +77,7 @@ reverse(s)
fmt.Println(s) // "[2 3 4 5 0 1]" 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我们必须自己展开每个元素进行比较

View File

@ -115,7 +115,7 @@ 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 ```Go
age, ok := ages["bob"] age, ok := ages["bob"]
@ -192,7 +192,7 @@ 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> <u><i>gopl.io/ch4/charcount</i></u>
```Go ```Go

View File

@ -16,7 +16,7 @@ p := Point{1, 2}
anim := gif.GIF{LoopCount: nframes} anim := gif.GIF{LoopCount: nframes}
``` ```
在这种形式的结构体字面值写法中,如果成员被忽略的话将默认用零值。因为,提供了成员的名字,所有成员出现的顺序并不重要。 在这种形式的结构体字面值写法中,如果成员被忽略的话将默认用零值。因为提供了成员的名字,所以成员出现的顺序并不重要。
两种不同形式的写法不能混合使用。而且,你不能企图在外部包中用第一种顺序赋值的技巧来偷偷地初始化结构体中未导出的成员。 两种不同形式的写法不能混合使用。而且,你不能企图在外部包中用第一种顺序赋值的技巧来偷偷地初始化结构体中未导出的成员。
@ -64,7 +64,7 @@ func AwardAnnualRaise(e *Employee) {
pp := &Point{1, 2} pp := &Point{1, 2}
``` ```
下面的语句是等价的 下面的语句是等价的
```Go ```Go
pp := new(Point) pp := new(Point)

View File

@ -66,7 +66,7 @@ type Wheel struct {
} }
``` ```
于匿名嵌入的特性,我们可以直接访问叶子属性而不需要给出完整的路径: 于匿名嵌入的特性,我们可以直接访问叶子属性而不需要给出完整的路径:
```Go ```Go
var w Wheel var w Wheel
@ -112,14 +112,14 @@ fmt.Printf("%#v\n", w)
需要注意的是Printf函数中%v参数包含的#副词它表示用和Go语言类似的语法打印值。对于结构体类型来说将包含每个成员的名字。 需要注意的是Printf函数中%v参数包含的#副词它表示用和Go语言类似的语法打印值。对于结构体类型来说将包含每个成员的名字。
因为匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致名字冲突。同时,因为成员的名字是由其类型隐式地决定的,所匿名成员也有可见性的规则约束。在上面的例子中Point和Circle匿名成员都是导出的。即使它们不导出比如改成小写字母开头的point和circle我们依然可以用简短形式访问匿名成员嵌套的成员 因为匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致名字冲突。同时,因为成员的名字是由其类型隐式地决定的,所匿名成员也有可见性的规则约束。在上面的例子中Point和Circle匿名成员都是导出的。即使它们不导出比如改成小写字母开头的point和circle我们依然可以用简短形式访问匿名成员嵌套的成员
```Go ```Go
w.X = 8 // equivalent to w.circle.point.X = 8 w.X = 8 // equivalent to w.circle.point.X = 8
``` ```
但是在包外部因为circle和point没有导出不能访问它们的成员因此简短的匿名成员访问语法也是禁止的。 但是在包外部因为circle和point没有导出不能访问它们的成员,因此简短的匿名成员访问语法也是禁止的。
到目前为止,我们看到匿名成员特性只是对访问嵌套成员的点运算符提供了简短的语法糖。稍后,我们将会看到匿名成员并不要求是结构体类型;其实任何命名的类型都可以作为结构体的匿名成员。但是为什么要嵌入一个没有任何子成员类型的匿名成员类型呢? 到目前为止,我们看到匿名成员特性只是对访问嵌套成员的点运算符提供了简短的语法糖。稍后,我们将会看到匿名成员并不要求是结构体类型;其实任何命名的类型都可以作为结构体的匿名成员。但是为什么要嵌入一个没有任何子成员类型的匿名成员类型呢?
答案是匿名类型的方法集。简短的点运算符语法可以用于选择匿名成员嵌套的成员,也可以用于访问它们的方法。实际上,外层的结构体不仅仅是获得了匿名成员类型的所有成员,而且也获得了该类型导出的全部的方法。这个机制可以用于将一有简单行为的对象组合成有复杂行为的对象。组合是Go语言中面向对象编程的核心我们将在6.3节中专门讨论。 答案是匿名类型的方法集。简短的点运算符语法可以用于选择匿名成员嵌套的成员,也可以用于访问它们的方法。实际上,外层的结构体不仅仅是获得了匿名成员类型的所有成员,而且也获得了该类型导出的全部的方法。这个机制可以用于将一有简单行为的对象组合成有复杂行为的对象。组合是Go语言中面向对象编程的核心我们将在6.3节中专门讨论。

View File

@ -1,6 +1,6 @@
## 4.4. 结构体 ## 4.4. 结构体
结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。用结构体的经典案例处理公司的员工信息,每个员工信息包含一个唯一的员工编号、员工的名字、家庭住址、出生日期、工作岗位、薪资、上级领导等等。所有的这些信息都需要绑定到一个实体中,可以作为一个整体单元被复制,作为函数的参数或返回值,或者是被存储到数组中,等等。 结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。用结构体的经典案例处理公司的员工信息,每个员工信息包含一个唯一的员工编号、员工的名字、家庭住址、出生日期、工作岗位、薪资、上级领导等等。所有的这些信息都需要绑定到一个实体中,可以作为一个整体单元被复制,作为函数的参数或返回值,或者是被存储到数组中,等等。
下面两个语句声明了一个叫Employee的命名的结构体类型并且声明了一个Employee类型的变量dilbert 下面两个语句声明了一个叫Employee的命名的结构体类型并且声明了一个Employee类型的变量dilbert
@ -76,7 +76,7 @@ type Employee struct {
结构体类型往往是冗长的因为它的每个成员可能都会占一行。虽然我们每次都可以重写整个结构体成员但是重复会令人厌烦。因此完整的结构体写法通常只在类型声明语句的地方出现就像Employee类型声明语句那样。 结构体类型往往是冗长的因为它的每个成员可能都会占一行。虽然我们每次都可以重写整个结构体成员但是重复会令人厌烦。因此完整的结构体写法通常只在类型声明语句的地方出现就像Employee类型声明语句那样。
一个命名为S的结构体类型将不能再包含S类型的成员因为一个聚合的值不能包含它自身。该限制同样适于数组。但是S类型的结构体可以包含`*S`指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。在下面的代码中,我们使用一个二叉树来实现一个插入排序: 一个命名为S的结构体类型将不能再包含S类型的成员因为一个聚合的值不能包含它自身。该限制同样适于数组。但是S类型的结构体可以包含`*S`指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。在下面的代码中,我们使用一个二叉树来实现一个插入排序:
<u><i>gopl.io/ch4/treesort</i></u> <u><i>gopl.io/ch4/treesort</i></u>
```Go ```Go

View File

@ -8,7 +8,7 @@ JSON是对JavaScript中各种类型的值——字符串、数字、布尔值和
基本的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语言的map类型key类型是字符串和结构体。例如 这些基础类型可以通过JSON的数组和对象类型进行递归组合。一个JSON数组是一个有序的值序列写在一个方括号中并以逗号分隔一个JSON数组可以用于编码Go语言的数组和slice。一个JSON对象是一个字符串到值的映射写成系列的name:value对形式用花括号包含并以逗号分隔JSON的对象类型可以用于编码Go语言的map类型key类型是字符串和结构体。例如
``` ```
boolean true boolean true
@ -42,7 +42,7 @@ var movies = []Movie{
} }
``` ```
这样的数据结构特别适合JSON格式并且在两之间相互转换也很容易。将一个Go语言中类似movies的结构体slice转为JSON的过程叫编组marshaling。编组通过调用json.Marshal函数完成 这样的数据结构特别适合JSON格式并且在两之间相互转换也很容易。将一个Go语言中类似movies的结构体slice转为JSON的过程叫编组marshaling。编组通过调用json.Marshal函数完成
```Go ```Go
data, err := json.Marshal(movies) data, err := json.Marshal(movies)
@ -105,16 +105,16 @@ 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"` Year int `json:"released"`
Color bool `json:"color,omitempty"` 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语言中一般叫unmarshaling通过json.Unmarshal函数完成。下面的代码将JSON格式的电影数据解码为一个结构体slice结构体中只有Title成员。通过定义合适的Go语言数据结构我们可以选择性地解码JSON中感兴趣的成员。当Unmarshal函数调用返回slice将被只含有Title信息值填充其它JSON成员将被忽略。 编码的逆操作是解码对应将JSON数据解码为Go语言的数据结构Go语言中一般叫unmarshaling通过json.Unmarshal函数完成。下面的代码将JSON格式的电影数据解码为一个结构体slice结构体中只有Title成员。通过定义合适的Go语言数据结构我们可以选择性地解码JSON中感兴趣的成员。当Unmarshal函数调用返回slice将被只含有Title信息值填充其它JSON成员将被忽略。
```Go ```Go
var titles []struct{ Title string } var titles []struct{ Title string }

View File

@ -1,6 +1,6 @@
# 第四章 复合数据类型 # 第四章 复合数据类型
在第三章我们讨论了基本数据类型它们可以用于构建程序中数据结构是Go语言世界的原子。在本章,我们将讨论复合数据类型,它是以不同的方式组合基本类型可以构造出来的复合数据类型。我们主要讨论四种类型——数组、slice、map和结构体——同时在本章的最后我们将演示如何使用结构体来解码和编码到对应JSON格式的数据并且通过结合使用模板来生成HTML页面。 在第三章我们讨论了基本数据类型,它们可以用于构建程序中数据结构是Go语言世界的原子。在本章我们将讨论复合数据类型它是以不同的方式组合基本类型构造出来的复合数据类型。我们主要讨论四种类型——数组、slice、map和结构体——同时在本章的最后我们将演示如何使用结构体来解码和编码到对应JSON格式的数据并且通过结合使用模板来生成HTML页面。
数组和结构体是聚合类型它们的值由许多元素或成员字段的值组成。数组是由同构的元素组成——每个数组元素都是完全相同的类型——结构体则是由异构的元素组成的。数组和结构体都是有固定内存大小的数据结构。相比之下slice和map则是动态的数据结构它们将根据需要动态增长。 数组和结构体是聚合类型它们的值由许多元素或成员字段的值组成。数组是由同构的元素组成——每个数组元素都是完全相同的类型——结构体则是由异构的元素组成的。数组和结构体都是有固定内存大小的数据结构。相比之下slice和map则是动态的数据结构它们将根据需要动态增长。