Skip to content

Commit

Permalink
第十章修改 (#840)
Browse files Browse the repository at this point in the history
Co-authored-by: Joe Chen <[email protected]>
  • Loading branch information
loftea and unknwon authored May 11, 2022
1 parent 471d59a commit d397979
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 106 deletions.
8 changes: 4 additions & 4 deletions eBook/10.0.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# 10.0 结构struct与方法method
# 10.0 结构 (struct) 与方法 (method)

Go 通过类型别名alias types和结构体的形式支持用户自定义类型,或者叫定制类型。一个带属性的结构体试图表示一个现实世界中的实体。结构体是复合类型composite types,当需要定义一个类型,它由一系列属性组成,每个属性都有自己的类型和值的时候,就应该使用结构体,它把数据聚集在一起。然后可以访问这些数据,就好像它是一个独立实体的一部分。结构体也是值类型,因此可以通过 **new** 函数来创建。
Go 通过类型别名 (alias types) 和结构体的形式支持用户自定义类型,或者叫定制类型。一个带属性的结构体试图表示一个现实世界中的实体。结构体是复合类型 (composite types),当需要定义一个类型,它由一系列属性组成,每个属性都有自己的类型和值的时候,就应该使用结构体,它把数据聚集在一起。然后可以访问这些数据,就好像它是一个独立实体的一部分。结构体也是值类型,因此可以通过 **new** 函数来创建。

组成结构体类型的那些数据称为 **字段fields**。每个字段都有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。
组成结构体类型的那些数据称为 **字段 (fields)**。每个字段都有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。

结构体的概念在软件工程上旧的术语叫 ADT(抽象数据类型:Abstract Data Type),在一些老的编程语言中叫 **记录Record**,比如 Cobol,在 C 家族的编程语言中它也存在,并且名字也是 **struct**,在面向对象的编程语言中,跟一个无方法的轻量级类一样。不过因为 Go 语言中没有类的概念,因此在 Go 中结构体有着更为重要的地位。
结构体的概念在软件工程上旧的术语叫 ADT(抽象数据类型:Abstract Data Type),在一些老的编程语言中叫 **记录 (Record)**,比如 Cobol,在 C 家族的编程语言中它也存在,并且名字也是 **struct**,在面向对象的编程语言中,跟一个无方法的轻量级类一样。不过因为 Go 语言中没有类的概念,因此在 Go 中结构体有着更为重要的地位。

## 链接

Expand Down
55 changes: 27 additions & 28 deletions eBook/10.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ type identifier struct {

`type T struct {a, b int}` 也是合法的语法,它更适用于简单的结构体。

结构体里的字段都有 **名字**,像 field1field2 等,如果字段在代码中从来也不会被用到,那么可以命名它为 **_**
结构体里的字段都有 **名字**,像 `field1``field2` 等,如果字段在代码中从来也不会被用到,那么可以命名它为 `_`

结构体的字段可以是任何类型,甚至是结构体本身(参考第 [10.5](10.5.md) 节),也可以是函数或者接口(参考第 11 章)。可以声明结构体类型的一个变量,然后像下面这样给它的字段赋值:
结构体的字段可以是任何类型,甚至是结构体本身(参考第 [10.5](10.5.md) 节),也可以是函数或者接口(参考[ 11 章](11.0.md))。可以声明结构体类型的一个变量,然后像下面这样给它的字段赋值:

```go
var s T
Expand All @@ -24,18 +24,18 @@ s.b = 8

数组可以看作是一种结构体类型,不过它使用下标而不是具名的字段。

**使用 new**
**使用 `new()`**

使用 **new** 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:`var t *T = new(T)`,如果需要可以把这条语句放在不同的行(比如定义是包范围的,但是分配却没有必要在开始就做)。
使用 `new()` 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:`var t *T = new(T)`,如果需要可以把这条语句放在不同的行(比如定义是包范围的,但是分配却没有必要在开始就做)。

```go
var t *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)

示例 10.1 [structs_fields.go](examples/chapter_10/structs_fields.go) 给出了一个非常简单的例子:

Expand Down Expand Up @@ -69,13 +69,13 @@ func main() {
The string is: Chris
&{10 15.5 Chris}

使用 `fmt.Println` 打印一个结构体的默认输出可以很好的显示它的内容,类似使用 **%v** 选项。
使用 `fmt.Println()` 打印一个结构体的默认输出可以很好的显示它的内容,类似使用 `%v` 选项。

就像在面向对象语言所作的那样,可以使用点号符给字段赋值:`structname.fieldname = value`

同样的,使用点号符可以获取结构体字段的值:`structname.fieldname`

在 Go 语言中这叫 **选择器selector**。无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的 **选择器符selector-notation** 来引用结构体的字段:
在 Go 语言中这叫 **选择器 (selector)**。无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的 **选择器符 (selector-notation)** 来引用结构体的字段:

```go
type myStruct struct { i int }
Expand All @@ -99,7 +99,7 @@ p.i
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{}` 是等价的。

时间间隔(开始和结束时间以秒为单位)是使用结构体的一个典型例子:

Expand All @@ -118,7 +118,7 @@ intr := Interval{end:5, start:1} (B)
intr := Interval{end:5} (C)
```

(A)中,值必须以字段在结构体定义时的顺序给出,**&** 不是必须的。(B)显示了另一种方式,字段名加一个冒号放在值的前面,这种情况下值的顺序不必一致,并且某些字段还可以被忽略掉,就像(C)中那样。
(A) 中,值必须以字段在结构体定义时的顺序给出,`&` 不是必须的。(B) 显示了另一种方式,字段名加一个冒号放在值的前面,这种情况下值的顺序不必一致,并且某些字段还可以被忽略掉,就像 (C) 中那样。

结构体类型和字段的命名遵循可见性规则(第 [4.2](04.2.md) 节),一个导出的结构体类型中有些字段是导出的,另一些不是,这是可能的。

Expand All @@ -128,17 +128,17 @@ intr := Interval{end:5} (C)
type Point struct { x, y int }
```

使用 new 初始化:
使用 `new()` 初始化:

![](images/10.1_fig10.1-1.jpg?raw=true)

作为结构体字面量初始化:

![](images/10.1_fig10.1-2.jpg?raw=true)

类型 struct1 在定义它的包 pack1 中必须是唯一的,它的完全类型名是:`pack1.struct1`
类型 `struct1` 在定义它的包 `pack1` 中必须是唯一的,它的完全类型名是:`pack1.struct1`

下面的例子 [Listing 10.2—person.go](examples/chapter_10/person.go) 显示了一个结构体 Person,一个方法,方法有一个类型为 `*Person` 的参数(因此对象本身是可以被改变的),以及三种调用这个方法的不同方式:
下面的例子 [Listing 10.2—person.go](examples/chapter_10/person.go) 显示了一个结构体 `Person`,一个方法 `upPerson()`,方法有一个类型为 `*Person` 的参数(因此对象本身是可以被改变的),以及三种调用这个方法的不同方式:

```go
package main
Expand Down Expand Up @@ -209,7 +209,7 @@ type Rect2 struct {Min, Max *Point }

![](images/10.1_fig10.3.jpg?raw=true)

这块的 `data` 字段用于存放有效数据(比如 float64),`su` 指针指向后继节点。
这块的 `data` 字段用于存放有效数据(比如 `float64`),`su` 指针指向后继节点。

Go 代码:

Expand All @@ -220,7 +220,7 @@ type Node struct {
}
```

链表中的第一个元素叫 `head`,它指向第二个元素;最后一个元素叫 `tail`,它没有后继元素,所以它的 `su` 为 nil 值。当然真实的链接会有很多数据节点,并且链表可以动态增长或收缩。
链表中的第一个元素叫 `head`,它指向第二个元素;最后一个元素叫 `tail`,它没有后继元素,所以它的 `su``nil` 值。当然真实的链接会有很多数据节点,并且链表可以动态增长或收缩。

同样地可以定义一个双向链表,它有一个前趋节点 `pr` 和一个后继节点 `su`

Expand All @@ -234,9 +234,9 @@ type Node struct {

二叉树:

![](images/10.1_fig10.4.jpg?raw=true)
<img src="images/10.1_fig10.4.jpg?raw=true" style="zoom: 80%;" />

二叉树中每个节点最多能链接至两个节点:左节点(le)和右节点(ri),这两个节点本身又可以有左右节点,依次类推。树的顶层节点叫根节点**root**,底层没有子节点的节点叫叶子节点**leaves**,叶子节点的 `le``ri` 指针为 nil 值。在 Go 中可以如下定义二叉树:
二叉树中每个节点最多能链接至两个节点:左节点 (`le`) 和右节点 (`ri`),这两个节点本身又可以有左右节点,依次类推。树的顶层节点叫根节点 (**root**),底层没有子节点的节点叫叶子节点 (**leaves**),叶子节点的 `le``ri` 指针为 `nil` 值。在 Go 中可以如下定义二叉树:

```go
type Tree struct {
Expand All @@ -248,7 +248,7 @@ type Tree struct {

**结构体转换**

Go 中的类型转换遵循严格的规则。当为结构体定义了一个 alias 类型时,此结构体类型和它的 alias 类型都有相同的底层类型,它们可以如示例 10.3 那样互相转换,同时需要注意其中非法赋值或转换引起的编译错误。
Go 中的类型转换遵循严格的规则。当为结构体定义了一个 `alias` 类型时,此结构体类型和它的 `alias` 类型都有相同的底层类型,它们可以如示例 10.3 那样互相转换,同时需要注意其中非法赋值或转换引起的编译错误。

示例 10.3:

Expand Down Expand Up @@ -278,30 +278,29 @@ func main() {

{5} {5} {5}

**练习 10.1** vcard.go:
**练习 10.1** [vcard.go](exercises\chapter_10\vcard.go)

定义结构体 Address 和 VCard,后者包含一个人的名字、地址编号、出生日期和图像,试着选择正确的数据类型。构建一个自己的 vcard 并打印它的内容。
定义结构体 `Address``VCard`,后者包含一个人的名字、地址编号、出生日期和图像,试着选择正确的数据类型。构建一个自己的 `vcard` 并打印它的内容。

提示:
VCard 必须包含住址,它应该以值类型还是以指针类型放在 VCard 中呢?
第二种会好点,因为它占用内存少。包含一个名字和两个指向地址的指针的 Address 结构体可以使用 %v 打印:
{Kersschot 0x126d2b80 0x126d2be0}

**练习 10.2** personex1.go:
**练习 10.2** [personex1.go](exercises\chapter_10\personex1.go)

修改 personex1.go,使它的参数 upPerson 不是一个指针,解释下二者的区别。
修改 personex1.go,使它的参数 `upPerson` 不是一个指针,解释下二者的区别。

**练习 10.3** point.go:
**练习 10.3** [point.go](exercises\chapter_10\point.go)

使用坐标 X、Y 定义一个二维 Point 结构体。同样地,对一个三维点使用它的极坐标定义一个 Polar 结构体。实现一个 `Abs()` 方法来计算一个 Point 表示的向量的长度,实现一个 `Scale` 方法,它将点的坐标乘以一个尺度因子(提示:使用 `math` 包里的 `Sqrt` 函数)(function Scale that multiplies the coordinates of a point with a scale
factor)。
使用坐标 `X``Y` 定义一个二维 `Point` 结构体。同样地,对一个三维点使用它的极坐标定义一个 `Polar` 结构体。实现一个 `Abs()` 方法来计算一个 `Point` 表示的向量的长度,实现一个 `Scale()` 方法,它将点的坐标乘以一个尺度因子(提示:使用 `math` 包里的 `Sqrt()` 函数)(function Scale that multiplies the coordinates of a point with a scale factor)。

**练习 10.4** rectangle.go:
**练习 10.4** [rectangle.go](exercises\chapter_10\rectangle.go)

定义一个 Rectangle 结构体,它的长和宽是 int 类型,并定义方法 `Area()``Perimeter()`,然后进行测试。
定义一个 `Rectangle` 结构体,它的长和宽是 `int` 类型,并定义方法 `Area()``Perimeter()`,然后进行测试。

## 链接

- [目录](directory.md)
- 上一节:[结构struct与方法method](10.0.md)
- 上一节:[结构 (struct) 与方法 (method)](10.0.md)
- 下一节:[使用工厂方法创建结构体](10.2.md)
12 changes: 6 additions & 6 deletions eBook/10.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 10.2.1 结构体工厂

Go 语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在 Go 中实现 “构造子工厂”方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new 或 New 开头。假设定义了如下的 File 结构体类型:
Go 语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在 Go 中实现 “构造子工厂”方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 `new...``New...` 开头。假设定义了如下的 `File` 结构体类型:

```go
type File struct {
Expand Down Expand Up @@ -37,11 +37,11 @@ f := NewFile(10, "./test.txt")

我们可以说是工厂实例化了类型的一个对象,就像在基于类的 OO 语言中那样。

如果想知道结构体类型 T 的一个实例占用了多少内存,可以使用:`size := unsafe.Sizeof(T{})`
如果想知道结构体类型 `T` 的一个实例占用了多少内存,可以使用:`size := unsafe.Sizeof(T{})`

**如何强制使用工厂方法**

通过应用可见性规则参考[4.2.1节](04.2.md)[9.5 节](09.5.md)就可以禁止使用 new 函数,强制用户使用工厂方法,从而使类型变成私有的,就像在面向对象语言中那样。
通过应用可见性规则参考 [4.2.1节](04.2.md)[9.5 节](09.5.md) 就可以禁止使用 `new()` 函数,强制用户使用工厂方法,从而使类型变成私有的,就像在面向对象语言中那样。

```go
type matrix struct {
Expand All @@ -66,13 +66,13 @@ right := matrix.NewMatrix(...) // 实例化 matrix 的唯一方式

## 10.2.2 map 和 struct vs new() 和 make()

new 和 make 这两个内置函数已经在第 [7.2.4](07.2.md) 节通过切片的例子说明过一次。
`new()``make()` 这两个内置函数已经在第 [7.2.4](07.2.md) 节通过切片的例子说明过一次。

现在为止我们已经见到了可以使用 `make()` 的三种类型中的其中两个:

slices / maps / channels(见第 14 章)

下面的例子说明了在映射上使用 new 和 make 的区别以及可能发生的错误:
下面的例子说明了在映射上使用 `new()``make()` 的区别以及可能发生的错误:

示例 10.4 [new_make.go](examples/chapter_10/new_make.go)(不能编译)

Expand Down Expand Up @@ -108,7 +108,7 @@ func main() {
}
```

试图 `make()` 一个结构体变量,会引发一个编译错误,这还不是太糟糕,但是 `new()` 一个 map 并试图向其填充数据,将会引发运行时错误! 因为 `new(Foo)` 返回的是一个指向 `nil` 的指针,它尚未被分配内存。所以在使用 `map` 时要特别谨慎。
试图 `make()` 一个结构体变量,会引发一个编译错误,这还不是太糟糕,但是 `new()` 一个 `map` 并试图向其填充数据,将会引发运行时错误! 因为 `new(Foo)` 返回的是一个指向 `nil` 的指针,它尚未被分配内存。所以在使用 `map` 时要特别谨慎。

## 链接

Expand Down
2 changes: 1 addition & 1 deletion eBook/10.3.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 10.3 使用自定义包中的结构体

下面的例子中,main.go 使用了一个结构体,它来自 struct_pack 下的包 structPack。
下面的例子中,main.go 使用了一个结构体,它来自 struct_pack 下的包 `structPack`

示例 10.5 [structPack.go](examples/chapter_10/struct_pack/structPack.go)

Expand Down
3 changes: 2 additions & 1 deletion eBook/10.4.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 10.4 带标签的结构体

结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 `reflect` 能获取它。我们将在下一章(第 [11.10 节](11.10.md))中深入的探讨 `reflect` 包,它可以在运行时自省类型、属性和方法,比如:在一个变量上调用 `reflect.TypeOf()` 可以获取变量的正确类型,如果变量是一个结构体类型,就可以通过 Field 来索引结构体的字段,然后就可以使用 Tag 属性。
结构体中的字段除了有名字和类型外,还可以有一个可选的标签 (tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 `reflect` 能获取它。我们将在下一章([第 11.10 节](11.10.md)中深入的探讨 `reflect` 包,它可以在运行时自省类型、属性和方法,比如:在一个变量上调用 `reflect.TypeOf()` 可以获取变量的正确类型,如果变量是一个结构体类型,就可以通过 Field 来索引结构体的字段,然后就可以使用 Tag 属性。


示例 10.7 [struct_tag.go](examples/chapter_10/struct_tag.go)

Expand Down
8 changes: 4 additions & 4 deletions eBook/10.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ func main() {
1 2 3 4
{1 2}

**练习 10.5** anonymous_struct.go:
**练习 10.5** [anonymous_struct.go](exercises\chapter_10\anonymous_struct.go)

创建一个结构体,它有一个具名的 float 字段,2 个匿名字段,类型分别是 int 和 string。通过结构体字面量新建一个结构体实例并打印它的内容。
创建一个结构体,它有一个具名的 `float32` 字段,2 个匿名字段,类型分别是 `int``string`。通过结构体字面量新建一个结构体实例并打印它的内容。

## 10.5.3 命名冲突

Expand All @@ -113,14 +113,14 @@ type C struct {A; B}
var c C
```

规则 2:使用 `c.a` 是错误的,到底是 `c.A.a` 还是 `c.B.a` 呢?会导致编译器错误:**ambiguous DOT reference c.a disambiguate with either c.A.a or c.B.a**
规则 2:使用 `c.a` 是错误的,到底是 `c.A.a` 还是 `c.B.a` 呢?会导致编译器错误:**`ambiguous DOT reference c.a disambiguate with either c.A.a or c.B.a`**

```go
type D struct {B; b float32}
var d D
```

规则1:使用 `d.b` 是没问题的:它是 float32,而不是 `B``b`。如果想要内层的 `b` 可以通过 `d.B.b` 得到。
规则1:使用 `d.b` 是没问题的:它是 `float32`,而不是 `B``b`。如果想要内层的 `b` 可以通过 `d.B.b` 得到。

## 链接

Expand Down
Loading

0 comments on commit d397979

Please sign in to comment.