* 审校10.1

This commit is contained in:
leisore
2015-06-10 16:26:49 +08:00
parent efc961c20e
commit fe5ea4c8da

View File

@@ -10,11 +10,11 @@ type identifier struct {
} }
``` ```
` type T struct {a, b int}`也是合法的语法,它更适用于简单的结构体。 `type T struct {a, b int}`也是合法的语法,它更适用于简单的结构体。
这个结构体里的成员都有名字(names)像field1field2等如果成员在代码从来也不会被用到那么可以命名为*_*。 这个结构体里的成员都有*名字*像field1field2等如果成员在代码从来也不会被用到,那么可以命名为*_*。
成员可以是任何类型,甚至是结构体本身(参考[10.5](10.5.md)可以是函数或者接口参考第11章。可以定义结构体类型的一个变量,然后给它的成员像下面这样赋值: 结构体的成员可以是任何类型,甚至是结构体本身(参考[10.5](10.5.md)可以是函数或者接口参考第11章。可以声明结构体类型的一个变量,然后给它的成员像下面这样赋值:
```go ```go
var s T var s T
@@ -22,7 +22,7 @@ s.a = 5
s.b = 8 s.b = 8
``` ```
数组可以看作是一种结构体类型,不过它使用下标而不是名的成员。 数组可以看作是一种结构体类型,不过它使用下标而不是名的成员。
**使用new** **使用new**
@@ -33,9 +33,9 @@ var t *T
t = new(T) t = new(T)
``` ```
写这条语句的惯用法是:`t := new(T)`,变量*t*是一个指向`T`的指针,此时结构体成员的值是它们所属类型的零值。 写这条语句的惯用法是:`t := new(T)`,变量`t`是一个指向`T`的指针,此时结构体成员的值是它们所属类型的零值。
声明`var t T`也会给`t`分配内存,并零值化内存,但是这个时候t是类型T。在这两种方式中t通常被称做类型T的一个实例(instance)或对象(Object)。 声明`var t T`也会给`t`分配内存,并零值化内存,但是这个时候`t`是类型T。在这两种方式中`t`通常被称做类型T的一个实例(instance)或对象(Object)。
[Listing 10.1—structs_fields.go](examples/chapter_10/structs_fields.go)给出了一个非常简单的例子: [Listing 10.1—structs_fields.go](examples/chapter_10/structs_fields.go)给出了一个非常简单的例子:
@@ -71,17 +71,17 @@ func main {
使用fmt.Println打印一个结构体的默认输出可以很好的显示它的内容类似使用*%v*选项。 使用fmt.Println打印一个结构体的默认输出可以很好的显示它的内容类似使用*%v*选项。
可以通过逗号符给成员赋不同的值,就像在面向对象语言中那样` structname.fieldname = value ` 就像在面向对象语言所作的那样,可以使用逗号符给成员赋值` structname.fieldname = value `
使用同样的逗号符可以获取结构体成员的值:` structname.fieldname ` 同样的,使用逗号符可以获取结构体成员的值:` structname.fieldname `
在Go中这叫*选择器(selector)*。无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的选择器符(selector-notation)来引用结构体的成员: 在Go语言中这叫*选择器(selector)*。无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的*选择器符(selector-notation)*来引用结构体的成员:
```go ```go
type myStruct struct { i int } type myStruct struct { i int }
var v myStruct // v has struct type var v myStruct // v是结构体类型变量
var p *myStruct // p is a pointer to a struct var p *myStruct // p是指向一个结构体类型变量的指针
v.i v.i
p.i p.i
``` ```
@@ -99,9 +99,9 @@ p.i
ms := struct1{10, 15.5, "Chris"} ms := struct1{10, 15.5, "Chris"}
``` ```
混合字面量语法(composite literal syntax) ` &struct1{a, b, c}` 是一种简写,底层仍然会调用` new ()`,这里值的顺序必须按照成员顺序来写。在下面的例子中能看到可以通过在值的前面放上成员名来初始化。表达式` new(Type)`` &Type{}`是等价的。 混合字面量语法(composite literal syntax)`&struct1{a, b, c}`是一种简写,底层仍然会调用`new ()`,这里值的顺序必须按照成员顺序来写。在下面的例子中能看到可以通过在值的前面放上成员名来初始化成员的方式。表达式`new(Type)``&Type{}`是等价的。
结构体的典型例子是一个时间间隔(开始和结束时间以秒为单位) 时间间隔(开始和结束时间以秒为单位)是使用结构体的一个典型例子
```go ```go
type Interval struct { type Interval struct {
@@ -118,7 +118,7 @@ intr := Interval(end:5, start:1) (B)
intr := Interval(end:5) (C) intr := Interval(end:5) (C)
``` ```
A中值必须以成员在结构体定义时的顺序给出*&*不是必须的。B显示了另一种方式成员名加一个冒号在值的前面这种情况下值的顺序不必一致并且某些成员还可以被忽略掉就像C那样。 A值必须以成员在结构体定义时的顺序给出,*&*不是必须的。B显示了另一种方式成员名加一个冒号在值的前面这种情况下值的顺序不必一致并且某些成员还可以被忽略掉就像C那样。
结构体类型和成员的命名遵循可见性规则([4.2](4.2.md),一个导出的结构体类型中有些成员是导出的,另一些不是,这是可能的。 结构体类型和成员的命名遵循可见性规则([4.2](4.2.md),一个导出的结构体类型中有些成员是导出的,另一些不是,这是可能的。
@@ -138,7 +138,7 @@ type Point struct { x, y int }
类型strcut1在定义它的包pack1中必须是唯一的它的完全类型名是`pack1.struct1` 类型strcut1在定义它的包pack1中必须是唯一的它的完全类型名是`pack1.struct1`
下面的例子[Listing 10.2—person.go](examples/person.go)显示了一个结构体Person一个方法方法有一个类型为*Person的参数因此对象本身是可以被改变的以及三种不同的调用这个方法的方式: 下面的例子[Listing 10.2—person.go](examples/person.go)显示了一个结构体Person一个方法方法有一个类型为*Person的参数因此对象本身是可以被改变的以及三种调用这个方法的不同方式:
```go ```go
package main package main
@@ -180,25 +180,19 @@ func main() {
} }
``` ```
第2种情况
输出: 输出:
The name of the person is CHRIS WOODWARD The name of the person is CHRIS WOODWARD
The name of the person is CHRIS WOODWARD The name of the person is CHRIS WOODWARD
The name of the person is CHRIS WOODWARD The name of the person is CHRIS WOODWARD
在上面例子的第二种情况中,`pers2.lastName="Woodward"`这样,可以直接通过指针,像`pers2.lastName="Woodward"`这样给结构体成员赋值没有像C++那样需要使用`->`操作符Go会自动做这样的转换。 在上面例子的第二种情况中,可以直接通过指针,像`pers2.lastName="Woodward"`这样给结构体成员赋值没有像C++那样需要使用`->`操作符Go会自动做这样的转换。
注意也可以通过解指针的方式来设置值: 注意也可以通过解指针的方式来设置值:`(*pers2).lastName = "Woodward"`
```go **结构体的内存布局**
(*pers2).lastName = "Woodward"
```
**结构体和内存布局** Go语言中结构体和它所包含的数据在内存中是以连续块的形式存在的即使结构体中嵌套有其他的结构体这在性能上带来了很大的优势。不像Java中的引用类型一个对象和它里面包含的对象可能会在不同的内存空间中这点和Go语言中的指针很像。下面的例子清晰地说明了这些情况
Go语言中的结构体和它所包含的数据在内存中是以连续块的形式存在的即使结构体中嵌套有其他的结构体这带来了很大的性能优势。不像Java中的引用类型一个对象和它里面包含的对象可能会在不同的内存空间中Go中的指针和这个很像。下面的例子清晰的说明了这些情况
```go ```go
type Rect1 struct {Min, Max Point } type Rect1 struct {Min, Max Point }
@@ -209,26 +203,26 @@ type Rect2 struct {Min, Max *Point }
**递归结构体** **递归结构体**
结构体类型可以通过自身来定义。在结构体变量是链表或二叉树的元素(通常叫节点)时,这种方法会特别有用,此时节点包含指向临近节点的链接(地址)。如下所示,链表中的`su`,树中的`ri`h`le`分别是指向另一个节点变量的指针。 结构体类型可以通过引用自身来定义。这在定义链表或二叉树的元素(通常叫节点)时特别有用,此时节点包含指向临近节点的链接(地址)。如下所示,链表中的`su`,树中的`ri``le`分别是指向别的节点的指针。
链表: 链表:
![](images/10.1_fig10.3.jpg?raw=true) ![](images/10.1_fig10.3.jpg?raw=true)
这块的data用于存放有数据比如float64su指针指向后继节点。 这块的`data`成员用于存放有数据比如float64`su`指针指向后继节点。
Go代码 Go代码
```go ```go
type Node struct { type Node struct {
data float64 data float64
su *Node su *Node
} }
``` ```
链表中的第一个元素叫`head`,它指向第二个元素;最后一个元素叫`tail`,它没有后继元素,所以它的su为nil值。当然真实的链接会有很多数据节点并且链表可以动态增长或收缩。 链表中的第一个元素叫`head`,它指向第二个元素;最后一个元素叫`tail`,它没有后继元素,所以它的`su`为nil值。当然真实的链接会有很多数据节点并且链表可以动态增长或收缩。
同样地可以定义一个双向链表,它有一个前趋节点pr和一个后继节点su 同样地可以定义一个双向链表,它有一个前趋节点`pr`和一个后继节点`su`
```go ```go
type Node struct { type Node struct {
@@ -242,7 +236,7 @@ type Node struct {
![](images/10.1_fig10.4.jpg?raw=true) ![](images/10.1_fig10.4.jpg?raw=true)
二叉树中每个节点最多能链接至两个节点:左节点(le)和右节点(ri),这两个节点本身又可以有左右节点,依次类推。树的顶层节点叫根节点(`root`),底层没有子节点的节点叫叶子节点(`leaves`),叶子节点的le和ri指针为空值。在Go中可以如下定义树 二叉树中每个节点最多能链接至两个节点:左节点(le)和右节点(ri),这两个节点本身又可以有左右节点,依次类推。树的顶层节点叫根节点(*root*),底层没有子节点的节点叫叶子节点(*leaves*),叶子节点的`le``ri`指针为nil值。在Go中可以如下定义二叉树:
```go ```go
type Tree strcut { type Tree strcut {
@@ -254,7 +248,9 @@ type Tree strcut {
**结构体转换** **结构体转换**
Go中的类型转换遵循严格的规则。当为结构体定义了一个alias类型时此结构体类型和它的alias类型都有相同的底层类型它们可以如[Listing 10.3]那样互相转换,同时需要注意其中非法赋值或转换引起的编译错误: Go中的类型转换遵循严格的规则。当为结构体定义了一个alias类型时此结构体类型和它的alias类型都有相同的底层类型它们可以如[Listing 10.3]那样互相转换,同时需要注意其中非法赋值或转换引起的编译错误:
Listing 10.3
```go ```go
package main package main
@@ -286,24 +282,19 @@ func main() {
**练习** **练习**
*练习 10.1* *练习 10.1* vcard.go定义结构体Address和VCard后者包含一个人的名字、地址编号、出生日期和图像试着选择正确的数据类型。构建一个自己的vcard并打印它的内容。
vcard.go定义结构体Address和VCard后者包含一个人的名字、地址编号、出生日期和图像试着选择正确的数据类型。构建一个自己的vcard并打印它的内容。
提示:
VCard必须包含住址它应该以值类型还是以指针类型放在VCard中呢
第二种会好点因为它占用内存少。包含一个名字和两个指向地址的指针的Address结构体可以使用%v打印
提示:
VCard必须包含住址它应该以值类型还是以指针类型放在VCard中呢
第二种会好点因为它占用内存少。包含一个名字和两个指向地址的指针的Address结构体可以使用%v打印
{Kersschot 0x126d2b80 0x126d2be0} {Kersschot 0x126d2b80 0x126d2be0}
*练习 10.2* 修改*persionext1.go*使它的参数upPerson不是一个指针解释下二者的区别。 *练习 10.2* 修改*persionext1.go*使它的参数upPerson不是一个指针解释下二者的区别。
*练习 10.3* point.go使用坐标X、Y定义一个二维Point结构体。同样地对一个三维点使用它的极坐标定义一个Polar结构体。实现一个Abs()方法来计算一个Point表示的向量的长度实现一个Scale方法它将点的坐标乘以一个尺度因子提示使用math包里的Sqrt函数 function Scale that multiplies the coordinates of a point with a scale *练习 10.3* point.go使用坐标X、Y定义一个二维Point结构体。同样地对一个三维点使用它的极坐标定义一个Polar结构体。实现一个Abs()方法来计算一个Point表示的向量的长度实现一个Scale方法它将点的坐标乘以一个尺度因子提示使用math包里的Sqrt函数 function Scale that multiplies the coordinates of a point with a scale
factor factor
*练习 10.3* rectangle.go定义一个Rectangle结构体它的长和宽是int类型并定义方法Area()和Primeter()测试 *练习 10.3* rectangle.go定义一个Rectangle结构体它的长和宽是int类型并定义方法Area()和Primeter(),然后进行测试
## 链接 ## 链接
- [目录](directory.md) - [目录](directory.md)