Conflicts:
	eBook/10.0.md
This commit is contained in:
leisore
2015-08-13 16:06:57 +08:00
88 changed files with 1969 additions and 1435 deletions

22
README_gc.md Normal file
View File

@@ -0,0 +1,22 @@
# Go 入门指南
## 本书介绍
在接触 Go 语言之后,对这门编程语言非常着迷,期间也陆陆续续开始一些帮助国内编程爱好者了解和发展 Go 语言的工作,比如开始录制视频教程[《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming)。但由于目前国内并没有比较好的 Go 语言书籍,而国外的优秀书籍因为英文的缘故在一定程度上也为不少 Go 语言爱好者带来了一些学习上的困扰,不仅为了加快扩散 Go 爱好者的国内群体,本人在完成阅读这本名叫 《The Way to Go》 之后,决定每天抽出一点时间来进行翻译的工作,并且以开源的形式免费分享给有需要的 Go 语言爱好者。
## 关于译者
本书的主要译者是 [@无闻Unknwon](http://www.weibo.com/Obahua),是一名 Go 语言爱好者和传播者,目前是 [Go Walker](https://gowalker.org)、Gopm、[Gogs](http://gogs.io) 和 [Macaron](https://github.com/Unknwon/macaron) 创始人,极客学院签约讲师。
## 适用人群
适合有一定编程基础,初学 Go 语言的爱好者。
>
Martini&Macaron 交流群371440803
>
Golang 编程245386165
|更新日期 |更新内容
|----------|------------------
|2015-08-11|9.11 在 Go 程序中使用外部库

81
TOC.md Normal file
View File

@@ -0,0 +1,81 @@
- [前言](eBook/preface.md)
- 第一部分:学习 Go 语言
- 第1章Go 语言的起源,发展与普及
- 1.1 [起源与发展](eBook/01.1.md)
- 1.2 [语言的主要特性与发展的环境和影响因素](eBook/01.2.md)
- 第2章安装与运行环境
- 2.1 [平台与架构](eBook/02.1.md)
- 2.2 [Go 环境变量](eBook/02.2.md)
- 2.3 [在 Linux 上安装 Go](eBook/02.3.md)
- 2.4 [在 Mac OS X 上安装 Go](eBook/02.4.md)
- 2.5 [在 Windows 上安装 Go](eBook/02.5.md)
- 2.6 [安装目录清单](eBook/02.6.md)
- 2.7 [Go 运行时runtime](eBook/02.7.md)
- 2.8 [Go 解释器](eBook/02.8.md)
- 第3章[编辑器、集成开发环境与其它工具](eBook/03.0.md)
- 3.1 [Go 开发环境的基本要求](eBook/03.1.md)
- 3.2 [编辑器和集成开发环境](eBook/03.2.md)
- 3.3 [调试器](eBook/03.3.md)
- 3.4 [构建并运行 Go 程序](eBook/03.4.md)
- 3.5 [格式化代码](eBook/03.5.md)
- 3.6 [生成代码文档](eBook/03.6.md)
- 3.7 [其它工具](eBook/03.7.md)
- 3.8 [Go 性能说明](eBook/03.8.md)
- 3.9 [与其它语言进行交互](eBook/03.9.md)
- 第二部分:语言的核心结构与技术
- 第4章基本结构和基本数据类型
- 4.1 [文件名、关键字与标识符](eBook/04.1.md)
- 4.2 [Go 程序的基本结构和要素](eBook/04.2.md)
- 4.3 [常量](eBook/04.3.md)
- 4.4 [变量](eBook/04.4.md)
- 4.5 [基本类型和运算符](eBook/04.5.md)
- 4.6 [字符串](eBook/04.6.md)
- 4.7 [strings 和 strconv 包](eBook/04.7.md)
- 4.8 [时间和日期](eBook/04.8.md)
- 4.9 [指针](eBook/04.9.md)
- 第5章[控制结构](eBook/05.0.md)
- 5.1 [if-else 结构](eBook/05.1.md)
- 5.2 [测试多返回值函数的错误](eBook/05.2.md)
- 5.3 [switch 结构](eBook/05.3.md)
- 5.4 [for 结构](eBook/05.4.md)
- 5.5 [Break 与 continue](eBook/05.5.md)
- 5.6 [标签与 goto](eBook/05.6.md)
- 第6章[函数function](eBook/06.0.md)
- 6.1 [介绍](eBook/06.1.md)
- 6.2 [函数参数与返回值](eBook/06.2.md)
- 6.3 [传递变长参数](eBook/06.3.md)
- 6.4 [defer 和追踪](eBook/06.4.md)
- 6.5 [内置函数](eBook/06.5.md)
- 6.6 [递归函数](eBook/06.6.md)
- 6.7 [将函数作为参数](eBook/06.7.md)
- 6.8 [闭包](eBook/06.8.md)
- 6.9 [应用闭包:将函数作为返回值](eBook/06.9.md)
- 6.10 [使用闭包调试](eBook/06.10.md)
- 6.11 [计算函数执行时间](eBook/06.11.md)
- 6.12 [通过内存缓存来提升性能](eBook/06.12.md)
- 第7章[数组与切片](eBook/07.0.md)
- 7.1 [声明和初始化](eBook/07.1.md)
- 7.2 [切片](eBook/07.2.md)
- 7.3 [For-range 结构](eBook/07.3.md)
- 7.4 [切片重组reslice](eBook/07.4.md)
- 7.5 [切片的复制与追加](eBook/07.5.md)
- 7.6 [字符串、数组和切片的应用](eBook/07.6.md)
- 第8章[Map](eBook/08.0.md)
- 8.1 [声明、初始化和 make](eBook/08.1.md)
- 8.2 [测试键值对是否存在及删除元素](eBook/08.2.md)
- 8.3 [for-range 的配套用法](eBook/08.3.md)
- 8.4 [map 类型的切片](eBook/08.4.md)
- 8.5 [map 的排序](eBook/08.5.md)
- 8.6 [将 map 的键值对调](eBook/08.6.md)
- 第9章[package](eBook/09.0.md)
- 9.1 [标准库概述](eBook/09.1.md)
- 9.2 [regexp 包](eBook/09.2.md)
- 9.3 [锁和 sync 包](eBook/09.3.md)
- 9.4 [精密计算和 big 包](eBook/09.4.md)
- 9.5 [自定义包和可见性](eBook/09.5.md)
- 9.6 [为自定义包使用 godoc](eBook/09.6.md)
- 9.7 [使用 go install 安装自定义包](eBook/09.7.md)
- 9.8 [自定义包的目录结构、go install 和 go test](eBook/09.8.md)
- 9.9 [通过 Git 打包和安装](eBook/09.9.md)
- 9.10 [Go 的外部包和项目](eBook/09.10.md)
- 9.11 [在 Go 程序中使用外部库](eBook/09.11.md)

9
config.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "Go 入门指南",
"introduction": "Go 经典书籍《The Way To Go》的中文译本。",
"path": {
"content": "eBook",
"readme": "README_gc.md"
}
}

BIN
cover/background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
cover/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -4,7 +4,7 @@ Go 语言起源 2007 年,并于 2009 年正式对外发布。它从 2009 年 9
**这是一个由计算机领域 “发明之父” 所组成的黄金团队,他们对系统编程语言,操作系统和并行都有着非常深刻的见解**
![](images/1.1.designers_of_Go.jpg?raw=true)
![](../images/1.1.designers_of_Go.jpg?raw=true)
图 1.1 Go 语言设计者Griesemer、Thompson 和 Pike
@@ -34,7 +34,7 @@ Go 语言的官方网站是 [golang.org](http://golang.org),这个站点采用
Go 通过以下的 Logo 来展示它的速度并以囊地鼠Gopher作为它的吉祥物。
![](images/1.2.Go_logo.jpg?raw=true)
![](../images/1.2.Go_logo.jpg?raw=true)
图1.2 Go 语言 Logo

View File

@@ -12,7 +12,7 @@
下图展示了一些其它编程语言对 Go 语言的影响:
![](images/1.3.influences_on_go.jpg?raw=true)
![](../images/1.3.influences_on_go.jpg?raw=true)
图 1.3 其它编程语言对 Go 语言的影响

View File

@@ -26,7 +26,7 @@ Go 语言开发团队开发了适用于以下操作系统的编译器:
编译器目前支持以下基于 Intel 或 AMD 处理器架构的程序构建。
![](images/2.1.gc.jpg?raw=true)
![](../images/2.1.gc.jpg?raw=true)
图2.1 gc 编译器支持的处理器架构

View File

@@ -47,7 +47,7 @@
在完成编译之后(通常在 1 分钟以内,如果你在 B 型树莓派上编译,一般需要 1 个小时),你会在终端看到如下信息被打印:
![](images/2.3.allbash.png?raw=true)
![](../images/2.3.allbash.png?raw=true)
图 2.3 完成编译后在终端打印的信息

View File

@@ -29,7 +29,7 @@ LiteIDE 是一款非常好用的轻量级 Go 集成开发环境(基于 QT、Ka
同时,它具备了抽象语法树视图的功能,可以清楚地纵览项目中的常量、变量、函数、不同类型以及他们的属性和方法。
![](images/3.2.liteide.jpg?raw=true)
![](../images/3.2.liteide.jpg?raw=true)
图 3.1 LiteIDE 代码编辑界面和抽象语法树视图
@@ -39,7 +39,7 @@ LiteIDE 是一款非常好用的轻量级 Go 集成开发环境(基于 QT、Ka
其依附于著名的 Eclipse 这个大型开发环境,虽然需要安装 JVM 运行环境,但却可以很容易地享有 Eclipse 本身所具有的诸多功能。这是一个非常好的编辑器,完善的代码补全、抽象语法树视图、项目管理和程序调试功能。
![](images/3.2.goclipse.jpg?raw=true)
![](../images/3.2.goclipse.jpg?raw=true)
图 3.2 GoClipse 代码编辑界面、抽象语法树视图和项目管理

View File

@@ -145,7 +145,7 @@ main 函数是每一个可执行程序所必须包含的,一般来说都是在
(这是因为编译器会产生 `func main() ;` 这样的结果,很明显这错误的)
**Go 语言虽然看起来不使用分号作为语句的结束,但实际上这一过程是由编译器自动完成,因此才会引发上面这样的错误**
**Go 语言虽然看起来不使用分号作为语句的结束,但实际上这一过程是由编译器自动完成,因此才会引发上面这样的错误**
右大括号 `}` 需要被放在紧接着函数体的下一行。如果你的函数非常简短,你也可以将它们放在同一行:

View File

@@ -80,7 +80,7 @@ var str string = "Go says hello to the world!"
```go
var a = 15
var b = false
var str = Go says hello to the world!
var str = "Go says hello to the world!"
```
或:
@@ -147,13 +147,13 @@ func main() {
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:
![](images/4.4.2_fig4.1.jpg?raw=true)
![](../images/4.4.2_fig4.1.jpg?raw=true)
另外,像数组(第 7 章)和结构(第 10 章)这些复合类型也是值类型。
当使用等号 `=` 将一个变量的值赋值给另一个变量时,如:`j = i`,实际上是在内存中将 i 的值进行了拷贝:
![](images/4.4.2_fig4.2.jpg?raw=true)
![](../images/4.4.2_fig4.2.jpg?raw=true)
你可以通过 &i 来获取变量 i 的内存地址(第 4.9 节例如0xf840000040每次的地址都可能不一样。值类型的变量的值存储在栈中。
@@ -163,7 +163,7 @@ func main() {
一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。
![](images/4.4.2_fig4.3.jpg?raw=true)
![](../images/4.4.2_fig4.3.jpg?raw=true)
这个内存地址为称之为指针(你可以从上图中很清晰地看到,第 4.9 节将会详细说明),这个指针实际上也被存在另外的某一个字中。

View File

@@ -22,17 +22,21 @@
示例:
var aVar = 10
aVar == 5 -> false
aVar == 10 -> true
```go
var aVar = 10
aVar == 5 -> false
aVar == 10 -> true
```
当不等运算符两边的值是不同的时候会返回 true否则返回 false。
示例:
var aVar = 10
aVar != 5 -> true
aVar != 10 -> false
```go
var aVar = 10
aVar != 5 -> true
aVar != 10 -> false
```
Go 对于值之间的比较有非常严格的限制只有两个类型相同的值才可以进行比较如果值的类型是接口interface第 11 章),它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。
@@ -44,26 +48,32 @@ Go 语言中包含以下逻辑运算符:
非运算符:`!`
!T -> false
!F -> true
```go
!T -> false
!F -> true
```
非运算符用于取得和布尔值相反的结果。
和运算符:`&&`
T && T -> true
T && F -> false
F && T -> false
F && F -> false
```go
T && T -> true
T && F -> false
F && T -> false
F && F -> false
```
只有当两边的值都为 true 的时候,和运算符的结果才是 true。
或运算符:`||`
T || T -> true
T || F -> true
F || T -> true
F || F -> false
```go
T || T -> true
T || F -> true
F || T -> true
F || F -> false
```
只有当两边的值都为 false 的时候,或运算符的结果才是 false其中任意一边的值为 true 就能够使得该表达式的结果为 true。
@@ -81,7 +91,7 @@ Go 语言中包含以下逻辑运算符:
### 4.5.2.1 整型 int 和浮点型 float
Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码(二的补码,详情参见[http://en.wikipedia.org/wiki/Two's_complement](http://en.wikipedia.org/wiki/Two's_complement))。
Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码(详情参见 [二的补码](http://en.wikipedia.org/wiki/Two's_complement) 页面)。
Go 也有基于架构的类型例如int、uint 和 uintptr。
@@ -127,17 +137,19 @@ float32 精确到小数点后 7 位float64 精确到小数点后 15 位。由
Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):
Example 4.8 [type_mixing.go](examples/chapter_4/type_mixing.go)
示例 4.8 [type_mixing.go](examples/chapter_4/type_mixing.go)
package main
```go
package main
func main() {
func main() {
var a int
var b int32
a = 15
b = a + a // 编译错误
b = b + 5 // 因为 5 是常量,所以可以通过编译
}
}
```
如果你尝试编译该程序,则将得到编译错误 `cannot use a + a (type int) as type int32 in assignment`
@@ -145,26 +157,31 @@ Example 4.8 [type_mixing.go](examples/chapter_4/type_mixing.go)
下面这个程序展示了通过显示转换来避免这个问题(第 4.2 节)。
Example 4.9 [casting.go](examples/chapter_4/casting.go)
示例 4.9 [casting.go](examples/chapter_4/casting.go)
package main
```go
package main
importfmt
import "fmt"
func main() {
func main() {
var n int16 = 34
var m int32
// compiler error: cannot use n (type int16) as type int32 in assignment
//m = n
m = int32(n)
fmt.Printf(“32 bit int is: %d\n”, m)
fmt.Printf(“16 bit int is: %d\n”, n)
}
fmt.Printf("32 bit int is: %d\n", m)
fmt.Printf("16 bit int is: %d\n", n)
}
```
// the output is:
32 bit int is: 34
16 bit int is: 34
输出:
```
32 bit int is: 34
16 bit int is: 34
```
**格式化说明符**
@@ -176,16 +193,19 @@ Example 4.9 [casting.go](examples/chapter_4/casting.go)
当进行类似 `a32bitInt = int32(a32Float)` 的转换时,小数点后的数字将被丢弃。这种情况一般发生当从取值范围较大的类型转换为取值范围较小的类型时,或者你可以写一个专门用于处理类型转换的函数来确保没有发生精度的丢失。下面这个例子展示如何安全地从 int 型转换为 int8
func Uint8FromInt(n int) (uint8, error) {
```go
func Uint8FromInt(n int) (uint8, error) {
if 0 <= n && n <= math.MaxUint8 { // conversion is safe
return uint8(n), nil
}
return 0, fmt.Errorf(“%d is out of the uint8 range”, n)
}
return 0, fmt.Errorf("%d is out of the uint8 range", n)
}
```
或者安全地从 float64 转换为 int
func IntFromFloat64(x float64) int {
```go
func IntFromFloat64(x float64) int {
if math.MinInt32 <= x && x <= math.MaxInt32 { // x lies in the integer range
whole, fraction := math.Modf(x)
if fraction >= 0.5 {
@@ -193,8 +213,9 @@ Example 4.9 [casting.go](examples/chapter_4/casting.go)
}
return int(whole)
}
panic(fmt.Sprintf(“%g is out of the int32 range”, x))
}
panic(fmt.Sprintf("%g is out of the int32 range", x))
}
```
不过如果你实际存的数字超出你要转换到的类型的取值范围的话,则会引发 panic第 13.2 节)。
@@ -211,13 +232,17 @@ Go 拥有以下复数类型:
示例:
var c1 complex64 = 5 + 10i
fmt.Printf(“The value is: %v”, c1)
// 输出: 5 + 10i
```go
var c1 complex64 = 5 + 10i
fmt.Printf("The value is: %v", c1)
// 输出: 5 + 10i
```
如果 `re``im` 的类型均为 float32那么类型为 complex64 的复数 c 可以通过以下方式来获得:
c = complex(re, im)
```go
c = complex(re, im)
```
函数 `real(c)``imag(c)` 可以分别获得相应的实数和虚数部分。
@@ -290,8 +315,9 @@ Go 拥有以下复数类型:
使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举
type ByteSize float64
const (
```go
type ByteSize float64
const (
_ = iota // 通过赋值给空白标识符来忽略值
KB ByteSize = 1<<(10*iota)
MB
@@ -301,18 +327,21 @@ Go 拥有以下复数类型:
EB
ZB
YB
)
)
```
**在通讯中使用位左移表示标识的用例**
type BitFlag int
const (
```go
type BitFlag int
const (
Active BitFlag = 1 << iota // 1 << 0 == 1
Send // 1 << 1 == 2
Receive // 1 << 2 == 4
)
)
flag := Active | Send // == 3
flag := Active | Send // == 3
```
### 4.5.2.4 逻辑运算符
@@ -320,7 +349,9 @@ Go 中拥有以下逻辑运算符:`==`、`!=`(第 4.5.1 节)、`<`、`<=`
它们之所以被称为逻辑运算符是因为它们的运算结果总是为布尔值 `bool`例如
b3:= 10 > 5 // b3 is true
```go
b3:= 10 > 5 // b3 is true
```
### 4.5.2.5 算术运算符
@@ -353,16 +384,17 @@ Go 中拥有以下逻辑运算符:`==`、`!=`(第 4.5.1 节)、`<`、`<=`
一些像游戏或者统计学类的应用需要用到随机数。`rand` 包实现了伪随机数的生成。
Example 4.10 [random.go](examples/chapter_4/random.go) 演示了如何生成 10 个非负随机数:
示例 4.10 [random.go](examples/chapter_4/random.go) 演示了如何生成 10 个非负随机数:
package main
import (
```go
package main
import (
"fmt"
"math/rand"
"time"
)
)
func main() {
func main() {
for i := 0; i < 10; i++ {
a := rand.Int()
fmt.Printf("%d / ", a)
@@ -377,7 +409,8 @@ Example 4.10 [random.go](examples/chapter_4/random.go) 演示了如何生成 10
for i := 0; i < 10; i++ {
fmt.Printf("%2.2f / ", 100*rand.Float32())
}
}
}
```
可能的输出:
@@ -410,18 +443,20 @@ Example 4.10 [random.go](examples/chapter_4/random.go) 演示了如何生成 10
在 `type TZ int` 中TZ 就是 int 类型的新名称(用于表示程序中的时区),然后就可以使用 TZ 来操作 int 类型的数据。
Example 4.11 [type.go](examples/chapter_4/type.go)
示例 4.11 [type.go](examples/chapter_4/type.go)
package main
import fmt
```go
package main
import "fmt"
type TZ int
type TZ int
func main() {
func main() {
var a, b TZ = 3, 4
c := a + b
fmt.Printf(“c has the value: %d, c) // 输出c has the value: 7
}
fmt.Printf("c has the value: %d", c) // 输出c has the value: 7
}
```
实际上,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法(第 10 章TZ 可以自定义一个方法用来输出更加人性化的时区信息。
@@ -433,7 +468,9 @@ Example 4.11 [type.go](examples/chapter_4/type.go)
在 ASCII 码表中A 的值是 65而使用 16 进制表示则为 41所以下面的写法是等效的
var ch byte = 65 var ch byte = \x41
```go
var ch byte = 65 var ch byte = '\x41'
```
`\x` 总是紧跟着长度为 2 的 16 进制数)
@@ -445,15 +482,17 @@ Example 4.11 [type.go](examples/chapter_4/type.go)
因为 Unicode 至少占用 2 个字节,所以我们使用 `int16` 或者 `int` 类型来表示。如果需要使用到 4 字节,则会加上 `\U` 前缀;前缀 `\u` 则总是紧跟着长度为 4 的 16 进制数,前缀 `\U` 紧跟着长度为 8 的 16 进制数。
Example 4.12 [char.go](examples/chapter_4/char.go)
示例 4.12 [char.go](examples/chapter_4/char.go)
var ch int = \u0041
var ch2 int = \u03B2
var ch3 int = \U00101234
fmt.Printf(“%d - %d - %d\n”, ch, ch2, ch3) // integer
fmt.Printf(“%c - %c - %c\n, ch, ch2, ch3) // character
fmt.Printf(“%X - %X - %X\n, ch, ch2, ch3) // UTF-8 bytes
fmt.Printf(“%U - %U - %U, ch, ch2, ch3) // UTF-8 code point
```go
var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
```
输出:
@@ -466,9 +505,9 @@ Example 4.12 [char.go](examples/chapter_4/char.go)
`unicode` 包含了一些针对测试字符的非常有用的函数(其中 `ch` 代表字符):
判断是否为字母 unicode.IsLetter(ch)
判断是否为数字 unicode.IsDigit(ch)
判断是否为空白符号 unicode.IsSpace(ch)
- 判断是否为字母`unicode.IsLetter(ch)`
- 判断是否为数字`unicode.IsDigit(ch)`
- 判断是否为空白符号`unicode.IsSpace(ch)`
这些函数返回一个布尔值。包 `utf8` 拥有更多与 rune 相关的函数。

View File

@@ -28,11 +28,11 @@ Go 支持以下 2 种形式的字面值:
一般的比较运算符(`==``!=``<``<=``>=``>`)通过在内存中按字节比较来实现字符串的对比。你可以通过函数 `len()` 来获取字符串所占的字节长度,例如:`len(str)`
字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 `[]` 内写入索引,索引从 0 开始计数
字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 `[]` 内写入索引,索引从 0 开始计数
字符串 str 的第 1 个字节: str[0]
第 i 个字节: str[i]
最后 1 个字节: str[len(str)-1]
- 字符串 str 的第 1 个字节:`str[0]`
- 第 i 个字节:`str[i]`
- 最后 1 个字节:`str[len(str)-1]`
需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。
@@ -46,16 +46,20 @@ Go 支持以下 2 种形式的字面值:
你可以通过以下方式来对代码中多行的字符串进行拼接:
str := “Beginning of the string “+
“second part of the string
```go
str := "Beginning of the string " +
"second part of the string"
```
由于编译器行尾自动补全分号的缘故,加号 `+` 必须放在第一行。
拼接的简写形式 `+=` 也可以用于字符串:
s := “hel” + “lo,”
s += “world!”
fmt.Println(s) //输出 “hello, world!
```go
s := "hel" + "lo,"
s += "world!"
fmt.Println(s) //输出 “hello, world!”
```
在循环中使用加号 `+` 拼接字符串并不是最高效的做法,更好的办法是使用函数 `strings.Join()`(第 4.7.10 节),有没有更好地办法了?有!使用字节缓冲(`bytes.Buffer`)拼接更加给力(第 7.2.6 节)!

View File

@@ -6,26 +6,32 @@
`HasPrefix` 判断字符串 `s` 是否以 `prefix` 开头:
strings.HasPrefix(s, prefix string) bool
```go
strings.HasPrefix(s, prefix string) bool
```
`HasSuffix` 判断字符串 `s` 是否以 `suffix` 结尾:
strings.HasSuffix(s, suffix string) bool
```go
strings.HasSuffix(s, suffix string) bool
```
Example 4.13 [presuffix.go](examples/chapter_4/presuffix.go)
示例 4.13 [presuffix.go](examples/chapter_4/presuffix.go)
package main
```go
package main
import (
import (
"fmt"
"strings"
)
)
func main() {
func main() {
var str string = "This is an example of a string"
fmt.Printf("T/F? Does the string \"%s\" have prefix %s? ", str, "Th")
fmt.Printf("%t\n", strings.HasPrefix(str, "Th"))
}
}
```
输出:
@@ -43,26 +49,33 @@ Example 4.13 [presuffix.go](examples/chapter_4/presuffix.go)
`Index` 返回字符串 `str` 在字符串 `s` 中的索引(`str` 的第一个字符的索引),-1 表示字符串 `s` 不包含字符串 `str`
strings.Index(s, str string) int
```go
strings.Index(s, str string) int
```
`LastIndex` 返回字符串 `str` 在字符串 `s` 中最后出现位置的索引(`str` 的第一个字符的索引),-1 表示字符串 `s` 不包含字符串 `str`
strings.LastIndex(s, str string) int
```go
strings.LastIndex(s, str string) int
```
如果 `ch` 是非 ASCII 编码的字符,建议使用以下函数来对字符进行定位:
strings.IndexRune(s string, ch int) int
```go
strings.IndexRune(s string, ch int) int
```
Example 4.14 [index_in_string.go](examples/chapter_4/index_in_string.go)
示例 4.14 [index_in_string.go](examples/chapter_4/index_in_string.go)
package main
```go
package main
import (
import (
"fmt"
"strings"
)
)
func main() {
func main() {
var str string = "Hi, I'm Marc, Hi."
fmt.Printf("The position of \"Marc\" is: ")
@@ -75,7 +88,8 @@ Example 4.14 [index_in_string.go](examples/chapter_4/index_in_string.go)
fmt.Printf("The position of \"Burger\" is: ")
fmt.Printf("%d\n", strings.Index(str, "Burger"))
}
}
```
输出:
@@ -88,24 +102,29 @@ Example 4.14 [index_in_string.go](examples/chapter_4/index_in_string.go)
`Replace` 用于将字符串 `str` 中的前 `n` 个字符串 `old` 替换为字符串 `new`,并返回一个新的字符串,如果 `n = -1` 则替换所有字符串 `old` 为字符串 `new`
strings.Replace(str, old, new, n) string
```go
strings.Replace(str, old, new, n) string
```
## 4.7.5 统计字符串出现次数
`Count` 用于计算字符串 `str` 在字符串 `s` 中出现的非重叠次数:
strings.Count(s, str string) int
```go
strings.Count(s, str string) int
```
Example 4.15 [count_substring.go](examples/chapter_4/count_substring.go)
示例 4.15 [count_substring.go](examples/chapter_4/count_substring.go)
package main
```go
package main
import (
import (
"fmt"
"strings"
)
)
func main() {
func main() {
var str string = "Hello, how is it going, Hugo?"
var manyG = "gggggggggg"
@@ -114,7 +133,8 @@ Example 4.15 [count_substring.go](examples/chapter_4/count_substring.go)
fmt.Printf("Number of double g's in %s is: ", manyG)
fmt.Printf("%d\n", strings.Count(manyG, "gg"))
}
}
```
输出:
@@ -125,24 +145,28 @@ Example 4.15 [count_substring.go](examples/chapter_4/count_substring.go)
`Repeat` 用于重复 `count` 次字符串 `s` 并返回一个新的字符串:
strings.Repeat(s, count int) string
```go
strings.Repeat(s, count int) string
```
Example 4.16 [repeat_string.go](examples/chapter_4/repeat_string.go)
示例 4.16 [repeat_string.go](examples/chapter_4/repeat_string.go)
package main
```go
package main
import (
import (
"fmt"
"strings"
)
)
func main() {
func main() {
var origS string = "Hi there! "
var newS string
newS = strings.Repeat(origS, 3)
fmt.Printf("The new repeated string is: %s\n", newS)
}
}
```
输出:
@@ -152,22 +176,27 @@ Example 4.16 [repeat_string.go](examples/chapter_4/repeat_string.go)
`ToLower` 将字符串中的 Unicode 字符全部转换为相应的小写字符:
strings.ToLower(s) string
```go
strings.ToLower(s) string
```
`ToUpper` 将字符串中的 Unicode 字符全部转换为相应的大写字符:
strings.ToUpper(s) string
```go
strings.ToUpper(s) string
```
Example 4.17 [toupper_lower.go](examples/chapter_4/toupper_lower.go)
示例 4.17 [toupper_lower.go](examples/chapter_4/toupper_lower.go)
package main
```go
package main
import (
import (
"fmt"
"strings"
)
)
func main() {
func main() {
var orig string = "Hey, how are you George?"
var lower string
var upper string
@@ -177,7 +206,8 @@ Example 4.17 [toupper_lower.go](examples/chapter_4/toupper_lower.go)
fmt.Printf("The lowercase string is: %s\n", lower)
upper = strings.ToUpper(orig)
fmt.Printf("The uppercase string is: %s\n", upper)
}
}
```
输出:
@@ -201,18 +231,21 @@ Example 4.17 [toupper_lower.go](examples/chapter_4/toupper_lower.go)
`Join` 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串:
Strings.Join(sl []string, sep string)
```go
Strings.Join(sl []string, sep string)
```
Example 4.18 [strings_splitjoin.go](examples/chapter_4/strings_splitjoin.go)
示例 4.18 [strings_splitjoin.go](examples/chapter_4/strings_splitjoin.go)
package main
```go
package main
import (
import (
"fmt"
"strings"
)
)
func main() {
func main() {
str := "The quick brown fox jumps over the lazy dog"
sl := strings.Fields(str)
fmt.Printf("Splitted in slice: %v\n", sl)
@@ -229,7 +262,8 @@ Example 4.18 [strings_splitjoin.go](examples/chapter_4/strings_splitjoin.go)
fmt.Println()
str3 := strings.Join(sl2,";")
fmt.Printf("sl2 joined by ;: %s\n", str3)
}
}
```
输出:
@@ -239,7 +273,7 @@ Example 4.18 [strings_splitjoin.go](examples/chapter_4/strings_splitjoin.go)
GO1 - The ABC of Go - 25 -
sl2 joined by ;: GO1;The ABC of Go;25
其它有关字符串操作的文档请参阅官方文档 [http://golang.org/pkg/strings/](http://golang.org/pkg/strings/) ***译者注:国内用户可访问 [http://docs.studygolang.com/pkg/strings/](http://docs.studygolang.com/pkg/strings/)*** )。
其它有关字符串操作的文档请参阅 [官方文档](http://golang.org/pkg/strings/) **译者注:国内用户可访问 [该页面](http://docs.studygolang.com/pkg/strings/)** )。
## 4.7.11 从字符串中读取内容
@@ -274,16 +308,17 @@ Example 4.18 [strings_splitjoin.go](examples/chapter_4/strings_splitjoin.go)
在下面这个示例中,我们忽略可能出现的转换错误:
Example 4.19 [string_conversion.go](examples/chapter_4/string_conversion.go)
示例 4.19 [string_conversion.go](examples/chapter_4/string_conversion.go)
package main
```go
package main
import (
import (
"fmt"
"strconv"
)
)
func main() {
func main() {
var orig string = "666"
var an int
var newS string
@@ -295,7 +330,8 @@ Example 4.19 [string_conversion.go](examples/chapter_4/string_conversion.go)
an = an + 5
newS = strconv.Itoa(an)
fmt.Printf("The new string is: %s\n", newS)
}
}
```
输出:
@@ -305,7 +341,7 @@ Example 4.19 [string_conversion.go](examples/chapter_4/string_conversion.go)
在第 5.1 节,我们将会利用 if 语句来对可能出现的错误进行分类处理。
更多有关该包的讨论,请参阅官方文档 [http://golang.org/pkg/strconv/](http://golang.org/pkg/strconv/) ***译者注:国内用户可访问 [http://docs.studygolang.com/pkg/strconv/](http://docs.studygolang.com/pkg/strconv/)*** )。
更多有关该包的讨论,请参阅 [官方文档](http://golang.org/pkg/strconv/) **译者注:国内用户可访问 [该页面](http://docs.studygolang.com/pkg/strconv/)** )。
## 链接

View File

@@ -2,7 +2,7 @@
`time` 包为我们提供了一个数据类型 `time.Time`(作为值使用)以及显示和测量时间和日期的功能函数。
当前时间可以使用 `time.Now()` 获取,或者使用 `t.Day()``t.Minute()` 等等来获取时间的一部分;你甚至可以自定义时间格式化字符串,例如: `fmt.Printf("%02d.%02d.%4d\n, t.Day(), t.Month(), t.Year())` 将会输出 `21.07.2011`
当前时间可以使用 `time.Now()` 获取,或者使用 `t.Day()``t.Minute()` 等等来获取时间的一部分;你甚至可以自定义时间格式化字符串,例如: `fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year())` 将会输出 `21.07.2011`
Duration 类型表示两个连续时刻所相差的纳秒数,类型为 int64。Location 类型映射某个时区的时间UTC 表示通用协调世界时间。
@@ -10,27 +10,30 @@ Duration 类型表示两个连续时刻所相差的纳秒数,类型为 int64
一般的格式化设计是通过对于一个标准时间的格式化描述来展现的,这听起来很奇怪,但看下面这个例子你就会一目了然:
fmt.Println(t.Format(“02 Jan 2006 15:04”))
```go
fmt.Println(t.Format("02 Jan 2006 15:04"))
```
输出:
21 Jul 2011 10:31
其它有关时间操作的文档请参阅官方文档 [http://golang.org/pkg/time/](http://golang.org/pkg/time/) **译者注:国内用户可访问 [http://docs.studygolang.com/pkg/time/](http://docs.studygolang.com/pkg/time/)** )。
其它有关时间操作的文档请参阅 [官方文档](http://golang.org/pkg/time/) **译者注:国内用户可访问 [该页面](http://docs.studygolang.com/pkg/time/)** )。
Example 4.20 [time.go](examples/chapter_4/time.go)
示例 4.20 [time.go](examples/chapter_4/time.go)
package main
import (
“fmt”
“time”
)
```go
package main
import (
"fmt"
"time"
)
var week time.Duration
func main() {
var week time.Duration
func main() {
t := time.Now()
fmt.Println(t) // e.g. Wed Dec 21 09:52:14 +0100 RST 2011
fmt.Printf(“%02d.%02d.%4d\n”, t.Day(), t.Month(), t.Year())
fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year())
// 21.12.2011
t = time.Now().UTC()
fmt.Println(t) // Wed Dec 21 08:52:14 +0000 UTC 2011
@@ -42,11 +45,12 @@ Example 4.20 [time.go](examples/chapter_4/time.go)
// formatting times:
fmt.Println(t.Format(time.RFC822)) // 21 Dec 11 0852 UTC
fmt.Println(t.Format(time.ANSIC)) // Wed Dec 21 08:56:34 2011
fmt.Println(t.Format(“02 Jan 2006 15:04”)) // 21 Dec 2011 08:52
s := t.Format(“20060102”)
fmt.Println(t, “=>”, s)
fmt.Println(t.Format("02 Jan 2006 15:04")) // 21 Dec 2011 08:52
s := t.Format("20060102")
fmt.Println(t, "=>", s)
// Wed Dec 21 08:52:14 +0000 UTC 2011 => 20111221
}
}
```
输出的结果已经写在每行 `//` 的后面。

View File

@@ -8,14 +8,18 @@
Go 语言的取地址符是 `&`,放到一个变量前使用就会返回相应变量的内存地址。
下面的代码片段(Example 4.9 [pointer.go](examples/chapter_4/pointer.go))可能输出 `An integer: 5, its location in memory: 0x6b0820`(这个值随着你每次运行程序而变化)。
下面的代码片段(示例 4.9 [pointer.go](examples/chapter_4/pointer.go))可能输出 `An integer: 5, its location in memory: 0x6b0820`(这个值随着你每次运行程序而变化)。
var i1 = 5
fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1)
```go
var i1 = 5
fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1)
```
这个地址可以存储在一个叫做指针的特殊数据类型中,在本例中这是一个指向 int 的指针,即 `i1`:此处使用 *int 表示。如果我们想调用指针 intP我们可以这样声明它
var intP *int
```go
var intP *int
```
然后使用 `intP = &i1` 是合法的,此时 intP 指向 i1。
@@ -39,17 +43,19 @@ intP 存储了 i1 的内存地址;它指向了 i1 的位置,它引用了变
现在,我们应当能理解 pointer.go 中的整个程序和他的输出:
Example 4.21 [pointer.go](examples/chapter_4/pointer.go):
示例 4.21 [pointer.go](examples/chapter_4/pointer.go):
package main
import "fmt"
func main() {
```go
package main
import "fmt"
func main() {
var i1 = 5
fmt.Printf("An integer: %d, its location in memory: %p\n", i1, &i1)
var intP *int
intP = &i1
fmt.Printf("The value at memory location %p is %d\n", intP, *intP)
}
}
```
输出:
@@ -58,24 +64,26 @@ Example 4.21 [pointer.go](examples/chapter_4/pointer.go):
我们可以用下图来表示内存使用的情况:
![](images/4.4.9_fig4.4.png?raw=true)
![](../images/4.4.9_fig4.4.png?raw=true)
程序 string_pointer.go 为我们展示了指针对string的例子。
它展示了分配一个新的值给 *p 并且更改这个变量自己的值(这里是一个字符串)。
Example 4.22 [string_pointer.go](examples/chapter_4/string_pointer.go)
示例 4.22 [string_pointer.go](examples/chapter_4/string_pointer.go)
package main
import "fmt"
func main() {
```go
package main
import "fmt"
func main() {
s := "good bye"
var p *string = &s
*p = "ciao"
fmt.Printf("Here is the pointer p: %p\n", p) // prints address
fmt.Printf("Here is the string *p: %s\n", *p) // prints string
fmt.Printf("Here is the string s: %s\n", s) // prints same string
}
}
```
输出:
@@ -87,15 +95,17 @@ Example 4.22 [string_pointer.go](examples/chapter_4/string_pointer.go)
内存示意图如下:
![](images/4.4.9_fig4.5.png?raw=true)
![](../images/4.4.9_fig4.5.png?raw=true)
**注意事项**
你不能得到一个文字或常量的地址,例如:
const i = 5
ptr := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10
```go
const i = 5
ptr := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10
```
所以说Go 语言和 C、C++ 以及 D 语言这些低级(系统)语言一样,都有指针的概念。但是对于经常导致 C 语言内存泄漏继而程序崩溃的指针运算(所谓的指针算法,如:`pointer+2`移动指针指向字符串的字节数或数组的某个位置是不被允许的。Go 语言中的指针保证了内存安全,更像是 Java、C# 和 VB.NET 中的引用。
@@ -111,15 +121,17 @@ Example 4.22 [string_pointer.go](examples/chapter_4/string_pointer.go)
对一个空指针的反向引用是不合法的,并且会使程序崩溃:
Example 4.23 [testcrash.go](examples/chapter_4/testcrash.go):
示例 4.23 [testcrash.go](examples/chapter_4/testcrash.go):
package main
func main() {
```go
package main
func main() {
var p *int = nil
*p = 0
}
// in Windows: stops only with: <exit code="-1073741819" msg="process crashed"/>
// runtime error: invalid memory address or nil pointer dereference
}
// in Windows: stops only with: <exit code="-1073741819" msg="process crashed"/>
// runtime error: invalid memory address or nil pointer dereference
```
**问题 4.2** 列举 Go 语言中 * 号的所有用法。

View File

@@ -2,13 +2,13 @@
到目前为止,我们看到的都是 Go 程序都是从 main() 函数开始执行然后按顺序执行该函数体中的代码。但我们经常会需要只有在满足一些特定情况时才执行某些代码也就是说在代码里进行条件判断。针对这种需求Go 提供了下面这些条件结构和分支结构:
if-else 结构
switch 结构
select 结构,用于 channel 的选择(第 14.4 节)
- if-else 结构
- switch 结构
- elect 结构,用于 channel 的选择第 14.4 节
可以使用迭代或循环结构来重复执行一次或多次某段代码(任务):
for (range) 结构
- for (range) 结构
一些如 `break``continue` 这样的关键字可以用于中途改变循环的状态。

View File

@@ -2,27 +2,33 @@
if 是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行 if 后由大括号括起来的代码块,否则就忽略该代码块继续执行后续的代码。
if condition {
```go
if condition {
// do something
}
}
```
如果存在第二个分支,则可以在上面代码的基础上添加 else 关键字以及另一代码块这个代码块中的代码只有在条件不满足时才会执行。if 和 else 后的两个代码块是相互独立的分支,只可能执行其中一个。
if condition {
```go
if condition {
// do something
} else {
} else {
// do something
}
}
```
如果存在第三个分支,则可以使用下面这种三个独立分支的形式:
if condition1 {
```go
if condition1 {
// do something
} else if condition2 {
} else if condition2 {
// do something else
}else {
}else {
// catch-all or default
}
}
```
else-if 分支的数量是没有限制的,但是为了代码的可读性,还是不要在 if 后面加入太多的 else-if 结构。如果你必须使用这种形式,则把尽可能先满足的条件放在前面。
@@ -30,12 +36,14 @@ else-if 分支的数量是没有限制的,但是为了代码的可读性,还
关键字 if 和 else 之后的左大括号 `{` 必须和关键字在同一行,如果你使用了 else-if 结构,则前段代码块的右大括号 `}` 必须和 else-if 关键字在同一行。这两条规则都是被编译器强制规定的。
非法的Go代码:
非法的 Go 代码:
if x{
}
else { // 无效的
}
```go
if x{
}
else { // 无效的
}
```
要注意的是,在你使用 `gofmt` 格式化代码之后,每个分支内的代码都会缩进 4 个或 8 个空格,或者是 1 个 tab并且右大括号与对应的 if 关键字垂直对齐。
@@ -43,18 +51,20 @@ else-if 分支的数量是没有限制的,但是为了代码的可读性,还
一种可能用到条件语句的场景是测试变量的值,在不同的情况执行不同的语句,不过将在第 5.3 节讲到的 switch 结构会更适合这种情况。
Example 5.1 [booleans.go](examples/chapter_5/booleans.go)
示例 5.1 [booleans.go](examples/chapter_5/booleans.go)
package main
import "fmt"
func main() {
```go
package main
import "fmt"
func main() {
bool1 := true
if bool1 {
fmt.Printf("The value is true\n")
} else {
fmt.Printf("The value is false\n")
}
}
}
```
输出:
@@ -66,22 +76,26 @@ Example 5.1 [booleans.go](examples/chapter_5/booleans.go)
当 if 结构内有 break、continue、goto 或者 return 语句时Go 代码的常见写法是省略 else 部分(另见第 5.2 节)。无论满足哪个条件都会返回 x 或者 y 时,一般使用以下写法:
if condition {
```go
if condition {
return x
}
return y
}
return y
```
**注意事项** 不要同时在 if-else 结构的两个分支里都使用 return 语句,这将导致编译报错 function ends without a return statement(你可以认为这是一个编译器的 Bug 或者特性)。( **译者注:该问题已经在 Go 1.1 中被修复或者说改进**
**注意事项** 不要同时在 if-else 结构的两个分支里都使用 return 语句,这将导致编译报错 `function ends without a return statement`(你可以认为这是一个编译器的 Bug 或者特性)。( **译者注:该问题已经在 Go 1.1 中被修复或者说改进**
这里举一些有用的例子:
1. 判断一个字符串是否为空:`if str == "" { ... }``if len(str) == 0 {...}`
1. 判断一个字符串是否为空:
- `if str == "" { ... }`
- `if len(str) == 0 {...}`
2. 判断运行 Go 程序的操作系统类型,这可以通过常量 `runtime.GOOS` 来判断(第 2.2 节)。
if runtime.GOOS == "windows" {
...
} else { // Unix - like
...
. ..
} else { // Unix-like
. ..
}
这段代码一般被放在 init() 函数中执行。这儿还有一段示例来演示如何根据操作系统来决定输入结束的提示:
@@ -116,32 +130,39 @@ Example 5.1 [booleans.go](examples/chapter_5/booleans.go)
在第四种情况中if 可以包含一个初始化语句(如:给一个变量赋值)。这种写法具有固定的格式(在初始化语句后方必须加上分号):
if initialization; condition {
```go
if initialization; condition {
// do something
}
}
```
例如:
val := 10
if val > max {
```go
val := 10
if val > max {
// do something
}
}
```
你也可以这样写:
if val := 10; val > max {
```go
if val := 10; val > max {
// do something
}
}
```
但要注意的是,使用简短方式 `:=` 声明的变量的作用域只存在于 if 结构中(在 if 结构的大括号之间,如果使用 if-else 结构则在 else 代码块中变量也会存在)。如果变量在 if 结构之前就已经存在,那么在 if 结构中,该变量原来的值会被隐藏。最简单的解决方案就是不要在初始化语句中声明变量(见 5.2 节的例 3 了解更多)。
Example 5.2 [ifelse.go](examples/chapter_5/ifelse.go)
示例 5.2 [ifelse.go](examples/chapter_5/ifelse.go)
package main
```go
package main
import "fmt"
import "fmt"
func main() {
func main() {
var first int = 10
var cond int
@@ -162,7 +183,8 @@ Example 5.2 [ifelse.go](examples/chapter_5/ifelse.go)
fmt.Printf("cond is not greater than 10\n")
}
}
}
```
输出:
@@ -170,11 +192,13 @@ Example 5.2 [ifelse.go](examples/chapter_5/ifelse.go)
下面的代码片段展示了如何通过在初始化语句中获取函数 `process()` 的返回值,并在条件语句中作为判定条件来决定是否执行 if 结构中的代码:
if value := process(data); value > max {
```go
if value := process(data); value > max {
...
if value := process(data); value > max {
if value := process(data); value > max {
...
}
}
```
## 链接

View File

@@ -4,7 +4,9 @@ Go 语言的函数经常使用两个返回值来表示执行是否成功:返
在第 4.7 节的程序 `string_conversion.go` 中,函数 `strconv.Atoi` 的作用是将一个字符串转换为一个整数。之前我们忽略了相关的错误检查:
anInt, _ = strconv.Atoi(origStr)
```go
anInt, _ = strconv.Atoi(origStr)
```
如果 origStr 不能被转换为整数anInt 的值会变成 0 而 `_` 无视了错误,程序会继续运行。
@@ -12,18 +14,20 @@ Go 语言的函数经常使用两个返回值来表示执行是否成功:返
我们在第二个版本中对代码进行了改进:
示例 1
Example 5.3 [string_conversion2.go](examples/chapter_5/string_conversion2.go)
示例 5.3 [string_conversion2.go](examples/chapter_5/string_conversion2.go)
package main
```go
package main
import (
import (
"fmt"
"strconv"
)
)
func main() {
func main() {
var orig string = "ABC"
// var an int
var newS string
@@ -40,18 +44,21 @@ Example 5.3 [string_conversion2.go](examples/chapter_5/string_conversion2.go)
an = an + 5
newS = strconv.Itoa(an)
fmt.Printf("The new string is: %s\n", newS)
}
}
```
这是测试 err 变量是否包含一个真正的错误(`if err != nil`)的习惯用法。如果确实存在错误,则会打印相应的错误信息然后通过 return 提前结束函数的执行。我们还可以使用携带返回值的 return 形式,例如 `return err`。这样一来,函数的调用者就可以检查函数执行过程中是否存在错误了。
**习惯用法**
value, err := pack1.Function1(param1)
if err!=nil {
```go
value, err := pack1.Function1(param1)
if err!=nil {
fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
return err
}
// 未发生错误,继续执行:
}
// 未发生错误,继续执行:
```
由于本例的函数调用者属于 main 函数,所以程序会直接停止运行。
@@ -59,10 +66,12 @@ Example 5.3 [string_conversion2.go](examples/chapter_5/string_conversion2.go)
**习惯用法**
if err !=nil {
```go
if err !=nil {
fmt.Printf("Program stopping with error %v", err)
os.Exit(1)
}
}
```
(此处的退出代码 1 可以使用外部脚本获取到)
@@ -72,12 +81,14 @@ Example 5.3 [string_conversion2.go](examples/chapter_5/string_conversion2.go)
示例 2我们尝试通过 `os.Open` 方法打开一个名为 `name` 的只读文件:
f, err := os.Open(name)
if err !=nil {
```go
f, err := os.Open(name)
if err !=nil {
return err
}
doSomething(f) // 当没有错误发生时,文件对象被传入到某个函数中
doSomething
}
doSomething(f) // 当没有错误发生时,文件对象被传入到某个函数中
doSomething
```
**练习 5.1** 尝试改写 [string_conversion2.go](examples/chapter_5/string_conversion2.go) 中的代码,要求使用 `:=` 方法来对 err 进行赋值,哪些地方可以被修改?
@@ -85,54 +96,66 @@ Example 5.3 [string_conversion2.go](examples/chapter_5/string_conversion2.go)
**习惯用法**
if err := file.Chmod(0664); err !=nil {
```go
if err := file.Chmod(0664); err !=nil {
fmt.Println(err)
return err
}
}
```
示例 4或者将 ok-pattern 的获取放置在 if 语句的初始化部分,然后进行判断:
**习惯用法**
if value, ok := readData(); ok {
}
```go
if value, ok := readData(); ok {
}
```
**注意事项**
如果您像下面一样,没有为多返回值的函数准备足够的变量来存放结果:
func mySqrt(f float64) (v float64, ok bool) {
```go
func mySqrt(f float64) (v float64, ok bool) {
if f < 0 { return } // error case
return math.Sqrt(f),true
}
}
func main() {
func main() {
t := mySqrt(25.0)
fmt.Println(t)
}
}
```
您会得到一个编译错误:`multiple-value mySqrt() in single-value context`
正确的做法是:
t, ok := mySqrt(25.0)
if ok { fmt.Println(t) }
```go
t, ok := mySqrt(25.0)
if ok { fmt.Println(t) }
```
**注意事项 2**
当您将字符串转换为整数时,且确定转换一定能够成功时,可以将 `Atoi` 函数进行一层忽略错误的封装:
func atoi (s string) (n int) {
```go
func atoi (s string) (n int) {
n, _ = strconv.Atoi(s)
return
}
}
```
实际上,`fmt` 包(第 4.4.3 节)最简单的打印函数也有 2 个返回值:
count, err := fmt.Println(x) // number of bytes printed, nil or 0, error
```go
count, err := fmt.Println(x) // number of bytes printed, nil or 0, error
```
当打印到控制台时可以将该函数返回的错误忽略但当输出到文件流网络流等具有不确定因素的输出对象时应该始终检查是否有错误发生另见 练习 6.1b)。
当打印到控制台时可以将该函数返回的错误忽略但当输出到文件流网络流等具有不确定因素的输出对象时应该始终检查是否有错误发生(另见练习 6.1b)。
## 链接

View File

@@ -2,7 +2,7 @@
相比较 C 和 Java 等其它语言而言Go 语言中的 switch 结构使用上更加灵活。它接受任意形式的表达式:
```
```go
switch var1 {
case val1:
...
@@ -25,7 +25,7 @@ switch var1 {
因此:
```
```go
switch i {
case 0: // 空分支,只有当 i == 0 时才会进入分支
case 1:
@@ -35,7 +35,7 @@ switch i {
并且:
```
```go
switch i {
case 0: fallthrough
case 1:
@@ -49,9 +49,9 @@ switch i {
可选的 `default` 分支可以出现在任何顺序,但最好将它放在最后。它的作用类似与 `if-else` 语句中的 `else`,表示不符合任何已给出条件时,执行相关语句。
Listing 5.4 [switch1.go](examples/chapter_5/switch1.go)
示例 5.4 [switch1.go](examples/chapter_5/switch1.go)
```
```go
package main
import "fmt"
@@ -77,7 +77,7 @@ func main() {
在第 12.1 节,我们会使用 switch 语句判断从键盘输入的字符(详见第 12.2 节的 switch.go。switch 语句的第二种形式是不提供任何被判断的值(实际上默认为判断是否为 true然后在每个 case 分支中进行测试不同的条件。当任一分支的测试结果为 true 时,该分支的代码会被执行。这看起来非常像链式的 `if-else` 语句,但是在测试条件非常多的情况下,提供了可读性更好的书写方式。
```
```go
switch {
case condition1:
...
@@ -90,7 +90,7 @@ switch {
例如:
```
```go
switch {
case i < 0:
f1()
@@ -103,9 +103,9 @@ switch {
任何支持进行相等判断的类型都可以作为测试表达式的条件,包括 int、string、指针等。
Listing 5.4 [switch2.go](examples/chapter_5/switch2.go)
示例 5.4 [switch2.go](examples/chapter_5/switch2.go)
```
```go
package main
import "fmt"
@@ -130,7 +130,7 @@ func main() {
switch 语句的第三种形式是包含一个初始化语句:
```
```go
switch initialization {
case val1:
...
@@ -143,7 +143,7 @@ switch initialization {
这种形式可以非常优雅地进行条件判断:
```
```go
switch result := calculate(); {
case result < 0:
...
@@ -156,7 +156,7 @@ switch result := calculate(); {
在下面这个代码片段中,变量 a 和 b 被平行初始化,然后作为判断条件:
```
```go
switch a, b := x[i], y[j]; {
case a < b: t = -1
case a == b: t = 0
@@ -170,7 +170,7 @@ switch 语句还可以被用于 type-switch详见第 11.4 节)来判断某
请说出下面代码片段输出的结果:
```
```go
k := 6
switch k {
case 4: fmt.Println("was <= 4"); fallthrough;

View File

@@ -10,9 +10,9 @@
for 初始化语句; 条件语句; 修饰语句 {}
Listing 5.6 [for1.go](examples/chapter_5/for1.go)
示例 5.6 [for1.go](examples/chapter_5/for1.go)
```
```go
package main
import "fmt"
@@ -26,13 +26,11 @@ func main() {
输出:
```
This is the 0 iteration
This is the 1 iteration
This is the 2 iteration
This is the 3 iteration
This is the 4 iteration
```
This is the 0 iteration
This is the 1 iteration
This is the 2 iteration
This is the 3 iteration
This is the 4 iteration
由花括号括起来的代码块会被重复执行已知次数,该次数是根据计数器(此例为 i决定的。循环开始前会执行且仅会执行一次初始化语句 `i := 0;`;这比在循环之前声明更为简短。紧接着的是条件语句 `i < 5;`,在每次循环开始前都会进行判断,一旦判断结果为 false则退出循环体。最后一部分为修饰语句 `i++`,一般用于增加或减少计数器。
@@ -44,13 +42,15 @@ This is the 4 iteration
您还可以在循环中同时使用多个计数器:
for i, j := 0, N; i < j; i, j = i+1, j-1 {}
```go
for i, j := 0, N; i < j; i, j = i+1, j-1 {}
```
这得益于 Go 语言具有的平行赋值的特性(可以查看第 7 章 string_reverse.go 中反转数组的示例)。
您可以将两个 for 循环嵌套起来:
```
```go
for i:=0; i<5; i++ {
for j:=0; j<10; j++ {
println(j)
@@ -60,9 +60,9 @@ for i:=0; i<5; i++ {
如果您使用 for 循环迭代一个 Unicode 编码的字符串,会发生什么?
Listing 5.7 [for_string.go](examples/chapter_5/for_string.go)
示例 5.7 [for_string.go](examples/chapter_5/for_string.go)
```
```go
package main
import "fmt"
@@ -83,46 +83,44 @@ func main() {
输出:
```
The length of str is: 27
Character on position 0 is: G
Character on position 1 is: o
Character on position 2 is:
Character on position 3 is: i
Character on position 4 is: s
Character on position 5 is:
Character on position 6 is: a
Character on position 7 is:
Character on position 8 is: b
Character on position 9 is: e
Character on position 10 is: a
Character on position 11 is: u
Character on position 12 is: t
Character on position 13 is: i
Character on position 14 is: f
Character on position 15 is: u
Character on position 16 is: l
Character on position 17 is:
Character on position 18 is: l
Character on position 19 is: a
Character on position 20 is: n
Character on position 21 is: g
Character on position 22 is: u
Character on position 23 is: a
Character on position 24 is: g
Character on position 25 is: e
Character on position 26 is: !
The length of str2 is: 9
Character on position 0 is: æ
Character on position 1 is: —
Character on position 2 is: ¥
Character on position 3 is: æ
Character on position 4 is: œ
Character on position 5 is: ¬
Character on position 6 is: è
Character on position 7 is: ª
Character on position 8 is: ž
```
The length of str is: 27
Character on position 0 is: G
Character on position 1 is: o
Character on position 2 is:
Character on position 3 is: i
Character on position 4 is: s
Character on position 5 is:
Character on position 6 is: a
Character on position 7 is:
Character on position 8 is: b
Character on position 9 is: e
Character on position 10 is: a
Character on position 11 is: u
Character on position 12 is: t
Character on position 13 is: i
Character on position 14 is: f
Character on position 15 is: u
Character on position 16 is: l
Character on position 17 is:
Character on position 18 is: l
Character on position 19 is: a
Character on position 20 is: n
Character on position 21 is: g
Character on position 22 is: u
Character on position 23 is: a
Character on position 24 is: g
Character on position 25 is: e
Character on position 26 is: !
The length of str2 is: 9
Character on position 0 is: æ
Character on position 1 is: —
Character on position 2 is: ¥
Character on position 3 is: æ
Character on position 4 is: œ
Character on position 5 is: ¬
Character on position 6 is: è
Character on position 7 is: ª
Character on position 8 is: ž
如果我们打印 str 和 str2 的长度,会分别得到 27 和 9。
@@ -139,14 +137,12 @@ Character on position 8 is: ž
创建一个程序,要求能够打印类似下面的结果(直到每行 25 个字符时为止):
```
G
GG
GGG
GGGG
GGGGG
GGGGGG
```
G
GG
GGG
GGGG
GGGGG
GGGGGG
1. 使用 2 层嵌套 for 循环。
2. 使用一层 for 循环以及字符串截断。
@@ -171,7 +167,7 @@ for 结构的第二种形式是没有头部的条件判断迭代(类似其它
Listing 5.8 [for2.go](examples/chapter_5/for2.go)
```
```go
package main
import "fmt"
@@ -188,14 +184,12 @@ func main() {
输出:
```
The variable i is now: 4
The variable i is now: 3
The variable i is now: 2
The variable i is now: 1
The variable i is now: 0
The variable i is now: -1
```
The variable i is now: 4
The variable i is now: 3
The variable i is now: 2
The variable i is now: 1
The variable i is now: 0
The variable i is now: -1
## 5.4.3 无限循环
@@ -209,7 +203,7 @@ The variable i is now: -1
无限循环的经典应用是服务器,用于不断等待和接受新的请求。
```
```go
for t, err = p.Token(); err == nil; t, err = p.Token() {
...
}
@@ -221,7 +215,7 @@ for t, err = p.Token(); err == nil; t, err = p.Token() {
要注意的是,`val` 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(**译者注:如果 `val` 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值**)。一个字符串是 Unicode 编码的字符(或称之为 `rune`)集合,因此您也可以用它迭代字符串:
```
```go
for pos, char := range str {
...
}
@@ -229,9 +223,9 @@ for pos, char := range str {
每个 rune 字符和索引在 for-range 循环中是一一对应的。它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。
Listing 5.9 [range_string.go](examples/chapter_5/range_string.go)
示例 5.9 [range_string.go](examples/chapter_5/range_string.go)
```
```go
package main
import "fmt"
@@ -323,7 +317,7 @@ index int(rune) rune char bytes
**练习 5.9** 以下程序的输出结果是什么?
```
```go
for i := 0; i < 5; i++ {
var v int
fmt.Printf("%d ", v)
@@ -335,7 +329,7 @@ for i := 0; i < 5; i++ {
1.
```
```go
for i := 0; ; i++ {
fmt.Println("Value of i is now:", i)
}
@@ -343,7 +337,7 @@ for i := 0; ; i++ {
2.
```
```go
for i := 0; i < 3; {
fmt.Println("Value of i:", i)
}
@@ -351,7 +345,7 @@ for i := 0; i < 3; {
3.
```
```go
s := ""
for ; s != "aaaaa"; {
fmt.Println("Value of s:", s)
@@ -361,7 +355,7 @@ for ; s != "aaaaa"; {
4.
```
```go
for i, j, s := 0, 5, "a"; i < 3 && j < 100 && s != "aaaaa"; i, j,
s = i+1, j+1, s + "a" {
fmt.Println("Value of i, j, s:", i, j, s)

View File

@@ -2,12 +2,12 @@
您可以使用 break 语句重写 for2.go 的代码:
Listing 5.10 [for3.go](examples/chapter_5/for3.go)
示例 5.10 [for3.go](examples/chapter_5/for3.go)
```
```go
for {
i = i - 1
fmt.Printf(“The variable i is now: %d\n”, i)
fmt.Printf("The variable i is now: %d\n", i)
if i < 0 {
break
}
@@ -20,9 +20,9 @@ for {
下面的示例中包含了嵌套的循环体for4.gobreak 只会退出最内层的循环
Listing 5.11 [for4.go](examples/chapter_5/for4.go)
示例 5.11 [for4.go](examples/chapter_5/for4.go)
```
```go
package main
func main() {
@@ -44,9 +44,9 @@ func main() {
关键字 continue 忽略剩余的循环体而直接进入下一次循环的过程但不是无条件执行下一次循环执行之前依旧需要满足循环的判断条件
Listing 5.12 [for5.go](examples/chapter_5/for5.go)
示例 5.12 [for5.go](examples/chapter_5/for5.go)
```
```go
package main
func main() {
@@ -64,10 +64,11 @@ func main() {
```
0 1 2 3 4 6 7 8 9
5 is skipped
```
关键字 continue 只能被用于 for 循环中
显然5 被跳过了
另外关键字 continue 只能被用于 for 循环中
## 链接

View File

@@ -1,12 +1,12 @@
# 5.6 标签与 goto
for、switch 或 select 语句都可以配合标签label形式的标识符使用即某一行第一个以分号结尾的单词gofmt 会将后续代码自动移至下一行)。
for、switch 或 select 语句都可以配合标签label形式的标识符使用即某一行第一个以冒号(`:`结尾的单词gofmt 会将后续代码自动移至下一行)。
Listing 5.13 [for6.go](examples/chapter_5/for6.go)
示例 5.13 [for6.go](examples/chapter_5/for6.go)
(标签的名称是大小写敏感的,为了提升可读性,一般建议使用全部大写字母)
```
```go
package main
import "fmt"
@@ -30,9 +30,9 @@ LABEL1:
您可以看到当 j==4 和 j==5 的时候,没有任何输出:标签的作用对象为外部循环,因此 i 会直接变成下一个循环的值,而此时 j 的值就被重设为 0即它的初始值。如果将 continue 改为 break则不会只退出内层循环而是直接退出外层循环了。另外还可以使用 goto 语句和标签配合使用来模拟循环。
Listing 5.14 [goto.go](examples/chapter_5/goto.go)
示例 5.14 [goto.go](examples/chapter_5/goto.go)
```
```go
package main
func main() {
@@ -59,9 +59,9 @@ func main() {
如果您必须使用 goto应当只使用正序的标签标签位于 goto 语句之后),但注意标签和 goto 语句之间不能出现定义新变量的语句,否则会导致编译失败。
Listing 5.15 [goto2.go](examples/chapter_5/got2o.go)
示例 5.15 [goto2.go](examples/chapter_5/got2o.go)
```
```go
// compile error goto2.go:8: goto TARGET jumps over declaration of b at goto2.go:8
package main
@@ -81,23 +81,23 @@ func main() {
1.
```
```go
i := 0
for { //since there are no checks, this is an infinite loop
if i >= 3 { break }
//break out of this for loop when this condition is met
fmt.Println(“Value of i is:”, i)
fmt.Println("Value of i is:", i)
i++;
}
fmt.Println(“A statement just after for loop.”)
fmt.Println("A statement just after for loop.")
```
2.
```
```go
for i := 0; i<7 ; i++ {
if i%2 == 0 { continue }
fmt.Println(“Odd:”, i)
fmt.Println("Odd:", i)
}
```

View File

@@ -1,6 +1,6 @@
# 6.0 函数
函数是 Go 里面的基本代码块Go 函数的功能非常强大,以至于被认为拥有函数式编程语言的多种特性。在这一章,我们将对[第 4.2.2 节](04.2.md)所简要描述的函数进行详细的讲解。
函数是 Go 里面的基本代码块Go 函数的功能非常强大,以至于被认为拥有函数式编程语言的多种特性。在这一章,我们将对 [第 4.2.2 节](04.2.md) 所简要描述的函数进行详细的讲解。
## 链接

View File

@@ -2,56 +2,64 @@
每一个程序都包含很多的函数:函数是基本的代码块。
Go是编译型语言所以函数编写的顺序是无关紧要的鉴于可读性的需求最好把 main() 函数写在文件的前面,其他函数按照一定逻辑顺序进行编写(例如函数被调用的顺序)。
Go是编译型语言所以函数编写的顺序是无关紧要的鉴于可读性的需求最好把 `main()` 函数写在文件的前面,其他函数按照一定逻辑顺序进行编写(例如函数被调用的顺序)。
编写多个函数的主要目的是将一个需要很多行代码的复杂问题分解为一系列简单的任务(那就是函数)来解决。而且,同一个任务(函数)可以被调用多次,有助于代码重用。
事实上好的程序是非常注意DRY原则的即不要重复你自己Don't Repeat Yourself意思是执行特定任务的代码只能在程序里面出现一次。
当函数执行到代码块最后一行(`}`之前)或者`return`语句的时候会退出,其中`return`语句可以带有零个或多个参数;这些参数将作为返回值(参考[第 6.2 节](06.2.md))供调用者使用。简单的 `return `语句也可以用来结束 for 死循环或者结束一个协程goroutine
当函数执行到代码块最后一行(`}` 之前)或者 `return` 语句的时候会退出,其中 `return` 语句可以带有零个或多个参数;这些参数将作为返回值(参考 [第 6.2 节](06.2.md))供调用者使用。简单的 `return ` 语句也可以用来结束 for 死循环或者结束一个协程goroutine
Go 里面拥三种类型的函数:
- 普通的带有名字的函数
- 匿名函数或者lambda函数参考[第 6.8 节](06.8.md)
- 方法Methods参考[第 10.6 节](10.6.md)
- 匿名函数或者lambda函数参考 [第 6.8 节](06.8.md)
- 方法Methods参考 [第 10.6 节](10.6.md)
所有类型的函数都可以有参数与返回值。函数参数、返回值以及它们的类型被统称为函数签名。
作为提醒,提前介绍一个语法:
这样是不正确的Go代码
这样是不正确的 Go 代码:
func g()
{
}
```go
func g()
{
}
```
它必须是这样的:
func g() {
}
```go
func g() {
}
```
函数被调用的基本格式如下:
pack1.Function(arg1, arg2, …, argn)
```go
pack1.Function(arg1, arg2, , argn)
```
Functionpack1包里面的一个函数括号里的是被调用函数的*实参*argument这些值被传递给被调用函数的*形参*parameter参考[第 6.2 节](06.2.md)。函数被调用的时候这些实参将被复制简单而言然后传递给被调用函数。函数一般是在其他函数里面被调用的这个其他函数被称为调用函数calling function。函数能多次调用其他函数这些被调用函数按顺序简单而言执行理论上函数调用其他函数的次数是无穷的直到函数调用栈被耗尽
`Function``pack1` 包里面的一个函数括号里的是被调用函数的实参argument这些值被传递给被调用函数的*形参*parameter参考 [第 6.2 节](06.2.md)。函数被调用的时候这些实参将被复制简单而言然后传递给被调用函数。函数一般是在其他函数里面被调用的这个其他函数被称为调用函数calling function。函数能多次调用其他函数这些被调用函数按顺序简单而言执行理论上函数调用其他函数的次数是无穷的直到函数调用栈被耗尽
一个简单的函数调用其他函数的例子:
示例 6.1 [greeting.go](examples/chapter_6/greeting.go)
package main
```go
package main
func main() {
func main() {
println("In main before calling greeting")
greeting()
println("In main after calling greeting")
}
}
func greeting() {
func greeting() {
println("In greeting: Hi!!!!!")
}
}
```
代码输出:
@@ -61,34 +69,38 @@ Function是pack1包里面的一个函数括号里的是被调用函数的*实
函数可以将其他函数调用作为它的参数,只要这个被调用函数的返回值个数、返回值类型和返回值的顺序与调用函数所需求的实参是一致的,例如:
假设 f1 需要3个参数 `f1(a, b, c int)`,同时 f2 返回3个参数 `f2(a, b int) (int, int, int)`,就可以这样调用 f1`f1(f2(a, b))`
假设 f1 需要 3 个参数 `f1(a, b, c int)`,同时 f2 返回 3 个参数 `f2(a, b int) (int, int, int)`,就可以这样调用 f1`f1(f2(a, b))`
函数重载function overloading指的是可以编写多个同名函数只要它们拥有不同的形参与/或者不同的返回值在Go里面函数重载是不被允许的。这将导致一个编译错误
函数重载function overloading指的是可以编写多个同名函数只要它们拥有不同的形参与/或者不同的返回值,在 Go 里面函数重载是不被允许的。这将导致一个编译错误:
funcName redeclared in this book, previous declaration at lineno
Go语言不支持这项特性的主要原因是函数重载需要进行多余的类型匹配影响性能没有重载意味着只是一个简单的函数调度。所以你需要给不同的函数使用不同的名字我们通常会根据函数的特征对函数进行命名参考[第 11.12.5 节](11.12.md))。
Go 语言不支持这项特性的主要原因是函数重载需要进行多余的类型匹配影响性能;没有重载意味着只是一个简单的函数调度。所以你需要给不同的函数使用不同的名字,我们通常会根据函数的特征对函数进行命名(参考 [第 11.12.5 节](11.12.md))。
如果需要申明一个在外部定义的函数,你只需要给出函数名与函数签名,不需要给出函数体:
func flushICache(begin, end uintptr) // implemented externally
```go
func flushICache(begin, end uintptr) // implemented externally
```
**函数也可以以申明的方式被使用,作为一个函数类型**,就像:
type binOp func(int, int) int
```go
type binOp func(int, int) int
```
在这里,不需要函数体`{}`
在这里,不需要函数体 `{}`
函数是一等值first-class value它们可以赋值给变量就像`add := binOp`一样。
函数是一等值first-class value它们可以赋值给变量就像 `add := binOp` 一样。
这个变量知道自己指向的函数的签名,所以给它赋一个具有不同签名的函数值是不可能的。
函数值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))来实现相似的功能。使用这些技术将导致代码更为复杂、性能更为低下,所以在非常注意性能的的场合,最好是为每一个类型单独创建一个函数,而且代码可读性更强。
## 链接
- [目录](directory.md)
- 上一节:[函数function](06.0.md)
- 下一节:[参数与返回值](06.2.md)
- 下一节:[函数参数与返回值](06.2.md)

View File

@@ -10,7 +10,7 @@ delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
```
您可以查看 Listing 6.20—fibonacci.go 作为实例学习。
您可以查看示例 6.20 [fibonacci.go](examples/chapter_6/fibonacci.go) 作为实例学习。
如果您对一段代码进行了所谓的优化,请务必对它们之间的效率进行对比再做出最后的判断。在接下来的章节中,我们会学习如何进行有价值的优化操作。

View File

@@ -2,7 +2,7 @@
当在进行大量的计算时,提升性能最直接有效的一种方式就是避免重复计算。通过在内存中缓存和重复利用相同计算的结果,称之为内存缓存。最明显的例子就是生成斐波那契数列的程序(详见第 6.6 和 6.11 节):
要计算数列中第 n 个数字,需要先得到之前两个数的值,但很明显绝大多数情况下前两个数的值都是已经计算过的。即每个更后面的数都是基于之前计算结果的重复计算,正如 listing 6.11 - fibonnaci.go 所展示的那样。
要计算数列中第 n 个数字,需要先得到之前两个数的值,但很明显绝大多数情况下前两个数的值都是已经计算过的。即每个更后面的数都是基于之前计算结果的重复计算,正如示例 6.11 [fibonnaci.go](examples/chapter_6/fibonnaci.go) 所展示的那样。
而我们要做就是将第 n 个数的值存在数组中索引为 n 的位置(详见第 7 章),然后在数组中查找是否已经计算过,如果没有找到,则再进行计算。

View File

@@ -1,14 +1,15 @@
# 6.2 函数参数与返回值
函数能够接收参数供自己使用也可以返回零个或多个值我们通常把返回多个值称为返回一组值。相比与C、C++、JavaC#多值返回是Go的一大特性为我们判断一个函数是否正常执行参考[第 5.2 节](05.2.md))提供了方便。
函数能够接收参数供自己使用,也可以返回零个或多个值(我们通常把返回多个值称为返回一组值)。相比与 C、C++、JavaC#,多值返回是 Go 的一大特性,为我们判断一个函数是否正常执行(参考 [第 5.2 节](05.2.md))提供了方便。
我们通过 `return `关键字返回一组值。事实上,任何一个有返回值(单个或多个)的函数都必须以 `return ``panic`(参考[第 13 章](13.0.md))结尾。
我们通过 `return` 关键字返回一组值。事实上,任何一个有返回值(单个或多个)的函数都必须以 `return ` `panic`(参考 [第 13 章](13.0.md))结尾。
在函数块里面,`return`之后的语句都不会执行。如果一个函数需要返回值那么这个函数里面的每一个代码分支code-path都要有`return`语句。
在函数块里面,`return` 之后的语句都不会执行。如果一个函数需要返回值那么这个函数里面的每一个代码分支code-path都要有 `return` 语句。
问题6.1:下面的函数将不会被编译,为什么呢?大家可以试着纠正过来。
问题 6.1:下面的函数将不会被编译,为什么呢?大家可以试着纠正过来。
func (st *Stack) Pop() int {
```go
func (st *Stack) Pop() int {
v := 0
for ix := len(st) - 1; ix >= 0; ix-- {
if v = st[ix]; v != 0 {
@@ -16,17 +17,18 @@
return v
}
}
}
}
```
函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,就像这样:`func f(int, int, float64)`
函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,就像这样:`func f(int, int, float64)`
没有参数的函数通常被称为 *niladic* 函数niladic function就像 `main.main()`
没有参数的函数通常被称为 **niladic** 函数niladic function就像 `main.main()`
# #6.2.1 按值传递call by value 按引用传递call by reference
## 6.2.1 按值传递call by value 按引用传递call by reference
Go默认使用按值传递来传递参数也就是传递参数的副本。函数接收参数副本之后在使用变量的过程中可能对副本的值进行更改但不会影响到原来的变量比如 `Function(arg1)`
Go 默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量,比如 `Function(arg1)`
如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符号,比如&variable传递给函数这就是按引用传递比如 `Function(&arg1)`,此时传递给函数的是一个指针。如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们可以通过这个指针的值来修改这个值所指向的地址上的值。(** 译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。 **
如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符号,比如 &variable传递给函数这就是按引用传递比如 `Function(&arg1)`,此时传递给函数的是一个指针。如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们可以通过这个指针的值来修改这个值所指向的地址上的值。(** 译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。 **
几乎在任何情况下传递指针一个32位或者64位的值的消耗都比传递副本来得少。
@@ -36,25 +38,27 @@ Go默认使用按值传递来传递参数也就是传递参数的副本。函
但是绝大部分的函数还是带有返回值的。
如下simple_function.go里的 `MultiPly3Nums` 函数带有三个形参,分别是 `a``b``c`,还有一个 `int` 类型的返回值(被注释的代码具有和未注释部分同样的功能,只是多引入了一个本地变量):
如下simple_function.go 里的 `MultiPly3Nums` 函数带有三个形参,分别是 `a``b``c`,还有一个 `int` 类型的返回值(被注释的代码具有和未注释部分同样的功能,只是多引入了一个本地变量):
示例 6.2 [simple_function.go](examples/chapter_6/simple_function.go)
package main
```go
package main
import "fmt"
import "fmt"
func main() {
func main() {
fmt.Printf("Multiply 2 * 5 * 6 = %d\n", MultiPly3Nums(2, 5, 6))
// var i1 int = MultiPly3Nums(2, 5, 6)
// fmt.Printf("MultiPly 2 * 5 * 6 = %d\n", i1)
}
}
func MultiPly3Nums(a int, b int, c int) int {
func MultiPly3Nums(a int, b int, c int) int {
// var product int = a * b * c
// return product
return a * b * c
}
}
```
输出显示:
@@ -76,42 +80,44 @@ Go默认使用按值传递来传递参数也就是传递参数的副本。函
## 6.2.2 命名的返回值named return variables
如下multiple_return.go里的函数带有一个`int`参数,返回两个`int`值;其中一个函数的返回值在函数调用时就已经被赋予了一个初始零值the return values are filled in at the calling function in a parallel assignment
如下multiple_return.go 里的函数带有一个 `int` 参数,返回两个 `int` 值;其中一个函数的返回值在函数调用时就已经被赋予了一个初始零值。
`getX2AandX3``getX2AndX3_2 `两个函数演示了如何使用非命名返回值与命名返回值的特性。当需要返回多个非命名返回值时,需要使用 `()` 把它们括起来,比如 `(int, int)`
`getX2AandX3``getX2AndX3_2` 两个函数演示了如何使用非命名返回值与命名返回值的特性。当需要返回多个非命名返回值时,需要使用 `()` 把它们括起来,比如 `(int, int)`
命名返回值作为结果形参result parameters被初始化为相应类型的零值当需要返回的时候我们只需要一条简单的不带参数的return语句。需要注意的是即使只有一个命名返回值也需要使用`()`括起来(参考[第 6. 6节](06.6.md)的fibonacci.go函数
命名返回值作为结果形参result parameters被初始化为相应类型的零值当需要返回的时候我们只需要一条简单的不带参数的return语句。需要注意的是即使只有一个命名返回值也需要使用 `()` 括起来(参考 [第 6.6 ](06.6.md)的 fibonacci.go 函数)。
示例 6.3 [multiple_return.go](examples/chapter_6/multiple_return.go)
package main
```go
package main
import "fmt"
import "fmt"
var num int = 10
var numx2, numx3 int
var num int = 10
var numx2, numx3 int
func main() {
func main() {
numx2, numx3 = getX2AndX3(num)
PrintValues()
numx2, numx3 = getX2AndX3_2(num)
PrintValues()
}
}
func PrintValues() {
func PrintValues() {
fmt.Printf("num = %d, 2x num = %d, 3x num = %d\n", num, numx2, numx3)
}
}
func getX2AndX3(input int) (int, int) {
func getX2AndX3(input int) (int, int) {
return 2 * input, 3 * input
}
}
func getX2AndX3_2(input int) (x2 int, x3 int) {
func getX2AndX3_2(input int) (x2 int, x3 int) {
x2 = 2 * input
x3 = 3 * input
// return x2, x3
return
}
}
```
输出结果:
@@ -120,15 +126,14 @@ Go默认使用按值传递来传递参数也就是传递参数的副本。函
警告:
return or return var 是可以的。不过,
return var = expression表达式 会引发一个编译错误:
syntax error: unexpected =, expecting semicolon or newline or }
- return return var 是可以的。
- 不过 `return var = expression`(表达式) 会引发一个编译错误:`syntax error: unexpected =, expecting semicolon or newline or }`
即使函数使用了命名返回值,你依旧可以无视它而返回明确的值。
任何一个非命名返回值(使用非命名返回值是很糟的编程习惯)在`return`语句里面都要明确指出包含返回值的变量或是一个可计算的值(就像上面警告所指出的那样)。
任何一个非命名返回值(使用非命名返回值是很糟的编程习惯)在 `return` 语句里面都要明确指出包含返回值的变量或是一个可计算的值(就像上面警告所指出的那样)。
**尽量使用命名返回值:会使代码更清晰、更简短,同时更加容易读懂**
** 尽量使用命名返回值:会使代码更清晰、更简短,同时更加容易读懂 **
练习 6.1 [mult_returnval.go](exercises/chapter_6/mult_returnval.go)
@@ -136,30 +141,32 @@ Go默认使用按值传递来传递参数也就是传递参数的副本。函
练习 6.2 [error_returnval.go](exercises/chapter_6/error_returnval.go)
编写一个名字为MySqrt的函数计算一个float64类型浮点数的平方根如果参数是一个负数的话将返回一个错误。编写两个版本一个是非命名返回值一个是命名返回值。
编写一个名字为 MySqrt 的函数,计算一个 float64 类型浮点数的平方根,如果参数是一个负数的话将返回一个错误。编写两个版本,一个是非命名返回值,一个是命名返回值。
## 6.2.3 空白符blank identifier
空白符用来匹配一些不需要的值然后丢弃掉下面的blank_identifier.go就是很好的例子。
空白符用来匹配一些不需要的值,然后丢弃掉,下面的 blank_identifier.go 就是很好的例子。
`ThreeValues `是拥有三个返回值的不需要任何参数的函数,在下面的例子中,我们将第一个与第三个返回值赋给了 `i1``f1`。第二个返回值赋给了空白符 `_`,然后自动丢弃掉。
`ThreeValues` 是拥有三个返回值的不需要任何参数的函数,在下面的例子中,我们将第一个与第三个返回值赋给了 `i1``f1`。第二个返回值赋给了空白符 `_`,然后自动丢弃掉。
示例 6.4 [blank_identifier.go](examples/chapter_6/blank_identifier.go)
package main
```go
package main
import "fmt"
import "fmt"
func main() {
func main() {
var i1 int
var f1 float32
i1, _, f1 = ThreeValues()
fmt.Printf("The int: %d, the float: %f \n", i1, f1)
}
}
func ThreeValues() (int, int, float32) {
func ThreeValues() (int, int, float32) {
return 5, 6, 7.5
}
}
```
输出结果:
@@ -169,17 +176,18 @@ Go默认使用按值传递来传递参数也就是传递参数的副本。函
示例 6.5 [minmax.go](examples/chapter_6/minmax.go)
package main
```go
package main
import "fmt"
import "fmt"
func main() {
func main() {
var min, max int
min, max = MinMax(78, 65)
fmt.Printf("Minmium is: %d, Maximum is: %d\n", min, max)
}
}
func MinMax(a int, b int) (min int, max int) {
func MinMax(a int, b int) (min int, max int) {
if a < b {
min = a
max = b
@@ -188,7 +196,8 @@ Go默认使用按值传递来传递参数也就是传递参数的副本。函
max = a
}
return
}
}
```
输出结果:
@@ -196,27 +205,29 @@ Go默认使用按值传递来传递参数也就是传递参数的副本。函
## 6.2.4 改变外部变量outside variable
传递指针给函数不但可以节省内存因为没有复制变量的值而且赋予了函数直接修改外部变量的能力所以被修改的变量不再需要使用`return`返回如下的例子`reply`是一个指向`int`变量的指针通过这个指针我们在函数内修改了这个`int`变量的数值
传递指针给函数不但可以节省内存因为没有复制变量的值而且赋予了函数直接修改外部变量的能力所以被修改的变量不再需要使用 `return` 返回如下的例子`reply` 是一个指向 `int` 变量的指针通过这个指针我们在函数内修改了这个 `int` 变量的数值
示例 6.6 [side_effect.go](examples/chapter_6/side_effect.go)
package main
```go
package main
import (
import (
"fmt"
)
)
// this function changes reply:
func Multiply(a, b int, reply *int) {
// this function changes reply:
func Multiply(a, b int, reply *int) {
*reply = a * b
}
}
func main() {
func main() {
n := 0
reply := &n
Multiply(10, 5, reply)
fmt.Println("Multiply:", *reply) // Multiply: 50
}
}
```
这仅仅是个指导性的例子,当需要在函数内改变一个占用内存比较大的变量时,性能优势就更加明显了。然而,如果不小心使用的话,传递一个指针很容易引发一些不确定的事,所以,我们要十分小心那些可以改变外部变量的函数,在必要时,需要添加注释以便其他人能够更加清楚的知道函数里面到底发生了什么。

View File

@@ -2,23 +2,28 @@
如果函数的最后一个参数是采用 `...type` 的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0这样的函数称为变参函数。
func myFunc(a, b, arg ...int) {}
```go
func myFunc(a, b, arg ...int) {}
```
这个函数接受一个类似某个类型的 slice 的参数(详见第 7 章),该参数可以通过第 5.4.4 中提到的 for 循环结构迭代。
这个函数接受一个类似某个类型的 slice 的参数(详见第 7 章),该参数可以通过第 5.4.4 中提到的 for 循环结构迭代。
示例函数和调用:
func Greeting(prefix string, who ...string)
Greeting("hello:", "Joe", "Anna", "Eileen")
```go
func Greeting(prefix string, who ...string)
Greeting("hello:", "Joe", "Anna", "Eileen")
```
在 Greeting 函数中,变量 `who` 的值为 `[]string{"Joe", "Anna", "Eileen"}`
如果参数被存储在一个数组 `arr` 中,则可以通过 `arr...` 的形式来传递参数调用变参函数。
示例 6.7 varnumpar.go
示例 6.7 [varnumpar.go](examples/chapter_6/varnumpar.go)
```go
package main
import "fmt"
func main() {
@@ -103,5 +108,5 @@ func F3(s []string) { }
## 链接
- [目录](directory.md)
- 上一节:[参数与返回值](06.2.md)
- 上一节:[函数参数与返回值](06.2.md)
- 下一节:[defer 和追踪](06.4.md)

View File

@@ -4,7 +4,7 @@
关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 `finally` 语句块,它一般用于释放某些已分配的资源。
下面这个示例很好地解释了它的用法Listing 6.8 defer.go
示例 6.8 [defer.go](examples/chapter_6/defer.go)
```go
package main
@@ -136,7 +136,7 @@ func untrace(s string) { fmt.Println("leaving:", s) }
以下代码展示了何时调用两个函数:
Listing 6.10—_defer_tracing.go:
示例 6.10 [defer_tracing.go](examples/chapter_6/defer_tracing.go):
```go
package main
@@ -175,7 +175,7 @@ leaving: a
leaving: b
```
上面的代码还可以修改为更加简便的版本(Listing 6.11—_defer_tracing2.go
上面的代码还可以修改为更加简便的版本(示例 6.11 [defer_tracing2.go](examples/chapter_6/defer_tracing2.go)
```go
package main
@@ -209,7 +209,7 @@ func main() {
**使用 defer 语句来记录函数的参数与返回值**
下面的代码展示了另一种在调试时使用 defer 语句的手法(Listing 6.12—_defer_logvalues.go
下面的代码展示了另一种在调试时使用 defer 语句的手法(示例 6.12 [defer_logvalues.go](examples/chapter_6/defer_logvalues.go)
```go
package main

View File

@@ -6,7 +6,7 @@
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
下面的程序可用于生成该数列(示例 6.13 [fibonacci.go](examples/chapter_6/fibonacci.go)
```go
package main
@@ -51,7 +51,7 @@ fibonacci(10) is: 89
在使用递归函数时经常会遇到的一个重要问题就是栈溢出:一般出现在大量的递归调用导致的程序栈内存分配耗尽。这个问题可以通过一个名为懒惰评估的技术解决,在 Go 语言中我们可以使用管道channel和 goroutine详见第 14.8 节)来实现。练习 14.12 也会通过这个方案来优化斐波那契数列的生成问题。
Go 语言中也可以使用相互调用的递归函数:多个函数之间相互调用形成闭环。因为 Go 语言编译器的特殊性,这些函数的声明顺序可以是任意的。下面这个简单的例子展示了函数 odd 和 even 之间的相互调用(Listing 6.14 mut_recurs.go
Go 语言中也可以使用相互调用的递归函数:多个函数之间相互调用形成闭环。因为 Go 语言编译器的特殊性,这些函数的声明顺序可以是任意的。下面这个简单的例子展示了函数 odd 和 even 之间的相互调用(示例 6.14 [mut_recurs.go](examples/chapter_6/mut_recurs.go)
```go
package main

View File

@@ -124,8 +124,8 @@ addJpeg := MakeAddSuffix(“.jpeg”)
然后调用它们:
```go
addBmp(file) // returns: file.bmp
addJpeg(file) // returns: file.jpeg
addBmp("file") // returns: file.bmp
addJpeg("file") // returns: file.jpeg
```
可以返回其它函数的函数和接受其它函数作为参数的函数均被称之为高阶函数,是函数式语言的特点。我们已经在第 6.7 中得知函数也是一种值,因此很显然 Go 语言具有一些函数式语言的特性。闭包在 Go 语言中非常常见,常用于 goroutine 和管道操作(详见第 14.8-14.9 节)。在第 11.14 节的程序中,我们将会看到 Go 语言中的函数在处理混合对象时的强大能力。

View File

@@ -3,26 +3,33 @@
##7.1.1 概念
数组是具有相同 **唯一类型** 的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以[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语言中是相似的)。元素的数目,也称为长度或者数组大小必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组长度最大为 2Gb。
数组元素可以通过 **索引**(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0第二个索引为 1以此类推。数组以 0 开始在所有类 C 语言中是相似的)。元素的数目,也称为长度或者数组大小必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组长度最大为 2Gb。
声明的格式是: `var identifier [len]type`
声明的格式是:
例如: `var arr1 [5]int`
```go
var identifier [len]type
```
在内存中的结构是:![](images/7.1_fig7.1.png?raw=true)
例如:
每个元素是一个整形值当声明数组时所有的元素都会被自动初始化为默认值0.
```go
var arr1 [5]int
```
arr1 的长度是 5索引范围从 0 到 len(arr1)-1
在内存中的结构是:![](../images/7.1_fig7.1.png?raw=true)
第一个元素是 arr1[0],第三个元素是 arr1[2];总体来说索引 i 代表的元素是 arr1[i],最后一个元素是 arr1[len(arr1)-1]
个元素是一个整形值,当声明数组时所有的元素都会被自动初始化为默认值 0
对索引项为 i 的数组元素赋值可以这么操作arr[i] = value所以数组是 **可变的**
arr1 的长度是 5索引范围从 0 到 `len(arr1)-1`
只有有效的索引可以被使用,当使用等于或者大于 len(arr1) 的索引时:如果编译器可以检测到,会给出索引超限的提示信息;如果检测不到的话编译会通过而运行时会 panic:(参考 [第 13 章](13.0.md)
第一个元素是 `arr1[0]`,第三个元素是 `arr1[2]`;总体来说索引 i 代表的元素是 `arr1[i]`,最后一个元素是 `arr1[len(arr1)-1]`
对索引项为 i 的数组元素赋值可以这么操作:`arr[i] = value`,所以数组是 **可变的**
只有有效的索引可以被使用,当使用等于或者大于 `len(arr1)` 的索引时:如果编译器可以检测到,会给出索引超限的提示信息;如果检测不到的话编译会通过而运行时会 panic:(参考 [第 13 章](13.0.md)
runtime error: index out of range
@@ -34,10 +41,11 @@ arr1 的长度是 5索引范围从 0 到 len(arr1)-1
示例 7.1 [for_arrays.go](examples/chapter_7/for_arrays.go)
package main
import "fmt"
```go
package main
import "fmt"
func main() {
func main() {
var arr1 [5]int
for i:=0; i < len(arr1); i++ {
@@ -47,7 +55,8 @@ arr1 的长度是 5索引范围从 0 到 len(arr1)-1
for i:=0; i < len(arr1); i++ {
fmt.Printf("Array at index %d is %d\n", i, arr1[i])
}
}
}
```
输出结果:
@@ -57,84 +66,95 @@ arr1 的长度是 5索引范围从 0 到 len(arr1)-1
Array at index 3 is 6
Array at index 4 is 8
for循环中的条件非常重要i < len(arr1)如果写成i <= len(arr1)的话会产生越界错误
for 循环中的条件非常重要`i < len(arr1)`,如果写成 `i <= len(arr1)` 的话会产生越界错误
IDIOM:
for i:=0; i < len(arr1); i++
```go
for i:=0; i < len(arr1); i++
arr1[i] = ...
}
}
```
也可以使用for-range的生成方式
也可以使用 for-range 的生成方式
IDIOM:
for _,i:= range arr1 {
...
}
```go
for i,_:= range arr1 {
...
}
```
在这里i也是数组的索引当然这两种 for 结构对于切片(slices)参考 [第 7 章](07.2.md)来说也同样适用
在这里i也是数组的索引。当然这两种 for 结构对于切片slices参考 [第 7 章](07.2.md)来说也同样适用
**问题 7.1** 下面代码段的输出是什么?
a := [...]string{"a", "b", "c", "d"}
for i := range a {
```go
a := [...]string{"a", "b", "c", "d"}
for i := range a {
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`
这样的结果就是当把一个数组赋值给另一个时,需要在做一次数组内存的拷贝操作。例如:
arr2 := arr1
arr2[2] = 100
```go
arr2 := arr1
arr2[2] = 100
```
这样两个数组就有了不同的值,在赋值后修改 arr2 不会对 arr1 生效。
所以在函数中数组作为参数传入时 func1(arr2)会产生一次数组拷贝func1 方法不会修改原始的数组 arr2
所以在函数中数组作为参数传入时,如 `func1(arr2)`会产生一次数组拷贝func1 方法不会修改原始的数组 arr2
如果你想修改原数组,那么 arr2 必须通过&操作符以引用方式传过来,例如 func1(&arr2下面是一个例子
Example 7.2 [pointer_array.go](examples/chapter_7/pointer_array.go):
示例 7.2 [pointer_array.go](examples/chapter_7/pointer_array.go):
package main
import "fmt"
func f(a [3]int) { fmt.Println(a) }
func fp(a *[3]int) { fmt.Println(a) }
```go
package main
import "fmt"
func f(a [3]int) { fmt.Println(a) }
func fp(a *[3]int) { fmt.Println(a) }
func main() {
func main() {
var ar [3]int
f(ar) // passes a copy of ar
fp(&ar) // passes a pointer to ar
}
}
```
输出结果:
[0 0 0]
&[0 0 0]
另一种方法就是生成数组切片并将其传递给函数参见传递数组参数给函数7.1.4节
另一种方法就是生成数组切片并将其传递给函数(详见第 7.1.4 节)。
**练习**
练习7.1array_value.go: 证明当数组赋值时,发生了数组内存拷贝。
练习7.2for_array.go: 写一个循环并用下标给数组赋值从0到15并且将数组打印在屏幕上
练习7.2for_array.go: 写一个循环并用下标给数组赋值(从 0 到 15并且将数组打印在屏幕上
练习7.3fibonacci_array.go: 在6.6节我们看到了一个递归计算Fibonacci数值的方法但是通过数组我们可以更快的计算出Fibonacci完成该方法并打印出前50Fibonacci数字
练习7.3fibonacci_array.go: 在第 6.6 节我们看到了一个递归计算 Fibonacci 数值的方法但是通过数组我们可以更快的计算出 Fibonacci 数。完成该方法并打印出前 50Fibonacci 数字
## 7.1.2 数组常量
如果数组值已经提前知道了,那么可以通过 **数组常量** 的方法来初始化数组,而不用依次使用 `[]=` 方法(所有的组成元素都有相同的常量语法)。
Example 7.3 [array_literals.go](examples/chapter_7/array_literals.go)
示例 7.3 [array_literals.go](examples/chapter_7/array_literals.go)
package main
import "fmt"
```go
package main
import "fmt"
func main() {
func main() {
// var arrAge = [5]int{18, 20, 15, 22, 16}
// var arrLazy = [...]int{5, 6, 7, 8, 22}
// var arrLazy = []int{5, 6, 7, 8, 22}
@@ -144,19 +164,30 @@ Example 7.3 [array_literals.go](examples/chapter_7/array_literals.go)
for i:=0; i < len(arrKeyValue); i++ {
fmt.Printf("Person at %d is %s\n", i, arrKeyValue[i])
}
}
}
```
第一种变化 `var arrAge = [5]int{18, 20, 15, 22, 16}`
第一种变化
```go
var arrAge = [5]int{18, 20, 15, 22, 16}
```
注意 `[5]int` 可以从左边起开始忽略:`[10]int {1, 2, 3}` :这是一个有 10 个元素的数组,除了前三个元素外其他元素都为 0。
第二种变化 `var arrLazy = [...]int{5, 6, 7, 8, 22}`
第二种变化
```go
var arrLazy = [...]int{5, 6, 7, 8, 22}
```
`...` 可同样可以忽略,从技术上说它们其实变化成了切片。
第三种变化:`key: value syntax`
var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}
```go
var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}
```
只有索引 3 和 4 被赋予实际的值,其他元素都被设置为空的字符串,所以输出结果为:
@@ -170,18 +201,20 @@ Example 7.3 [array_literals.go](examples/chapter_7/array_literals.go)
你可以取任意数组常量的地址来作为指向新实例的指针。
Example 7.4 [pointer_array2.go](examples/chapter_7/pointer_array2.go)
示例 7.4 [pointer_array2.go](examples/chapter_7/pointer_array2.go)
package main
import "fmt"
```go
package main
import "fmt"
func fp(a *[3]int) { fmt.Println(a) }
func fp(a *[3]int) { fmt.Println(a) }
func main() {
func main() {
for i := 0; i < 3; i++ {
fp(&[3]int{i, i * i, i * i * i})
}
}
}
```
输出结果:
@@ -191,33 +224,37 @@ Example 7.4 [pointer_array2.go](examples/chapter_7/pointer_array2.go)
几何点(或者数学向量)是一个使用数组的经典例子。为了简化代码通常使用一个别名:
type Vector3D [3]float32
var vec Vector3D
```go
type Vector3D [3]float32
var vec Vector3D
```
## 7.1.3 多维数组
数组通常是1维的但是可以用来组装成多维数组例如`[3][5]int``[2][2][2]float64`
数组通常是维的但是可以用来组装成多维数组,例如:`[3][5]int``[2][2][2]float64`
内部数组总是长度相同的。Go 语言的多维数组是矩形式的(唯一的例外是切片的数组,参见第 7.2.5 节)。
Example 7.5 [multidim_array.go](examples/chapter_7/multidim_array.go)
示例 7.5 [multidim_array.go](examples/chapter_7/multidim_array.go)
package main
const (
```go
package main
const (
WIDTH = 1920
HEIGHT = 1080
)
)
type pixel int
var screen [WIDTH][HEIGHT]pixel
type pixel int
var screen [WIDTH][HEIGHT]pixel
func main() {
func main() {
for y := 0; y < HEIGHT; y++ {
for x := 0; x < WIDTH; x++ {
screen[x][y] = 0
}
}
}
}
```
## 7.1.4 将数组传递给函数
@@ -228,29 +265,32 @@ Example 7.5 [multidim_array.go](examples/chapter_7/multidim_array.go)
接下来的例子阐明了第一种方法:
Example 7.6 [array_sum.go](examples/chapter_7/array_sum.go)
示例 7.6 [array_sum.go](examples/chapter_7/array_sum.go)
package main
import "fmt"
```go
package main
import "fmt"
func main() {
func main() {
array := [3]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Note the explicit address-of operator
// to pass a pointer to the array
fmt.Printf("The sum of the array is: %f", x)
}
}
func Sum(a *[3]float64) (sum float64) {
func Sum(a *[3]float64) (sum float64) {
for _, v := range a { // derefencing *a to get back to the array is not necessary!
sum += v
}
return
}
}
```
输出结果:
The sum of the array is: 24.600000
但这在 Go 中并不常用通常使用切片。(参考 [第 7](07.2.md)
但这在 Go 中并不常用,通常使用切片(参考 [第 7.2 节](07.2.md))。
## 链接

View File

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

View File

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

View File

@@ -1,24 +1,30 @@
# 7.4 切片重组
# 7.4 切片重组reslice
我们已经知道 slice 创建的时候通常比相关数组小,例如
我们已经知道切片创建的时候通常比相关数组小,例如
slice1 := make([]type, start_length, capacity)
```go
slice1 := make([]type, start_length, capacity)
```
其中 start_length 作为 slice 初始长度而 capacity 作为相关数组的长度。
其中 `start_length` 作为切片初始长度而 `capacity` 作为相关数组的长度。
这么做的好处是我们的 slice 在达到容量上限后可以扩容。改变 slice 长度的过程称之为切片重组 **reslicing**,做法如下:`slice1 = slice1[0:end]`,其中 end 是新的末尾索引(即长度)。
这么做的好处是我们的切片在达到容量上限后可以扩容。改变切片长度的过程称之为切片重组 **reslicing**,做法如下:`slice1 = slice1[0:end]`,其中 end 是新的末尾索引(即长度)。
slice 扩展 1 位可以这么做:`sl = sl[0:len(sl)+1]`
切片扩展 1 位可以这么做:
slice 可以反复扩展直到占据整个相关数组。
```go
sl = sl[0:len(sl)+1]
```
切片可以反复扩展直到占据整个相关数组。
示例 7.11 [reslicing.go](examples/chapter_7/reslicing.go)
package main
import "fmt"
```go
package main
import "fmt"
func main() {
func main() {
slice1 := make([]int, 0, 10)
// load the slice, cap(slice1) is 10:
for i := 0; i < cap(slice1); i++ {
@@ -31,7 +37,8 @@ slice 可以反复扩展直到占据整个相关数组。
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
}
}
```
输出结果:
@@ -58,18 +65,22 @@ slice 可以反复扩展直到占据整个相关数组。
另一个例子:
var ar = [10]int{0,1,2,3,4,5,6,7,8,9}
var a = ar[5:7] // reference to subarray {5,6} - len(a) is 2 and cap(a) is 5
```go
var ar = [10]int{0,1,2,3,4,5,6,7,8,9}
var a = ar[5:7] // reference to subarray {5,6} - len(a) is 2 and cap(a) is 5
```
将a重新分片
将 a 重新分片
a = a[0:4] // ref of subarray {5,6,7,8} - len(a) is now 4 but cap(a) is still 5
```go
a = a[0:4] // ref of subarray {5,6,7,8} - len(a) is now 4 but cap(a) is still 5
```
问题 7.7
**问题 7.7**
1) 如果 a 是一个 slice那么 s[n:n] 的长度是多少
1) 如果 a 是一个切片,那么 `s[n:n]` 的长度是多少
2) s[n:n+1] 的长度又是多少
2) `s[n:n+1]` 的长度又是多少
## 链接

View File

@@ -1,13 +1,14 @@
# 7.5 切片的复制与追加
如果想增加 slice 的容量,我们必须创建一个新的更大的 slice 并把原分片的内容都拷贝过来。下面的代码描述了从拷贝 slice 的 copy 方法和向 slice 追加新元素的 append 方法
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。下面的代码描述了从拷贝切片的 copy 函数和向切片追加新元素的 append 函数
示例 7.12 [copy_append_slice.go](examples/chapter_7/copy_append_slice.go)
package main
import "fmt"
```go
package main
import "fmt"
func main() {
func main() {
sl_from := []int{1, 2, 3}
sl_to := make([]int, 10)
@@ -18,11 +19,12 @@
sl3 := []int{1, 2, 3}
sl3 = append(sl3, 4, 5, 6)
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 的元素追加到切片后面并且返回新的切片;追加的元素必须和原切片的元素同类型。如果 s 的容量不足以存储新增元素append 会分配新的切片来保证已有切片元素和新增元素的存储。因此,返回的切片可能已经指向一个不同的相关数组了。append 方法总是返回成功,除非系统内存耗尽了。
如果你想将 slice y 追加到 slice x 后面,只要将第二个参数扩展成一个列表即可:`x = append(x, y...)`
如果你想将切片 y 追加到切片 x 后面,只要将第二个参数扩展成一个列表即可:`x = append(x, y...)`
**注意** append 在大多数情况下很好用,但是如果你想完全掌控整个追加过程,你可以实现一个这样的 AppendByte 方法:
@@ -42,7 +44,7 @@ func AppendByte(slice []byte, data ...byte) []byte {
}
```
`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 的切片从源地址 src 拷贝到目标地址 dst覆盖 dst 的相关元素,并且返回拷贝的元素个数。源地址和目标地址可能会有重叠。拷贝个数是 src 和 dst 的长度最小值。如果 src 是字符串那么元素类型就是 byte。如果你还想继续使用 src在拷贝技术后执行 `src = dst`
**练习 7.9**
@@ -50,18 +52,18 @@ func AppendByte(slice []byte, data ...byte) []byte {
**练习 7.10**
用顺序函数过滤容器s 是前 10 个整形的 slice。构造一个函数 Filter第一个参数是 s第二个参数是一个 `fn func(int) bool`,返回满足函数 fn 的元素 slice。通过 fn 测试方法测试当整型值是偶数时的情况。
用顺序函数过滤容器s 是前 10 个整形的切片。构造一个函数 Filter第一个参数是 s第二个参数是一个 `fn func(int) bool`,返回满足函数 fn 的元素切片。通过 fn 测试方法测试当整型值是偶数时的情况。
**练习 7.11**
写一个函数 InsertStringSlice 将 slice 插入到另一个 slice 的指定位置。
写一个函数 InsertStringSlice 将切片插入到另一个切片的指定位置。
**练习 7.12**
写一个函数 RemoveStringSlice 将从 start 到 end 索引的元素从 slice 中移除。
写一个函数 RemoveStringSlice 将从 start 到 end 索引的元素从切片 中移除。
## 链接
- [目录](directory.md)
- 上一节:[切片重组](07.4.md)
- 上一节:[切片重组reslice](07.4.md)
- 下一节:[字符串、数组和切片的应用](07.6.md)

View File

@@ -37,7 +37,7 @@ b = append(b, s...)
## 7.6.2 获取字符串的某一部分
使用 `substr := str[start:end]` 可以从字符串 str 获取到从索引 start 开始到 end-1 位置的子字符串。同样的,`str[start:]` 则表示获取从 start 开始到 len(str)-1 位置的子字符串。而 `str[:end]` 表示获取从 0 开始到 end-1 的子字符串。
使用 `substr := str[start:end]` 可以从字符串 str 获取到从索引 start 开始到 `end-1` 位置的子字符串。同样的,`str[start:]` 则表示获取从 start 开始到 `len(str)-1` 位置的子字符串。而 `str[:end]` 表示获取从 0 开始到 `end-1` 的子字符串。
## 7.6.3 字符串和切片的内存结构
@@ -45,11 +45,11 @@ b = append(b, s...)
字符串 `string s = "hello"` 和子字符串 `t = s[2:3]` 在内存中的结构可以用下图表示:
![](images/7.6_fig7.4.png)
![](../images/7.6_fig7.4.png)
## 7.6.4 修改字符串中的某个字符
Go 语言中的字符串是不可变的,也就是说 `str[index]` 这样的表达式是不可以被放在等号左侧的。如果尝试运行 `str[i] = D` 会得到错误:`cannot assign to str[i]`
Go 语言中的字符串是不可变的,也就是说 `str[index]` 这样的表达式是不可以被放在等号左侧的。如果尝试运行 `str[i] = 'D'` 会得到错误:`cannot assign to str[i]`
因此,您必须先将字符串转换成字节数组,然后再通过修改数组中的元素值来达到修改字符串的目的,最后将字节数组转换会字符串格式。
@@ -66,7 +66,7 @@ s2 := string(c) // s2 == "cello"
## 7.6.5 字节数组对比函数
下面的 `Compare` 函数会返回两个字节数组字典顺序的整数对比结果,即 `0 if a ==b, -1 if a < b, 1 if a > b`
下面的 `Compare` 函数会返回两个字节数组字典顺序的整数对比结果,即 `0 if a == b, -1 if a < b, 1 if a > b`
```go
func Compare(a, b[]byte) int {
@@ -150,7 +150,7 @@ func FindDigits(filename string) []byte {
}
```
这段代码可以顺利运行,但返回的 []byte 指向的底层是整个文件的数据。只要该返回的切片不被释放,垃圾回收器就不能释放整个文件所占用的内存。换句话说,一点点有用的数据却占用了整个文件的内存。
这段代码可以顺利运行,但返回的 `[]byte` 指向的底层是整个文件的数据。只要该返回的切片不被释放,垃圾回收器就不能释放整个文件所占用的内存。换句话说,一点点有用的数据却占用了整个文件的内存。
想要避免这个问题,可以通过拷贝我们需要的部分到一个新的切片中:
@@ -174,11 +174,11 @@ func FindDigits(filename string) []byte {
**练习 7.14**
编写一个程序,要求能够反转字符串,即将 “Google” 转换成 “elgooG”提示使用 []byte 类型的切片)。
编写一个程序,要求能够反转字符串,即将 “Google” 转换成 “elgooG”提示使用 `[]byte` 类型的切片)。
如果您使用两个切片来实现反转,请再尝试使用一个切片(提示:使用交换法)。
如果您想要反转 Unicode 编码的字符串,请使用 []int 类型的切片。
如果您想要反转 Unicode 编码的字符串,请使用 `[]int` 类型的切片。
**练习 7.15**
@@ -203,4 +203,4 @@ func FindDigits(filename string) []byte {
- [目录](directory.md)
- 上一节:[切片的复制与追加](07.5.md)
- 下一章:[Maps](08.0.md)
- 下一章:[Map](08.0.md)

View File

@@ -1,10 +1,11 @@
# 8.0 Maps
# 8.0 Map
Maps 是一种特殊的数据结构一种元素对pair的无序集合pair 的一个元素是 key对应的另一个元素是 value所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构给定 key对应的 value 可以迅速定位。
map 是一种特殊的数据结构一种元素对pair的无序集合pair 的一个元素是 key对应的另一个元素是 value所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构给定 key对应的 value 可以迅速定位。
Maps 这种数据结构在其他编程语言中也称为字典Python、hash 和 HashTable 等。
map 这种数据结构在其他编程语言中也称为字典Python、hash 和 HashTable 等。
## 链接
- [目录](directory.md)
- 上一章:[字符串、数组和切片的应用](07.6.md)
- 下一节:[声明、初始化和 make](08.1.md)

View File

@@ -9,27 +9,27 @@ var map1 map[keytype]valuetype
var map1 map[string]int
```
[keytype]valuetype之间允许有空格但是 gofmt 移除了空格)
`[keytype]`` 和 `valuetype` 之间允许有空格,但是 gofmt 移除了空格)
在声明的时候不需要知道 map 的长度map 是可以动态增长的。
未初始化的 map 的值是 nil。
key 可以是任意可以用 == 或者 != 操作符比较的类型,比如 stringintfloat。所以数组slice 和结构体不能作为 key但是指针和接口类型可以。如果要用结构体作为 key 可以提供 Key() 和 Hash() 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key。
key 可以是任意可以用 == 或者 != 操作符比较的类型,比如 stringintfloat。所以数组、切片和结构体不能作为 key但是指针和接口类型可以。如果要用结构体作为 key 可以提供 `Key()``Hash()` 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key。
value 可以是任意类型的;通过使用空接口类型(详见第 11.9 节),我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言(详见第 11.3 节)。
Maps 传递给函数的代价很小:在 32 位机器上占 4 个字节64 位机器上占 8 个字节,无论实际上存储了多少数据。通过 key 在 map 中寻找值是很快的,比线性查找快得多,但是仍然比从数组和 slice 的索引中直接读取要慢 100 倍;所以如果你很在乎性能的话还是建议用 slice 来解决问题。
map 传递给函数的代价很小:在 32 位机器上占 4 个字节64 位机器上占 8 个字节,无论实际上存储了多少数据。通过 key 在 map 中寻找值是很快的,比线性查找快得多,但是仍然比从数组和切片的索引中直接读取要慢 100 倍;所以如果你很在乎性能的话还是建议用切片来解决问题。
Maps 也可以用函数作为自己的值,这样就可以用来做分支结构(详见第 5 章key 用来选择要执行的函数。
map 也可以用函数作为自己的值,这样就可以用来做分支结构(详见第 5 章key 用来选择要执行的函数。
如果 key1 是 map1 的key那么 map1[key1] 就是对应 key1 的值,就如同数组索引符号一样(数组可以视为一种简单形式的 mapkey 是从 0 开始的整数)。
如果 key1 是 map1 的key那么 `map1[key1]` 就是对应 key1 的值,就如同数组索引符号一样(数组可以视为一种简单形式的 mapkey 是从 0 开始的整数)。
key1 对应的值可以通过赋值符号来设置为 val1map1[key1] = val1
key1 对应的值可以通过赋值符号来设置为 `val1map1[key1] = val1`。
v: = map1[key1] 可以将 key1 对应的值赋值为 v如果 map 中没有 key1 存在,那么 v 将被赋值为 map1 的值类型的空值。
令 `v: = map1[key1]` 可以将 key1 对应的值赋值为 v如果 map 中没有 key1 存在,那么 v 将被赋值为 map1 的值类型的空值。
常用的 len(map1) 方法可以获得 map 中的 pair 数目,这个数目是可以伸缩的,因为 map-pairs 在运行时可以动态添加和删除。
常用的 `len(map1)` 方法可以获得 map 中的 pair 数目,这个数目是可以伸缩的,因为 map-pairs 在运行时可以动态添加和删除。
示例 8.1 [make_maps.go](examples/chapter_8/make_maps.go)
@@ -64,21 +64,21 @@ func main() {
Map assigned at "two" is: 3
Mpa literal at "ten" is: 0
mapLit 说明了 `map literals` 的使用方法: map 可以用 {key1: val1, key2: val2} 的描述方法来初始化,就像数组和结构体一样。
mapLit 说明了 `map literals` 的使用方法: map 可以用 `{key1: val1, key2: val2}` 的描述方法来初始化,就像数组和结构体一样。
maps**引用类型** 的: 内存用 make 方法来分配。
map 是 **引用类型** 的: 内存用 make 方法来分配。
map 的初始化:`var map1[keytype]valuetype = make(map[keytype]valuetype)`
map 的初始化:`var map1[keytype]valuetype = make(map[keytype]valuetype)`。
或者简写为:`map1 := make(map[keytype]valuetype)`
或者简写为:`map1 := make(map[keytype]valuetype)`。
上面例子中的 mapCreated 就是用这种方式创建的:`mapCreated := make(map[string]float)`
上面例子中的 mapCreated 就是用这种方式创建的:`mapCreated := make(map[string]float)`。
相当于:`mapCreated := map[string]float{}`
相当于:`mapCreated := map[string]float{}`。
mapAssigned 也是 mapList 的引用,对 mapAssigned 的修改也会影响到 mapLit 的值。
**不要使用 new永远用 make 来构造 map**
**不要使用 new永远用 make 来构造 map**
**注意** 如果你错误的使用 new() 分配了一个引用对象,你会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址:
@@ -108,7 +108,7 @@ func main() {
}
```
输出结果为:`map[1:0x10903be0 5:0x10903ba0 2:0x10903bc0]``: 整形都被映射到函数地址。
输出结果为:`map[1:0x10903be0 5:0x10903ba0 2:0x10903bc0]`: 整形都被映射到函数地址。
## 8.1.2 map 容量
@@ -128,9 +128,9 @@ noteFrequency := map[string]float32 {
"G0": 24.50, "A0": 27.50, "B0": 30.87, "A4": 440}
```
## 8.1.3 用 slice 作为 map 的值
## 8.1.3 用切片作为 map 的值
既然一个 key 只能对应一个 value而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办例如当我们要处理unix机器上的所有进程以父进程pid 为整形)作为 key所有的子进程以所有子进程的 pid 组成的 slice)作为 value。通过将 value 定义为 []int 类型或者其他类型的 slice,就可以优雅的解决这个问题。
既然一个 key 只能对应一个 value而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办例如当我们要处理unix机器上的所有进程以父进程pid 为整形)作为 key所有的子进程以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 `[]int` 类型或者其他类型的切片,就可以优雅的解决这个问题。
这里有一些定义这种 map 的例子:
@@ -140,6 +140,7 @@ mp2 := make(map[int]*[]int)
```
## 链接
- [目录](directory.md)
- 上一节:[Maps](08.0.md)
- 上一节:[Map](08.0.md)
- 下一节:[测试键值对是否存在及删除元素](08.2.md)

View File

@@ -2,7 +2,7 @@
测试 map1 中是否存在 key1
在例子 8.1 中,我们已经见过可以使用 `val1 = map1[key1]` `的方法获取 key1 对应的值 val1。如果 map 中不存在 key1val1 就是一个值类型的空值。
在例子 8.1 中,我们已经见过可以使用 `val1 = map1[key1]` 的方法获取 key1 对应的值 val1。如果 map 中不存在 key1val1 就是一个值类型的空值。
这就会给我们带来困惑了:现在我们没法区分到底是 key1 不存在还是它对应的 value 就是空值。

View File

@@ -49,7 +49,7 @@ func main() {
key is: 4 - value is: 4.000000
key is: 2 - value is: 2.000000
注意map不是按照key的顺序排列的也不是按照value的序排列的。
注意 map 不是按照 key 的顺序排列的,也不是按照 value 的序排列的。
问题 8.1 下面这段代码的输出是什么?
@@ -62,7 +62,7 @@ for key := range capitals {
**练习 8.1**
创建一个map来保存每周7天的名字,将它们打印出来并且测试是否存在tuesday和hollyday。
创建一个 map 来保存每周 7 天的名字,将它们打印出来并且测试是否存在 Tuesday 和 Hollyday。
## 链接

View File

@@ -1,8 +1,8 @@
# 8.3 map 类型的切片
# 8.4 map 类型的切片
假设我们想获取一个 map 类型的切片,我们必须使用两次 make() 方法,第一次分配 slice,第二次分配 slice 中每个 map 元素(参见下面的例子 8.3)。
假设我们想获取一个 map 类型的切片,我们必须使用两次 `make()` 函数,第一次分配切片,第二次分配 切片中每个 map 元素(参见下面的例子 8.4)。
示例 8.3 [maps_forrange.go](examples/chapter_8/maps_forrange.go)
示例 8.4 [maps_forrange2.go](examples/chapter_8/maps_forrange2.go)
```go
package main
@@ -32,7 +32,7 @@ func main() {
Version A: Value of items: [map[1:2] map[1:2] map[1:2] map[1:2] map[1:2]]
Version B: Value of items: [map[] map[] map[] map[] map[]]
需要注意的是,应当像 A 版本那样通过索引使用 slice 的 map 元素。在 B 版本中获得的项只是 map 值的一个拷贝而已,所以真正的 map 元素没有得到初始化。
需要注意的是,应当像 A 版本那样通过索引使用切片的 map 元素。在 B 版本中获得的项只是 map 值的一个拷贝而已,所以真正的 map 元素没有得到初始化。
## 链接

View File

@@ -2,7 +2,7 @@
map 默认是无序的,不管是按照 key 还是按照 value 默认都不排序(详见第 8.3 节)。
如果你想为 map 排序,需要将 key或者 value拷贝到一个 slice再对 slice 排序(使用 sort 包,详见第 7.6.6 节),然后可以使用 slice 的 for-range 方法打印出所有的 key 和 value。
如果你想为 map 排序,需要将 key或者 value拷贝到一个切片,再对切片排序(使用 sort 包,详见第 7.6.6 节),然后可以使用切片的 for-range 方法打印出所有的 key 和 value。
下面有一个示例:
@@ -50,7 +50,7 @@ func main() {
sorted:
Key: alpha, Value: 34 / Key: bravo, Value: 56 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: echo, Value: 56 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: kili, Value: 43 / Key: lima, Value: 98 / [fangjun@st01-dstream-0001.st01.baidu.com go]$ sz -be sort_map.go
但是如果你想要一个排序的列表你最好使用结构体 slice,这样会更有效:
但是如果你想要一个排序的列表你最好使用结构体切片,这样会更有效:
```go
type struct {

View File

@@ -1,16 +1,16 @@
# 9.1 标准库概述
像 fmtos 等这样具有常用功能的内置包在 Go 语言中有 150 个以上,它们被称为标准库,大部分(一些底层的除外)内置于 Go 本身。记录在: [http://golang.org/pkg/](http://golang.org/pkg/)
`fmt``os` 等这样具有常用功能的内置包在 Go 语言中有 150 个以上,它们被称为标准库,大部分(一些底层的除外)内置于 Go 本身。完整列表可以在 [Go Walker](https://gowalker.org/search?q=gorepos) 查看
在贯穿本书的例子和练习中,我们都是用标准库的包。可以通过查阅第 350 页包中的内容快速找到相关的包的实例。这里我们只是按功能进行分组来介绍这些包的简单用途,我们不会深入讨论他们的内部结构。
- unsafe: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。
- syscall-os-os/exec:
- os: 提供给我们一个平台无关性的操作系统功能接口采用类Unix设计隐藏了不同操作系统间差异让不同的文件系统和操作系统对象表现一致。
- os/exec: 提供我们运行外部操作系统命令和程序的方式。
- syscall: 底层的外部包,提供了操作系统底层调用的基本接口。
- `unsafe`: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。
- `syscall`-`os`-`os/exec`:
- `os`: 提供给我们一个平台无关性的操作系统功能接口采用类Unix设计隐藏了不同操作系统间差异让不同的文件系统和操作系统对象表现一致。
- `os/exec`: 提供我们运行外部操作系统命令和程序的方式。
- `syscall`: 底层的外部包,提供了操作系统底层调用的基本接口。
通过一个Go程序让Linux重启来体现它的能力(通过 `sudo ./6.out` 来执行程序)
通过一个 Go 程序让Linux重启来体现它的能力。
示例 9.1 [reboot.go](examples/chapter_9/reboot.go)
@@ -32,30 +32,30 @@ func main() {
}
```
- archive/tar 和 /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: 双链表。
- `archive/tar``/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`)
下面代码演示了如何遍历一个链表(当 l 是 `*List`)
```go
for e := l.Front(); e != nil; e = e.Next() {
@@ -63,33 +63,34 @@ for e := l.Front(); e != nil; e = e.Next() {
}
```
- ring: 环形链表。
- `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: 实现通过程序运行时自省,让程序操作任意类型的变量。
- `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发布的时候并不包含过时或者实验性的包。
`exp` 包中有许多将被编译为新包的实验性的包。它们将成为独立的包在下次稳定版本发布的时候。如果前一个版本已经存在了,它们将被作为过时的包被回收。然而 Go1.0 发布的时候并不包含过时或者实验性的包。
**练习9.1**
**练习 9.1**
使用 container/list 包实现一个双向链表,将 101,102,103 放入其中并打印出来。
使用 `container/list` 包实现一个双向链表,将 101102103 放入其中并打印出来。
**练习9.2**
**练习 9.2**
通过使用 unsafe 包中的方法来测试你电脑上一个整型变量占用多少个字节。
通过使用 `unsafe` 包中的方法来测试你电脑上一个整型变量占用多少个字节。
## 链接
- [目录](directory.md)
- 上一节:[package](09.0.md)
- 下一节:[regexp 包](09.2.md)

View File

@@ -2,39 +2,16 @@
现在我们知道如何使用 Go 以及它的标准库了,但是 Go 的生态要比这大的多。当着手自己的 Go 项目时,最好先查找下是否有些存在的第三方的包或者项目不能使用。大多数可以通过 go install 来进行安装。
第一个要去查看的地方是把项目用标签方式放在包展板上的 Go 项目网站
(运行在Google App Engine): [https://godashboard.appspot.com/project](https://godashboard.appspot.com/project)。这是一个手动维护的记录表。
通过类别进行分类,例如编译工具,压缩工具,数据结构,数据库存储,开发工具等,包含了 500 个以上的项目,列出了每个项目的名字,对其简短的描述和下载链接。这些可以在下面的代码仓库中找到,()中为仓库所使用的代码控制系统。
- on Google Code, e.g. https://code.google.com/p/goprotobuf (Mercurial(hg) or Subversion)
- on Github: e.g. https://github.com/kr/pretty.go (Git)
- on BitBucket, e.g. https://bitbucket.org/binet/igo/ (Mercurial(hg))
- on Launchpad, e.g. http://launchpad.net/mgo (Bazaar)
或者在其他的知名的代码分享网站,或者作者的网站。
在库中你也可以在通过管理员审核后,提交您自己的项目。
如果你想看到实际的项目状态可以看看 go 项目网站上的包在包展板上的标签:[http://godashboard.appspot.com/package](http://godashboard.appspot.com/package)。
它给出一个“安装最多的包”(本周和所有时间)和最近安装包的概述,根据下载包的外部开发者的数量。如果编译栏的状态显示ok,这表示这个包是可以通过最新版本Go来安装的。Go项目和Go包页面是没有关系的。如果一个包出现在一边就没有必要出现在另一边。
其他整理资源(部分内容重叠):
http://go-lang.cat-v.org/dev-utils (Developer-oriented)
http://go-lang.cat-v.org/go-code (Programs and applications)
http://go-lang.cat-v.org/library-bindings (Library bindings)
http://go-lang.cat-v.org/pure-go-libs (Pure Go libraries)
[Go Walker][https://gowalker.org] 支持根据包名在海量数据中查询。
目前已经有许多非常好的外部库,如:
* MySQL(GoMySQL), PostgreSQL(go-pgsql), MongoDB (mgo, gomongo), CouchDB (couch-go), ODBC (godbcl), Redis (redis.go) and SQLite3 (gosqlite) database drivers
* SDL bindings
* Google's Protocal Buffers(goprotobuf)
* XML-RPC(go-xmlrpc)
* Twitter(twitterstream)
* OAuth libraries(GoAuth)
- MySQL(GoMySQL), PostgreSQL(go-pgsql), MongoDB (mgo, gomongo), CouchDB (couch-go), ODBC (godbcl), Redis (redis.go) and SQLite3 (gosqlite) database drivers
- SDL bindings
- Google's Protocal Buffers(goprotobuf)
- XML-RPC(go-xmlrpc)
- Twitter(twitterstream)
- OAuth libraries(GoAuth)
## 链接

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +1,22 @@
# 9.2 regexp 包
正则表达式语法和使用的详细信息请参考 http://en.wikipedia.org/wiki/Regular_expression
正则表达式语法和使用的详细信息请参考 [维基百科](http://en.wikipedia.org/wiki/Regular_expression)。
在下面的程序里,我们将在字符串中对正则表达式进行匹配。
如果是简单模式,使用 Match 方法便可:`ok, _ := regexp.Match(pat, []byte(searchIn))`
如果是简单模式,使用 `Match` 方法便可:
变量 ok 将返回 true 或者 false,我们也可以使用 MatchString`ok, _ := regexp.MathString(pat, searchIn)`
```go
ok, _ := regexp.Match(pat, []byte(searchIn))
```
更多方法中,必须先将正则通过 Compile 方法返回一个 Regexp 对象。然后我们将掌握一些匹配,查找,替换相关的功能。
变量 ok 将返回 true 或者 false,我们也可以使用 `MatchString`
```go
ok, _ := regexp.MathString(pat, searchIn)
```
更多方法中,必须先将正则通过 `Compile` 方法返回一个 Regexp 对象。然后我们将掌握一些匹配,查找,替换相关的功能。
示例 9.2 [pattern.go](examples/chapter_9/pattern.go)
@@ -49,7 +57,7 @@ func main() {
John: ##.# William: ##.# Steve: ##.#
John: 5156.68 William: 9134.46 Steve: 11264.36
Compile 函数也可能返回一个错误,我们在使用时忽略对错误的判断是因为我们确信自己正则表达式是有效的。当用户输入或从数据中获取正则表达式的时候,我们有必要去检验它的正确性。另外我们也可以使用 MustCompile 方法,它可以像 Compile 方法一样检验正则的有效性,但是当正则不合法时程序将 panic详情查看第 13.2 节)。
`Compile` 函数也可能返回一个错误,我们在使用时忽略对错误的判断是因为我们确信自己正则表达式是有效的。当用户输入或从数据中获取正则表达式的时候,我们有必要去检验它的正确性。另外我们也可以使用 `MustCompile` 方法,它可以像 `Compile` 方法一样检验正则的有效性,但是当正则不合法时程序将 panic详情查看第 13.2 节)。
## 链接

View File

@@ -8,22 +8,41 @@
在 Go 语言中这种锁的机制是通过 sync 包中 Mutex 来实现的。sync 来源于 "synchronized" 一词,这意味着线程将有序的对同一变量进行访问。
sync.Mutex 是一个互斥锁,它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区。
`sync.Mutex` 是一个互斥锁,它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区。
假设 info 是一个需要上锁的放在共享内存中的变量。通过包含 Mutex 来实现的一个典型例子如下:
假设 info 是一个需要上锁的放在共享内存中的变量。通过包含 `Mutex` 来实现的一个典型例子如下:
```go
import sync
import sync
type Info struct {
mu sync.Mutex
// ... other fields, e.g.: Str string
}
```
如果一个函数想要改变这个变量可以这样写:
```go
func Update(info *Info) {
mu sync.Mutex
// ... other fields, e.g.: Str string
info.mu.Lock()
// critical section:
info.Str = // new value
// end critical section
info.mu.Unlock()
}
```
还有一个很有用的例子是通过 Mutex 来实现一个可以上锁的共享缓冲器:
```go
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
```
在 sync 包中还有一个 `RWMutex` 锁:他能通过 `RLock()` 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 `Lock()` 将和普通的 `Mutex` 作用相同。包中还有一个方便的 `Once` 类型变量的方法 `once.Do(call)`,这个方法确保被调用函数只能被调用一次。
相对简单的情况下,通过使用 sync 包可以解决同一时间只能一个线程访问变量或 map 类型数据的问题。如果这种方式导致程序明显变慢或者引起其他问题,我们要重新思考来通过 goroutines 和 channels 来解决问题,这是在 Go 语言中所提倡用来实现并发的技术。我们将在第 14 章对其深入了解,并在第 14.7 节中对这两种方式进行比较。

View File

@@ -1,19 +1,51 @@
# 精密计算和 big 包
# 9.4 精密计算和 big 包
我们知道有些时候通过编程的方式去进行计算是不精确的。如果你使用 Go 语言中的 fload64 类型进行浮点运算,返回结果将精确到 15 位,足以满足大多数的任务。当对超出 int64 或者 uint64 类型这样的大数进行计算时如果对精度没有要求float32 或者 float64 可以胜任,但如果对精度有严格要求的时候,我们不能使用浮点数,在内存中它们只能被近似的表示。
我们知道有些时候通过编程的方式去进行计算是不精确的。如果你使用 Go 语言中的 float64 类型进行浮点运算,返回结果将精确到 15 位,足以满足大多数的任务。当对超出 int64 或者 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 类型的临时变量来存放中间结果,所以这样的运算可通过内存链式存储。
大的整型数字是通过 `big.NewInt(n)` 来构造的,其中 n 位 int64 类型整数。而大有理数是用过 `big.NewRat(N,D)` 方法构造。N分子和 D分母都是 int64 型整数。因为 Go 语言不支持运算符重载,所以所有大数字类型都有像是 `Add()``Mul()` 这样的方法。它们作用于作为 receiver 的整数和有理数,大多数情况下它们修改 receiver 并以 receiver 作为返回结果。因为没有必要创建 `big.Int` 类型的临时变量来存放中间结果,所以这样的运算可通过内存链式存储。
示例 9.2 [big.go](examples/chapter_9/big.go)
```go
package main
import (
``` go
// big.go
package main
"math""
import (
"fmt"
"math"
"math/big"
)
func main() {
// Here are some calculations with bigInts:
im := big.NewInt(math.MaxInt64)
in := im
io := big.NewInt(1956)
ip := big.NewInt(1)
ip.Mul(im, in).Add(ip, im).Div(ip, io)
fmt.Printf("Big Int: %v\n", ip)
// Here are some calculations with bigInts:
rm := big.NewRat(math.MaxInt64, 1956)
rn := big.NewRat(-1956, math.MaxInt64)
ro := big.NewRat(19, 56)
rp := big.NewRat(1111, 2222)
rq := big.NewRat(1, 1)
rq.Mul(rm, rn).Add(rq, ro).Mul(rq, rp)
fmt.Printf("Big Rat: %v\n", rq)
}
/* Output:
Big Int: 43492122561469640008497075573153004
Big Rat: -37/112
*/
```
输出结果:
Big Int: 43492122561469640008497075573153004
Big Rat: -37/112
## 链接

View File

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

View File

@@ -1,6 +1,6 @@
# 9.6 为自定义包使用 godoc
godoc工具第 3.6 节)在显示自定义包中的注释也有很好的效果:注释必须以 // 开始并无空行放在声明类型函数前。godoc 会为每个文件生成一系列的网页。
godoc工具第 3.6 节)在显示自定义包中的注释也有很好的效果:注释必须以 `//`` 开始并无空行放在声明类型函数前。godoc 会为每个文件生成一系列的网页。
例如:
@@ -9,7 +9,7 @@ godoc工具第 3.6 节)在显示自定义包中的注释也有很好的效
godoc -http =:6060 -paht="."
`.` 是指当前目录,-path 参数可以是 /path/to/my/package1 这样的形式指出 package1 在你源码中的位置或接受用冒号形式分隔的路径,无根目录的路径为相对于当前目录的相对路径)
`.` 是指当前目录,-path 参数可以是 `/path/to/my/package1` 这样的形式指出 package1 在你源码中的位置或接受用冒号形式分隔的路径,无根目录的路径为相对于当前目录的相对路径)
- 在浏览器打开地址http://localhost:6060
@@ -26,12 +26,67 @@ sort 包
```go
func Float64sAreSorted
type IntArray
func IntsAreSortedfunc IsSortedfunc Sort
func (IntArray) Len
func SortFloat64s
type IntArray
func IntsAreSortedfunc IsSortedfunc Sort
func (IntArray) Len
func SortFloat64s
func (IntArray) Less
func SortInts
func (IntArray) Swap
func SortStrings type Interface
func StringsAreSorted type StringArray type Float64Array
func (StringArray) Len
func (Float64Array) Len
func (StringArray) Less
func (Float64Array) Less
func (StringArray) Swap
func (Float64Array) Swap
// Other packages
import "doc_example"
```
func (IntArray) Less
使用通用的接口排序:
```
func Float64sAreSorted[Top]
func Float64sAreSorted(a []float64) bool
func IntsAreSorted[Top]
func IntsAreSorted(a []int) bool
func IsSorted[Top]
func IsSorted(data Interface) bool
Test if data is sorted
func Sort[Top]
func Sort(data Interface)
General sort function
func SortInts[Top]
func SortInts(a []int)
Convenience wrappers for common cases: type IntArray[Top]
Convenience types for common cases: IntArray type IntArray []int
```
如果你在一个团队中工作,并在源代码树被存储在网络硬盘上,就可以使用 godoc 给所有团队成员连续文档的支持。通过设置 `sync_minutes=n`,你甚至可以让它每 n 分钟自动更新您的文档!
## 链接
- [目录](directory.md)
- 上一节:[自定义包和可见性](09.5.md)
- 下一节:[使用 go install 安装自定义包](09.7.md)

View File

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

View File

@@ -1,13 +1,30 @@
# 9.8 自定义包的目录结构、go install 和 go test
为了示范,我们创建了一个名为 uc 的简单包,它含有一个 UpperCase 函数将字符串的所有字母转换为大写。当然这并不值得创建一个自己包,同样的功能已被包含在 "strings" 包里,但是同样的技术也可以应用在更复杂的包中。
为了示范,我们创建了一个名为 uc 的简单包,它含有一个 `UpperCase` 函数将字符串的所有字母转换为大写。当然这并不值得创建一个自己包,同样的功能已被包含在 `strings` 包里,但是同样的技术也可以应用在更复杂的包中。
## 9.8.1 自定义包的目录结构
下面的结构给了你一个好的示范(uc 代表通用包名, 名字为粗体的代表目录,斜体代表可执行文件):
/home/user/goprograms
ucmain.go (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](examples/chapter_9/uc.go)
@@ -53,21 +70,23 @@ func TestUC(t *testing.T) {
另外,使用 make,通过以下内容创建一个包的 Makefile(1) 在 src/uc 目录下:
ucTest{"abc", "ABC"},
```
include $GOROOT/src/Make.inc
ucTest{"Antwerp", "ANTWERP"},
}
TARG=uc
GOFILES=\
uc.go\
for _, ut := range ucTests {
include $(GOROOT)/scr/Make.pkg
```
在该目录下的命令行调用: gomake
ut.out)
这将创建一个 _obj 目录并将包编译生成的存档 uc.a 放在该目录下。
}
这个包可以通过 go test 测试。
```
创建一个 ud.a 的测试文件在目录下,输出为 PASS 时测试通过。
在第 13.8 节我们将给出另外一个测试例子并进行深入研究。
@@ -92,32 +111,38 @@ func main() {
然后在这个目录下输入 `go install`
另外复制 uc.a 到 uc 目录并创建一个 Makefile(2) 并写入文本包含在 `$GOROOT/src/Make.inc`
```
TARG=ucmain
GOFILES=\
ucmain.go\
include $GOROOT/src/Make.cmd
```
"fmt"
执行 gomake 编译 `ucmain.go` 到 ucmain 目录
)
运行 `./ucmain` 显示: `USING package uc!`
## 9.8.2 本地安装包
本地包在用户目录下,使用给出的目录结构,以下命令用来从源码安装本地包:
```
然后在这个目录下输入 `go install`
go install /home/user/goprograms/src/uc # 编译安装uc
cd /home/user/goprograms/uc
go install ./uc # 编译安装uc和之前的指令一样
cd ..
go install . # 编译安装ucmain
安装到 `$GOPATH` 下:
GOFILES=\
如果我们想安装的包在系统上的其他 Go 程序中被使用,它一定要安装到 `$GOPATH` 下。
这样做,在 .profile 和 .bashrc 中设置 `export GOPATH=/home/user/goprograms`
然后执行 go install uc 将会复制包存档到 `$GOPATH/pkg/LINUX_AMD64/uc`
现在uc 包可以通过 `import "uc"` 在任何 Go 程序中被引用。
## 9.8.3 依赖系统的代码
@@ -125,13 +150,17 @@ uc 包可以通过 "import uc" 在任何 Go 程序中被引用。
你有一个很好的理由去写平台平台特定的代码,例如汇编语言。这种情况下,按照下面的约定是合理的:
本地包在用户目录下,使用给出的目录结构,以下命令用来从源码安装本地包:
go install /home/user/goprograms/src/uc # 编译安装uc
prog1.go
prog1_linux.go
prog1_darwin.go
prog1_windows.go
prog1.go 定义了不同操作系统通用的接口,并将系统特定的代码写到 prog1_os.go 中。
对于 Go 工具你可以指定 `prog1_$GOOS.go``prog1_$GOARCH.go`
或在平台 Makefile 中:`prog1_$(GOOS).go\``prog1_$(GOARCH).go\`
## 链接
- [目录](directory.md)
- 上一节:[使用 go install 安装自定义包](09.7.md)
安装到 $GOROOT 下:
- 下一节:[通过 Git 打包和安装](09.9.md)

View File

@@ -1,62 +1,55 @@
# 9.9 通过 git 打包和安装
# 9.9 通过 Git 打包和安装
## 9.9.1 安装到 GitHub
以上的方式对于本地包来说是可以的,但是我们如何打包代码到开发者圈子呢?那么我们需要一个云端的源码的版本控制系统,比如著名的 git。
以上的方式对于本地包来说是可以的,但是我们如何打包代码到开发者圈子呢?那么我们需要一个云端的源码的版本控制系统,比如著名的 Git。
在 Linux 和 OS X 的机器上 Git 是默认安装的,在 Windows 上你必须先自行安装,参见 [GitHub 帮助页面](http://help.github.com/win-set-up-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 add README uc.go uc_test.go Makefile`
- 标记为第一个版本:`git commit -m "initial rivision"`
现在必须登录 [GitHub 网站](https://github.com)。
如果您还没有账号,可以去注册一个开源项目的免费帐号。输入正确的帐号密码和有效的邮箱地址并进一步创建用户。然后你将获得一个 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 安装
如果有人想安装您的远端项目到本地机器打开终端并执行NNNN 是你在 GitHub 上的用户名):`go get 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将通过 `$GOPATH` 下的本地版本进行工作。
网站和版本控制系统的其他的选择(括号中为网站所使用的版本控制系统)
- BitBucket(hg/Git)
- GitHub(Git)
- Google Code(hg/Git/svn)
- Launchpad(bzr)
* bitbucket(hg)
* github(git)
* googlecode(hg/git/svn)
* launchpad(bzr)
版本控制系统可以选择你熟悉的或者本地使用的代码版本控制。Go 核心代码的仓库是使用 Mercurial(hg) 来控制的所以它是一个最可能保证你可以得到开发者项目中最好的软件。Git 也很出名,同样也适用。如果你从未使用过的版本控制,这些网站有一些很好的帮助并且你可以通过在谷歌搜索 "{name} tutorial",其中 name 为你想要使用的版本控制系统得到许多很好的教程。
## 链接

View File

@@ -5,3 +5,8 @@ Go通过类型别名(alias types)或结构体的形式支持用户自定义类
组成结构体类型的那些数据称为 *字段(fields)*。一个字段有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。
结构体的概念在软件工程上旧的术语叫ADT(抽象数据类型Abstract Data Type),在一些老的编程语言中叫*记录(Record)*比如Cobol在C家族的编程语言中它也存在并且名字也是*struct*在面向对象的编程语言中跟一个无方法的轻量级类一样。不过因为Go语言中没有类的概念因此在Go中结构体有着更为重要的地位。
## 链接
- [目录](directory.md)
- 上一章:[在 Go 程序中使用外部库](09.11.md)
- 下一节:[结构体定义](10.1.md)

0
eBook/11.0.md Normal file
View File

0
eBook/11.12.md Normal file
View File

0
eBook/11.3.md Normal file
View File

0
eBook/11.8.md Normal file
View File

0
eBook/11.9.md Normal file
View File

0
eBook/12.3.md Normal file
View File

0
eBook/12.8.md Normal file
View File

0
eBook/13.0.md Normal file
View File

0
eBook/15.4.md Normal file
View File

View File

@@ -47,7 +47,7 @@
- 5.6 [标签与 goto](05.6.md)
- 第6章[函数function](06.0.md)
- 6.1 [介绍](06.1.md)
- 6.2 [参数与返回值](06.2.md)
- 6.2 [函数参数与返回值](06.2.md)
- 6.3 [传递变长参数](06.3.md)
- 6.4 [defer 和追踪](06.4.md)
- 6.5 [内置函数](06.5.md)
@@ -62,10 +62,10 @@
- 7.1 [声明和初始化](07.1.md)
- 7.2 [切片](07.2.md)
- 7.3 [For-range 结构](07.3.md)
- 7.4 [切片重组](07.4.md)
- 7.4 [切片重组reslice](07.4.md)
- 7.5 [切片的复制与追加](07.5.md)
- 7.6 [字符串、数组和切片的应用](07.6.md)
- 第8章[Maps](08.0.md)
- 第8章[Map](08.0.md)
- 8.1 [声明、初始化和 make](08.1.md)
- 8.2 [测试键值对是否存在及删除元素](08.2.md)
- 8.3 [for-range 的配套用法](08.3.md)
@@ -81,7 +81,7 @@
- 9.6 [为自定义包使用 godoc](09.6.md)
- 9.7 [使用 go install 安装自定义包](09.7.md)
- 9.8 [自定义包的目录结构、go install 和 go test](09.8.md)
- 9.9 [通过 git 打包和安装](09.9.md)
- 9.9 [通过 Git 打包和安装](09.9.md)
- 9.10 [Go 的外部包和项目](09.10.md)
- 9.11 [在 Go 程序中使用外部库](09.11.md)
- 第10章[结构struct与方法method](10.0.md)

View File

@@ -0,0 +1,21 @@
package main
import "fmt"
func main() {
// Version A:
items := make([]map[int]int, 5)
for i := range items {
items[i] = make(map[int]int, 1)
items[i][1] = 2
}
fmt.Printf("Version A: Value of items: %v\n", items)
// Version B: NOT GOOD!
items2 := make([]map[int]int, 5)
for _, item := range items2 {
item = make(map[int]int, 1) // item is only a copy of the slice element.
item[1] = 2 // This 'item' will be lost on the next iteration.
}
fmt.Printf("Version B: Value of items: %v\n", items2)
}

View File

@@ -0,0 +1,23 @@
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)
}
}
}

View File

@@ -0,0 +1,7 @@
package uc
import "strings"
func UpperCase(str string) string {
return strings.ToUpper(str)
}

View File

@@ -0,0 +1,11 @@
package main
import (
"./uc/uc"
"fmt"
)
func main() {
str1 := "USING package uc"
fmt.Println(uc.UpperCase(str1))
}

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 146 KiB

View File

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB