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