Merge pull request #1 from Unknwon/master

合并最新版
This commit is contained in:
itfanr
2014-12-14 15:21:15 +08:00
38 changed files with 1794 additions and 208 deletions

View File

@@ -1,7 +1,7 @@
《Go入门指南》 《Go入门指南》
=================== ===================
在接触 Go 语言之后,对这门编程语言非常着迷,期间也陆陆续续开始一些帮助国内编程爱好者了解和发展 Go 语言的工作,比如开始录制视频教程[《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming)。但由于目前国内并没有比较好的 Go 语言书籍,而国外的优秀书籍因为英文的缘故在一定程度上也为不少 Go 语言爱好者带来了一些学习上的困扰,不仅为了加快扩散 Go 爱好者的国内群体,同时充分贯彻 [Asta谢](https://github.com/astaxie) 的为己为人精神,本人在完成阅读这本名叫 《The Way to Go》 之后,决定每天抽出一点时间来进行翻译的工作,并且以开源的形式免费分享给有需要的 Go 语言爱好者。 在接触 Go 语言之后,对这门编程语言非常着迷,期间也陆陆续续开始一些帮助国内编程爱好者了解和发展 Go 语言的工作,比如开始录制视频教程[《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming)。但由于目前国内并没有比较好的 Go 语言书籍,而国外的优秀书籍因为英文的缘故在一定程度上也为不少 Go 语言爱好者带来了一些学习上的困扰,不仅为了加快扩散 Go 爱好者的国内群体,本人在完成阅读这本名叫 《The Way to Go》 之后,决定每天抽出一点时间来进行翻译的工作,并且以开源的形式免费分享给有需要的 Go 语言爱好者。
尽管该书对目前 Go 语言版本来说有小部分内容相对过时,但是为当下不可多得的好书,部分内容已获得作者同意根据当前 Go 语言版本进行修改。 尽管该书对目前 Go 语言版本来说有小部分内容相对过时,但是为当下不可多得的好书,部分内容已获得作者同意根据当前 Go 语言版本进行修改。
@@ -9,11 +9,11 @@
## 翻译进度 ## 翻译进度
6.2 [参数与返回值](eBook/06.2.md) 7.5 [切片的复制与追加](eBook/07.5.md)
## 支持本书 ## 支持本书
如果你喜欢本书 《Go入门指南》你可以参与到本书的翻译或纠正工作中来具体请联系【无闻 E-mailjoe2010xtmf#163.com】,一同完善本书并帮助壮大 Go 语言在国内的学习群体,给大家提供更好的学习资源。 如果你喜欢本书 《Go入门指南》你可以参与到本书的翻译或纠正工作中来具体请联系【无闻 E-mailu#gogs.io】,一同完善本书并帮助壮大 Go 语言在国内的学习群体,给大家提供更好的学习资源。
## 交流社区 ## 交流社区
@@ -23,27 +23,21 @@
- 2012 年 3 月 28 日以前的博文中的内容基本过时,不要再看 - 2012 年 3 月 28 日以前的博文中的内容基本过时,不要再看
- 符合等式 ***百度+思考+失败+翻墙+谷歌+尝试=解决*** 的问题最好不要发问 - 符合等式 ***百度+思考+失败+翻墙+谷歌+尝试=解决*** 的问题最好不要发问
- 不要问 Go 现在的发展前景如何
- 不要问学习 Go 语言能不能找到工作
- 不要问现在 Go 语言有哪些实际应用
## 致谢 ## 致谢
- 本书原作者Ivo Balbaert - 本书原作者Ivo Balbaert
- 协助或参与翻译: - 参与翻译人员
- [zhanming](https://github.com/zhanming) - [zhanming](https://github.com/zhanming)
- [themorecolor](https://github.com/themorecolor) - [themorecolor](https://github.com/themorecolor)
- [everyx](https://github.com/everyx) - [everyx](https://github.com/everyx)
- [chidouhu](https://github.com/chidouhu) - [chidouhu](https://github.com/chidouhu)
- [spawnris](https://github.com/spawnris)
## 授权许可 ## 授权许可
除特别声明外,本书中的内容使用 [CC BY-SA 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/)(创作共用 署名-相同方式共享3.0 许可协议)授权,代码遵循 [BSD 3-Clause License](https://github.com/astaxie/build-web-application-with-golang/blob/master/LICENSE.md)3 项条款的 BSD 许可协议)。 除特别声明外,本书中的内容使用 [CC BY-SA 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/)(创作共用 署名-相同方式共享3.0 许可协议)授权,代码遵循 [BSD 3-Clause License](https://github.com/astaxie/build-web-application-with-golang/blob/master/LICENSE.md)3 项条款的 BSD 许可协议)。
## 捐助译者
如果您觉得本书翻译确实不错,并认为译者的努力值得肯定,可以通过 [此链接](https://me.alipay.com/obahua) 为译者提供小额捐助,以资鼓励。
## 开始阅读 ## 开始阅读
[前言](./eBook/preface.md) [前言](./eBook/preface.md)

View File

@@ -95,7 +95,7 @@
package main package main
func main() { func main() {
println(Hello, world) println("Hello", "world")
} }
切换相关目录到下,然后执行指令 `go run hello_world1.go`,将会打印信息:`Hello, world`。 切换相关目录到下,然后执行指令 `go run hello_world1.go`,将会打印信息:`Hello, world`。

View File

@@ -10,7 +10,7 @@
// #include <stdio.h> // #include <stdio.h>
// #include <stdlib.h> // #include <stdlib.h>
import “C” import "C"
名称 "C" 并不属于标准库的一部分,这只是 cgo 集成的一个特殊名称用于引用 C 的命名空间。在这个命名空间里所包含的 C 类型都可以被使用,例如 C.uint、C.long 等等,还有 libc 中的函数 C.random() 等也可以被调用。 名称 "C" 并不属于标准库的一部分,这只是 cgo 集成的一个特殊名称用于引用 C 的命名空间。在这个命名空间里所包含的 C 类型都可以被使用,例如 C.uint、C.long 等等,还有 libc 中的函数 C.random() 等也可以被调用。
@@ -26,12 +26,12 @@ Example 3.2 [c1.go](examples/chapter_3/CandGo/c1.go)
package rand package rand
// #include <stdlib.h> // #include <stdlib.h>
import “C” import "C"
func Random() int { func Random() int {
return int(C.random()) return int(C.random())
} }
func Seed(i int) { func Seed(i int) {
C.srandom(C.uint(i)) C.srandom(C.uint(i))
} }
C 当中并没有明确的字符串类型,如果你想要将一个 string 类型的变量从 Go 转换到 C可以使用 `C.CString(s)`;同样,可以使用 `C.GoString(cs)` 从 C 转换到 Go 中的 string 类型。 C 当中并没有明确的字符串类型,如果你想要将一个 string 类型的变量从 Go 转换到 C可以使用 `C.CString(s)`;同样,可以使用 `C.GoString(cs)` 从 C 转换到 Go 中的 string 类型。
@@ -49,12 +49,12 @@ Example 3.3 [c2.go](examples/chapter_3/CandGo/c2.go)
package print package print
// #include <stdio.h> // #include <stdio.h>
// #include <stdlib.h> // #include <stdlib.h>
import “C” import "C"
import unsafe import "unsafe"
func Print(s string) { func Print(s string) {
cs := C.CString(s) cs := C.CString(s)
defer C.free(unsafe.Pointer(cs)) defer C.free(unsafe.Pointer(cs))
C.fputs(cs, (*C.FILE)(C.stdout)) C.fputs(cs, (*C.FILE)(C.stdout))
} }
**构建 cgo 包** **构建 cgo 包**

View File

@@ -63,7 +63,7 @@ Go 中的包模型采用了显式依赖关系的机制来达到快速编译的
"os" "os"
) )
(它甚至还可以更短:`import ("fmt", "os")` 但使用 gofmt 后将会被强制换行) (它甚至还可以更短:`import ("fmt"; "os")` 但使用 gofmt 后将会被强制换行)
当你导入多个包时,导入的顺序会按照字母排序。 当你导入多个包时,导入的顺序会按照字母排序。

View File

@@ -28,7 +28,7 @@
这种因式分解关键字的写法一般用于声明全局变量。 这种因式分解关键字的写法一般用于声明全局变量。
当一个变量被声明之后系统自动赋予它该类型的零值int 为 0flost 为 0.0bool 为 falsestring 为空字符串,指针为 nil。记住所有的内存在 Go 中都是经过初始化的。 当一个变量被声明之后系统自动赋予它该类型的零值int 为 0float 为 0.0bool 为 falsestring 为空字符串,指针为 nil。记住所有的内存在 Go 中都是经过初始化的。
变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:`numShips`, `startDate` 变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:`numShips`, `startDate`

View File

@@ -100,7 +100,7 @@ Example 5.3 [string_conversion2.go](examples/chapter_5/string_conversion2.go)
**注意事项** **注意事项**
如果您下面一样,没有为多返回值的函数准备足够的变量来存放结果: 如果您下面一样,没有为多返回值的函数准备足够的变量来存放结果:
func mySqrt(f float64) (v float64, ok bool) { func mySqrt(f float64) (v float64, ok bool) {
if f < 0 { return } // error case if f < 0 { return } // error case

View File

@@ -219,7 +219,7 @@ for t, err = p.Token(); err == nil; t, err = p.Token() {
这是 Go 特有的一种的迭代结构您会发现它在许多情况下都非常有用它可以迭代任何一个集合包括数组和 map详见第 7 8 )。语法上很类似其它语言中 foreach 语句但您依旧可以获得每次迭代所对应的索引一般形式为`for ix, val := range coll { }` 这是 Go 特有的一种的迭代结构您会发现它在许多情况下都非常有用它可以迭代任何一个集合包括数组和 map详见第 7 8 )。语法上很类似其它语言中 foreach 语句但您依旧可以获得每次迭代所对应的索引一般形式为`for ix, val := range coll { }`
要注意的是`val` 始终为集合中对应索引的值拷贝因此它一般只具有只读性质对它所做的任何修改都不会影响到集合中原有的值** 译者注如果 `val` 为指针则会产生指针的拷贝依旧可以修改集合中的原值 **)。一个字符串是 Unicode 编码的字符或称之为 `rune`集合因此您也可以用它迭代字符串 要注意的是`val` 始终为集合中对应索引的值拷贝因此它一般只具有只读性质对它所做的任何修改都不会影响到集合中原有的值**译者注如果 `val` 为指针则会产生指针的拷贝依旧可以修改集合中的原值**)。一个字符串是 Unicode 编码的字符或称之为 `rune`集合因此您也可以用它迭代字符串
``` ```
for pos, char := range str { for pos, char := range str {

View File

@@ -85,7 +85,7 @@ Go语言不支持这项特性的主要原因是函数重载需要进行多余的
函数值functions value之间可以相互比较如果它们引用的是相同的函数或者都是nil的话则认为它们是相同的函数。函数不能在其它函数里面声明不能嵌套不过我们可以通过使用匿名函数参考[第 6.8 节](06.8.md))来破除这个限制。 函数值functions value之间可以相互比较如果它们引用的是相同的函数或者都是nil的话则认为它们是相同的函数。函数不能在其它函数里面声明不能嵌套不过我们可以通过使用匿名函数参考[第 6.8 节](06.8.md))来破除这个限制。
目前Go没有泛型generic的概念也就是说它不支持那种支持多种类型的函数。不过在大部分情况下可以通过接口interface特别是空接口与类型选择type switch参考[第 11.12 节](11.12.md))与/或者通过使用反射reflection参考[第 6.8 节](06.8.md))来实现相似的功能。使用这些技术将导致代码更为复杂、性能更为下,所以在非常注意性能的的场合,最好是为每一个类型单独创建一个函数,而且代码可读性更强。 目前Go没有泛型generic的概念也就是说它不支持那种支持多种类型的函数。不过在大部分情况下可以通过接口interface特别是空接口与类型选择type switch参考[第 11.12 节](11.12.md))与/或者通过使用反射reflection参考[第 6.8 节](06.8.md))来实现相似的功能。使用这些技术将导致代码更为复杂、性能更为下,所以在非常注意性能的的场合,最好是为每一个类型单独创建一个函数,而且代码可读性更强。
## 链接 ## 链接

42
eBook/06.10.md Normal file
View File

@@ -0,0 +1,42 @@
# 6.10 使用闭包调试
当您在分析和调试复杂的程序时,无数个函数在不同的代码文件中相互调用,如果这时候能够准确地知道哪个文件中的具体哪个函数正在执行,对于调试是十分有帮助的。您可以使用 `runtime``log` 包中的特殊函数来实现这样的功能。包 `runtime` 中的函数 `Caller()` 提供了相应的信息,因此可以在需要的时候实现一个 `where()` 闭包函数来打印函数执行的位置:
```go
where := func() {
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
where()
// some code
where()
// some more code
where()
```
您也可以设置 `log` 包中的 flag 参数来实现:
```go
log.SetFlags(log.Llongfile)
log.Print("")
```
或使用一个更加简短版本的 `where` 函数:
```go
var where = log.Print
func func1() {
where()
... some code
where()
... some code
where()
}
```
## 链接
- [目录](directory.md)
- 上一节:[应用闭包:将函数作为返回值](06.9.md)
- 下一节:[计算函数执行时间](06.11.md)

21
eBook/06.11.md Normal file
View File

@@ -0,0 +1,21 @@
# 6.11 计算函数执行时间
有时候,能够知道一个计算执行消耗的时间是非常有意义的,尤其是在对比和基准测试中。最简单的一个办法就是在计算开始之前设置一个起始时候,再由计算结束时的结束时间,最后取出它们的差值,就是这个计算所消耗的时间。想要实现这样的做法,可以使用 `time` 包中的 `Now()``Sub` 函数:
```go
start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
```
您可以查看 Listing 6.20—fibonacci.go 作为实例学习。
如果您对一段代码进行了所谓的优化,请务必对它们之间的效率进行对比再做出最后的判断。在接下来的章节中,我们会学习如何进行有价值的优化操作。
## 链接
- [目录](directory.md)
- 上一节:[使用闭包调试](06.10.md)
- 下一节:[通过内存缓存来提升性能](06.12.md)

61
eBook/06.12.md Normal file
View File

@@ -0,0 +1,61 @@
# 6.12 通过内存缓存来提升性能
当在进行大量的计算时,提升性能最直接有效的一种方式就是避免重复计算。通过在内存中缓存和重复利用相同计算的结果,称之为内存缓存。最明显的例子就是生成斐波那契数列的程序(详见第 6.6 和 6.11 节):
要计算数列中第 n 个数字,需要先得到之前两个数的值,但很明显绝大多数情况下前两个数的值都是已经计算过的。即每个更后面的数都是基于之前计算结果的重复计算,正如 listing 6.11 - fibonnaci.go 所展示的那样。
而我们要做就是将第 n 个数的值存在数组中索引为 n 的位置(详见第 7 章),然后在数组中查找是否已经计算过,如果没有找到,则再进行计算。
程序 Listing 6.17 - fibonacci_memoization.go 就是依照这个原则实现的,下面是计算到第 40 位数字的性能对比:
- 普通写法4.730270 秒
- 内存缓存0.001000 秒
内存缓存的优势显而易见,而且您还可以将它应用到其它类型的计算中,例如使用 map详见第 7 章而不是数组或切片Listing 6.21 - fibonacci_memoization.go
```go
package main
import (
"fmt"
"time"
)
const LIM = 41
var fibs [LIM]uint64
func main() {
var result uint64 = 0
start := time.Now()
for i := 0; i < LIM; i++ {
result = fibonacci(i)
fmt.Printf("fibonacci(%d) is: %d\n", i, result)
}
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
}
func fibonacci(n int) (res uint64) {
// memoization: check if fibonacci(n) is already known in array:
if fibs[n] != 0 {
res = fibs[n]
return
}
if n <= 1 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
fibs[n] = res
return
}
```
内存缓存的技术在使用计算成本相对昂贵的函数时非常有用(不仅限于例子中的递归),譬如大量进行相同参数的运算。这种技术还可以应用于纯函数中,即相同输入必定获得相同输出的函数。
## 链接
- [目录](directory.md)
- 上一节:[计算函数执行时间](06.11.md)
- 下一章:[数组与切片](07.0.md)

View File

@@ -1,19 +1,107 @@
## 啊哦,亲,你看得也太快了。。。还没翻译完呢 0 0
要不等到 **2014 年 7 月 12 日** 再来看看吧~~
这里还有一些其它的学习资源噢~
- [《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming)
- [《Go Web编程》](https://github.com/astaxie/build-web-application-with-golang)
- [《Go名库讲解》](https://github.com/Unknwon/go-rock-libraries-showcases)
神马?你说你不想学习?那好吧,去逛逛看看行情也行~
- [Go Walker](https://gowalker.org) **Go 项目 API 文档在线浏览工具**
- [Go 中国社区](http://bbs.go-china.org)
- [Go语言学习园地](http://studygolang.com/)
- [Golang中国](http://golangtc.com)
# 6.3 传递变长参数 # 6.3 传递变长参数
如果函数的最后一个参数是采用 `...type` 的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0这样的函数称为变参函数。
func myFunc(a, b, arg ...int) {}
这个函数接受一个类似某个类型的 slice 的参数(详见第 7 章),该参数可以通过第 5.4.4 章中提到的 for 循环结构迭代。
示例函数和调用:
func Greeting(prefix string, who ...string)
Greeting("hello:", "Joe", "Anna", "Eileen")
在 Greeting 函数中,变量 `who` 的值为 `[]string{"Joe", "Anna", "Eileen"}`
如果参数被存储在一个数组 `arr` 中,则可以通过 `arr...` 的形式来传递参数调用变参函数。
示例 6.7 varnumpar.go
```go
package main
import "fmt"
func main() {
x := Min(1, 3, 2, 0)
fmt.Printf("The minimum is: %d\n", x)
arr := []int{7,9,3,5,1}
x = Min(arr...)
fmt.Printf("The minimum in the array arr is: %d", x)
}
func Min(a ...int) int {
if len(a)==0 {
return 0
}
min := a[0]
for _, v := range a {
if v < min {
min = v
}
}
return min
}
```
输出:
The minimum is: 0
The minimum in the array arr is: 1
**练习 6.3** varargs.go
写一个函数,该函数接受一个变长参数并对每个元素进行换行打印。
一个接受变长参数的函数可以将这个参数作为其它函数的参数进行传递:
```go
function F1(s string) {
F2(s )
F3(s)
}
func F2(s string) { }
func F3(s []string) { }
```
变长参数可以作为对应类型的 slice 进行二次传递。
但是如果变长参数的类型并不是都相同的呢?使用 5 个参数来进行传递并不是很明智的选择,有 2 种方案可以解决这个问题:
1. 使用结构(详见第 10 章):
定义一个结构类型,假设它叫 `Options`,用以存储所有可能的参数:
```go
type Options struct {
par1 type1,
par2 type2,
...
}
```
函数 F1 可以使用正常的参数 a 和 b以及一个没有任何初始化的 Options 结构: `F1(a, b, Options {})`。如果需要对选项进行初始化,则可以使用 `F1(a, b, Options {par1:val1, par2:val2})`。
2. 使用空接口:
如果一个变长参数的类型没有被指定,则可以使用默认的空接口 `interface{}`,这样就可以接受任何类型的参数(详见第 11.9 节)。该方案不仅可以用于长度未知的参数,还可以用于任何不确定类型的参数。一般而言我们会使用一个 for-range 循环以及 switch 结构对每个参数的类型进行判断:
```go
func typecheck(..,..,values … interface{}) {
for _, value := range values {
switch v := value.(type) {
case int: …
case float: …
case string: …
case bool: …
default: …
}
}
}
```
## 链接
- [目录](directory.md)
- 上一节:[参数与返回值](06.2.md)
- 下一节:[defer 和追踪](06.4.md)

243
eBook/06.4.md Normal file
View File

@@ -0,0 +1,243 @@
# 6.4 defer 和追踪
关键字 defer 允许我们推迟到函数返回之前(或任意位置执行 `return` 语句之后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为 `return` 语句同样可以包含一些操作,而不是单纯地返回某个值)。
关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 `finally` 语句块,它一般用于释放某些已分配的资源。
下面这个示例很好地解释了它的用法Listing 6.8 defer.go
```go
package main
import "fmt"
func main() {
Function1()
}
func Function1() {
fmt.Printf("In Function1 at the top\n")
defer Function2()
fmt.Printf("In Function1 at the bottom!\n")
}
func Function2() {
fmt.Printf("Function2: Deferred until the end of the calling function!")
}
```
输出:
```
In Function1 at the top
In Function1 at the bottom!
Function2: Deferred until the end of the calling function!
```
请将 defer 关键字去掉并对比输出结果。
使用 defer 的语句同样可以接受参数,下面这个例子就会在执行 defer 语句时打印 `0`
```go
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
```
当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出):
```go
func f() {
for i := 0; i < 5; i++ {
defer fmt.Printf(%d , i)
}
}
```
上面的代码将会输出:`4 3 2 1 0`
关键字 defer 允许我们进行一些函数执行完成后的收尾工作,例如:
1. 关闭文件流:
// open a file
defer file.Close() (详见第 12.2 节)
2. 解锁一个加锁的资源
mu.Lock()
defer mu.Unlock() (详见第 9.3 节)
3. 打印最终报告
printHeader()
defer printFooter()
4. 关闭数据库链接
// open a database connection
defer disconnectFromDB()
合理使用 defer 语句能够使得代码更加简洁。
以下代码模拟了上面描述的第 4 种情况:
```go
package main
import "fmt"
func main() {
doDBOperations()
}
func connectToDB() {
fmt.Println("ok, connected to db")
}
func disconnectFromDB() {
fmt.Println("ok, disconnected from db")
}
func doDBOperations() {
connectToDB()
fmt.Println("Defering the database disconnect.")
defer disconnectFromDB() //function called here with defer
fmt.Println("Doing some DB operations ...")
fmt.Println("Oops! some crash or network error ...")
fmt.Println("Returning from function here!")
return //terminate the program
// deferred function executed here just before actually returning, even if
// there is a return or abnormal termination before
}
```
输出:
```
ok, connected to db
Defering the database disconnect.
Doing some DB operations ...
Oops! some crash or network error ...
Returning from function here!
ok, disconnected from db
```
**使用 defer 语句实现代码追踪**
一个基础但十分实用的实现代码执行追踪的方案就是在进入和离开某个函数打印相关的消息,即可以提炼为下面两个函数:
```go
func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }
```
以下代码展示了何时调用两个函数:
Listing 6.10—_defer_tracing.go:
```go
package main
import "fmt"
func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }
func a() {
trace("a")
defer untrace("a")
fmt.Println("in a")
}
func b() {
trace("b")
defer untrace("b")
fmt.Println("in b")
a()
}
func main() {
b()
}
```
输出:
```
entering: b
in b
entering: a
win a
leaving: a
leaving: b
```
上面的代码还可以修改为更加简便的版本Listing 6.11—_defer_tracing2.go
```go
package main
import "fmt"
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a"))
fmt.Println("in a")
}
func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}
func main() {
b()
}
```
**使用 defer 语句来记录函数的参数与返回值**
下面的代码展示了另一种在调试时使用 defer 语句的手法Listing 6.12—_defer_logvalues.go
```go
package main
import (
"io"
"log"
)
func func1(s string) (n int, err error) {
defer func() {
log.Printf("func1(%q) = %d, %v", s, n, err)
}()
return 7, io.EOF
}
func main() {
func1("Go")
}
```
输出:
Output: 2011/10/04 10:46:11 func1(“Go”) = 7, EOF
## 链接
- [目录](directory.md)
- 上一节:[传递变长参数](06.3.md)
- 下一节:[内置函数](06.5.md)

21
eBook/06.5.md Normal file
View File

@@ -0,0 +1,21 @@
# 6.5 内置函数
Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作例如len、cap 和 append或必须用于系统级的操作例如panic。因此它们需要直接获得编译器的支持。
以下是一个简单的列表,我们会在后面的章节中对它们进行逐个深入的讲解。
|名称|说明|
|---|---|
|close|用于管道通信|
|len、cap|len 用于返回某个类型的长度或数量字符串、数组、切片、map 和管道cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map|
|new、make|new 和 make 均是用于分配内存new 用于值类型和用户定义的类型如自定义结构make 用户内置引用类型切片、map 和管道。它们的用法就像是函数但是将类型作为参数new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针(详见第 10.1 节)。它也可以被用于基本类型:`v := new(int)`。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作(详见第 7.2.3/4 节、第 8.1.1 节和第 14.2.1 节)**new() 是一个函数,不要忘记它的括号**|
|copy、append|用于复制和连接切片|
|panic、recover|两者均用于错误处理机制|
|print、println|底层打印函数(详见第 4.2 节),在部署环境中建议使用 fmt 包|
|complex、real imag|用于创建和操作复数(详见第 4.5.2.2 节)|
## 链接
- [目录](directory.md)
- 上一节:[defer 和追踪](06.4.md)
- 下一节:[递归函数](06.6.md)

119
eBook/06.6.md Normal file
View File

@@ -0,0 +1,119 @@
# 6.6 递归函数
当一个函数在其函数体内调用自身,则称之为递归。最经典的例子便是计算斐波那契数列,即每个数均为前两个数之和。
数列如下所示:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, …
下面的程序可用于生成该数列Listing 6.13 fibonacci.go
```go
package main
import "fmt"
func main() {
result := 0
for i := 0; i <= 10; i++ {
result = fibonacci(i)
fmt.Printf("fibonacci(%d) is: %d\n", i, result)
}
}
func fibonacci(n int) (res int) {
if n <= 1 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
return
}
```
输出:
```
fibonacci(0) is: 1
fibonacci(1) is: 1
fibonacci(2) is: 2
fibonacci(3) is: 3
fibonacci(4) is: 5
fibonacci(5) is: 8
fibonacci(6) is: 13
fibonacci(7) is: 21
fibonacci(8) is: 34
fibonacci(9) is: 55
fibonacci(10) is: 89
```
许多问题都可以使用优雅的递归来解决,比如说著名的快速排序算法。
在使用递归函数时经常会遇到的一个重要问题就是栈溢出:一般出现在大量的递归调用导致的程序栈内存分配耗尽。这个问题可以通过一个名为懒惰评估的技术解决,在 Go 语言中我们可以使用管道channel和 goroutine详见第 14.8 节)来实现。练习 14.12 也会通过这个方案来优化斐波那契数列的生成问题。
Go 语言中也可以使用相互调用的递归函数:多个函数之间相互调用形成闭环。因为 Go 语言编译器的特殊性,这些函数的声明顺序可以是任意的。下面这个简单的例子展示了函数 odd 和 even 之间的相互调用Listing 6.14 mut_recurs.go
```go
package main
import (
"fmt"
)
func main() {
fmt.Printf("%d is even: is %t\n", 16, even(16)) // 16 is even: is true
fmt.Printf("%d is odd: is %t\n", 17, odd(17))
// 17 is odd: is true
fmt.Printf("%d is odd: is %t\n", 18, odd(18))
// 18 is odd: is false
}
func even(nr int) bool {
if nr == 0 {
return true
}
return odd(RevSign(nr) - 1)
}
func odd(nr int) bool {
if nr == 0 {
return false
}
return even(RevSign(nr) - 1)
}
func RevSign(nr int) int {
if nr < 0 {
return -nr
}
return nr
}
```
### 练习题
**练习 6.4**
重写本节中生成斐波那契数列的程序并返回两个命名返回值(详见第 6.2 节),即数列中的位置和对应的值,例如 5 与 489 与 10。
**练习 6.5**
使用递归函数从 10 打印到 1。
**练习 6.6**
实现一个输出前 30 个整数的阶乘的程序。
n! 的阶乘定义为:`n! = n * (n-1)!, 0! = 1`,因此它非常适合使用递归函数来实现。
然后,使用命名返回值来实现这个程序的第二个版本。
特别注意的是,使用 int 类型最多只能计算到 12 的阶乘,因为一般情况下 int 类型的大小为 32 位,继续计算会导致溢出错误。那么,如何才能解决这个问题呢?
最好的解决方案就是使用 big 包(详见第 9.4 节)。
## 链接
- [目录](directory.md)
- 上一节:[内置函数](06.5.md)
- 下一节:[将函数作为参数](06.7.md)

59
eBook/06.7.md Normal file
View File

@@ -0,0 +1,59 @@
# 6.7 将函数作为参数
函数可以作为其它函数的参数进行传递然后在其它函数内调用执行一般称之为回调。下面是一个将函数作为参数的简单例子function_parameter.go
```go
package main
import (
"fmt"
)
func main() {
callback(1, Add)
}
func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}
```
输出:
The sum of 1 and 2 is: 3
将函数作为参数的最好的例子是函数 `strings.IndexFunc()`
该函数的签名是 `func IndexFunc(s string, f func(c int) bool) int`,它的返回值是在函数 `f(c)` 返回 true、-1 或从未返回时的索引值。
例如 `strings.IndexFunc(line, unicode.IsSpace)` 就会返回 `line` 中第一个空白字符的索引值。当然,您也可以书写自己的函数:
```go
func IsAscii(c int) bool {
if c > 255 {
return false
}
return true
}
```
在第 14.10.1 节中,我们将会根据一个客户端/服务端程序作为示例对这个用法进行深入讨论。
```go
type binOp func(a, b int) int
func run(op binOp, req *Request) { }
```
**练习 6.7**
`strings` 中的 `Map` 函数和 `strings.IndexFunc()` 一样都是非常好的使用例子。请学习它的源代码并基于该函数书写一个程序,要求将指定文本内的所有非 ASCII 字符替换成 `?` 或空格。您需要怎么做才能删除这些字符呢?
## 链接
- [目录](directory.md)
- 上一节:[将函数作为参数](06.7.md)
- 下一节:[闭包](06.8.md)

100
eBook/06.8.md Normal file
View File

@@ -0,0 +1,100 @@
# 6.8 闭包
当我们不希望给函数起名字的时候,可以使用匿名函数,例如:`func(x, y int) int { return x + y }`
这样的一个函数不能够独立存在(编译器会返回错误:`non-declaration statement
outside function body`),但可以被赋值于某个变量,即保存函数的地址到变量中:`fplus := func(x, y int) int { return x + y }`,然后通过变量名对函数进行调用:`fplus(3,4)`
当然,您也可以直接对匿名函数进行调用:`func(x, y int) int { return x + y } (3, 4)`
下面是一个计算从 1 到 1 百万整数的总和的匿名函数:
```go
func() {
sum = 0.0
for i := 1; i <= 1e6; i++ {
sum += i
}
}()
```
表示参数列表的第一对括号必须紧挨着关键字 `func`,因为匿名函数没有名称。花括号 `{}` 涵盖着函数体,最后的一对括号表示对该匿名函数的调用。
下面的例子展示了如何将匿名函数赋值给变量并对其进行调用function_literal.go
```go
package main
import "fmt"
func main() {
f()
}
func f() {
for i := 0; i < 4; i++ {
g := func(i int) { fmt.Printf("%d ", i) }
g(i)
fmt.Printf(" - g is of type %T and has value %v\n", g, g)
}
}
```
输出:
```
0 - g is of type func(int) and has value 0x681a80
1 - g is of type func(int) and has value 0x681b00
2 - g is of type func(int) and has value 0x681ac0
3 - g is of type func(int) and has value 0x681400
```
我们可以看到变量 `g` 代表的是 `func(int)`,变量的值是一个内存地址。
所以我们实际上拥有的是一个函数值:匿名函数可以被赋值给变量并作为值使用。
**练习 6.8** 在 main 函数中写一个用于打印 `Hello World` 字符串的匿名函数并赋值给变量 `fv`,然后调用该函数并打印变量 `fv` 的类型。
匿名函数像所有其它可以接受或不接受参数。下面的例子展示了如何传递参数到匿名函数中:
```go
func (u string) {
fmt.Println(u)
}(v)
```
请学习以下示例并思考return_defer.go函数 `f` 返回时,变量 `ret` 的值是什么?
```go
package main
import "fmt"
func f() (ret int) {
defer func() {
ret++
}()
return 1
}
func main() {
fmt.Println(f())
}
```
变量 `ret` 的值为 2因此 `ret++`,这是在执行 `reutrn 1` 语句后发生的。
这可用于在返回语句之后修改返回的 `error` 时使用。
**defer 语句和匿名函数**
关键字 `defer` (详见第 6.4 节)经常配合匿名函数使用,它可以用于改变函数的命名返回值。
匿名函数还可以配合 `go` 关键字来作为 goroutine 使用(详见第 14 章和第 16.9 节)。
匿名函数同样被称之为闭包(函数式语言的术语):它们被允许调用定义在其它环境下的变量。闭包可使得某个函数捕捉到一些外部状态,例如:函数被创建时的状态。另一种表示方式为:一个闭包继承了函数所声明时的作用域。这种状态(作用域内的变量)都被共享到闭包的环境中,因此这些变量可以在闭包中被操作,直到被销毁,详见第 6.9 节中的示例。闭包经常被用作包装函数:它们会预先定义好 1 个或多个参数以用于包装,详见下一节中的示例。另一个不错的应用就是使用闭包来完成更加简洁的错误检查(详见第 16.10.2 节)。
## 链接
- [目录](directory.md)
- 上一节:[递归函数](06.6.md)
- 下一节:[应用闭包:将函数作为返回值](06.9.md)

137
eBook/06.9.md Normal file
View File

@@ -0,0 +1,137 @@
# 6.9 应用闭包:将函数作为返回值
在程序 `function_return.go` 中我们将会看到函数 Add2 和 Adder 均会返回签名为 `func(b int) int` 的函数:
```go
func Add2() (func(b int) int)
func Adder(a int) (func(b int) int)
```
函数 Add2 不接受任何参数,但函数 Adder 接受一个 int 类型的整数作为参数。
我们也可以将 Adder 返回的函数存到变量中function_return.go
```go
package main
import "fmt"
func main() {
// make an Add2 function, give it a name p2, and call it:
p2 := Add2()
fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3))
// make a special Adder function, a gets value 3:
TwoAdder := Adder(2)
fmt.Printf("The result is: %v\n", TwoAdder(3))
}
func Add2() func(b int) int {
return func(b int) int {
return b + 2
}
}
func Adder(a int) func(b int) int {
return func(b int) int {
return a + b
}
}
```
输出:
```
Call Add2 for 3 gives: 5
The result is: 5
```
下例为一个略微不同的实现function_closure.go
```go
package main
import "fmt"
func main() {
var f = Adder()
fmt.Print(f(1), " - ")
fmt.Print(f(20), " - ")
fmt.Print(f(300))
}
func Adder() func(int) int {
var x int
return func(delta int) int {
x += delta
return x
}
}
```
函数 Adder() 现在被赋值到变量 f 中(类型为 `func(int) int`)。
输出:
1 - 21 - 321
三次调用函数 f 的过程中函数 Adder() 中变量 delta 的值分别为1、20 和 300。
我们可以看到,在多次调用中,变量 x 的值是被保留的,即 `0 + 1 = 1`,然后 `1 + 20 = 21`,最后 `21 + 300 = 321`:闭包函数保存并积累其中的变量的值,不管外部函数退出与否,它都能够继续操作外部函数中的局部变量。
这些局部变量同样可以是参数,例如之前例子中的 `Adder(as int)`
这些例子清楚地展示了如何在 Go 语言中使用闭包。
在闭包中使用到的变量可以是在闭包函数体内声明的,也可以是在外部函数声明的:
```go
var g int
go func(i int) {
s := 0
for j := 0; j < i; j++ { s += j }
g = s
}(1000) // Passes argument 1000 to the function literal.
```
这样闭包函数就能够被应用到整个集合的元素上,并修改它们的值。然后这些变量就可以用于表示或计算全局或平均值。
**练习 6.9** 不使用递归但使用闭包改写第 6.6 节中的斐波那契数列程序。
**练习 6.10**
学习并理解以下程序的工作原理:
一个返回值为另一个函数的函数可以被称之为工厂函数,这在您需要创建一系列相似的函数的时候非常有用:书写一个工厂函数而不是针对每种情况都书写一个函数。下面的函数演示了如何动态返回追加后缀的函数:
```go
func MakeAddSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
```
现在,我们可以生成如下函数:
```go
addBmp := MakeAddSuffix(.bmp)
addJpeg := MakeAddSuffix(.jpeg)
```
然后调用它们:
```go
addBmp(file) // returns: file.bmp
addJpeg(file) // returns: file.jpeg
```
可以返回其它函数的函数和接受其它函数作为参数的函数均被称之为高阶函数,是函数式语言的特点。我们已经在第 6.7 中得知函数也是一种值,因此很显然 Go 语言具有一些函数式语言的特性。闭包在 Go 语言中非常常见,常用于 goroutine 和管道操作(详见第 14.8-14.9 节)。在第 11.14 节的程序中,我们将会看到 Go 语言中的函数在处理混合对象时的强大能力。
## 链接
- [目录](directory.md)
- 上一节:[闭包](06.8.md)
- 下一节:[使用闭包调试](06.10.md)

View File

@@ -1,9 +1,11 @@
#7.0 数组与 # 7.0 数组与
本章我们从包含大量项的检测数据结构开始例如数组和map我们称之为容器。在这里很明显Go受到了python的影响。
以[ ]符号标识的数组类型几乎在所有的编程语言中都是一个基本主力。Go语言中的数组也是类似的只是有一些特点。Go没有C那么灵活但是拥有分片slice类型。这是一种建立在Go语言数组类型智商的抽象要想理解slice我们必须先理解数组。数组有特定的用处但是却有一些呆板所以在Go语言的代码里并不是特别常见。相对的slice确实随处可见的。它们构建在数组之上并且提供更强大的能力和便捷 这章我们开始剖析 **容器**, 它是可以包含大量条目item的数据结构, 例如数组、切片和 map。从这看到 Go 明显受到 Python 的影响
`[]` 符号标识的数组类型几乎在所有的编程语言中都是一个基本主力。Go 语言中的数组也是类似的只是有一些特点。Go 没有 C 那么灵活但是拥有切片slice类型。这是一种建立在 Go 语言数组类型智商的抽象,要想理解切片我们必须先理解数组。数组有特定的用处,但是却有一些呆板,所以在 Go 语言的代码里并不是特别常见。相对的,切片确实随处可见的。它们构建在数组之上并且提供更强大的能力和便捷。
## 链接
##链接
- [目录](directory.md) - [目录](directory.md)
- 上一[]() - 上一[通过内存缓存来提升性能](06.12.md)
- 下一节:[声明和初始化](07.1.md) - 下一节:[声明和初始化](07.1.md)

View File

@@ -1,36 +1,38 @@
#7.1 声明和初始化 # 7.1 声明和初始化
157
##7.1.1 概念 ##7.1.1 概念
数组是具有相同`唯一类型`的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以[5]int和[10]int是属于不同类型的。数组的编译时值初始化是按照数组顺序完成的如下 数组是具有相同 **唯一类型** 的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以[5]int和[10]int是属于不同类型的。数组的编译时值初始化是按照数组顺序完成的如下
**注意事项** **注意事项**
如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型(参考[第11章](11.9.md))。当使用值时我们必须先做一个类型判断(参考[第11章](11.3.md))。 如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型(参考 [ 11 ](11.9.md))。当使用值时我们必须先做一个类型判断(参考 [ 11 ](11.3.md))。
数组元素可以通过`索引`位置来读取或者修改索引从0开始第一个元素索引为0第二个索引为1以此类推。数组以0开始在所有类C语言中是相似的。元素的数目也称为`length`或者数组大小必须是固定的并且在声明该数组时就给出编译时需要知道数组长度以便分配内存数组长度最大为2Gb。 数组元素可以通过 **索引**位置来读取或者修改索引从0开始第一个元素索引为 0第二个索引为 1以此类推。数组以 0 开始在所有类C语言中是相似的。元素的数目也称为长度或者数组大小必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组长度最大为 2Gb。
声明的格式是: var identifier [len]type 声明的格式是: `var identifier [len]type`
例如: var arr1 [5]int 例如: `var arr1 [5]int`
在内存中的结构是:![](images/7.1_fig7.1.png?raw=true) 在内存中的结构是:![](images/7.1_fig7.1.png?raw=true)
每个元素是一个整形值当声明数组时所有的元素都会被自动初始化为默认值0. 每个元素是一个整形值当声明数组时所有的元素都会被自动初始化为默认值0.
arr1的长度是5索引范围从0到len(arr1)-1 arr1 的长度是 5索引范围从 0 到 len(arr1)-1
第一个元素是arr1[0],第3个元素是arr1[2];总体来说索引i代表的元素是arr1[i]最后一个元素是arr1[len(arr1)-1]。 第一个元素是 arr1[0],第个元素是 arr1[2];总体来说索引 i 代表的元素是 arr1[i],最后一个元素是 arr1[len(arr1)-1]。
对索引项为i的数组元素赋值可以这么操作arr[i] = value所以数组是`可变的` 对索引项为 i 的数组元素赋值可以这么操作arr[i] = value所以数组是 **可变的**
只有有效的索引可以被使用当使用等于或者大于len(arr1)的索引时如果编译器可以检测到会给出索引超限的提示信息如果检测不到的话编译会通过而运行时会panic:(参考[第13章](13.0.md) 只有有效的索引可以被使用,当使用等于或者大于 len(arr1) 的索引时:如果编译器可以检测到,会给出索引超限的提示信息;如果检测不到的话编译会通过而运行时会 panic:(参考 [ 13 ](13.0.md)
runtime error: index out of range runtime error: index out of range
由于索引的存在遍历数组的方法自然就是使用for-construct: 由于索引的存在,遍历数组的方法自然就是使用 for-construct:
- 通过for初始化数组项 - 通过 for 初始化数组项
- 通过for打印数组元素 - 通过 for 打印数组元素
- 通过for依次处理元素 - 通过 for 依次处理元素
示例 7.1 [for_arrays.go](exmaples/chapter_7/for_arrays.go) 示例 7.1 [for_arrays.go](exmaples/chapter_7/for_arrays.go)
@@ -73,7 +75,7 @@ IDIOM:
... ...
} }
在这里i也是数组的索引当然这两种for-construct方式对于(slices)参考[7](07.2.md)来说也同样适用 在这里i也是数组的索引当然这两种 for-construct 方式对于(slices)参考 [ 7 ](07.2.md)来说也同样适用
**问题 7.1** 下面代码段的输出是什么 **问题 7.1** 下面代码段的输出是什么
@@ -82,20 +84,20 @@ IDIOM:
fmt.Println("Array item", i, "is", a[i]) fmt.Println("Array item", i, "is", a[i])
} }
Go语言中的数组是一种`值类型`不像C/C++中是指向首元素的指针所以可以通过`new()`来创建 var arr1 = new([5]int) Go语言中的数组是一种 **值类型**不像C/C++中是指向首元素的指针所以可以通过 `new()` 来创建 `var arr1 = new([5]int)`
那么这种方式和var arr2 [5]int的区别是什么呢arr1的类型是*[5]int而arr2的类型是[5]int 那么这种方式和 `var arr2 [5]int` 的区别是什么呢arr1 的类型是 `*[5]int` arr2的类型是 `[5]int`
这样的结果就是当把一个数组赋值给另一个时需要在做一次数组内存的拷贝操作例如 这样的结果就是当把一个数组赋值给另一个时需要在做一次数组内存的拷贝操作例如
arr := arr1 arr := arr1
arr2[2] = 100 arr2[2] = 100
这样两个数组就有了不同的值在赋值后修改arr2不会对arr1生效 这样两个数组就有了不同的值在赋值后修改 arr2 不会对 arr1 生效
所以在函数中数组作为参数传入时如func1(arr1)会产生一次数组拷贝func1方法不会修改原始的数组arr1 所以在函数中数组作为参数传入时 func1(arr1)会产生一次数组拷贝func1 方法不会修改原始的数组 arr1
如果你想修改原数组那么arr1必须通过&操作符以引用方式传过来例如func1(&arr1下面是一个例子 如果你想修改原数组那么 arr1 必须通过&操作符以引用方式传过来例如 func1(&arr1下面是一个例子
Example 7.2 [pointer_array.go](examples/chapter_7/pointer_array.go): Example 7.2 [pointer_array.go](examples/chapter_7/pointer_array.go):
@@ -115,7 +117,7 @@ Example 7.2 [pointer_array.go](examples/chapter_7/pointer_array.go):
[0 0 0] [0 0 0]
&[0 0 0] &[0 0 0]
另一种方法就是生成数组分片slice并将其传递给函数参见传递数组参数给函数7.1.4节 另一种方法就是生成数组切片并将其传递给函数参见传递数组参数给函数7.1.4节
**练习** **练习**
@@ -125,8 +127,9 @@ Example 7.2 [pointer_array.go](examples/chapter_7/pointer_array.go):
练习7.3fibonacci_array.go: 在6.6节我们看到了一个递归计算Fibonacci数值的方法但是通过数组我们可以更快的计算出Fibonacci数完成该方法并打印出前50个Fibonacci数字 练习7.3fibonacci_array.go: 在6.6节我们看到了一个递归计算Fibonacci数值的方法但是通过数组我们可以更快的计算出Fibonacci数完成该方法并打印出前50个Fibonacci数字
##7.1.2 数组常量 ## 7.1.2 数组常量
如果数组值已经提前知道了那么可以通过`数组常量`的方法来初始化数组而不用依次使用[]=方法。(所有的组成元素都有相同的常量语法
如果数组值已经提前知道了那么可以通过 **数组常量** 的方法来初始化数组而不用依次使用 `[]=` 方法所有的组成元素都有相同的常量语法)。
Example 7.3 [array_literals.go](examples/chapter_7/array_literals.go) Example 7.3 [array_literals.go](examples/chapter_7/array_literals.go)
@@ -145,19 +148,19 @@ Example 7.3 [array_literals.go](examples/chapter_7/array_literals.go)
} }
} }
第一种变化 var arrAge = [5]int{18, 20, 15, 22, 16} 第一种变化 `var arrAge = [5]int{18, 20, 15, 22, 16}`
注意[5]int可以从左边起开始忽略[10]int {1, 2, 3} :这是一个有10个元素的数组除了前三个元素外其他元素都为0 注意 `[5]int` 可以从左边起开始忽略`[10]int {1, 2, 3}` :这是一个有 10 个元素的数组除了前三个元素外其他元素都为 0
第二种变化 var arrLazy = [...]int{5, 6, 7, 8, 22} 第二种变化 `var arrLazy = [...]int{5, 6, 7, 8, 22}`
`...`可同样可以忽略从技术上说它们其实变化成了slice `...` 可同样可以忽略从技术上说它们其实变化成了切片
第三种变化key: value syntax 第三种变化`key: value syntax`
var arrKeyValue = [5]string{3: "Chris", 4:Ron"} var arrKeyValue = [5]string{3: "Chris", 4:Ron"}
只有索引3和4被赋予实际的值其他元素都被设置为空的字符串所以输出结果为 只有索引 3 4 被赋予实际的值其他元素都被设置为空的字符串所以输出结果为
Person at 0 is Person at 0 is
Person at 1 is Person at 1 is
@@ -165,7 +168,7 @@ Example 7.3 [array_literals.go](examples/chapter_7/array_literals.go)
Person at 3 is Chris Person at 3 is Chris
Person at 4 is Ron Person at 4 is Ron
在这里数组长度同样可以写成`...`或者直接忽略 在这里数组长度同样可以写成 `...` 或者直接忽略
你可以取任意数组常量的地址来作为指向新实例的指针 你可以取任意数组常量的地址来作为指向新实例的指针
@@ -194,9 +197,10 @@ Example 7.4 [pointer_array2.go](examples/chapter_7/pointer_array2.go)
var vec Vector3D var vec Vector3D
## 7.1.3 多维数组 ## 7.1.3 多维数组
数组通常是1维的但是可以用来组装成多维数组例如[3][5]int[2][2][2]float64
内部数组总是长度相同Go语言的多维数组是矩形式的唯一的例外是分片slice的数组参见7.2.5 数组通常是1维但是可以用来组装成多维数组例如`[3][5]int``[2][2][2]float64`
内部数组总是长度相同的Go 语言的多维数组是矩形式的唯一的例外是切片的数组参见第 7.2.5 )。
Example 7.5 [multidim_array.go](examples/chapter_7/multidim_array.go) Example 7.5 [multidim_array.go](examples/chapter_7/multidim_array.go)
@@ -218,9 +222,11 @@ Example 7.5 [multidim_array.go](examples/chapter_7/multidim_array.go)
} }
## 7.1.4 将数组传递给函数 ## 7.1.4 将数组传递给函数
把第一个大数组传递给函数会消耗很多内存有两种方法可以避免这种现象 把第一个大数组传递给函数会消耗很多内存有两种方法可以避免这种现象
- 传递数组的指针 - 传递数组的指针
- 使用数组的分片slice - 使用数组的切片
接下来的例子阐明了第一种方法 接下来的例子阐明了第一种方法
@@ -246,9 +252,10 @@ Example 7.6 [array_sum.go](examples/chapter_7/array_sum.go)
输出结果 输出结果
The sum of the array is: 24.600000 The sum of the array is: 24.600000
但这在Go中并不常用通常使用分片slice。(参考[7](07.2.md) 但这在 Go 中并不常用通常使用切片。(参考 [ 7 ](07.2.md)
## 链接
##链接
- [目录](directory.md) - [目录](directory.md)
- 上一节[数组与](07.0.md) - 上一节[数组与](07.0.md)
- 下一节[分片slices](07.2.md) - 下一节[切片](07.2.md)

View File

@@ -1,49 +1,53 @@
#7.2 # 7.2
## 7.2.1 概念 ## 7.2.1 概念
slice是对数组一个连续片段的引用该数组我们称之为相关数组通常是匿名的所以slice是一个引用类型因此更类似于C/C++中的数组类型或者Python中的list类型。这个片段可以是整个数组或者是由起始和终止索引标识的一些项的子集。需要注意的是终止索引标识的项不包括在slice内。Slice提供了一个相关数组的动态窗口。
Slice是可索引的并且可以由len()方法获取长度 slice 是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以 slice 是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在 slice 内。 Slice 提供了一个相关数组的动态窗口
给定项的slice索引可能比相关数组的相同元素的索引小。和数组不同的是slice的长度可以在运行时修改最小为0最大为相关数组的长度slice是一个`长度可变的数组` Slice 是可索引的,并且可以由 len() 方法获取长度
slice提供了计算容量的方法cap()可以测量slice最长可以达到多少它等于slice的长度 + 数组除slice之外的长度。如果s是一个slicecap就是从s[0]到数组末尾的数组长度slice的长度永远不会超过它的容量所以对于slice s来说该不等式永远成立 0 <= len(s) <= cap(s) 给定项的 slice 索引可能比相关数组的相同元素的索引小。和数组不同的是,slice 的长度可以在运行时修改,最小为 0 最大为相关数组的长度slice 是一个 **长度可变的数组**
多个slice如果表示同一个数组的片段它们可以共享数据因此一个slice和相关数组的其他slice是共享存储的相反不同的数组总是代表不同的存储。数组实际上是slice的构建块。 slice 提供了计算容量的方法 cap() 可以测量 slice 最长可以达到多少:它等于 slice 的长度 + 数组除 slice 之外的长度。如果 s 是一个 slicecap 就是从 s[0] 到数组末尾的数组长度。slice 的长度永远不会超过它的容量,所以对于 slice s 来说该不等式永远成立: 0 <= len(s) <= cap(s)
`优点: ` 因为slice是引用所以它们不需要使用额外的内存并且比使用数组更有效率所以在Go代码中slice比数组更常用 多个 slice 如果表示同一个数组的片段,它们可以共享数据;因此一个 slice 和相关数组的其他 slice 是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是 slice 的构建块
声明slice的格式是 var identifier []type 不需要说明长度 **优点** 因为 slice 是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 slice 比数组更常用。
一个slice在未初始化之前默认为nil长度为0。 声明 slice 的格式是: `var identifier []type` 不需要说明长度
slice的初始化格式是: var slice1 []type = arr1[start:end] 一个 slice 在未初始化之前默认为 nil长度为 0。
这表示slice1是由数组arr1从start索引到end-1索引之间的元素构成的子集切分数组start:end被称为slice表达式。所以slice1[0]就等于arr1[start]。这可以在arr1被填充前就定义好。 slice 的初始化格式是:`var slice1 []type = arr1[start:end]`
如果某个人写: var slice1 []type = arr1[:]那么slice1就等于完整的arr1数组所以这种表示方式是arr1[0:len(arr1)]的一种缩写。另外一种表述方式是slice1 = &arr1 这表示 slice1 是由数组 arr1 从 start 索引到 end-1 索引之间的元素构成的子集切分数组start:end 被称为 slice 表达式)。所以 slice1[0] 就等于 arr1[start]。这可以在 arr1 被填充前就定义好
arr1[2:]arr1[2:len(arr1)]相同,都包含了数组从第二个到最后的所有元素 如果某个人写:`var slice1 []type = arr1[:]` 那么 slice1 就等于完整的 arr1 数组(所以这种表示方式是 `arr1[0:len(arr1)]` 的一种缩写)。另外一种表述方式是:`slice1 = &arr1`
arr1[:3]和arr1[0:3]相同,包含了从第个到第三个元素(不包括第三个) arr1[2:] 和 arr1[2:len(arr1)] 相同,包含了数组从第个到最后的所有元素
如果你想去掉slice1的最后一个元素只要 slice1 = slice1[:len(slice1)-1] arr1[:3] 和 arr1[0:3] 相同,包含了从第一个到第三个元素(不包括第三个)。
一个由数组第12,3个元素组成的分片可以这么生成 s := [3]int{1,2,3} 或者 s := [...]int{1,2,3}[:]甚至更简单的s := []int{1,2,3} 如果你想去掉 slice1 的最后一个元素,只要 `slice1 = slice1[:len(slice1)-1]`
s2 := s[:]是用slice组成的slice拥有相同的元素但是仍然指向相同的相关数组 一个由数组第 12,3 个元素组成的分片可以这么生成:`s := [3]int{1,2,3}` 或者 `s := [...]int{1,2,3}[:]` 甚至更简单的 `s := []int{1,2,3}`
一个slice s可以这样扩展到它的大小上限s = s[:cap(s)]如果再扩大的话就会导致运行时错误参见7.7 `s2 := s[:]` 是用 slice 组成的 slice拥有相同的元素但是仍然指向相同的相关数组
对于每一个slice包括string以下状态总是成立的 一个 slice s 可以这样扩展到它的大小上限:`s = s[:cap(s)]`,如果再扩大的话就会导致运行时错误(参见第 7.7 节)。
对于每一个 slice包括 string以下状态总是成立的
s == s[:i] + s[i:] // i是一个整数且: 0 <= i <= len(s) s == s[:i] + s[i:] // i是一个整数且: 0 <= i <= len(s)
len(s) < cap(s) len(s) < cap(s)
Slice也可以用类似数组的方式初始化 var x = []int{2, 3, 5, 7, 11}这样就创建了一个长度为5的数组并且创建了一个相关slice Slice 也可以用类似数组的方式初始化`var x = []int{2, 3, 5, 7, 11}`这样就创建了一个长度为5的数组并且创建了一个相关 slice
slice在内存中的组织方式实际上是一个有3个域的结构体指向相关数组的指针slice长度以及slice容量下图给出了一个长度为2容量为4的slice slice 在内存中的组织方式实际上是一个有 3 个域的结构体指向相关数组的指针slice 长度以及 slice 容量下图给出了一个长度为 2容量为 4 slice
y[0] = 3且y[1] = 5slice y[0:4]元素3, 5 7和11组成 ![](images/7.2_fig7.2.png?raw=true) `y[0] = 3` `y[1] = 5`slice y[0:4] 元素 3, 5 7 11 组成
![](images/7.2_fig7.2.png?raw=true)
示例 7.7 [array_slices.go](exmaples/chapter_7/array_slices.go) 示例 7.7 [array_slices.go](exmaples/chapter_7/array_slices.go)
@@ -94,14 +98,15 @@ y[0] = 3且y[1] = 5。slice y[0:4]由元素3, 5 7和11组成。 ![](images/7.
The length of slice1 is 4 The length of slice1 is 4
The capacity of slice1 is 4 The capacity of slice1 is 4
如果s2是一个slice你可以将s2向后移动一位s2 = s2[1:]但是末尾没有移动slice只能向后移动s2 = s2[-1:]会导致编译错误slice不能被重新分片以获取数组的前一个元素 如果 s2 是一个 slice你可以将 s2 向后移动一位 `s2 = s2[1:]`但是末尾没有移动slice 只能向后移动`s2 = s2[-1:]` 会导致编译错误slice 不能被重新分片以获取数组的前一个元素
`注意`绝对不要用指针指向sliceslice本身已经是一个引用类型所以它本身就是一个指针!! **注意** 绝对不要用指针指向 sliceslice 本身已经是一个引用类型所以它本身就是一个指针!!
问题7.2 给定slice b:= []byte{'g', 'o', 'l', 'a', 'n', 'g'}那么b[1:4]b[:2]b[2:]和b[:]分别是什么 问题7.2 给定 `slice b:= []byte{'g', 'o', 'l', 'a', 'n', 'g'}`那么 b[1:4]b[:2]b[2:] b[:] 分别是什么
## 7.2.2 将slice传递给函数 ## 7.2.2 将 slice 传递给函数
如果你有一个函数需要对数组做操作你可能总是需要把参数声明为slice当你调用该函数时把数组分片创建为一个slice引用并传递给该函数这里有一个计算数组元素和的方法:
如果你有一个函数需要对数组做操作你可能总是需要把参数声明为 slice当你调用该函数时把数组分片创建为一个 slice 引用并传递给该函数这里有一个计算数组元素和的方法:
func sum(a []int) int { func sum(a []int) int {
s := 0 s := 0
@@ -116,25 +121,26 @@ y[0] = 3且y[1] = 5。slice y[0:4]由元素3, 5 7和11组成。 ![](images/7.
sum(arr[:]) sum(arr[:])
} }
## 7.2.3 用make()创建一个slice ## 7.2.3 用 make() 创建一个 slice
当相关数组还没有定义时我们可以使用make()方法来创建一个slice同时创建好相关数组 var slice1 []type = make([]type, len)
也可以简写为 slice1 := make([]type, len)这里len是数组的长度并且也是slice的初始长度 当相关数组还没有定义时我们可以使用 make() 方法来创建一个 slice 同时创建好相关数组`var slice1 []type = make([]type, len)`
所以定义s2 := make([]int, 10)那么cap(s2) == len(s2) == 10 也可以简写为 `slice1 := make([]type, len)`这里 `len` 是数组的长度并且也是 `slice` 的初始长度
make接受2个参数元素的类型以及slice的元素个数 所以定义 `s2 := make([]int, 10)`那么 `cap(s2) == len(s2) == 10`
如果你想创建一个slice1它不占用整个数组而只是占用以len为个数个项那么只要 slice1 := make([]type, len, cap) make 接受 2 个参数元素的类型以及 slice 的元素个数
make的使用方式是 func make([]T, len, cap) 其中cap是可选参数 如果你想创建一个 slice1它不占用整个数组而只是占用以 len 为个数个项那么只要`slice1 := make([]type, len, cap)`
所以下面两种方法可以生成相同的slice: make 的使用方式是`func make([]T, len, cap)` 其中 cap 是可选参数
所以下面两种方法可以生成相同的 slice:
make([]int, 50, 100) make([]int, 50, 100)
new([100]int)[0:50] new([100]int)[0:50]
下图描述了使用make方法生成的slice的内存结构![](images/7.2_fig7.2.1.png?raw=true) 下图描述了使用 make 方法生成的 slice 的内存结构![](images/7.2_fig7.2.1.png?raw=true)
示例 7.8 [make_slice.go](exmaples/chapter_7/make_slice.go) 示例 7.8 [make_slice.go](exmaples/chapter_7/make_slice.go)
@@ -170,25 +176,28 @@ make的使用方式是 func make([]T, len, cap) 其中cap是可选参数。
The length of slice1 is 10 The length of slice1 is 10
The capacity of slice1 is 10 The capacity of slice1 is 10
因为字符串是纯粹不可变的字节数组它们也可以被切分成slice 因为字符串是纯粹不可变的字节数组它们也可以被切分成 slice
练习 7.4 fobinacci_funcarray.go: 为练习7.3写一个新的版本主函数调用一个使用序列个数作为参数的函数该函数返回一个大小为序列个数的Fibonacci slice 练习 7.4 fobinacci_funcarray.go: 为练习 7.3 写一个新的版本主函数调用一个使用序列个数作为参数的函数该函数返回一个大小为序列个数的 Fibonacci slice
## 7.2.4 new() 和 make() 的区别
## 7.2.4 new()和make()的区别
看起来二者没有什么区别都在堆上分配内存但是它们的行为不同适用于不同的类型 看起来二者没有什么区别都在堆上分配内存但是它们的行为不同适用于不同的类型
new(T)为每个新的类型T分配一片内存初始化为0并且返回内存地址类型*T这种方法`返回一个指向类型为T值为0的地址的指针`它适用于值类型如数组和结构体参见第它相当于&T{} new(T) 为每个新的类型T分配一片内存初始化为 0 并且返回内存地址类型 *T这种方法 **返回一个指向类型为 T值为 0 的地址的指针**它适用于值类型如数组和结构体参见第 10 它相当于 `&T{}`
make(T)`返回一个类型为T的初始值`它只适用于3种内建的引用类型slice, mapchannel参见第8章第13 make(T) **返回一个类型为 T 的初始值**它只适用于3种内建的引用类型slice, map channel参见第 8 13
换言之new方法分配内存make方法初始化下图给出了区别![](images/7.3_fig7.3.png?raw=true) 换言之new 方法分配内存make 方法初始化下图给出了区别
在图7.3的第一幅图中 ![](images/7.3_fig7.3.png?raw=true)
在图 7.3 的第一幅图中
var p *[]int = new([]int) // *p == nil; with len and cap 0 var p *[]int = new([]int) // *p == nil; with len and cap 0
p := new([]int) p := new([]int)
在第二幅图中 p := make([]int, 0) slice已经被初始化但是指向一个空的数组 在第二幅图中 `p := make([]int, 0)` slice 已经被初始化但是指向一个空的数组
这两种方式实用性都不高下面的方法 这两种方式实用性都不高下面的方法
@@ -198,35 +207,37 @@ make(T)`返回一个类型为T的初始值`它只适用于3种内建的引用
v := make([]int, 10, 50) v := make([]int, 10, 50)
这样分配一个有50个int值的数组并且创建了一个长度为10容量为50的slice v该slice指向数组的前10个元素 这样分配一个有 50 int 值的数组并且创建了一个长度为 10容量为 50 slice v slice 指向数组的前 10 个元素
问题 7.3给定s := make([]byte, 5)len(s)和cap(s)分别是多少s = s[2:4]len(s)和cap(s)又分别是多少? 问题 7.3给定 `s := make([]byte, 5)`len(s) cap(s) 分别是多少`s = s[2:4]`len(s) cap(s) 又分别是多少
问题 7.4假设s1 := []byte{'p', 'o', 'e', 'm'}且s2 := d[2:]s2的值是多少如果我们执行s2[1] == 't's1和s2现在的值又分配是多少 问题 7.4假设 `s1 := []byte{'p', 'o', 'e', 'm'}` `s2 := d[2:]`s2 的值是多少如果我们执行 `s2[1] == 't'`s1 s2 现在的值又分配是多少
## 7.2.5 多维slice ## 7.2.5 多维 slice
和数组一样slice通常也是一维的但是也可以由一维组合成高维通过分片的分片或者slice的数组长度可以任意动态变化所以Go语言的多维slice可以任意切分而且内层的slice必须单独分配通过make方法
## 7.2.6 bytes包 和数组一样slice 通常也是一维的但是也可以由一维组合成高维通过分片的分片或者 slice 的数组长度可以任意动态变化所以 Go 语言的多维 slice 可以任意切分而且内层的 slice 必须单独分配通过 make 方法
bytes的slice十分常见Go语言有一个bytes包专门用来解决这种类型的操作方法
bytes包和字符串包十分类似参见4.7)。而且它还包含一个十分有用的类型Buffer: ## 7.2.6 bytes 包
bytes slice 十分常见Go 语言有一个 bytes 包专门用来解决这种类型的操作方法
bytes 包和字符串包十分类似参见第 4.7 )。而且它还包含一个十分有用的类型 Buffer:
import "bytes" import "bytes"
type Buffer struct { type Buffer struct {
... ...
} }
这是一个bytes的定长buffer提供ReadWrite方法因为读写不知道长度的bytes最好使用buffer 这是一个 bytes 的定长 buffer提供 Read Write 方法因为读写不知道长度的 bytes 最好使用 buffer
Buffer可以这样定义 var buffer bytes.Buffer Buffer 可以这样定义`var buffer bytes.Buffer`
或者new出一个指针 var r *bytes.Buffer = new(bytes.Buffer) 或者 new 出一个指针`var r *bytes.Buffer = new(bytes.Buffer)`
或者通过函数 func NewBuffer(buf []byte) *Buffer这就用创建了一个Buffer对象并且用buf初始化好了NewBuffer最好用在从buf读取的时候使用 或者通过函数`func NewBuffer(buf []byte) *Buffer`这就用创建了一个 Buffer 对象并且用 buf 初始化好了NewBuffer 最好用在从 buf 读取的时候使用
通过buffer串联字符串类似于JavaStringBuilder类 通过 buffer 串联字符串类似于 Java StringBuilder
创建一个Buffer通过buffer.WriteString(s)方法将每个string s追加到后面最后再通过buffer.String()方法转换为string下面是代码段 创建一个 Buffer通过 buffer.WriteString(s) 方法将每个 string s 追加到后面最后再通过 buffer.String() 方法转换为 string下面是代码段
var buffer bytes.Buffer var buffer bytes.Buffer
for { for {
@@ -238,14 +249,15 @@ Buffer可以这样定义 var buffer bytes.Buffer
} }
fmt.Print(buffer.String(), "\n") fmt.Print(buffer.String(), "\n")
这种实现方式比使用`+=`要更节省内存和CPU尤其是要串联的字符串数目特别多的时候 这种实现方式比使用 `+=` 要更节省内存和 CPU尤其是要串联的字符串数目特别多的时候
练习 练习
练习 7.5 给定slice sl将a []byte数组追加到sl后面写一个函数Append(slice, data []byte) []byte该函数在sl不能存储更多数据的时候自动扩容 练习 7.5 给定 slice sl a []byte 数组追加到 sl 后面写一个函数 `Append(slice, data []byte) []byte`该函数在 sl 不能存储更多数据的时候自动扩容
练习 7.6 把一个缓存buf分片成两个slice第一个是前n个bytes后一个是剩余的用一行代码实现 练习 7.6 把一个缓存 buf 分片成两个 slice第一个是前 n bytes后一个是剩余的用一行代码实现
## 链接
##链接
- [目录](directory.md) - [目录](directory.md)
- 上一节[声明和初始化](07.1.md) - 上一节[声明和初始化](07.1.md)
- 下一节[For range构建方法](07.3.md) - 下一节[For-range](07.3.md)

View File

@@ -1,11 +1,12 @@
#7.3 For range构建方法 # 7.3 For-range
这种构建方法可以应用与数组和 slice:
这种构建方法可以应用与数组和slice:
for ix, value := range slice1 { for ix, value := range slice1 {
... ...
} }
第一个返回值dx是数组或者slice的索引第二个是在该索引位置的值他们都是仅在for循环内部可见的局部变量所以该值只是该索引项slice值的一个拷贝并且不能被修改。 第一个返回值 dx 是数组或者 slice 的索引,第二个是在该索引位置的值;他们都是仅在 for 循环内部可见的局部变量,所以该值只是该索引项 slice 值的一个拷贝并且不能被修改。
示例 7.9 [slices_forrange.go](exmaples/chapter_7/slices_forrange.go) 示例 7.9 [slices_forrange.go](exmaples/chapter_7/slices_forrange.go)
@@ -41,7 +42,7 @@
} }
} }
slices_forrange2.go给出了一个关于字符串的例子 `_`可以用于忽略索引 slices_forrange2.go 给出了一个关于字符串的例子 `_` 可以用于忽略索引
如果你只需要索引你可以忽略第二个变量例如 如果你只需要索引你可以忽略第二个变量例如
@@ -50,11 +51,11 @@ slices_forrange2.go给出了一个关于字符串的例子 `_`可以用于忽
} }
// Output: 0 1 2 3 // Output: 0 1 2 3
如果你需要修改seasons[ix]的值可以使用这个版本 如果你需要修改 seasons[ix] 的值可以使用这个版本
多维slice下的for range方法 多维 slice 下的 for-range 方法
通过计算行数和矩阵值可以很方便的写出如参考7.1.3的for-loops方法来例如参考7.5的例子multidim_array.go 通过计算行数和矩阵值可以很方便的写出如参考 7.1.3 for-loops 方法来例如参考 7.5 节的例子 multidim_array.go
for row := range screen { for row := range screen {
for column := range screen[0] { for column := range screen[0] {
@@ -62,33 +63,34 @@ slices_forrange2.go给出了一个关于字符串的例子 `_`可以用于忽
} }
} }
问题 7.5 假设我们有如下slice items := [...]int{10, 20, 30, 40, 50} 问题 7.5 假设我们有如下 slice`items := [...]int{10, 20, 30, 40, 50}`
a) 如果我们写了如下的for循环那么执行完for循环后的item的值是多少如果你不确定的话可以测试一下:) a) 如果我们写了如下的 for 循环那么执行完 for 循环后的 item 的值是多少如果你不确定的话可以测试一下:)
for _, item := range items { for _, item := range items {
item *= 2 item *= 2
} }
b) 如果a)无法正常工作写一个for循环让值可以double b) 如果 a) 无法正常工作写一个 for 循环让值可以 double
问题 7.6 通过使用省略号操作符`...`来实现累加方法 问题 7.6 通过使用省略号操作符 `...` 来实现累加方法
练习 练习
练习 7.7 sum_array.go 练习 7.7 sum_array.go
a) 写一个Sum函数传入参数为一个4位float数组成的数组arrF返回该数组的所有数字和 a) 写一个 Sum 函数传入参数为一个 4 float 数组成的数组 arrF返回该数组的所有数字和
如果把数组修改为slice的话代码要做怎样的修改如果用slice形式方法实现不同长度数组的的和呢 如果把数组修改为 slice 的话代码要做怎样的修改如果用 slice 形式方法实现不同长度数组的的和呢
b) 写一个SumAndAverage方法返回两个int和float32类型的未命名变量的和与平均值 b) 写一个 SumAndAverage 方法返回两个 int float32 类型的未命名变量的和与平均值
练习 7.8 min_max.go 练习 7.8 min_max.go
写一个minSlice方法传入一个int的slice并且返回最小值再写一个maxSlice方法返回最大值 写一个 minSlice 方法传入一个 int slice 并且返回最小值再写一个 maxSlice 方法返回最大值
## 链接
##链接
- [目录](directory.md) - [目录](directory.md)
- 上一节[](07.2.md) - 上一节[](07.2.md)
- 下一节[片重组](07.4.md) - 下一节[片重组](07.4.md)

View File

@@ -1,15 +1,16 @@
#7.4 片重组 # 7.4 片重组
我们已经知道slice创建的时候通常比相关数组小例如
我们已经知道 slice 创建的时候通常比相关数组小,例如
slice1 := make([]type, start_length, capacity) slice1 := make([]type, start_length, capacity)
其中start_length作为slice初始长度而capacity作为相关数组的长度。 其中 start_length 作为 slice 初始长度而 capacity 作为相关数组的长度。
这么做的好处是我们的slice在达到容量上限后可以扩容。改变slice长度的过程称之为片重组`reslicing`做法如下slice1 = slice1[0:end]其中end是新的末尾索引即长度 这么做的好处是我们的 slice 在达到容量上限后可以扩容。改变 slice 长度的过程称之为片重组 **reslicing**,做法如下:`slice1 = slice1[0:end]`,其中 end 是新的末尾索引(即长度)。
将slice扩展1位可以这么做: sl = sl[0:len(sl)+1] slice 扩展 1 位可以这么做:`sl = sl[0:len(sl)+1]`
slice可以反复扩展直到占据整个相关数组。 slice 可以反复扩展直到占据整个相关数组。
示例 7.11 [reslicing.go](exmaples/chapter_7/reslicing.go) 示例 7.11 [reslicing.go](exmaples/chapter_7/reslicing.go)
@@ -33,6 +34,7 @@ slice可以反复扩展直到占据整个相关数组。
} }
输出结果 输出结果
The length of slice is 1 The length of slice is 1
The length of slice is 2 The length of slice is 2
The length of slice is 3 The length of slice is 3
@@ -65,11 +67,12 @@ slice可以反复扩展直到占据整个相关数组。
问题 7.7 问题 7.7
1) 如果a是一个slice那么s[n:n]的长度是多少 1) 如果 a 是一个 slice那么 s[n:n] 的长度是多少
2) s[n:n+1]的长度又是多少 2) s[n:n+1] 的长度又是多少
## 链接
##链接
- [目录](directory.md) - [目录](directory.md)
- 上一节[For range构建方法](07.3.md) - 上一节[For-range](07.3.md)
- 下一节[拷贝与追加slice](07.5.md) - 下一节[切片的复制与追加](07.5.md)

View File

@@ -1,5 +1,6 @@
#7.5 拷贝与追加slice # 7.5 切片的复制与追加
如果想增加slice的容量我们必须创建一个新的更大的slice并把原分片的内容都拷贝过来。下面的代码描述了从拷贝slice的copy方法和向slice追加新元素的append方法。
如果想增加 slice 的容量,我们必须创建一个新的更大的 slice 并把原分片的内容都拷贝过来。下面的代码描述了从拷贝 slice 的 copy 方法和向 slice 追加新元素的 append 方法。
示例 7.12 [copy_append_slice.go](exmaples/chapter_7/copy_append_slice.go) 示例 7.12 [copy_append_slice.go](exmaples/chapter_7/copy_append_slice.go)
@@ -19,37 +20,40 @@
fmt.Println(sl3) fmt.Println(sl3)
} }
func append(s[]T, x ...T) []T 其中append方法将0个或多个具有相同类型s的元素追加到slice后面并且返回新的slice追加的元素必须和原slice的元素同类型。如果s的容量不足以存储新增元素append会分配新的slice来保证已有slice元素和新增元素的存储。因此返回的slice可能已经指向一个不同的相关数组了。append方法总是返回成功除非系统内存耗尽了。 `func append(s[]T, x ...T) []T` 其中 append 方法将 0 个或多个具有相同类型 s 的元素追加到 slice 后面并且返回新的 slice追加的元素必须和原 slice 的元素同类型。如果 s 的容量不足以存储新增元素append 会分配新的 slice 来保证已有 slice 元素和新增元素的存储。因此,返回的 slice 可能已经指向一个不同的相关数组了。append 方法总是返回成功,除非系统内存耗尽了。
如果你想将slice y追加到slice x后面只要将第二个参数扩展成一个列表即可 x = append(x, y...) 如果你想将 slice y 追加到 slice x 后面,只要将第二个参数扩展成一个列表即可:`x = append(x, y...)`
**注意** append在大多数情况下很好用但是如果你想完全掌控整个追加过程你可以实现一个这样的AppendByte方法 **注意** append 在大多数情况下很好用,但是如果你想完全掌控整个追加过程,你可以实现一个这样的 AppendByte 方法:
func AppendByte(slice []byte, data ...byte[]) []byte { ```go
m := len(slice) func AppendByte(slice []byte, data ...byte) []byte {
n := m + len(data) m := len(slice)
if n > cap(slice) { // if necessary, reallocate n := m + len(data)
// allocate doublke what's needed, for future growth. if n > cap(slice) { // if necessary, reallocate
newSlice := make([]byte, (n+1)*2) // allocate doublke what's needed, for future growth.
copy(newSlice, slice) newSlice := make([]byte, (n+1)*2)
slice = newSlice copy(newSlice, slice)
} slice = newSlice
slice = slice[0:n]
copy(slice[m:n], data)
return slice
} }
slice = slice[0:n]
copy(slice[m:n], data)
return slice
}
```
func copy(dst, src []T) int copy方法将类型为T的slice从源地址src拷贝到目标地址dst覆盖dst的相关元素并且返回拷贝的元素个数。源地址和目标地址可能会有重叠。拷贝个数是srcdst的长度最小值。如果src是字符串那么元素类型就是byte。如果你还想继续使用src在拷贝技术后执行src =dst。 `func copy(dst, src []T) int` copy 方法将类型为 T 的 slice 从源地址 src 拷贝到目标地址 dst覆盖 dst 的相关元素,并且返回拷贝的元素个数。源地址和目标地址可能会有重叠。拷贝个数是 srcdst 的长度最小值。如果 src 是字符串那么元素类型就是 byte。如果你还想继续使用 src在拷贝技术后执行 src =dst。
练习 7.9 magnify_slice.go: 给定slice s[]int和一个int类型的因子扩展s使其长度为len(s) * factor。 练习 7.9 magnify_slice.go: 给定 `slice s[]int` 和一个 int 类型的因子,扩展 s 使其长度为 `len(s) * factor`
练习 7.10filter_slice.go 用顺序函数过滤容器s是前10个整形的slice。构造一个函数Filter第一个参数是s第二个参数是一个fn func(int) bool返回满足函数fn的元素slice。通过fn测试方法测试当整型值是偶数时的情况。 练习 7.10filter_slice.go 用顺序函数过滤容器s 是前 10 个整形的 slice。构造一个函数 Filter第一个参数是 s第二个参数是一个 `fn func(int) bool`,返回满足函数 fn 的元素 slice。通过 fn 测试方法测试当整型值是偶数时的情况。
练习 7.11insert_slice.go写一个函数InsertStringSliceslice插入到另一个slice的指定位置。 练习 7.11insert_slice.go写一个函数 InsertStringSliceslice 插入到另一个 slice 的指定位置。
练习 7.12remove_slice.go写一个函数RemoveStringSlice将从startend索引的元素从slice中移除。 练习 7.12remove_slice.go写一个函数 RemoveStringSlice 将从 startend 索引的元素从 slice 中移除。
## 链接
##链接
- [目录](directory.md) - [目录](directory.md)
- 上一节:[片重组](07.4.md) - 上一节:[片重组](07.4.md)
- 下一节:[字符串数组和slice应用](07.6.md) - 下一节:[字符串数组和切片的应用](07.6.md)

3
eBook/07.6.md Normal file
View File

@@ -0,0 +1,3 @@
# 7.6 字符串、数组和切片的应用
178

7
eBook/09.0.md Normal file
View File

@@ -0,0 +1,7 @@
#9.0 包
##链接
- [目录](directory.md)
- 上一节:[]()
- 下一节:[标准库概述](09.1.md)

95
eBook/09.1.md Normal file
View File

@@ -0,0 +1,95 @@
#A 标准库
#9.1 标准库概述
像fmtos等这样具有常用功能的内置包在Go语言中有150个以上它们被称为标准库大部分(一些底层的除外)内置于Go本身。记录在:http://golang.org/pkg/。
在贯穿本书的例子和练习中我们都是用标准库的包。可以通过查阅p.350包中的内容快速找到相关的包的实例。这里我们只是按功能进行分组来介绍这些包的简单用途,我们不会深入讨论他们的内部结构。
unsafe: 包含了一些打破Go语言“类型安全”的命令一般的程序中不会被使用可用在C/C++程序的调用中。
syscall-os-os/exec:
os: 提供给我们一个平台无关性的操作系统功能接口采用类Unix设计隐藏了不同操作系统间差异让不同的文件系统和操作系统对象表现一致。
os/exec: 提供我们运行外部操作系统命令和程序的方式。
syscall: 底层的外部包,提供了操作系统底层调用的基本接口。
通过一个Go程序让Linux重启来体现它的能力(通过sudo ./6.out来执行程序):
示例 9.1 [reboot.go](exmaples/chapter_9/reboot.go)
package main
import (
"syscall"
)
const LINUX_REBOOT_MAGIC1 uintptr = 0xfee1dead
const LINUX_REBOOT_MAGIC2 uintptr = 672274793
const LINUX_REBOOT_CMD_RESTART uintptr = 0x1234567
func main() {
syscall.Syscall(syscall.SYS_REBOOT,
LINUX_REBOOT_MAGIC1,
LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART)
}
archive/tar and /zip-compress:压缩(解压缩)文件功能。
fmt-io-bufio-path/filepath-flag:
fmt: 提供了格式化输入输出功能。
io: 提供了基本输入输出功能,大多数是围绕系统功能的封装。
bufio: 缓冲输入输出功能的封装。
path/filepath: 用来操作在当前系统中的目标文件名路径。
flag: 对命令行参数的操作。  
strings-strconv-unicode-regexp-bytes:
strings: 提供对字符串的操作。
strconv: 提供将字符串转换为基础类型的功能。
unicode: 为unicode型的字符串提供特殊的功能。
regexp: 正则表达式功能。
bytes: 提供对字符型分片的操作。
index/suffixarray: 子字符串快速查询。
math-math/cmath-math/big-math/rand-sort:
math: 基本的数学函数。
math/cmath: 对负责数字的操作。
math/rand: 伪随机数生成。
sort: 为数组排序和自定义集合。
math/big: 大数的实现和计算。   
container: /list-ring-heap: 实现对集合的操作。
list: 双链表。
示例,如何遍历一个链表(当l是*List):
for e := l.Front(); e != nil; e = e.Next() {
//do something with e.Value
}
ring: 环形链表。
time-log:
time: 日期和时间的基本操作。
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章)
net: 网络数据的基本操作。
http: 提供了一个可扩展的HTTP服务器和基础客户端解析HTTP请求和回复。
html: HTML5解析器。
runtime: Go程序运行时的交互操作例如垃圾回收和协程创建。
reflect: 实现通过程序运行时自省,让程序操作任意类型的变量。
exp包中有许多将被编译为新包的实验性的包。它们将成为独立的包在下次稳定版本发布的时候。如果前一个版本已经存在了它们将被作为果实的包被回收。然而Go1.0发布的时候并不包含过时或者实验性的包。
练习9.1: dlinked_list.go
使用container/list包实现一个双向链表将101,102,103放入其中并打印出来。
练习9.2: size_int.go
通过使用unsafe包中的方法来测试你电脑上一个整型变量占用多少个字节。
##链接
- [目录](directory.md)
- 上一节:
- 下一节:[regexp包](09.2.md)

BIN
eBook/09.10.md Normal file

Binary file not shown.

62
eBook/09.11.md Normal file
View File

@@ -0,0 +1,62 @@
#9.11 在Go程序中使用外部库
(本节我们将创建一个web应用和它的Google App Engine版本,在第19和21章分别说明当你阅读到这些章节时可以再回到这个例子。)
当开始一个新项目或增加新的功能到现有的项目你可以通过在应用程序中使用已经存在的库来节省开发时间。为了做到这一点你必须理解库的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我们可以在我们自己的应用程序中调用释放到指定的限制。他们也生成了一个Go语言客户端库使其变得更容易。
备注谷歌让通过使用Google API Go客户端服务的开发者生活变得更简单Go客户端程序自动生成于Google库的JSON描述。更多详情在http://code.google.com/p/google-api-go-client/。
下载并安装Go客户端库:
将通过go install实现。但是首先要验证环境变量中是否含有GOPATH变量因为外部源码将被下载到$GOPATH/src目录下并被安装到$GOPATH/PKG/"machine_arch"/目录下。
我们将通过在终端调用以下命令来安装API:
go install google-api-go-client.google.com/hg/urlshortener/v1
go install将下载源码编译并安装包
(在Linux Ubuntu下使用6g r60 9841安装是可以的,被安装文件被放在pkg/linux_amd64下)
使用urlshortener服务的web程序:
现在我们可以通过导入并赋予别名来使用已安装的包import urlshortener "google-api-go-client.googlecode.com/hg/urlshortener/v1"
现在我们写一个web应用(参见第十五章4-8节)通过表单实现短地址和长地址的相互转换。我们将使用template包并写三个处理函数root函数通过执行表单模板来展示表单。short函数将长地址转换为短地址long函数逆向转换。
要调用urlshortener接口必须先通过http包中的默认客户端创建一个服务实例urlshortenerSvc
urlshortenerSvc, _ := urlshortener.New(http.DefaultClient)
我们通过调用服务中的Url.Insert中的Do方法传入包含长地址的Url数据结构从而获取短地址
url, _ := urlshortenerSvc.Url.Insert(&urlshortener.Url{LongUrl: longUrl}).Do()
返回url的Id便是我们需要的短地址。
我们通过调用服务中的Url.Get中的Do方法传入包含短地址的Url数据结构从而获取长地址
url, error := urlshortenerSvc.Url.Get(shwortUrl).Do()
返回的长地址便是转换前的原始地址。
实例 9.9 [urlshortener.go](examples/chapter_9/urlshortener.go)
package main
import (
“fmt”
“net/http”
“text/template”
rlshortener “google-api-go-client.googlecode.com/hg/urlshortener/v1”
)
func main() {
http.HandleFunc(“/”, root)
http.HandleFunc(“/short”, short)
http.HandleFunc(“/long”, long)
http.ListenAndServe(“localhost:8080”, nil)
}
// the template used to show the forms and the results web page to the user
var rootHtmlTmpl = template.Must(template.New(“rootHtml”).Parse(`

56
eBook/09.2.md Normal file
View File

@@ -0,0 +1,56 @@
#9.2 regexp包
正则表达式语法和使用的详细信息请参考http://en.wikipedia.org/wiki/Regular_expression
在下面的程序里,我们将在字符串中对正则表达式进行匹配
如果是简单模式使用Match方法便可: ok, _ := regexp.Match(pat, []byte(searchIn))
变量ok将返回true或者false,我们也可以使用MatchString: ok, _ := regexp.MathString(pat, searchIn)
更多方法中必须先将正则通过Compile方法返回一个Regexp对象。然后我们将掌握一些匹配查找替换相关的功能。
示例 9.2 [pattern.go](exmaples/chapter_9/pattern.go)
package main
import (
"fmt"
"regexp"
"strconv"
)
func main() {
//目标字符串
searchIn := "John: 2578.34 William: 4567.23 Steve: 5632.18"
pat := "[0-9]+.[0-9]+" //正则
f := func(s string) string{
v, _ := strconv.ParseFloat(s, 32)
return strconv.FormatFloat(v * 2, 'f', 2, 32)
}
if ok, _ := regexp.Match(pat, []byte(searchIn)); ok {
fmt.Println("Match Found!")
}
re, _ := regexp.Compile(pat)
//将匹配到的部分替换为"##.#"
str := re.ReplaceAllString(searchIn, "##.#")
fmt.Println(str)
//参数为函数时
str2 := re.ReplaceAllStringFunc(searchIn, f)
fmt.Println(str2)
}
输出结果:
Match Found!
John: ##.# William: ##.# Steve: ##.#
John: 5156.68 William: 9134.46 Steve: 11264.36
Compile函数也可能返回一个错误我们在使用时忽略对错误的判断是因为我们确信自己正则表达式是有效的。当用户输入或从数据中获取正则表达式的时候我们有必要去检验它的正确性。另外我们也可以使用MustCompile方法它可以像Compile方法一样检验正则的有效性但是当正则不合法时程序将panic(详情查看13.2章节)。
##链接
- [目录](directory.md)
- 上一节:[标准库概述](09.1.md)
- 下一节:[锁和sync包](09.2.md)

32
eBook/09.3.md Normal file
View File

@@ -0,0 +1,32 @@
#9.3锁和sync包
在一些复杂的程序中,通常通过不同线程执行不同应用来实现程序的并发。当不同线程要使用同一个变量时,经常会出现一个问题:无法预知变量被不同线程修改的顺序!(这通常被称为资源竞争,指不同线程对同一变量使用的竞争。)显然这无法让人容忍,那我们该如何解决这个问题呢?
经典的做法是一次只能让一个线程对共享变量进行操作。当变量被一个线程改变时(临界区),我们为它上锁,直到这个线程执行完成并解锁后,其他线程才能访问它。
特别是我们之前章节学习的map类型是不存在锁的机制来实现这种效果(出于对性能的考虑),所以map类型是非线程安全的.当并行访问一个共享的map类型的数据map数据将会出错。
在Go语言中这种锁的机制是通过sync包中Mutex来实现的。sync来源于"synchronized"一词,这意味着线程将有序的对同一变量进行访问。
sync.Mutex是一个互斥锁它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区。
假设info是一个需要上锁的放在共享内存中的变量。通过包含Mutex来实现的一个典型例子如下
import “sync”
type Info struct {
mu sync.Mutex
// ... other fields, e.g.: Str string
}
如果一个函数想要改变这个变量可以这样写:
func Update(info *Info) {
info.mu.Lock()
// critical section:
info.Str = // new value
// end critical section
info.mu.Unlock()
}
还有一个很有用的例子是通过Mutex来实现一个可以上锁的共享缓冲器:
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer

13
eBook/09.4.md Normal file
View File

@@ -0,0 +1,13 @@
#精密计算和big包
我们知道有些时候通过编程的方式去进行计算是不精确的。如果你使用Go语言中的fload64类型进行浮点运算返回结果将精确到15位足以满足大多数的任务。当对超出in64或者uint64类型这样的大数进行计算时如果对精度没有要求float32或者float64可以胜任但如果对精度有严格要求的时候我们不能使用浮点数在内存中它们只能被近似的表示。
对于整数的高精度计算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类型的临时变量来存放中间结果所以这样的运算可通过内存链式存储。
示例 9.2 [big.go](exmaples/chapter_9/big.go)
package main
import (
"fmt"
"math""

139
eBook/09.5.md Executable file
View File

@@ -0,0 +1,139 @@
#B 自定义和外部包的使用,编译,测试,文档和安装
#9.5 自定义包和可见性
包是Go语言中代码组成和代码编译的主要方式。很多关于它们的基本信息已经在4.2章节中给出,最引人注目的便是可见性。现在我们来看看具体如何来使用自己写的包。在下一节,我们将回顾一些标准库中的包,自定义的包和标准库以外的包。
当写自己包的时候要使用短小的不含有_(下划线)的小写单词来为文件命名。这里有个简单例子来说明包是如何相互调用以及可见性是如何实现的。
当前目录下(examples/chapter9)有一个名为package_test.go的程序, 它使用了自定义包pack1中pack1.go的代码。这段程序(联通编译链接生成的pack1.a)存放在当前目录下一个名为pack1的文件夹下。所以链接器将包的对象和主程序对象链接在一起。
示例 9.4 [pack1.go](exmaples/chapter_9/pack1.go)
package pack1
var Pack1Int int = 42
var PackFloat = 3.14
func ReturnStr() string {
return "Hello main!"
}
它包含了一个整型变量PackInt和一个返回字符串的函数ReturnStr。这段程序在运行时不做任何的事情因为它不包含有一个main函数。
在主程序pack_test.go中这个包通过声明的方式被导入
import “./pack1/pack1”
import的一般格式如下:
import “包的路径或url地址“ 像 import "github.com/org1/pack1”
路径是指当前目录的相对路径。
示例 9.5 [package_test.go](exmaples/chapter_9/package_test.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”
因此你项目的路径将成为导入包的网络地址
在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"代替imort "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:

34
eBook/09.6.md Normal file
View File

@@ -0,0 +1,34 @@
# 9.6 为你的自定义包使用godoc
godoc工具(3.6章节)在显示自定义包中的注释也有很好的效果:注释必须以//开始并无空行放在声明(包,类型,函数)前。godoc会为每个文件生成一系列的网页。
例如:
-在do_examples目录下我们有11.7章节中的用来排序的go文件文件中有一些注释文件需要未编译
-命令行下进入目录下并输入命令:
godoc -http =:6060 -paht="."
(.是指当前目录,-path参数可以是/path/to/my/package1这样的形式指出package1在你源码中的位置或接受用冒号形式分隔的路径无根目录的路径为相对于当前目录的相对路径)
-在浏览器打开地址 http://localhost:6060
然后你会看到本地的godoc页面(参见3.6章节)从左到右一次显示出目录中的包
doc_example:
doc_example | Packages | Commands | Specification
下面是链接到源码和所有对象时有序概述(所以是很好的浏览和查找源代码的方式),连同文件/评论。
sort包
func Float64sAreSorted
type IntArray
func IntsAreSortedfunc IsSortedfunc Sort
func (IntArray) Len
func SortFloat64s

37
eBook/09.7.md Normal file
View File

@@ -0,0 +1,37 @@
# 9.7使用go install安装自定义包
go install是Go中自动包安装工具如需要将包安装到本地它会从远端仓库下载包检出编译安装一气呵成。
在包安装前的先决条件是要自动处理包自身依赖关系的安装。被依赖的包也会安装到子目录下,但是没有文档和示例:可以到网上浏览。
被安装包的列表可以在$GORROT/goinstall.log找到。
go install使用了GOPATH变量(参见2.2章节)。
远端包(参见9.5章节)
假设我们要安装一个有趣的包tideland(它包含了许多帮助示例参见http://code.google.com/p/tideland-cgl)。
因为我们需要创建目录在Go安装目录下所以我们需要使用root或者su的身份执行命令。
确保Go环境变量已经设置在root用户下的./bashrc文件中。
使用命令安装go install tideland-cgl.googlecode.com/hg
可执行文件hg.a将被放到$GOROOT/pkg/linux_amd64/tideland-cgl.googlecode.com目录下源码文件被放置在$GOROOT/src/tideland-cgl.googlecode.com/hg目录下同样有个hg.a放置在_obj的子目录下。
现在就可以在go代码中使用这个包中的功能了例如使用报名cgl导入
import cgl "tideland-cgl.googlecode.com/hg"
从Go1起go install安装Google Code的导入路径形式是"code.google.com/p/tideland-cgl"
升级到新的版本:
更新到新版本的Go之后本地安装包的二进制文件将全被删除。当调用install-a工具将通过读取$GOROOT/goinstall.log重新安装以前的安装包。如果你想更新重编译重安装所有的go安装包可以使用go install -a -u -clean 或者 go install -a -u -nuke
go的版本发布的很频繁所以需要注意发布版本和包的兼容性。go1之后都是自己编译自己了。
go install同样可以使用go install编译链接并安装本地自己的包参见9.8.2章节。
更多信息可以在http://golang.org/cmd/go和http://golang.org/cmd/goinstall找到。

124
eBook/09.8.md Executable file
View File

@@ -0,0 +1,124 @@
#9.8 自定义包:目录结构,go install和go test
为了示范我们创建了一个名为uc的简单包,它含有一个UpperCase函数将字符串的所有字母转换为大写。当然这并不值得创建一个自己包同样的功能已被包含在"strings"包里,但是同样的技术也可以应用在更复杂的包中。
##9.8.1 自定义包的目录结构
下面的结构给了你一个好的示范(uc代表通用包名, 名字为粗体的代表目录,斜体代表可执行文件):
/home/user/goprograms
ucmain.go (uc包主程序)
Makefile (ucmain的2-makefile)
ucmain
src/uc (包含uc包的go源码)
uc.go
uc_test.go
Makefile (包的1-makefile)
uc.a
_obj
uc.a
_test
uc.a
bin (包含最终的执行文件)
ucmain
pkg/linux_amd64
uc.a (包的目标文件)
将你的项目放在goprograms目录下(你可以创建一个环境变量GOPATH,参考2.2/3章节:在.profile和.bashrc文件中添加export GOPATH=/home/user/goprograms)而你的项目将作为src的子目录。uc包 中的功能在uc.go中实现。
示例 9.6 [uc.go](exmaples/chapter_9/uc.go)
package uc
import "strings"
func UpperCase(str string) string {
return strings.ToUpper(str)
}
包通常附带一个或多个测试文件在这我们创建了一个uc_test.go文件如9.8章节所述
示例 9.7 [test.go](examples/chapter_9/uc.go)
package uc
import "testing"
type ucTest struct {
in, out string
}
var ucTests = []ucTest {
ucTest{"abc", "ABC"},
ucTest{"cvo-az", "CVO-AZ"},
ucTest{"Antwerp", "ANTWERP"},
}
func TestUC(t *testing.T) {
for _, ut := range ucTests {
uc := UpperCase(ut.in)
if uc != ut.out {
t.Errorf("UpperCase(%s) = %s, must be %s", ut.in, uc,
ut.out)
}
}
}
通过指令编译并安装包到本地go install src/uc, 这会将uc.a复制到pkg/linux_amd64下面
另外使用make,通过以下内容创建一个包的Makefile(1)在src/uc目录下:
include $GOROOT/src/Make.inc
TARG=uc
GOFILES=\
uc.go\
include $(GOROOT)/scr/Make.pkg
在该目录下的命令行调用: gomake
这将创建一个_obj目录并将包编译生成的存档uc.a放在该目录下
这个包可以通过go test测试
创建一个ud.a的测试文件在目录下输出为PASS时测试通过
在13.8章节我们将给出另外一个测试例子并进行深入研究
备注有可能你当前的用户不具有足够的资格使用go install(没有权限)。这种情况下选择root用户su。确保Go环境变量和Go源码路径也设置给su同样也适用你的普通用户(详见2.3章节)
接下来我们创建主程序ucmain.go:
示例 9.8 [ucmain.go](/examples/chapter_9/ucmain.go)
package main
import (
"fmt"
"./uc/uc"
)
func main() {
str1 := "USING package uc"
fmt.Println(uc.UpperCase(str1))
}
然后在这个目录下输入go install
另外复制uc.a到uc目录并创建一个Makefile(2)并写入文本:
包含在$GOROOT/src/Make.inc
TARG=ucmain
GOFILES=\
ucmain.go\
include $GOROOT/src/Make.cmd
执行gomake编译ucmain.go到ucmain目录
运行./ucmain显示: USING package uc!
## 9.8.2 本地安装包
本地包在用户目录下:
使用给出的目录结构,以下命令用来从源码安装本地包:
go install /home/user/goprograms/src/uc # 编译安装uc
cd /home/user/goprograms/uc
go install ./uc # 编译安装uc和之前的指令一样
cd ..

54
eBook/09.9.md Normal file
View File

@@ -0,0 +1,54 @@
#9.9 通过git打包和安装
##9.9.1 安装到github
以上的方式对于本地包来说是可以的但是我们如何打包代码到开发者圈子呢那么我们需要一个云端的源码的版本控制系统比如著名的git。
在Linux和OS X的机器上git是默认安装的在windows上你必须先自行安装,参见http://
help.github.com/win-set-up-git/
这里将通过为9.8章节中的uc包创建一个git仓库作为演示
进入到uc包目录下并创建一个git仓库在里面: git init .
信息提示: Initialized empty git repository in .../uc
每一个git项目都需要一个对包进行描述的README文件,所以打开你的文本编辑器(gedit,notepad,LiteIde)添加一些说明进去。
然后添加所有文件到仓库git add README uc.go uc_test.go Makefile
标记为第一个版本: git commit -m "initial rivision"
现在必须去登录github网站: https://github.com
也许你还不能登录你可以去https://github.com/plans注册一个开源项目的免费帐号。输入正确的帐号密码和有效的邮箱地址并进一步创建用户。然后你将获得一个git命令的列表。本地仓库的操作的命令已经完成。一个优秀的系统http://help.github.com/在你遇到任何问题的时候将引导你。
在云端创建一个新的uc仓库;发布的指令为(NNNN替代用户名):
git remote add orign git@github.com:NNNN/uc.git
git push -u origin master
操作完成后检查github上的包页面: http://github.com/NNNN/uc
##9.9.2 从github安装
如果有人想你的远端项目到本地机器,打开终端并执行: go install github.com/NNNN/uc
NNNN是你在github上的用户名
复制
->uc.a包到目录$GOROOT/PKG/LINUX_AMD64/github.com
->源码到$GOROOT/src/pkg/github.com/NNNN/uc
这样现在这台机器上的其他Go应用程序也可以通过导入路径"github.com/NNNN/uc"代替"./uc/uc"来使用。
也可以将其缩写为: import uc "github.com/NNNN/uc"
然修改Makefile: 将TARG=uc替换为TARG-github.com/NNNN/uc
Gomake(和go install)将通过$GOROOT下的本地版本进行工作。
网站和版本控制系统:其他的选择
主要网站有(括号中为网站所使用的版本控制系统)
* bitbucket(hg)
* github(git)
* googlecode(hg/git/svn)
* launchpad(bzr)

View File

@@ -49,7 +49,22 @@
- 6.1 [介绍](06.1.md) - 6.1 [介绍](06.1.md)
- 6.2 [参数与返回值](06.2.md) - 6.2 [参数与返回值](06.2.md)
- 6.3 [传递变长参数](06.3.md) - 6.3 [传递变长参数](06.3.md)
- 第7章数组array与切片slice - 6.4 [defer 和追踪](06.4.md)
- 6.5 [内置函数](06.5.md)
- 6.6 [递归函数](06.6.md)
- 6.7 [将函数作为参数](06.7.md)
- 6.8 [闭包](06.8.md)
- 6.9 [应用闭包:将函数作为返回值](06.9.md)
- 6.10 [使用闭包调试](06.10.md)
- 6.11 [计算函数执行时间](06.11.md)
- 6.12 [通过内存缓存来提升性能](06.12.md)
- 第7章[数组与切片](07.0.md)
- 7.1 [声明和初始化](07.1.md)
- 7.2 [切片](07.2.md)
- 7.3 [For-range 结构](07.3.md)
- 7.4 [切片重组](07.4.md)
- 7.5 [切片的复制与追加](07.5.md)
- 7.6 [字符串、数组和切片的应用](07.6.md)
- 第8章Maps - 第8章Maps
- 第9章package - 第9章package
- 第10章结构struct与方法method - 第10章结构struct与方法method