Skip to content

Commit

Permalink
第九章修改 (#841)
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 10, 2022
1 parent 30ca13a commit 471d59a
Showing 11 changed files with 80 additions and 70 deletions.
3 changes: 2 additions & 1 deletion eBook/09.0.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 9.0 包package
# 9.0 包 (package)

本章主要针对 Go 语言的包展开讲解。

@@ -7,3 +7,4 @@
- [目录](directory.md)
- 上一节:[将 map 的键值对调](08.6.md)
- 下一节:[标准库概述](09.1.md)

14 changes: 7 additions & 7 deletions eBook/09.1.md
Original file line number Diff line number Diff line change
@@ -69,9 +69,9 @@ for e := l.Front(); e != nil; e = e.Next() {
- `log`: 记录程序运行时产生的日志,我们将在后面的章节使用它。
- `encoding/json`-`encoding/xml`-`text/template`:
- `encoding/json`: 读取并解码和写入并编码 JSON 数据。
- `encoding/xml`: 简单的 XML1.0 解析器,有关 JSON 和 XML 的实例请查阅第 12.9/10 章节。
- `text/template`:生成像 HTML 一样的数据与文本混合的数据驱动模板(参见第 15.7 节)。
- `net`-`net/http`-`html`:(参见第 15 章)
- `encoding/xml`: 简单的 XML1.0 解析器,有关 JSON 和 XML 的实例请查阅第 [12.9](12.9.md)/[10](10.0.md) 章节。
- `text/template`:生成像 HTML 一样的数据与文本混合的数据驱动模板(参见[第 15.7 节](15.7.md))。
- `net`-`net/http`-`html`:(参见[ 15 章](15.0.md)
- `net`: 网络数据的基本操作。
- `http`: 提供了一个可扩展的 HTTP 服务器和基础客户端,解析 HTTP 请求和回复。
- `html`: HTML5 解析器。
@@ -80,16 +80,16 @@ for e := l.Front(); e != nil; e = e.Next() {

`exp` 包中有许多将被编译为新包的实验性的包。在下次稳定版本发布的时候,它们将成为独立的包。如果前一个版本已经存在了,它们将被作为过时的包被回收。然而 Go1.0 发布的时候并没有包含过时或者实验性的包。

**练习 9.1**
**练习 9.1** [Q20_linked_list.go](exercises\chapter_9\dlinked_list.go)

使用 `container/list` 包实现一个双向链表,将 101102 和 103 放入其中并打印出来。
使用 `container/list` 包实现一个双向链表,将 `101``102``103` 放入其中并打印出来。

**练习 9.2**
**练习 9.2** [size_int.go](exercises\chapter_9\size_int.go)

通过使用 `unsafe` 包中的方法来测试你电脑上一个整型变量占用多少个字节。

## 链接

- [目录](directory.md)
- 上一节:[package](09.0.md)
- 上一节:[ (package)](09.0.md)
- 下一节:[regexp 包](09.2.md)
11 changes: 6 additions & 5 deletions eBook/09.11.md
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@

当开始一个新项目或增加新的功能到现有的项目,你可以通过在应用程序中使用已经存在的库来节省开发时间。为了做到这一点,你必须理解库的 API(应用编程接口),那就是:库中有哪些方法可以调用,如何调用。你可能没有这个库的源代码,但作者肯定有记载的 API 以及详细介绍了如何使用它。

作为一个例子,我们将使用谷歌的 API 的 urlshortener 编写一个小程序:你可以尝试一下在 http://goo.gl/ 输入一个像 "http://www.destandaard.be" 这样的 URL,你会看到一个像 "http://goo.gl/O9SUO" 这样更短的 URL 返回,也就是说,在 Twitter 之类的服务中这是非常容易嵌入的。谷歌 urlshortener 服务的文档可以在 "http://code.google.com/apis/urlshortener/" 找到。(第 19 章,我们将开发自己版本的 urlshortener)。
作为一个例子,我们将使用谷歌的 API 的 urlshortener 编写一个小程序:你可以尝试一下在 http://goo.gl/ 输入一个像 "http://www.destandaard.be" 这样的 URL,你会看到一个像 "http://goo.gl/O9SUO" 这样更短的 URL 返回,也就是说,在 Twitter 之类的服务中这是非常容易嵌入的。谷歌 urlshortener 服务的文档可以在 "http://code.google.com/apis/urlshortener/" 找到。([第 19 章](19.0.md),我们将开发自己版本的 urlshortener)。

谷歌将这项技术提供给其他开发者,我们可以在我们自己的应用程序中调用 API (释放到指定的限制)。他们也生成了一个 Go 语言客户端库使调用变得更容易。

@@ -24,9 +24,9 @@ go install 将下载源码,编译并安装包

import "google.golang.org/api/urlshortener/v1"

现在我们写一个 Web 应用(参见第 15 章 4-8 节)通过表单实现短地址和长地址的相互转换。我们将使用 `template` 包并写三个处理函数:root 函数通过执行表单模板来展示表单,short 函数将长地址转换为短地址,long 函数逆向转换。
现在我们写一个 Web 应用(参见[ 15 章 4-8 节](15.4.md))通过表单实现短地址和长地址的相互转换。我们将使用 `template` 包并写三个处理函数:`root()` 函数通过执行表单模板来展示表单,`short()` 函数将长地址转换为短地址,`long()` 函数逆向转换。

要调用 urlshortener 接口必须先通过 http 包中的默认客户端创建一个服务实例 urlshortenerSvc:
要调用 `urlshortener` 接口必须先通过 `http` 包中的默认客户端创建一个服务实例 `urlshortenerSvc`
```go
urlshortenerSvc, _ := urlshortener.New(http.DefaultClient)
```
@@ -47,7 +47,7 @@ url, error := urlshortenerSvc.Url.Get(shwortUrl).Do()

返回的长地址便是转换前的原始地址。

示例 9.9 [urlshortener.go](examples/chapter_9/use_urlshortener.go)
示例 9.9 [urlshortener.go](examples/chapter_9/use_urlshortener.go)

```go
package main
@@ -146,4 +146,5 @@ script: _go_app

- [目录](directory.md)
- 上一节:[Go 的外部包和项目](09.10.md)
- 下一章:[结构(struct)与方法(method)](10.0.md)
- 下一章:[结构 (struct) 与方法 (method)](10.0.md)

10 changes: 5 additions & 5 deletions eBook/09.2.md
Original file line number Diff line number Diff line change
@@ -2,21 +2,21 @@

正则表达式语法和使用的详细信息请参考 [维基百科](http://en.wikipedia.org/wiki/Regular_expression)

在下面的程序里,我们将在字符串中对正则表达式模式pattern进行匹配。
在下面的程序里,我们将在字符串中对正则表达式模式 (pattern) 进行匹配。

如果是简单模式,使用 `Match` 方法便可:
如果是简单模式,使用 `Match()` 方法便可:

```go
ok, _ := regexp.Match(pat, []byte(searchIn))
```

变量 ok 将返回 true 或者 false,我们也可以使用 `MatchString`
变量 `ok` 将返回 `true` 或者 `false`,我们也可以使用 `MatchString()`

```go
ok, _ := regexp.MatchString(pat, searchIn)
```

更多方法中,必须先将正则模式通过 `Compile` 方法返回一个 Regexp 对象。然后我们将掌握一些匹配,查找,替换相关的功能。
更多方法中,必须先将正则模式通过 `Compile()` 方法返回一个 `Regexp` 对象。然后我们将掌握一些匹配,查找,替换相关的功能。

示例 9.2 [pattern.go](examples/chapter_9/pattern.go)

@@ -59,7 +59,7 @@ func main() {
John: ##.# William: ##.# Steve: ##.#
John: 5156.68 William: 9134.46 Steve: 11264.36

`Compile` 函数也可能返回一个错误,我们在使用时忽略对错误的判断是因为我们确信自己正则表达式是有效的。当用户输入或从数据中获取正则表达式的时候,我们有必要去检验它的正确性。另外我们也可以使用 `MustCompile` 方法,它可以像 `Compile` 方法一样检验正则的有效性,但是当正则不合法时程序将 panic(详情查看第 13.2 节)
`Compile()` 函数也可能返回一个错误,我们在使用时忽略对错误的判断是因为我们确信自己正则表达式是有效的。当用户输入或从数据中获取正则表达式的时候,我们有必要去检验它的正确性。另外我们也可以使用 `MustCompile()` 方法,它可以像 `Compile()` 方法一样检验正则的有效性,但是当正则不合法时程序将 `panic()`(详情查看[ 13.2 节](13.2.md)

## 链接

12 changes: 6 additions & 6 deletions eBook/09.3.md
Original file line number Diff line number Diff line change
@@ -4,13 +4,13 @@

经典的做法是一次只能让一个线程对共享变量进行操作。当变量被一个线程改变时(临界区),我们为它上锁,直到这个线程执行完成并解锁后,其他线程才能访问它。

特别是我们之前章节学习的 map 类型是不存在锁的机制来实现这种效果(出于对性能的考虑),所以 map 类型是非线程安全的。当并行访问一个共享的 map 类型的数据,map 数据将会出错。
特别是我们之前章节学习的 `map` 类型是不存在锁的机制来实现这种效果(出于对性能的考虑),所以 map 类型是非线程安全的。当并行访问一个共享的 `map` 类型的数据,`map` 数据将会出错。

在 Go 语言中这种锁的机制是通过 sync 包中 Mutex 来实现的。sync 来源于 "synchronized" 一词,这意味着线程将有序的对同一变量进行访问。
在 Go 语言中这种锁的机制是通过 `sync` 包中 `Mutex` 来实现的。sync 来源于 "synchronized" 一词,这意味着线程将有序的对同一变量进行访问。

`sync.Mutex` 是一个互斥锁,它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区。

假设 info 是一个需要上锁的放在共享内存中的变量。通过包含 `Mutex` 来实现的一个典型例子如下:
假设 `info` 是一个需要上锁的放在共享内存中的变量。通过包含 `Mutex` 来实现的一个典型例子如下:

```go
import "sync"
@@ -33,7 +33,7 @@ func Update(info *Info) {
}
```

还有一个很有用的例子是通过 Mutex 来实现一个可以上锁的共享缓冲器:
还有一个很有用的例子是通过 `Mutex` 来实现一个可以上锁的共享缓冲器:

```go
type SyncedBuffer struct {
@@ -42,9 +42,9 @@ type SyncedBuffer struct {
}
```

在 sync 包中还有一个 `RWMutex` 锁:它能通过 `RLock()` 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 `Lock()` 将和普通的 `Mutex` 作用相同。包中还有一个方便的 `Once` 类型变量的方法 `once.Do(call)`,这个方法确保被调用函数只能被调用一次。
`sync` 包中还有一个 `RWMutex` 锁:它能通过 `RLock()` 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 `Lock()` 将和普通的 `Mutex` 作用相同。包中还有一个方便的 `Once` 类型变量的方法 `once.Do(call)`,这个方法确保被调用函数只能被调用一次。

相对简单的情况下,通过使用 sync 包可以解决同一时间只能一个线程访问变量或 map 类型数据的问题。如果这种方式导致程序明显变慢或者引起其他问题,我们要重新思考来通过 goroutines 和 channels 来解决问题,这是在 Go 语言中所提倡用来实现并发的技术。我们将在第 14 章对其深入了解,并在第 14.7 节中对这两种方式进行比较
相对简单的情况下,通过使用 `sync` 包可以解决同一时间只能一个线程访问变量或 `map` 类型数据的问题。如果这种方式导致程序明显变慢或者引起其他问题,我们要重新思考来通过 goroutines 和 channels 来解决问题,这是在 Go 语言中所提倡用来实现并发的技术。我们将在[ 14 ](14.0.md)对其深入了解,并在[ 14.7 ](14.7.md)中对这两种方式进行比较

## 链接

6 changes: 3 additions & 3 deletions eBook/09.4.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# 9.4 精密计算和 big 包

我们知道有些时候通过编程的方式去进行计算是不精确的。如果你使用 Go 语言中的 float64 类型进行浮点运算,返回结果将精确到 15 位,足以满足大多数的任务。当对超出 int64 或者 uint64 类型这样的大数进行计算时,如果对精度没有要求,float32 或者 float64 可以胜任,但如果对精度有严格要求的时候,我们不能使用浮点数,在内存中它们只能被近似的表示。
我们知道有些时候通过编程的方式去进行计算是不精确的。如果你使用 Go 语言中的 `float64` 类型进行浮点运算,返回结果将精确到 15 位,足以满足大多数的任务。当对超出 `int64` 或者 `uint64` 类型这样的大数进行计算时,如果对精度没有要求,`float32` 或者 `float64` 可以胜任,但如果对精度有严格要求的时候,我们不能使用浮点数,在内存中它们只能被近似的表示。

对于整数的高精度计算 Go 语言中提供了 big 包,被包含在 math 包下:有用来表示大整数的 `big.Int` 和表示大有理数的 `big.Rat` 类型(可以表示为 2/5 或 3.1416 这样的分数,而不是无理数或 π)。这些类型可以实现任意位类型的数字,只要内存足够大。缺点是更大的内存和处理开销使它们使用起来要比内置的数字类型慢很多。
对于整数的高精度计算 Go 语言中提供了 `big` 包,被包含在 `math` 包下:有用来表示大整数的 `big.Int` 和表示大有理数的 `big.Rat` 类型(可以表示为 2/5 或 3.1416 这样的分数,而不是无理数或 π)。这些类型可以实现任意位类型的数字,只要内存足够大。缺点是更大的内存和处理开销使它们使用起来要比内置的数字类型慢很多。

大的整型数字是通过 `big.NewInt(n)` 来构造的,其中 n 为 int64 类型整数。而大有理数是通过 `big.NewRat(n, d)` 方法构造。n(分子)和 d(分母)都是 int64 型整数。因为 Go 语言不支持运算符重载,所以所有大数字类型都有像是 `Add()``Mul()` 这样的方法。它们作用于作为 receiver 的整数和有理数,大多数情况下它们修改 receiver 并以 receiver 作为返回结果。因为没有必要创建 `big.Int` 类型的临时变量来存放中间结果,所以运算可以被链式地调用,并节省内存。
大的整型数字是通过 `big.NewInt(n)` 来构造的,其中 `n``int64` 类型整数。而大有理数是通过 `big.NewRat(n, d)` 方法构造。`n`(分子)和 `d`(分母)都是 `int64` 型整数。因为 Go 语言不支持运算符重载,所以所有大数字类型都有像是 `Add()``Mul()` 这样的方法。它们作用于作为 receiver 的整数和有理数,大多数情况下它们修改 receiver 并以 receiver 作为返回结果。因为没有必要创建 `big.Int` 类型的临时变量来存放中间结果,所以运算可以被链式地调用,并节省内存。

示例 9.2 [big.go](examples/chapter_9/big.go)

46 changes: 26 additions & 20 deletions eBook/09.5.md
Original file line number Diff line number Diff line change
@@ -88,11 +88,12 @@ Import with `_` :

import _ "./pack1/pack1"

pack1 包只导入其副作用,也就是说,只执行它的 init 函数并初始化其中的全局变量。
`pack1` 包只导入其副作用,也就是说,只执行它的 `init()` 函数并初始化其中的全局变量。

**导入外部安装包:**

如果你要在你的应用中使用一个或多个外部包,首先你必须使用 `go install`(参见第 9.7 节)在你的本地机器上安装它们。
如果你要在你的应用中使用一个或多个外部包,首先你必须使用 `go install`(参见[第 9.7 节](09.7.md))在你的本地机器上安装它们。


假设你想使用 `http://codesite.ext/author/goExample/goex` 这种托管在 Google Code、GitHub 和 Launchpad 等代码网站上的包。

@@ -112,17 +113,19 @@ pack1 包只导入其副作用,也就是说,只执行它的 init 函数并

**包的初始化:**

程序的执行开始于导入包,初始化 main 包然后调用 main 函数。
程序的执行开始于导入包,初始化 `main` 包然后调用 `main()` 函数。

一个没有导入的包将通过分配初始值给所有的包级变量和调用源码中定义的包级 init 函数来初始化。一个包可能有多个 init 函数甚至在一个源码文件中。它们的执行是无序的。这是最好的例子来测定包的值是否只依赖于相同包下的其他值或者函数。
一个没有导入的包将通过分配初始值给所有的包级变量和调用源码中定义的包级 `init()` 函数来初始化。一个包可能有多个 `init()` 函数甚至在一个源码文件中。它们的执行是无序的。这是最好的例子来测定包的值是否只依赖于相同包下的其他值或者函数。

init 函数是不能被调用的。
`init()` 函数是不能被调用的。

导入的包在包自身初始化前被初始化,而一个包在程序执行中只能初始化一次。

**编译并安装一个包(参见第 9.7 节):**
**编译并安装一个包(参见[第 9.7 节](09.7.md)):**


在 Linux/OS X 下可以用类似[第 3.9 节](03.9.md)的 Makefile 脚本做到这一点:

在 Linux/OS X 下可以用类似第 3.9 节的 Makefile 脚本做到这一点:

include $(GOROOT)/src/Make.inc
TARG=pack1
@@ -131,39 +134,42 @@ init 函数是不能被调用的。
pack1b.go\
include $(GOROOT)/src/Make.pkg

通过 `chmod 777 ./Makefile`确保它的可执行性。
通过 `chmod 777 ./Makefile` 确保它的可执行性。

上面脚本内的 include 语句引入了相应的功能,将自动检测机器的架构并调用正确的编译器和链接器。
上面脚本内的 `include` 语句引入了相应的功能,将自动检测机器的架构并调用正确的编译器和链接器。

然后终端执行 make 或 `gomake` 工具:他们都会生成一个包含静态库 pack1.a 的 _obj 目录。
然后终端执行 `make``gomake` 工具:他们都会生成一个包含静态库 `pack1.a``_obj` 目录。

go install(参见第 9.7 节,从 Go1 的首选方式)同样复制 pack1.a 到本地的 $GOROOT/pkg 的目录中一个以操作系统为名的子目录下。像 `import "pack1"` 代替 `import "path to pack1"`,这样只通过名字就可以将包在程序中导入。
go install(参见[第 9.7 节](09.7.md),从 Go1 的首选方式)同样复制 `pack1.a` 到本地的 `$GOROOT/pkg` 的目录中一个以操作系统为名的子目录下。像 `import "pack1"` 代替 `import "path to pack1"`,这样只通过名字就可以将包在程序中导入。


[第 13 章](13.0.md) 我们遇到使用测试工具进行测试的时候我们将重新回到自己的包的制作和编译这个话题。

当第 13 章我们遇到使用测试工具进行测试的时候我们将重新回到自己的包的制作和编译这个话题。

**问题 9.1**

a)一个包能分成多个源文件么?

b)一个源文件是否能包含多个包?

**练习 9.3**
**练习 9.3** [main_greetings.go](exercises\chapter_9\main_greetings.go)

创建一个程序 main_greetings.go 能够和用户说 "Good Day" 或者 "Good Night"。不同的问候应该放到单独的 greetings 包中。
创建一个程序 main_greetings.go 能够和用户说 `"Good Day"` 或者 `"Good Night"`。不同的问候应该放到单独的 `greetings` 包中。

在同一个包中创建一个 `IsAM` 函数返回一个布尔值用来判断当前时间是 AM 还是 PM,同样创建 `IsAfternoon``IsEvening` 函数。

使用 main_greetings 作出合适的问候(提示:使用 time 包)。
使用 main_greetings 作出合适的问候(提示:使用 `time` 包)。

**练习 9.4** 创建一个程序 [main_oddven.go](exercises\chapter_9\main_oddeven.go) 判断前 100 个整数是不是偶数,将判断所用的函数编写在 `even` 包里。

**练习 9.4** 创建一个程序 main_oddven.go 判断前 100 个整数是不是偶数,将判断所用的函数编写在 even 包里。
**练习 9.5** 使用[第 6.6 节](06.6.md)的斐波那契程序:

**练习 9.5** 使用第 6.6 节的斐波那契程序:

1)将斐波那契功能放入自己的 fibo 包中并通过主程序调用它,存储最后输入的值在函数的全局变量。
1)将斐波那契功能放入自己的 `fibo` 包中并通过主程序调用它,存储最后输入的值在函数的全局变量。

2)扩展 fibo 包将通过调用斐波那契的时候,操作也作为一个参数。实验 "+" 和 *
2)扩展 `fibo` 包将通过调用斐波那契的时候,操作也作为一个参数。实验 `"+"``"*"`

main_fibo.go / fibonacci.go
[main_fibo.go](exercises\chapter_9\main_fibo.go) / [fibonacci.go](exercises/chapter_6/fibonacci.go)

## 链接

Loading

0 comments on commit 471d59a

Please sign in to comment.