Merge pull request #79 from domainname/master

bug fixes
This commit is contained in:
无闻
2015-03-16 22:15:57 -04:00
8 changed files with 346 additions and 153 deletions

View File

@@ -1,6 +1,6 @@
# 9.1 标准库概述
像 fmtos 等这样具有常用功能的内置包在 Go 语言中有 150 个以上,它们被称为标准库,大部分(一些底层的除外)内置于 Go 本身。记录在: http://golang.org/pkg/。
像 fmtos 等这样具有常用功能的内置包在 Go 语言中有 150 个以上,它们被称为标准库,大部分(一些底层的除外)内置于 Go 本身。记录在: [http://golang.org/pkg/](http://golang.org/pkg/)
在贯穿本书的例子和练习中,我们都是用标准库的包。可以通过查阅第 350 页包中的内容快速找到相关的包的实例。这里我们只是按功能进行分组来介绍这些包的简单用途,我们不会深入讨论他们的内部结构。
@@ -81,11 +81,11 @@ for e := l.Front(); e != nil; e = e.Next() {
exp 包中有许多将被编译为新包的实验性的包。它们将成为独立的包在下次稳定版本发布的时候。如果前一个版本已经存在了它们将被作为果实的包被回收。然而Go1.0发布的时候并不包含过时或者实验性的包。
** 练习9.1 **
**练习9.1**
使用 container/list 包实现一个双向链表,将 101,102,103 放入其中并打印出来。
**练习9.2 **
**练习9.2**
通过使用 unsafe 包中的方法来测试你电脑上一个整型变量占用多少个字节。

Binary file not shown.

View File

@@ -1,150 +1,169 @@
# 9.5 自定义包和可见性
包是 Go 语言中代码组成和代码编译的主要方式。很多关于它们的基本信息已经在4.2章节中给出,最引人注目的便是可见性。现在我们来看看具体如何来使用自己写的包。在下一节,我们将回顾一些标准库中的包,自定义的包和标准库以外的包。
当写自己包的时候要使用短小的不含有_(下划线)的小写单词来为文件命名。这里有个简单例子来说明包是如何相互调用以及可见性是如何实现的。
当前目录下(examples/chapter9)有一个名为 package_test.go 的程序, 它使用了自定义包 pack1中pack1.go 的代码。这段程序(联通编译链接生成的pack1.a)存放在当前目录下一个名为 pack1 的文件夹下。所以链接器将包的对象和主程序对象链接在一起。
示例 9.4 [pack1.go](examples/chapter_9/pack1.go)
```go
package pack1
var Pack1Int int = 42
var PackFloat = 3.14
func ReturnStr() string {
return "Hello main!"
}
```
它包含了一个整型变量 PackInt 和一个返回字符串的函数 ReturnStr。这段程序在运行时不做任何的事情因为它不包含有一个 main 函数。
在主程序 pack_test.go 中这个包通过声明的方式被导入
```go
import ./pack1/pack1
```
import 的一般格式如下:
import “包的路径或url地址“ 像 import "github.com/org1/pack1”
路径是指当前目录的相对路径。
示例 9.5 [package_test.go](examples/chapter_9/package_test.go)
```go
package main
import (
"fmt"
"./pack1/pack1"
)
func main() {
var test1 string
test1 = pack1.ReturnStr()
fmt.Printf("ReturnStr from package1: %s\n", test1)
fmt.Printf(Integer from package1: %d\n, pack1.Pack1Int)
// fmt.Printf(“Float from package1: %f\n”, pack1.pack1Float)
# 9.5 自定义包和可见性
```
输出结果:
包是 Go 语言中代码组成和代码编译的主要方式。很多关于它们的基本信息已经在4.2章节中给出,最引人注目的便是可见性。现在我们来看看具体如何来使用自己写的包。在下一节,我们将回顾一些标准库中的包,自定义的包和标准库以外的包。
ReturnStr from package1: Hello main!
Integer from package1: 42
如果包 pack1 和我们的程序在统同一路径下,我们可以通过 `"import ./pack1"` 这样的方式来引入,但这不被视为一个好的方法。
`fmt.Printf(“Float from package1: %f\n”, pack1.pack1Float)` 这行代码试图访问一个未引用的变量或者函数,甚至没有编译。将会返回一个错误:
cannot refer to unexported name pack1.pack1Float
主程序利用的包必须在主程序编写之前被编译。主程序中每个 pack1 项目都要通过包名来使用使用pack1.Item。具体使用方法请参见示例 4.6 和 4.7。
因此,按照惯例子目录和包之间有着密切的联系:为了区分不同包存放在不同的目录,每个包(所有属于这个包中的 go 文件)都存放在和包名相同的子目录下:
Import with . : import . “./pack1”
当使用.来做为包的别名时,你可以不通过包名来使用其中的项目。例如:`test := ReturnStr()`
在当前的命名空间导入 pack1 包,一般是为了具有更好的测试效果。
Import with _ : import _ “./pack1/pack1”
pack1包只导入其副作用只执行了它的init函数并初始化了其中的全局变量
导入外部安装包:
如果你要在你的应用中使用一个或多个外部包,首先你必须使用 go install(参见第 9.7 节)在你的本地机器上安装它们。
假设你想使用 http://codesite.ext/author/goExample/goex 这种托管在 googlecode, github,launchpad 等代码网站上的包。
你可以通过如下命令安装:
go install codesite.ext/author/goExample/goex
将一个名为 codesite.ext/author/goExample/goex 的 map 安装在 `$GOROOT/src/` 目录下。
通过以下方式,一次性安装,并导入到你的代码中:
import goex “codesite.ext/author/goExample/goex”
因此你项目的路径将成为导入包的网络地址
在 http://golang.org/cmd/goinstall/ 的 go install 文档中列出了一些广泛被使用的托管在网络代码仓库的包的导入路径
包的初始化:
程序的执行开始于导入包,初始化 main 包然后调用 main 函数。
一个没有导入的包将通过分配初始值给所有的包级变量和调用源码中定义的包级 init 函数来初始化。一个包可能有多个 init 函数甚至在一个源码文件中。它们的执行是无序的。这是最好的例子来测定包的值是否只依赖于相同包下的其他值或者函数。
init 函数是不能被调用的。
导入的包在包自身初始化前被初始化,而一个包在程序执行中只能初始化一次。
编译并安装一个包(参见第 9.7 节):
在 Linux/OSX 下可以用类似第 4.3 节的 Makefile 脚本做到这一点:
include $(GOROOT)/src/Make.inc
TARG=pack1
GOFILES=\
pack1.go\
pack1b.go\
include $(GOROOT)/src/Make.pkg
确保的它可执行性通过 `chmod 777 ./Makefile`
内置声明了自动检测机器体系结构和使用正确的编译器和链接器的功能
然后终端执行 make 或 gomake:都会生成一个包含静态库 pack1.a 的 _obj 目录
go install(参见第 9.7 节,从 Go1 的首选方式)同样复制 pack1.a 到本地的 $GOROOT/pkg 的目录中一个以操作系统为名的子目录下。像 import "pack1" 代替 import "path to pack1",这样只通过名字就可以将包在程序中导入。
如果不可取或不被允许,通过 6/8g 使用 -I 选项来编译:
6g—I map_pack1 package_test.go # where map_pack1 is the map which contains pack1.a
(I 选项让编译器查找选项后的目录下是否包含这个包)
使用 6/8l 的 -L 选项链接:
6l—L map_pack1 package_test.6
当第 13 章我们遇到使用测试工具进行测试的时候我们将重新回到自己的包的制作和编译这个话题。
练习:
问题9.1:
(a)一个包能分成多个源文件么?
(b)一个源文件是否能包含多个包?
练习 9.3
创建一个程序 main_greetings.go 能够和用户说 "Good Day" 或者 "Good Night"。不同的问候应该放到 greetings 包中。
当写自己包的时候要使用短小的不含有_(下划线)的小写单词来为文件命名。这里有个简单例子来说明包是如何相互调用以及可见性是如何实现的。
当前目录下(examples/chapter9)有一个名为 package_test.go 的程序, 它使用了自定义包 pack1中pack1.go 的代码。这段程序(联通编译链接生成的pack1.a)存放在当前目录下一个名为 pack1 的文件夹下。所以链接器将包的对象和主程序对象链接在一起。
示例 9.4 [pack1.go](examples/chapter_9/pack1.go)
```go
package pack1
var Pack1Int int = 42
var PackFloat = 3.14
func ReturnStr() string {
return "Hello main!"
}
```
它包含了一个整型变量 PackInt 和一个返回字符串的函数 ReturnStr。这段程序在运行时不做任何的事情因为它不包含有一个 main 函数。
在主程序 pack_test.go 中这个包通过声明的方式被导入
```go
import ./pack1/pack1
```
import 的一般格式如下:
import “包的路径或url地址“ 像 import "github.com/org1/pack1”
路径是指当前目录的相对路径。
示例 9.5 [package_test.go](examples/chapter_9/package_test.go)
```go
package main
import (
"fmt"
"./pack1/pack1"
)
func main() {
var test1 string
test1 = pack1.ReturnStr()
fmt.Printf("ReturnStr from package1: %s\n", test1)
fmt.Printf(Integer from package1: %d\n, pack1.Pack1Int)
// fmt.Printf(“Float from package1: %f\n”, pack1.pack1Float)
}
```
输出结果:
ReturnStr from package1: Hello main!
Integer from package1: 42
如果包 pack1 和我们的程序在统同一路径下,我们可以通过 `"import ./pack1"` 这样的方式来引入,但这不被视为一个好的方法。
`fmt.Printf(“Float from package1: %f\n”, pack1.pack1Float)` 这行代码试图访问一个未引用的变量或者函数,甚至没有编译。将会返回一个错误:
cannot refer to unexported name pack1.pack1Float
主程序利用的包必须在主程序编写之前被编译。主程序中每个 pack1 项目都要通过包名来使用使用pack1.Item。具体使用方法请参见示例 4.6 和 4.7。
因此,按照惯例子目录和包之间有着密切的联系:为了区分不同包存放在不同的目录,每个包(所有属于这个包中的 go 文件)都存放在和包名相同的子目录下:
**Import with .** : import . “./pack1”
当使用.来做为包的别名时,你可以不通过包名来使用其中的项目。例如:`test := ReturnStr()`
在当前的命名空间导入 pack1 包,一般是为了具有更好的测试效果
**Import with _** : import _ “./pack1/pack1”
pack1包只导入其副作用也就是说只执行它的init函数并初始化其中的全局变量。
**导入外部安装包:**
如果你要在你的应用中使用一个或多个外部包,首先你必须使用 go install(参见第 9.7 节)在你的本地机器上安装它们。
假设你想使用 http://codesite.ext/author/goExample/goex 这种托管在 googlecode, github,launchpad 等代码网站上的包。
你可以通过如下命令安装:
go install codesite.ext/author/goExample/goex
将一个名为 codesite.ext/author/goExample/goex 的 map 安装在 `$GOROOT/src/` 目录下。
通过以下方式,一次性安装,并导入到你的代码中:
import goex “codesite.ext/author/goExample/goex”
因此该包的URL将用作导入路径。
在 http://golang.org/cmd/goinstall/ 的 go install 文档中列出了一些广泛被使用的托管在网络代码仓库的包的导入路径
**包的初始化:**
程序的执行开始于导入包,初始化 main 包然后调用 main 函数。
一个没有导入的包将通过分配初始值给所有的包级变量和调用源码中定义的包级 init 函数来初始化。一个包可能有多个 init 函数甚至在一个源码文件中。它们的执行是无序的。这是最好的例子来测定包的值是否只依赖于相同包下的其他值或者函数。
init 函数是不能被调用的。
导入的包在包自身初始化前被初始化,而一个包在程序执行中只能初始化一次。
**编译并安装一个包(参见第 9.7 节):**
在 Linux/OS X 下可以用类似第 4.3 节的 Makefile 脚本做到这一点:
include $(GOROOT)/src/Make.inc
TARG=pack1
GOFILES=\
pack1.go\
pack1b.go\
include $(GOROOT)/src/Make.pkg
确保的它可执行性通过 `chmod 777 ./Makefile`
上面脚本内的include语引入了相应的功能将自动检测机器的架构并调用正确的编译器和链接器。
然后终端执行 make 或 `gomake` 工具:他们都会生成一个包含静态库 pack1.a 的 _obj 目录。
go install(参见第 9.7 节,从 Go1 的首选方式)同样复制 pack1.a 到本地的 $GOROOT/pkg 的目录中一个以操作系统为名的子目录下。像 import "pack1" 代替 import "path to pack1",这样只通过名字就可以将包在程序中导入。
如果不可取或不被允许,通过 6/8g 使用 -I 选项来编译:
6g—I map_pack1 package_test.go # where map_pack1 is the map which contains pack1.a
(I 选项让编译器查找选项后的目录下是否包含这个包)
使用 6/8l 的 -L 选项链接:
6l—L map_pack1 package_test.6
当第 13 章我们遇到使用测试工具进行测试的时候我们将重新回到自己的包的制作和编译这个话题。
练习:
问题9.1:
(a)一个包能分成多个源文件么?
(b)一个源文件是否能包含多个包?
练习 9.3
创建一个程序 main_greetings.go 能够和用户说 "Good Day" 或者 "Good Night"。不同的问候应该放到 greetings 包中。
在同一个包中创建一个 ISAM 函数返回一个布尔值用来判断当前时间是 AM 还是 PM同样创建 IsAfternoon 和 IsEvening 函数。
使用 main_greetings 作出合适的问候(提示:使用 time 包)。
练习 9.4:创建一个程序 main_oddven.go 判断前 100 个整数是不是偶数,包内同时包含测试的功能。
练习 9.5:使用第 6.6 节的斐波那契程序:
(1)将斐波那契功能放入自己的 fibo 包中并通过主程序调用它,存储最后输入的值在函数的全局变量。
(2)扩展 fibo 包将通过调用斐波那契的时候,操作也作为一个参数。实验 "+" 和 “*”
main_fibo.go / fibonacci.go
## 链接
- [目录](directory.md)
- 上一节:[精密计算和 big 包](09.4.md)
- 下一节:[为自定义包使用 godoc](09.6.md)

View File

@@ -4,7 +4,7 @@ go install 是 Go 中自动包安装工具:如需要将包安装到本地它
在包安装前的先决条件是要自动处理包自身依赖关系的安装。被依赖的包也会安装到子目录下,但是没有文档和示例:可以到网上浏览。
被安装包的列表可以在 $GORROT/goinstall.log 找到。
被安装包的列表可以在 $GOROOT/goinstall.log 找到。
go install 使用了 GOPATH 变量(详见第 2.2 节)。

9
eBook/12.0.md Normal file
View File

@@ -0,0 +1,9 @@
# 读写数据
除了 fmt 和 os 包,我们还需要用到 bufio 包来处理缓冲的输入和输出。
## 链接
- [目录](directory.md)
- 上一章:[接口interface与反射reflection](11.0.md)
- 下一节:[读取用户的输入](12.1.md)

151
eBook/12.1.md Normal file
View File

@@ -0,0 +1,151 @@
# 读取用户的输入
我们如何读取用户的键盘(控制台)输入呢?从键盘和标准输入 `os.Stdin` 读取输入,最简单的办法是使用 `fmt` 包提供的 Scan 和 Sscan 开头的函数。请看以下程序:
**Listing 12.1—readinput1.go:**
```go
// 从控制台读取输入:
package main
import "fmt"
var (
firstName, lastName, s string
i int
f float32
input = "56.12 / 5212 / Go"
format = "%f / %d / %s"
)
func main() {
fmt.Println("Please enter your full name: ")
fmt.Scanln(&firstName, &lastName)
// fmt.Scanf("%s %s", &firstName, &lastName)
fmt.Printf("Hi %s %s!\n", firstName, lastName) // Hi Chris Naegels
fmt.Sscanf(input, format, &f, &i, &s)
fmt.Println("From the string we read: ", f, i, s)
// 输出结果: From the string we read: 56.12 5212 Go
}
```
`Scanln` 扫描来自标准输入的文本,将空格分隔的值依次存放到后续的参数内,直到碰到换行。`Scanf` 与其类似,除了 `Scanf` 的第一个参数用作格式字符串,用来决定如何读取。`Sscan` 和以 `Sscan` 开头的函数则是从字符串读取,除此之外,与 `Scanf` 相同。如果这些函数读取到的结果与您预想的不同,您可以检查成功读入数据的个数和返回的错误。
您也可以使用 `bufio` 包提供的缓冲读取buffered reader来读取数据正如以下例子所示
**Listing 12.2—readinput2.go:**
```go
package main
import (
"fmt"
"bufio"
"os"
)
var inputReader *bufio.Reader
var input string
var err error
func main() {
inputReader = bufio.NewReader(os.Stdin)
fmt.Println("Please enter some input: ")
input, err = inputReader.ReadString('\n')
if err == nil {
fmt.Printf("The input was: %s\n", input)
}
}
```
`inputReader` 是一个指向 `bufio.Reader` 的指针。`inputReader := bufio.NewReader(os.Stdin)` 这行代码,将会创建一个读取器,并将其与标准输入绑定。
`bufio.NewReader()` 构造函数的签名为:`func NewReader(rd io.Reader) *Reader`
该函数的实参可以是满足 `io.Reader` 接口的任意对象(任意包含有适当的 `Read()` 方法的对象,请参考[章节11.8](11.8.md)),函数返回一个新的带缓冲的 `io.Reader` 对象,它将从指定读取器(例如 `os.Stdin`)读取内容。
返回的读取器对象提供一个方法 `ReadString(delim byte)`,该方法从输入中读取内容,直到碰到 `delim` 指定的字符,然后将读取到的内容连同 `delim` 字符一起放到缓冲区。
`ReadString` 返回读取到的字符串,如果碰到错误则返回 `nil`。如果它一直读到文件结束,则返回读取到的字符串和 `io.EOF`。如果读取过程中没有碰到 `delim` 字符,将返回错误 `err != nil`
在上面的例子中,我们会读取键盘输入,直到回车键(\n被按下。
屏幕是标准输出 `os.Stdout``os.Stderr` 用于显示错误信息,大多数情况下等同于 `os.Stdout`
一般情况下,我们会省略变量声明,而使用 `:=`,例如:
```go
inputReader := bufio.NewReader(os.Stdin)
input, err := inputReader.ReadString('\n')
```
我们将从现在开始使用这种写法。
第二个例子从键盘读取输入,使用了 `switch` 语句:
**Listing 12.3—switch_input.go:**
```go
package main
import (
"fmt"
"os"
"bufio"
)
func main() {
inputReader := bufio.NewReader(os.Stdin)
fmt.Println("Please enter your name:")
input, err := inputReader.ReadString('\n')
if err != nil {
fmt.Println("There were errors reading, exiting program.")
return
}
fmt.Printf("Your name is %s", input)
// For Unix: test with delimiter "\n", for Windows: test with "\r\n"
switch input {
case "Philip\r\n": fmt.Println("Welcome Philip!")
case "Chris\r\n": fmt.Println("Welcome Chris!")
case "Ivo\r\n": fmt.Println("Welcome Ivo!")
default: fmt.Printf("You are not welcome here! Goodbye!")
}
// version 2:
switch input {
case "Philip\r\n": fallthrough
case "Ivo\r\n": fallthrough
case "Chris\r\n": fmt.Printf("Welcome %s\n", input)
default: fmt.Printf("You are not welcome here! Goodbye!\n")
}
// version 3:
switch input {
case "Philip\r\n", "Ivo\r\n": fmt.Printf("Welcome %s\n", input)
default: fmt.Printf("You are not welcome here! Goodbye!\n")
}
}
```
注意Unix和Windows的行结束符是不同的
**练习**
**Exercise 12.1:** word_letter_count.go
Write a program which reads text from the keybord. When the user enters S in order to signal the end of the input, the program shows 3 numbers:
i) the number of characters including spaces (but excluding \r and \n)
ii) the number of words
iii) the number of lines
**Exercise 12.2:** calculator.go
Make a simple (reverse polish notation) calculator. This program accepts input from the user in the
form of integers (maximum 999999) and operators (+, -, *, /).
The input is like this: number1 ENTER number2 ENTER operator ENTER result is displayed.
The programs stops if the user inputs “q”. Use the package stack you developed in Ex. 11.3
## 链接
- [目录](directory.md)
- 上一节:[读写数据](12.0.md)
- 下一节:[文件读写](12.2.md)

View File

@@ -89,7 +89,7 @@
## 第三部分Go 高级编程
- 第12章读写数据
- 第12章[读写数据](12.0.md)
- 第13章错误处理与测试
- 第14章goroutine 与 channel
- 第15章网络、模版与网页应用

View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"./pack1/pack1"
)
func main() {
var test1 string
test1 = pack1.ReturnStr()
fmt.Printf("ReturnStr from package1: %s\n", test1)
fmt.Printf(Integer from package1: %d\n, pack1.Pack1Int)
// fmt.Printf(“Float from package1: %f\n”, pack1.pack1Float)
}