mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-11 19:41:43 +08:00
精校:10.0-10.1
This commit is contained in:
@@ -19,4 +19,4 @@ Golang 编程:245386165
|
||||
|
||||
|更新日期 |更新内容
|
||||
|----------|------------------
|
||||
|2015-08-11|9.11 在 Go 程序中使用外部库
|
||||
|2015-08-14|10.1 结构体定义
|
4
TOC.md
4
TOC.md
@@ -78,4 +78,6 @@
|
||||
- 9.8 [自定义包的目录结构、go install 和 go test](eBook/09.8.md)
|
||||
- 9.9 [通过 Git 打包和安装](eBook/09.9.md)
|
||||
- 9.10 [Go 的外部包和项目](eBook/09.10.md)
|
||||
- 9.11 [在 Go 程序中使用外部库](eBook/09.11.md)
|
||||
- 9.11 [在 Go 程序中使用外部库](eBook/09.11.md)
|
||||
- 第10章:[结构(struct)与方法(method)](eBook/10.0.md)
|
||||
- 10.1 [结构体定义](eBook/10.1.md)
|
@@ -1,12 +1,13 @@
|
||||
# 10 结构(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 中结构体有着更为重要的地位。
|
||||
|
||||
## 链接
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一章:[在 Go 程序中使用外部库](09.11.md)
|
||||
- 下一节:[结构体定义](10.1.md)
|
||||
|
@@ -10,11 +10,11 @@ type identifier struct {
|
||||
}
|
||||
```
|
||||
|
||||
`type T struct {a, b int}`也是合法的语法,它更适用于简单的结构体。
|
||||
`type T struct {a, b int}` 也是合法的语法,它更适用于简单的结构体。
|
||||
|
||||
结构体里的字段都有*名字*,像field1,field2等,如果字段在代码中从来也不会被用到,那么可以命名它为*_*。
|
||||
结构体里的字段都有 **名字**,像 field1、field2 等,如果字段在代码中从来也不会被用到,那么可以命名它为 **_**。
|
||||
|
||||
结构体的字段可以是任何类型,甚至是结构体本身(参考[10.5](10.5.md)),可以是函数或者接口(参考第11章)。可以声明结构体类型的一个变量,然后给它的字段像下面这样赋值:
|
||||
结构体的字段可以是任何类型,甚至是结构体本身(参考第 [10.5](10.5.md) 节),也可以是函数或者接口(参考第 11 章)。可以声明结构体类型的一个变量,然后像下面这样给它的字段赋值:
|
||||
|
||||
```go
|
||||
var s T
|
||||
@@ -24,20 +24,20 @@ 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)。
|
||||
|
||||
[Listing 10.1—structs_fields.go](examples/chapter_10/structs_fields.go)给出了一个非常简单的例子:
|
||||
示例 10.1 [structs_fields.go](examples/chapter_10/structs_fields.go) 给出了一个非常简单的例子:
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -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 = value`。
|
||||
|
||||
同样的,使用逗号符可以获取结构体字段的值:` structname.fieldname `。
|
||||
同样的,使用逗号符可以获取结构体字段的值:`structname.fieldname`。
|
||||
|
||||
在Go语言中这叫*选择器(selector)*。无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的*选择器符(selector-notation)*来引用结构体的字段:
|
||||
在 Go 语言中这叫 **选择器(selector)**。无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的 **选择器符(selector-notation)** 来引用结构体的字段:
|
||||
|
||||
```go
|
||||
type myStruct struct { i int }
|
||||
@@ -99,9 +99,9 @@ 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{}` 是等价的。
|
||||
|
||||
时间间隔(开始和结束时间以秒为单位)是使用结构体的一个典型例子:
|
||||
时间间隔(开始和结束时间以秒为单位)是使用结构体的一个典型例子:
|
||||
|
||||
```go
|
||||
type Interval struct {
|
||||
@@ -118,9 +118,9 @@ intr := Interval(end:5, start:1) (B)
|
||||
intr := Interval(end:5) (C)
|
||||
```
|
||||
|
||||
在(A)中,值必须以字段在结构体定义时的顺序给出,*&*不是必须的。(B)显示了另一种方式,字段名加一个冒号放在值的前面,这种情况下值的顺序不必一致,并且某些字段还可以被忽略掉,就像(C)中那样。
|
||||
在(A)中,值必须以字段在结构体定义时的顺序给出,**&** 不是必须的。(B)显示了另一种方式,字段名加一个冒号放在值的前面,这种情况下值的顺序不必一致,并且某些字段还可以被忽略掉,就像(C)中那样。
|
||||
|
||||
结构体类型和字段的命名遵循可见性规则([4.2](4.2.md)),一个导出的结构体类型中有些字段是导出的,另一些不是,这是可能的。
|
||||
结构体类型和字段的命名遵循可见性规则(第 [4.2](4.2.md) 节),一个导出的结构体类型中有些字段是导出的,另一些不是,这是可能的。
|
||||
|
||||
下图说明了结构体类型实例和一个指向它的指针的内存布局:
|
||||
|
||||
@@ -128,7 +128,7 @@ intr := Interval(end:5) (C)
|
||||
type Point struct { x, y int }
|
||||
```
|
||||
|
||||
使用new初始化:
|
||||
使用 new 初始化:
|
||||
|
||||

|
||||
|
||||
@@ -136,9 +136,9 @@ 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
|
||||
package main
|
||||
@@ -186,13 +186,13 @@ func main() {
|
||||
The name of the person is CHRIS WOODWARD
|
||||
The name of the person is CHRIS WOODWARD
|
||||
|
||||
在上面例子的第二种情况中,可以直接通过指针,像`pers2.lastName="Woodward"`这样给结构体字段赋值,没有像C++中那样需要使用`->`操作符,Go会自动做这样的转换。
|
||||
在上面例子的第二种情况中,可以直接通过指针,像 `pers2.lastName="Woodward"` 这样给结构体字段赋值,没有像 C++ 中那样需要使用 `->` 操作符,Go 会自动做这样的转换。
|
||||
|
||||
注意也可以通过解指针的方式来设置值:`(*pers2).lastName = "Woodward"`
|
||||
|
||||
**结构体的内存布局**
|
||||
|
||||
Go语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。不像Java中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中,这点和Go语言中的指针很像。下面的例子清晰地说明了这些情况:
|
||||
Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。不像 Java 中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中,这点和 Go 语言中的指针很像。下面的例子清晰地说明了这些情况:
|
||||
|
||||
```go
|
||||
type Rect1 struct {Min, Max Point }
|
||||
@@ -203,15 +203,15 @@ type Rect2 struct {Min, Max *Point }
|
||||
|
||||
**递归结构体**
|
||||
|
||||
结构体类型可以通过引用自身来定义。这在定义链表或二叉树的元素(通常叫节点)时特别有用,此时节点包含指向临近节点的链接(地址)。如下所示,链表中的`su`,树中的`ri`和`le`分别是指向别的节点的指针。
|
||||
结构体类型可以通过引用自身来定义。这在定义链表或二叉树的元素(通常叫节点)时特别有用,此时节点包含指向临近节点的链接(地址)。如下所示,链表中的 `su`,树中的 `ri` 和 `le` 分别是指向别的节点的指针。
|
||||
|
||||
链表:
|
||||
|
||||

|
||||
|
||||
这块的`data`字段用于存放有效数据(比如float64),`su`指针指向后继节点。
|
||||
这块的 `data` 字段用于存放有效数据(比如 float64),`su` 指针指向后继节点。
|
||||
|
||||
Go代码:
|
||||
Go 代码:
|
||||
|
||||
```go
|
||||
type Node struct {
|
||||
@@ -220,9 +220,9 @@ type Node struct {
|
||||
}
|
||||
```
|
||||
|
||||
链表中的第一个元素叫`head`,它指向第二个元素;最后一个元素叫`tail`,它没有后继元素,所以它的`su`为nil值。当然真实的链接会有很多数据节点,并且链表可以动态增长或收缩。
|
||||
链表中的第一个元素叫 `head`,它指向第二个元素;最后一个元素叫 `tail`,它没有后继元素,所以它的 `su` 为 nil 值。当然真实的链接会有很多数据节点,并且链表可以动态增长或收缩。
|
||||
|
||||
同样地可以定义一个双向链表,它有一个前趋节点`pr`和一个后继节点`su`:
|
||||
同样地可以定义一个双向链表,它有一个前趋节点 `pr` 和一个后继节点 `su`:
|
||||
|
||||
```go
|
||||
type Node struct {
|
||||
@@ -236,7 +236,7 @@ type Node struct {
|
||||
|
||||

|
||||
|
||||
二叉树中每个节点最多能链接至两个节点:左节点(le)和右节点(ri),这两个节点本身又可以有左右节点,依次类推。树的顶层节点叫根节点(*root*),底层没有子节点的节点叫叶子节点(*leaves*),叶子节点的`le`和`ri`指针为nil值。在Go中可以如下定义二叉树:
|
||||
二叉树中每个节点最多能链接至两个节点:左节点(le)和右节点(ri),这两个节点本身又可以有左右节点,依次类推。树的顶层节点叫根节点(**root**),底层没有子节点的节点叫叶子节点(**leaves**),叶子节点的 `le` 和 `ri` 指针为 nil 值。在 Go 中可以如下定义二叉树:
|
||||
|
||||
```go
|
||||
type Tree strcut {
|
||||
@@ -248,9 +248,9 @@ type Tree strcut {
|
||||
|
||||
**结构体转换**
|
||||
|
||||
Go中的类型转换遵循严格的规则。当为结构体定义了一个alias类型时,此结构体类型和它的alias类型都有相同的底层类型,它们可以如[Listing 10.3]那样互相转换,同时需要注意其中非法赋值或转换引起的编译错误:
|
||||
Go 中的类型转换遵循严格的规则。当为结构体定义了一个 alias 类型时,此结构体类型和它的 alias 类型都有相同的底层类型,它们可以如示例 10.3 那样互相转换,同时需要注意其中非法赋值或转换引起的编译错误。
|
||||
|
||||
Listing 10.3
|
||||
示例 10.3:
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -280,31 +280,30 @@ func main() {
|
||||
|
||||
{5} {5} {5}
|
||||
|
||||
**练习**
|
||||
**练习 10.1** vcard.go:
|
||||
|
||||
练习 10.1 vcard.go:
|
||||
|
||||
定义结构体Address和VCard,后者包含一个人的名字、地址编号、出生日期和图像,试着选择正确的数据类型。构建一个自己的vcard并打印它的内容。
|
||||
定义结构体 Address 和 VCard,后者包含一个人的名字、地址编号、出生日期和图像,试着选择正确的数据类型。构建一个自己的 vcard 并打印它的内容。
|
||||
|
||||
提示:
|
||||
VCard必须包含住址,它应该以值类型还是以指针类型放在VCard中呢?
|
||||
第二种会好点,因为它占用内存少。包含一个名字和两个指向地址的指针的Address结构体可以使用%v打印:
|
||||
VCard 必须包含住址,它应该以值类型还是以指针类型放在 VCard 中呢?
|
||||
第二种会好点,因为它占用内存少。包含一个名字和两个指向地址的指针的 Address 结构体可以使用 %v 打印:
|
||||
{Kersschot 0x126d2b80 0x126d2be0}
|
||||
|
||||
练习 10.2 persionext1.go:
|
||||
**练习 10.2** persionext1.go:
|
||||
|
||||
修改persionext1.go,使它的参数upPerson不是一个指针,解释下二者的区别。
|
||||
修改 persionext1.go,使它的参数 upPerson 不是一个指针,解释下二者的区别。
|
||||
|
||||
练习 10.3 point.go:
|
||||
**练习 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
|
||||
使用坐标 X、Y 定义一个二维 Point 结构体。同样地,对一个三维点使用它的极坐标定义一个 Polar 结构体。实现一个 `Abs()` 方法来计算一个 Point 表示的向量的长度,实现一个 `Scale` 方法,它将点的坐标乘以一个尺度因子(提示:使用 `math` 包里的 `Sqrt` 函数)(function Scale that multiplies the coordinates of a point with a scale
|
||||
factor)。
|
||||
|
||||
练习 10.3 rectangle.go:
|
||||
**练习 10.3** rectangle.go:
|
||||
|
||||
定义一个Rectangle结构体,它的长和宽是int类型,并定义方法Area()和Primeter(),然后进行测试。
|
||||
定义一个 Rectangle 结构体,它的长和宽是 int 类型,并定义方法 `Area()` 和 `Primeter()`,然后进行测试。
|
||||
|
||||
## 链接
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[10 结构(struct)与方法(method)](10.0.md)
|
||||
- 下一节:[10.2 使用工厂方法创建结构体](10.2.md)
|
||||
- 上一节:[结构(struct)与方法(method)](10.0.md)
|
||||
- 下一节:[使用工厂方法创建结构体](10.2.md)
|
||||
|
Reference in New Issue
Block a user