mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-11 22:06:51 +08:00
16
README.md
16
README.md
@@ -1,7 +1,7 @@
|
||||
《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 语言版本进行修改。
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
## 翻译进度
|
||||
|
||||
6.2 [参数与返回值](eBook/06.2.md)
|
||||
7.5 [切片的复制与追加](eBook/07.5.md)
|
||||
|
||||
## 支持本书
|
||||
|
||||
如果你喜欢本书 《Go入门指南》,你可以参与到本书的翻译或纠正工作中来,具体请联系【无闻 E-mail:joe2010xtmf#163.com】,一同完善本书并帮助壮大 Go 语言在国内的学习群体,给大家提供更好的学习资源。
|
||||
如果你喜欢本书 《Go入门指南》,你可以参与到本书的翻译或纠正工作中来,具体请联系【无闻 E-mail:u#gogs.io】,一同完善本书并帮助壮大 Go 语言在国内的学习群体,给大家提供更好的学习资源。
|
||||
|
||||
## 交流社区
|
||||
|
||||
@@ -23,27 +23,21 @@
|
||||
|
||||
- 2012 年 3 月 28 日以前的博文中的内容基本过时,不要再看
|
||||
- 符合等式 ***百度+思考+失败+翻墙+谷歌+尝试=解决*** 的问题最好不要发问
|
||||
- 不要问 Go 现在的发展前景如何
|
||||
- 不要问学习 Go 语言能不能找到工作
|
||||
- 不要问现在 Go 语言有哪些实际应用
|
||||
|
||||
## 致谢
|
||||
|
||||
- 本书原作者:Ivo Balbaert
|
||||
- 协助或参与翻译:
|
||||
- 参与翻译人员:
|
||||
- [zhanming](https://github.com/zhanming)
|
||||
- [themorecolor](https://github.com/themorecolor)
|
||||
- [everyx](https://github.com/everyx)
|
||||
- [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 许可协议)。
|
||||
|
||||
## 捐助译者
|
||||
|
||||
如果您觉得本书翻译确实不错,并认为译者的努力值得肯定,可以通过 [此链接](https://me.alipay.com/obahua) 为译者提供小额捐助,以资鼓励。
|
||||
|
||||
## 开始阅读
|
||||
|
||||
[前言](./eBook/preface.md)
|
||||
|
@@ -95,7 +95,7 @@
|
||||
|
||||
package main
|
||||
func main() {
|
||||
println(“Hello”, “world”)
|
||||
println("Hello", "world")
|
||||
}
|
||||
|
||||
切换相关目录到下,然后执行指令 `go run hello_world1.go`,将会打印信息:`Hello, world`。
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
// #include <stdio.h>
|
||||
// #include <stdlib.h>
|
||||
import “C”
|
||||
import "C"
|
||||
|
||||
名称 "C" 并不属于标准库的一部分,这只是 cgo 集成的一个特殊名称用于引用 C 的命名空间。在这个命名空间里所包含的 C 类型都可以被使用,例如 C.uint、C.long 等等,还有 libc 中的函数 C.random() 等也可以被调用。
|
||||
|
||||
@@ -26,7 +26,7 @@ Example 3.2 [c1.go](examples/chapter_3/CandGo/c1.go)
|
||||
|
||||
package rand
|
||||
// #include <stdlib.h>
|
||||
import “C”
|
||||
import "C"
|
||||
func Random() int {
|
||||
return int(C.random())
|
||||
}
|
||||
@@ -49,8 +49,8 @@ Example 3.3 [c2.go](examples/chapter_3/CandGo/c2.go)
|
||||
package print
|
||||
// #include <stdio.h>
|
||||
// #include <stdlib.h>
|
||||
import “C”
|
||||
import “unsafe”
|
||||
import "C"
|
||||
import "unsafe"
|
||||
func Print(s string) {
|
||||
cs := C.CString(s)
|
||||
defer C.free(unsafe.Pointer(cs))
|
||||
|
@@ -63,7 +63,7 @@ Go 中的包模型采用了显式依赖关系的机制来达到快速编译的
|
||||
"os"
|
||||
)
|
||||
|
||||
(它甚至还可以更短:`import ("fmt", "os")` 但使用 gofmt 后将会被强制换行)
|
||||
(它甚至还可以更短:`import ("fmt"; "os")` 但使用 gofmt 后将会被强制换行)
|
||||
|
||||
当你导入多个包时,导入的顺序会按照字母排序。
|
||||
|
||||
|
@@ -28,7 +28,7 @@
|
||||
|
||||
这种因式分解关键字的写法一般用于声明全局变量。
|
||||
|
||||
当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,flost 为 0.0,bool 为 false,string 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。
|
||||
当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。
|
||||
|
||||
变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:`numShips`, `startDate`。
|
||||
|
||||
|
@@ -100,7 +100,7 @@ Example 5.3 [string_conversion2.go](examples/chapter_5/string_conversion2.go)
|
||||
|
||||
**注意事项**
|
||||
|
||||
如果您想下面一样,没有为多返回值的函数准备足够的变量来存放结果:
|
||||
如果您像下面一样,没有为多返回值的函数准备足够的变量来存放结果:
|
||||
|
||||
func mySqrt(f float64) (v float64, ok bool) {
|
||||
if f < 0 { return } // error case
|
||||
|
@@ -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 { }`。
|
||||
|
||||
要注意的是,`val` 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(** 译者注:如果 `val` 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值 **)。一个字符串是 Unicode 编码的字符(或称之为 `rune`)集合,因此您也可以用它迭代字符串:
|
||||
要注意的是,`val` 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(**译者注:如果 `val` 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值**)。一个字符串是 Unicode 编码的字符(或称之为 `rune`)集合,因此您也可以用它迭代字符串:
|
||||
|
||||
```
|
||||
for pos, char := range str {
|
||||
|
@@ -85,7 +85,7 @@ Go语言不支持这项特性的主要原因是函数重载需要进行多余的
|
||||
|
||||
函数值(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
42
eBook/06.10.md
Normal 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
21
eBook/06.11.md
Normal 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
61
eBook/06.12.md
Normal 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)
|
122
eBook/06.3.md
122
eBook/06.3.md
@@ -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 传递变长参数
|
||||
|
||||
如果函数的最后一个参数是采用 `...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
243
eBook/06.4.md
Normal 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
21
eBook/06.5.md
Normal 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
119
eBook/06.6.md
Normal 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 与 4,89 与 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
59
eBook/06.7.md
Normal 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
100
eBook/06.8.md
Normal 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
137
eBook/06.9.md
Normal 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)
|
@@ -1,9 +1,11 @@
|
||||
#7.0 数组与分片
|
||||
本章我们从包含大量项的检测数据结构开始(例如数组和map),我们称之为容器。在这里很明显Go受到了python的影响。
|
||||
# 7.0 数组与切片
|
||||
|
||||
以[ ]符号标识的数组类型几乎在所有的编程语言中都是一个基本主力。Go语言中的数组也是类似的,只是有一些特点。Go没有C那么灵活,但是拥有分片(slice)类型。这是一种建立在Go语言数组类型智商的抽象,要想理解slice我们必须先理解数组。数组有特定的用处,但是却有一些呆板,所以在Go语言的代码里并不是特别常见。相对的,slice确实随处可见的。它们构建在数组之上并且提供更强大的能力和便捷。
|
||||
这章我们开始剖析 **容器**, 它是可以包含大量条目(item)的数据结构, 例如数组、切片和 map。从这看到 Go 明显受到 Python 的影响。
|
||||
|
||||
以 `[]` 符号标识的数组类型几乎在所有的编程语言中都是一个基本主力。Go 语言中的数组也是类似的,只是有一些特点。Go 没有 C 那么灵活,但是拥有切片(slice)类型。这是一种建立在 Go 语言数组类型智商的抽象,要想理解切片我们必须先理解数组。数组有特定的用处,但是却有一些呆板,所以在 Go 语言的代码里并不是特别常见。相对的,切片确实随处可见的。它们构建在数组之上并且提供更强大的能力和便捷。
|
||||
|
||||
## 链接
|
||||
|
||||
##链接
|
||||
- [目录](directory.md)
|
||||
- 上一节:[]()
|
||||
- 上一章:[通过内存缓存来提升性能](06.12.md)
|
||||
- 下一节:[声明和初始化](07.1.md)
|
@@ -1,36 +1,38 @@
|
||||
#7.1 声明和初始化
|
||||
# 7.1 声明和初始化
|
||||
|
||||
157
|
||||
|
||||
##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`
|
||||
|
||||
在内存中的结构是:
|
||||
|
||||
每个元素是一个整形值,当声明数组时所有的元素都会被自动初始化为默认值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
|
||||
|
||||
由于索引的存在,遍历数组的方法自然就是使用for-construct:
|
||||
由于索引的存在,遍历数组的方法自然就是使用 for-construct:
|
||||
|
||||
- 通过for初始化数组项
|
||||
- 通过for打印数组元素
|
||||
- 通过for依次处理元素
|
||||
- 通过 for 初始化数组项
|
||||
- 通过 for 打印数组元素
|
||||
- 通过 for 依次处理元素
|
||||
|
||||
示例 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** 下面代码段的输出是什么?
|
||||
|
||||
@@ -82,20 +84,20 @@ IDIOM:
|
||||
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
|
||||
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):
|
||||
|
||||
@@ -115,7 +117,7 @@ Example 7.2 [pointer_array.go](examples/chapter_7/pointer_array.go):
|
||||
[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.3:fibonacci_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)
|
||||
|
||||
@@ -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"}
|
||||
|
||||
只有索引3和4被赋予实际的值,其他元素都被设置为空的字符串,所以输出结果为:
|
||||
只有索引 3 和 4 被赋予实际的值,其他元素都被设置为空的字符串,所以输出结果为:
|
||||
|
||||
Person at 0 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 4 is Ron
|
||||
|
||||
在这里数组长度同样可以写成`...`或者直接忽略。
|
||||
在这里数组长度同样可以写成 `...` 或者直接忽略。
|
||||
|
||||
你可以取任意数组常量的地址来作为指向新实例的指针。
|
||||
|
||||
@@ -194,9 +197,10 @@ Example 7.4 [pointer_array2.go](examples/chapter_7/pointer_array2.go)
|
||||
var vec Vector3D
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -218,9 +222,11 @@ Example 7.5 [multidim_array.go](examples/chapter_7/multidim_array.go)
|
||||
}
|
||||
|
||||
## 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
|
||||
|
||||
但这在Go中并不常用,通常使用分片slice。(参考[第7章](07.2.md))
|
||||
但这在 Go 中并不常用,通常使用切片。(参考 [第 7 章](07.2.md))
|
||||
|
||||
## 链接
|
||||
|
||||
##链接
|
||||
- [目录](directory.md)
|
||||
- 上一节:[数组与分片](07.0.md)
|
||||
- 下一节:[分片slices](07.2.md)
|
||||
- 上一节:[数组与切片](07.0.md)
|
||||
- 下一节:[切片](07.2.md)
|
138
eBook/07.2.md
138
eBook/07.2.md
@@ -1,49 +1,53 @@
|
||||
#7.2 分片
|
||||
# 7.2 切片
|
||||
|
||||
## 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是一个slice,cap就是从s[0]到数组末尾的数组长度。slice的长度永远不会超过它的容量,所以对于slice s来说该不等式永远成立: 0 <= len(s) <= cap(s)
|
||||
给定项的 slice 索引可能比相关数组的相同元素的索引小。和数组不同的是,slice 的长度可以在运行时修改,最小为 0 最大为相关数组的长度:slice 是一个 **长度可变的数组**。
|
||||
|
||||
多个slice如果表示同一个数组的片段,它们可以共享数据;因此一个slice和相关数组的其他slice是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是slice的构建块。
|
||||
slice 提供了计算容量的方法 cap() 可以测量 slice 最长可以达到多少:它等于 slice 的长度 + 数组除 slice 之外的长度。如果 s 是一个 slice,cap 就是从 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] 相同,包含了从第一个到第三个元素(不包括第三个)。
|
||||
|
||||
一个由数组第1,2,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,拥有相同的元素,但是仍然指向相同的相关数组。
|
||||
一个由数组第 1,2,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)
|
||||
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] = 5。slice y[0:4]由元素3, 5, 7和11组成。 
|
||||
`y[0] = 3` 且 `y[1] = 5`。slice y[0:4]由 元素 3, 5, 7 和 11 组成。
|
||||
|
||||

|
||||
|
||||
示例 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组成。  int {
|
||||
s := 0
|
||||
@@ -116,25 +121,26 @@ y[0] = 3且y[1] = 5。slice y[0:4]由元素3, 5, 7和11组成。 
|
||||
}
|
||||
|
||||
## 7.2.3 用make()创建一个slice
|
||||
当相关数组还没有定义时,我们可以使用make()方法来创建一个slice同时创建好相关数组: var slice1 []type = make([]type, len)
|
||||
## 7.2.3 用 make() 创建一个 slice
|
||||
|
||||
也可以简写为 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)
|
||||
new([100]int)[0:50]
|
||||
|
||||
下图描述了使用make方法生成的slice的内存结构:
|
||||
下图描述了使用 make 方法生成的 slice 的内存结构:
|
||||
|
||||
示例 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 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, map和channel(参见第8章,第13章)
|
||||
make(T) **返回一个类型为 T 的初始值**,它只适用于3种内建的引用类型:slice, map 和 channel(参见第 8 章,第 13 章)
|
||||
|
||||
换言之,new方法分配内存,make方法初始化;下图给出了区别:
|
||||
换言之,new 方法分配内存,make 方法初始化;下图给出了区别:
|
||||
|
||||
在图7.3的第一幅图中:
|
||||

|
||||
|
||||
在图 7.3 的第一幅图中:
|
||||
|
||||
var p *[]int = new([]int) // *p == nil; with len and cap 0
|
||||
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)
|
||||
|
||||
这样分配一个有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.4:假设s1 := []byte{'p', 'o', 'e', 'm'}且s2 := d[2:],s2的值是多少?如果我们执行s2[1] == 't',s1和s2现在的值又分配是多少?
|
||||
问题 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.2.5 多维slice
|
||||
和数组一样,slice通常也是一维的,但是也可以由一维组合成高维。通过分片的分片(或者slice的数组),长度可以任意动态变化,所以Go语言的多维slice可以任意切分。而且,内层的slice必须单独分配(通过make方法)
|
||||
## 7.2.5 多维 slice
|
||||
|
||||
## 7.2.6 bytes包
|
||||
bytes的slice十分常见,Go语言有一个bytes包专门用来解决这种类型的操作方法。
|
||||
和数组一样,slice 通常也是一维的,但是也可以由一维组合成高维。通过分片的分片(或者 slice 的数组),长度可以任意动态变化,所以 Go 语言的多维 slice 可以任意切分。而且,内层的 slice 必须单独分配(通过 make 方法)
|
||||
|
||||
bytes包和字符串包十分类似(参见4.7)。而且它还包含一个十分有用的类型Buffer:
|
||||
## 7.2.6 bytes 包
|
||||
|
||||
bytes 的 slice 十分常见,Go 语言有一个 bytes 包专门用来解决这种类型的操作方法。
|
||||
|
||||
bytes 包和字符串包十分类似(参见第 4.7 节)。而且它还包含一个十分有用的类型 Buffer:
|
||||
|
||||
import "bytes"
|
||||
type Buffer struct {
|
||||
...
|
||||
}
|
||||
|
||||
这是一个bytes的定长buffer,提供Read和Write方法,因为读写不知道长度的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串联字符串:类似于Java的StringBuilder类。
|
||||
通过 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
|
||||
for {
|
||||
@@ -238,14 +249,15 @@ Buffer可以这样定义: var buffer bytes.Buffer
|
||||
}
|
||||
fmt.Print(buffer.String(), "\n")
|
||||
|
||||
这种实现方式比使用`+=`要更节省内存和CPU,尤其是要串联的字符串数目特别多的时候。
|
||||
这种实现方式比使用 `+=` 要更节省内存和 CPU,尤其是要串联的字符串数目特别多的时候。
|
||||
|
||||
练习:
|
||||
|
||||
练习 7.5: 给定slice sl,将a []byte数组追加到sl后面。写一个函数Append(slice, data []byte) []byte,该函数在sl不能存储更多数据的时候自动扩容。
|
||||
练习 7.6: 把一个缓存buf分片成两个slice:第一个是前n个bytes,后一个是剩余的,用一行代码实现。
|
||||
练习 7.5: 给定 slice sl,将 a []byte 数组追加到 sl 后面。写一个函数 `Append(slice, data []byte) []byte`,该函数在 sl 不能存储更多数据的时候自动扩容。
|
||||
练习 7.6: 把一个缓存 buf 分片成两个 slice:第一个是前 n 个 bytes,后一个是剩余的,用一行代码实现。
|
||||
|
||||
## 链接
|
||||
|
||||
##链接
|
||||
- [目录](directory.md)
|
||||
- 上一节:[声明和初始化](07.1.md)
|
||||
- 下一节:[For range构建方法](07.3.md)
|
||||
- 下一节:[For-range 结构](07.3.md)
|
@@ -1,11 +1,12 @@
|
||||
#7.3 For range构建方法
|
||||
# 7.3 For-range 结构
|
||||
|
||||
这种构建方法可以应用与数组和 slice:
|
||||
|
||||
这种构建方法可以应用与数组和slice:
|
||||
for ix, value := range slice1 {
|
||||
...
|
||||
}
|
||||
|
||||
第一个返回值dx是数组或者slice的索引,第二个是在该索引位置的值;他们都是仅在for循环内部可见的局部变量,所以该值只是该索引项slice值的一个拷贝并且不能被修改。
|
||||
第一个返回值 dx 是数组或者 slice 的索引,第二个是在该索引位置的值;他们都是仅在 for 循环内部可见的局部变量,所以该值只是该索引项 slice 值的一个拷贝并且不能被修改。
|
||||
|
||||
示例 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
|
||||
|
||||
如果你需要修改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 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 {
|
||||
item *= 2
|
||||
}
|
||||
|
||||
b) 如果a)无法正常工作,写一个for循环让值可以double。
|
||||
b) 如果 a) 无法正常工作,写一个 for 循环让值可以 double。
|
||||
|
||||
问题 7.6: 通过使用省略号操作符`...`来实现累加方法。
|
||||
问题 7.6: 通过使用省略号操作符 `...` 来实现累加方法。
|
||||
|
||||
练习:
|
||||
|
||||
练习 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
|
||||
|
||||
写一个minSlice方法,传入一个int的slice并且返回最小值,再写一个maxSlice方法返回最大值。
|
||||
写一个 minSlice 方法,传入一个 int 的 slice 并且返回最小值,再写一个 maxSlice 方法返回最大值。
|
||||
|
||||
## 链接
|
||||
|
||||
##链接
|
||||
- [目录](directory.md)
|
||||
- 上一节:[分片](07.2.md)
|
||||
- 下一节:[分片重组](07.4.md)
|
||||
- 上一节:[切片](07.2.md)
|
||||
- 下一节:[切片重组](07.4.md)
|
@@ -1,15 +1,16 @@
|
||||
#7.4 分片重组
|
||||
我们已经知道slice创建的时候通常比相关数组小,例如
|
||||
# 7.4 切片重组
|
||||
|
||||
我们已经知道 slice 创建的时候通常比相关数组小,例如
|
||||
|
||||
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)
|
||||
@@ -33,6 +34,7 @@ slice可以反复扩展直到占据整个相关数组。
|
||||
}
|
||||
|
||||
输出结果:
|
||||
|
||||
The length of slice is 1
|
||||
The length of slice is 2
|
||||
The length of slice is 3
|
||||
@@ -65,11 +67,12 @@ slice可以反复扩展直到占据整个相关数组。
|
||||
|
||||
问题 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)
|
||||
- 上一节:[For range构建方法](07.3.md)
|
||||
- 下一节:[拷贝与追加slice](07.5.md)
|
||||
- 上一节:[For-range 结构](07.3.md)
|
||||
- 下一节:[切片的复制与追加](07.5.md)
|
@@ -1,5 +1,6 @@
|
||||
#7.5 拷贝与追加slice
|
||||
如果想增加slice的容量,我们必须创建一个新的更大的slice并把原分片的内容都拷贝过来。下面的代码描述了从拷贝slice的copy方法和向slice追加新元素的append方法。
|
||||
# 7.5 切片的复制与追加
|
||||
|
||||
如果想增加 slice 的容量,我们必须创建一个新的更大的 slice 并把原分片的内容都拷贝过来。下面的代码描述了从拷贝 slice 的 copy 方法和向 slice 追加新元素的 append 方法。
|
||||
|
||||
示例 7.12 [copy_append_slice.go](exmaples/chapter_7/copy_append_slice.go)
|
||||
|
||||
@@ -19,13 +20,14 @@
|
||||
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
|
||||
func AppendByte(slice []byte, data ...byte) []byte {
|
||||
m := len(slice)
|
||||
n := m + len(data)
|
||||
if n > cap(slice) { // if necessary, reallocate
|
||||
@@ -37,19 +39,21 @@ func append(s[]T, x ...T) []T 其中append方法将0个或多个具有相同类
|
||||
slice = slice[0:n]
|
||||
copy(slice[m:n], data)
|
||||
return slice
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
func copy(dst, src []T) int copy方法将类型为T的slice从源地址src拷贝到目标地址dst,覆盖dst的相关元素,并且返回拷贝的元素个数。源地址和目标地址可能会有重叠。拷贝个数是src和dst的长度最小值。如果src是字符串那么元素类型就是byte。如果你还想继续使用src,在拷贝技术后执行src =dst。
|
||||
`func copy(dst, src []T) int` copy 方法将类型为 T 的 slice 从源地址 src 拷贝到目标地址 dst,覆盖 dst 的相关元素,并且返回拷贝的元素个数。源地址和目标地址可能会有重叠。拷贝个数是 src 和 dst 的长度最小值。如果 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.10:filter_slice.go: 用顺序函数过滤容器:s是前10个整形的slice。构造一个函数Filter,第一个参数是s,第二个参数是一个fn func(int) bool,返回满足函数fn的元素slice。通过fn测试方法测试当整型值是偶数时的情况。
|
||||
练习 7.10:filter_slice.go: 用顺序函数过滤容器:s 是前 10 个整形的 slice。构造一个函数 Filter,第一个参数是 s,第二个参数是一个 `fn func(int) bool`,返回满足函数 fn 的元素 slice。通过 fn 测试方法测试当整型值是偶数时的情况。
|
||||
|
||||
练习 7.11:insert_slice.go:写一个函数InsertStringSlice将slice插入到另一个slice的指定位置。
|
||||
练习 7.11:insert_slice.go:写一个函数 InsertStringSlice 将 slice 插入到另一个 slice 的指定位置。
|
||||
|
||||
练习 7.12:remove_slice.go:写一个函数RemoveStringSlice将从start到end索引的元素从slice中移除。
|
||||
练习 7.12:remove_slice.go:写一个函数 RemoveStringSlice 将从 start 到 end 索引的元素从 slice 中移除。
|
||||
|
||||
## 链接
|
||||
|
||||
##链接
|
||||
- [目录](directory.md)
|
||||
- 上一节:[分片重组](07.4.md)
|
||||
- 下一节:[字符串,数组和slice应用](07.6.md)
|
||||
- 上一节:[切片重组](07.4.md)
|
||||
- 下一节:[字符串、数组和切片的应用](07.6.md)
|
||||
|
3
eBook/07.6.md
Normal file
3
eBook/07.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 7.6 字符串、数组和切片的应用
|
||||
|
||||
178
|
7
eBook/09.0.md
Normal file
7
eBook/09.0.md
Normal file
@@ -0,0 +1,7 @@
|
||||
#9.0 包
|
||||
|
||||
|
||||
##链接
|
||||
- [目录](directory.md)
|
||||
- 上一节:[]()
|
||||
- 下一节:[标准库概述](09.1.md)
|
95
eBook/09.1.md
Normal file
95
eBook/09.1.md
Normal file
@@ -0,0 +1,95 @@
|
||||
#A 标准库
|
||||
#9.1 标准库概述
|
||||
像fmt,os等这样具有常用功能的内置包在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
BIN
eBook/09.10.md
Normal file
Binary file not shown.
62
eBook/09.11.md
Normal file
62
eBook/09.11.md
Normal 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
56
eBook/09.2.md
Normal 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
32
eBook/09.3.md
Normal 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
13
eBook/09.4.md
Normal 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
139
eBook/09.5.md
Executable 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
34
eBook/09.6.md
Normal 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
37
eBook/09.7.md
Normal 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
124
eBook/09.8.md
Executable 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
54
eBook/09.9.md
Normal 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)
|
||||
|
@@ -49,7 +49,22 @@
|
||||
- 6.1 [介绍](06.1.md)
|
||||
- 6.2 [参数与返回值](06.2.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
|
||||
- 第9章:包(package)
|
||||
- 第10章:结构(struct)与方法(method)
|
||||
|
Reference in New Issue
Block a user