Update 04.2.md

This commit is contained in:
Mosesofmason
2015-10-12 10:32:56 +08:00
parent 0c77ecaa06
commit e6a534f885

View File

@@ -1,7 +1,7 @@
# 4.2 Go 程序的基本结构和要素 # 4.2 Go 程序的基本结构和要素
示例 4.1 [hello_world.go](examples/chapter_4/hello_world.go) 示例 4.1 [hello_world.go](examples/chapter_4/hello_world.go)
```go ```go
package main package main
@@ -9,7 +9,7 @@ import "fmt"
func main() { func main() {
fmt.Println("hello, world") fmt.Println("hello, world")
} }
``` ```
## 4.2.1 包的概念、导入与可见性 ## 4.2.1 包的概念、导入与可见性
@@ -50,31 +50,31 @@ Go 中的包模型采用了显式依赖关系的机制来达到快速编译的
`import "fmt"` 告诉 Go 编译器这个程序需要使用 `fmt` 包(的函数,或其他元素),`fmt` 包实现了格式化 IO输入/输出)的函数。包名被封闭在半角双引号 `""` 中。如果你打算从已编译的包中导入并加载公开声明的方法,不需要插入已编译包的源代码。 `import "fmt"` 告诉 Go 编译器这个程序需要使用 `fmt` 包(的函数,或其他元素),`fmt` 包实现了格式化 IO输入/输出)的函数。包名被封闭在半角双引号 `""` 中。如果你打算从已编译的包中导入并加载公开声明的方法,不需要插入已编译包的源代码。
如果需要多个包,它们可以被分别导入: 如果需要多个包,它们可以被分别导入:
```go ```go
import "fmt" import "fmt"
import "os" import "os"
``` ```
或: 或:
```go ```go
import "fmt"; import "os" import "fmt"; import "os"
``` ```
但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 const、var 和 type 的声明或定义): 但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 const、var 和 type 的声明或定义):
```go ```go
import ( import (
"fmt" "fmt"
"os" "os"
) )
``` ```
它甚至还可以更短的形式,但使用 gofmt 后将会被强制换行: 它甚至还可以更短的形式,但使用 gofmt 后将会被强制换行:
```go ```go
import ("fmt"; "os") import ("fmt"; "os")
``` ```
当你导入多个包时,导入的顺序会按照字母排序。 当你导入多个包时,导入的顺序会按照字母排序。
@@ -102,19 +102,19 @@ import ("fmt"; "os")
你可以通过使用包的别名来解决包名之间的名称冲突,或者说根据你的个人喜好对包名进行重新设置,如:`import fm "fmt"`。下面的代码展示了如何使用包的别名: 你可以通过使用包的别名来解决包名之间的名称冲突,或者说根据你的个人喜好对包名进行重新设置,如:`import fm "fmt"`。下面的代码展示了如何使用包的别名:
示例 4.2 [alias.go](examples/chapter_4/alias.go) 示例 4.2 [alias.go](examples/chapter_4/alias.go)
```go ```go
package main package main
import fm "fmt" // alias3 import fm "fmt" // alias3
func main() { func main() {
fm.Println("hello, world") fm.Println("hello, world")
} }
``` ```
**注意事项** **注意事项**
如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如 `imported and not used: os`,这正是遵循了 Go 的格言:“没有不必要的代码!“。 如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如 `imported and not used: os`,这正是遵循了 Go 的格言:“没有不必要的代码!“。
**包的分级声明和初始化** **包的分级声明和初始化**
@@ -123,10 +123,10 @@ func main() {
## 4.2.2 函数 ## 4.2.2 函数
这是定义一个函数最简单的格式: 这是定义一个函数最简单的格式:
```go ```go
func functionName() func functionName()
``` ```
你可以在括号 `()` 中写入 0 个或多个函数的参数(使用逗号 `,` 分隔),每个参数的名称后面必须紧跟着该参数的类型。 你可以在括号 `()` 中写入 0 个或多个函数的参数(使用逗号 `,` 分隔),每个参数的名称后面必须紧跟着该参数的类型。
@@ -139,28 +139,28 @@ main 函数是每一个可执行程序所必须包含的,一般来说都是在
函数里的代码(函数体)使用大括号 `{}` 括起来。 函数里的代码(函数体)使用大括号 `{}` 括起来。
左大括号 `{` 必须与方法的声明放在同一行,这是编译器的强制规定,否则你在使用 gofmt 时就会出现错误提示: 左大括号 `{` 必须与方法的声明放在同一行,这是编译器的强制规定,否则你在使用 gofmt 时就会出现错误提示:
`build-error: syntax error: unexpected semicolon or newline before {` `build-error: syntax error: unexpected semicolon or newline before {`
(这是因为编译器会产生 `func main() ;` 这样的结果,很明显这错误的) (这是因为编译器会产生 `func main() ;` 这样的结果,很明显这错误的)
**Go 语言虽然看起来不使用分号作为语句的结束,但实际上这一过程是由编译器自动完成,因此才会引发像上面这样的错误** **Go 语言虽然看起来不使用分号作为语句的结束,但实际上这一过程是由编译器自动完成,因此才会引发像上面这样的错误**
右大括号 `}` 需要被放在紧接着函数体的下一行。如果你的函数非常简短,你也可以将它们放在同一行: 右大括号 `}` 需要被放在紧接着函数体的下一行。如果你的函数非常简短,你也可以将它们放在同一行:
```go ```go
func Sum(a, b int) int { return a + b } func Sum(a, b int) int { return a + b }
``` ```
对于大括号 `{}` 的使用规则在任何时候都是相同的if 语句等)。 对于大括号 `{}` 的使用规则在任何时候都是相同的if 语句等)。
因此符合规范的函数一般写成如下的形式: 因此符合规范的函数一般写成如下的形式:
```go ```go
func functionName(parameter_list) (return_value_list) { func functionName(parameter_list) (return_value_list) {
} }
``` ```
其中: 其中:
@@ -170,10 +170,10 @@ func functionName(parameter_list) (return_value_list) {
只有当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。 只有当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。
下面这一行调用了 `fmt` 包中的 `Println` 函数,可以将字符串输出到控制台,并在最后自动增加换行字符 `\n` 下面这一行调用了 `fmt` 包中的 `Println` 函数,可以将字符串输出到控制台,并在最后自动增加换行字符 `\n`
```go ```go
fmt.Println"hello, workd" fmt.Println"hello, world"
``` ```
使用 `fmt.Print("hello, world\n")` 可以得到相同的结果。 使用 `fmt.Print("hello, world\n")` 可以得到相同的结果。
@@ -191,15 +191,15 @@ fmt.Println"hello, workd"
## 4.2.3 注释 ## 4.2.3 注释
示例 4.2 [hello_world2.go](examples/chapter_4/hello_world2.go) 示例 4.2 [hello_world2.go](examples/chapter_4/hello_world2.go)
```go
package main
import "fmt" // Package implementing formatted I/O. ```go
package main
import "fmt" // Package implementing formatted I/O.
func main() { func main() {
fmt.Printf("Καλημέρα κόσμε; or こんにちは 世界\n") fmt.Printf("Καλημέρα κόσμε; or こんにちは 世界\n")
} }
``` ```
上面这个例子通过打印 `Καλημέρα κόσμε; or こんにちは 世界` 展示了如何在 Go 中使用国际化字符,以及如何使用注释。 上面这个例子通过打印 `Καλημέρα κόσμε; or こんにちは 世界` 展示了如何在 Go 中使用国际化字符,以及如何使用注释。
@@ -211,25 +211,25 @@ func main() {
每一个包应该有相关注释,在 package 语句之前的块注释将被默认认为是这个包的文档说明,其中应该提供一些相关信息并对整体功能做简要的介绍。一个包可以分散在多个文件中,但是只需要在其中一个进行注释说明即可。当开发人员需要了解包的一些情况时,自然会用 godoc 来显示包的文档说明,在首行的简要注释之后可以用成段的注释来进行更详细的说明,而不必拥挤在一起。另外,在多段注释之间应以空行分隔加以区分。 每一个包应该有相关注释,在 package 语句之前的块注释将被默认认为是这个包的文档说明,其中应该提供一些相关信息并对整体功能做简要的介绍。一个包可以分散在多个文件中,但是只需要在其中一个进行注释说明即可。当开发人员需要了解包的一些情况时,自然会用 godoc 来显示包的文档说明,在首行的简要注释之后可以用成段的注释来进行更详细的说明,而不必拥挤在一起。另外,在多段注释之间应以空行分隔加以区分。
示例: 示例:
```go ```go
// Package superman implements methods for saving the world. // Package superman implements methods for saving the world.
// //
// Experience has shown that a small number of procedures can prove // Experience has shown that a small number of procedures can prove
// helpful when attempting to save the world. // helpful when attempting to save the world.
package superman package superman
``` ```
几乎所有全局作用域的类型、常量、变量、函数和被导出的对象都应该有一个合理的注释。如果这种注释(称为文档注释)出现在函数前面,例如函数 Abcd则要以 `"Abcd..."` 作为开头。 几乎所有全局作用域的类型、常量、变量、函数和被导出的对象都应该有一个合理的注释。如果这种注释(称为文档注释)出现在函数前面,例如函数 Abcd则要以 `"Abcd..."` 作为开头。
示例: 示例:
```go ```go
// enterOrbit causes Superman to fly into low Earth orbit, a position // enterOrbit causes Superman to fly into low Earth orbit, a position
// that presents several possibilities for planet salvation. // that presents several possibilities for planet salvation.
func enterOrbit() error { func enterOrbit() error {
... ...
} }
``` ```
godoc 工具(第 3.6 节)会收集这些注释并产生一个技术文档。 godoc 工具(第 3.6 节)会收集这些注释并产生一个技术文档。
@@ -243,57 +243,57 @@ godoc 工具(第 3.6 节)会收集这些注释并产生一个技术文档。
结构化的类型没有真正的值,它使用 nil 作为默认值(在 Objective-C 中是 nil在 Java 中是 null在 C 和 C++ 中是NULL或 0。值得注意的是Go 语言中不存在类型继承。 结构化的类型没有真正的值,它使用 nil 作为默认值(在 Objective-C 中是 nil在 Java 中是 null在 C 和 C++ 中是NULL或 0。值得注意的是Go 语言中不存在类型继承。
函数也可以是一个确定的类型,就是以函数作为返回类型。这种类型的声明要写在函数名和可选的参数列表之后,例如: 函数也可以是一个确定的类型,就是以函数作为返回类型。这种类型的声明要写在函数名和可选的参数列表之后,例如:
```go ```go
func FunctionName (a typea, b typeb) typeFunc func FunctionName (a typea, b typeb) typeFunc
``` ```
你可以在函数体中的某处返回使用类型为 typeFunc 的变量 var 你可以在函数体中的某处返回使用类型为 typeFunc 的变量 var
```go ```go
return var return var
``` ```
一个函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 `()` 将它们括起来,如: 一个函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 `()` 将它们括起来,如:
```go ```go
func FunctionName (a typea, b typeb) (t1 type1, t2 type2) func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
``` ```
示例: 函数 Atoi (第 4.7 节)`func Atoi(s string) (i int, err error)` 示例: 函数 Atoi (第 4.7 节)`func Atoi(s string) (i int, err error)`
返回的形式: 返回的形式:
```go ```go
return var1, var2 return var1, var2
``` ```
这种多返回值一般用于判断某个函数是否执行成功true/false或与其它返回值一同返回错误消息详见之后的并行赋值 这种多返回值一般用于判断某个函数是否执行成功true/false或与其它返回值一同返回错误消息详见之后的并行赋值
使用 type 关键字可以定义你自己的类型,你可能想要定义一个结构体(第 10 章),但是也可以定义一个已经存在的类型的别名,如: 使用 type 关键字可以定义你自己的类型,你可能想要定义一个结构体(第 10 章),但是也可以定义一个已经存在的类型的别名,如:
```go ```go
type IZ int type IZ int
``` ```
**这里并不是真正意义上的别名,因为使用这种方法定义之后的类型可以拥有更多的特性,且在类型转换时必须显式转换。** **这里并不是真正意义上的别名,因为使用这种方法定义之后的类型可以拥有更多的特性,且在类型转换时必须显式转换。**
然后我们可以使用下面的方式声明变量: 然后我们可以使用下面的方式声明变量:
```go ```go
var a IZ = 5 var a IZ = 5
``` ```
这里我们可以看到 int 是变量 a 的底层类型,这也使得它们之间存在相互转换的可能(第 4.2.6 节)。 这里我们可以看到 int 是变量 a 的底层类型,这也使得它们之间存在相互转换的可能(第 4.2.6 节)。
如果你有多个类型需要定义,可以使用因式分解关键字的方式,例如: 如果你有多个类型需要定义,可以使用因式分解关键字的方式,例如:
```go ```go
type ( type (
IZ int IZ int
FZ float64 FZ float64
STR string STR string
) )
``` ```
每个值都必须在经过编译后属于某个类型(编译器必须能够推断出所有值的类型),因为 Go 语言是一种静态类型语言。 每个值都必须在经过编译后属于某个类型(编译器必须能够推断出所有值的类型),因为 Go 语言是一种静态类型语言。
@@ -310,37 +310,37 @@ type (
- 然后定义其余的函数,首先是类型的方法,接着是按照 main 函数中先后调用的顺序来定义相关函数,如果有很多函数,则可以按照字母顺序来进行排序。 - 然后定义其余的函数,首先是类型的方法,接着是按照 main 函数中先后调用的顺序来定义相关函数,如果有很多函数,则可以按照字母顺序来进行排序。
示例 4.4 [gotemplate.go](examples/chapter_4/gotemplate.go) 示例 4.4 [gotemplate.go](examples/chapter_4/gotemplate.go)
```go ```go
package main package main
import ( import (
"fmt" "fmt"
) )
const c = "C" const c = "C"
var v int = 5 var v int = 5
type T struct{} type T struct{}
func init() { // initialization of package func init() { // initialization of package
} }
func main() { func main() {
var a int var a int
Func1() Func1()
// ... // ...
fmt.Println(a) fmt.Println(a)
} }
func (t T) Method1() { func (t T) Method1() {
//... //...
} }
func Func1() { // exported function Func1 func Func1() { // exported function Func1
//... //...
} }
``` ```
Go 程序的执行(程序启动)顺序如下: Go 程序的执行(程序启动)顺序如下:
@@ -353,29 +353,29 @@ Go 程序的执行(程序启动)顺序如下:
## 4.2.6 类型转换 ## 4.2.6 类型转换
在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于 Go 语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数): 在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于 Go 语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):
```go ```go
valueOfTypeB = typeB(valueOfTypeA) valueOfTypeB = typeB(valueOfTypeA)
``` ```
**类型 B 的值 = 类型 B(类型 A 的值)** **类型 B 的值 = 类型 B(类型 A 的值)**
示例: 示例:
```go ```go
a := 5.0 a := 5.0
b := int(a) b := int(a)
``` ```
但这只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(例如将 int16 转换为 int32。当从一个取值范围较大的转换到取值范围较小的类型时例如将 int32 转换为 int16 或将 float32 转换为 int会发生精度丢失截断的情况。当编译器捕捉到非法的类型转换时会引发编译时错误否则将引发运行时错误。 但这只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(例如将 int16 转换为 int32。当从一个取值范围较大的转换到取值范围较小的类型时例如将 int32 转换为 int16 或将 float32 转换为 int会发生精度丢失截断的情况。当编译器捕捉到非法的类型转换时会引发编译时错误否则将引发运行时错误。
具有相同底层类型的变量之间可以相互转换: 具有相同底层类型的变量之间可以相互转换:
```go ```go
var a IZ = 5 var a IZ = 5
c := int(a) c := int(a)
d := IZ(c) d := IZ(c)
``` ```
## 4.2.7 Go 命名规范 ## 4.2.7 Go 命名规范