Files
the-way-to-go_ZH_CN/eBook/04.2.md
2013-04-15 19:59:13 +08:00

311 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#4.2 Go 程序的基本结构和要素
***译者注:由于 Go 语言版本更替,本节中的相关内容经原作者同意将被直接替换而不作另外说明***
Example 4.1 [hello_world.go](examples/chapter_4/hello_world.go)
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
##4.2.1 包,导入和可见性
包是结构化代码的一种方式:一个程序可能被其他包使用,被构建为一个“包”(通常简称为 pkg )。
每个 go 文件属于(且只属于)一个包(想其他语言的类库或命名空间)。
很多不同的 .go 文件可以属于同一个包,所以文件名和包名一般是不一样的。
在代码文件里,包必须在第一行标明,如:`package main``package main`表示一个独立可执行的程序。每个 Go 应用程序都包含一个包,包名为`main`
一个应用程序可以包含不同的包而且即使你只使用main包也不必把所有的代码写在一个文件里你可以用一些较小的文件每个文件使用`package main`开头。如果编译一个包名不是`package main`的源文件,如 pack1则生成的对象文件保存在 pack1.a 中;包名应使用小写字母。
_标准库_
Go 安装之后,包含一些可以直接使用的包,即标准库。
在Windows上标准库的位置在 Go 根目录下的子目录 pkg\windows_386中。在Linux中标准库在 Go 根目录下的子目录 pkg\linux_amd64中如果是安装的是32位则在linux_386目录中。一般情况下标准包存放在 $GOROOT/pkg/$GOOS_$GOARCH/ 目录下。
Go 的标准库包含了大量的包(如 fmt, os但是你也可以创建自己的包参见第 8 章)。
构建一个程序,包和包内的文件,必须以正确的顺序进行编译。包的依赖关系决定了构建的顺序。
在一个包中,源文件都必须被编译在一起。包为编译的单位,按惯例每个目录为一个包。
_如果对一个包进行更改或重新编译所有的引用这个包的客户端程序必须也重新编译。_
为了更快的构建包原型使用显示依赖Go 编译器会从 .o 的对象文件(只需要这个文件)中提取传递依赖类型的信息。
如果 A.go 依赖 B.go 依赖 C.go
- 编译 C.go, B.go, 然后是 A.go.
- 为了编译 A.go, 编译器读取 B.o 而不是 C.o.
对于规模较大的程序,这样可以大大的加快编译速度。
_每一段代码只会被编译一次_
一个 Go 程序是通过 import 关键字将一组包链接在一起。
___import “fmt”___ 告诉 Go 编译器,这个程序需要使用 fmt 包的函数或其他元素fmt 包实现了格式化 IO输入/输出)的函数。包名封闭在 `""` 中。从已编译的包中导入并加载公开声明方法,不用插入已编译包的源代码。
如果需要多个包,他们可以使用分隔声明,一个个的导入:
import “fmt”
import “os”
或:
import “fmt”; import “os”
但是更短并且更优雅的方法称为_factoring the keyword_同样适用于constvar 和 type也是有的
import (
"fmt"
"os"
)
(它还可以更短:`import ("fmt", "os")`但是对于分发版本gofmt后会强制换行
当需要导入多个包时,它会按字母顺序排列包名。
包名如果不是以`.``/`开头,如"fmt"或者"container/list"Go 会在全局的 Go 树进行查找。包名如果以`./`开头Go 会在实际的目录中搜索;以`/`开始即使在Windows中,将会在系统(绝对)路径中搜索。
包已经包含了其他的所有引用的代码对象。
除了`_`代码对象的标识符必须在包中唯一这样就没有_名称冲突_了。但是相同的标识符可以用在不同的包中_包名保证_了它们的不同。
包暴露自己的代码对象到包外是根据以下的规则,
这是由编译器执行:
___可见行规则___
当标识符(包括常量,变量,类型,函数名,结构体字段,...)以一个大写字母开头,如 Group1那么对象的这个方法在包外是可见的对客户端程序可见需要导入相应的包这被叫做被导出像面向对象语言的 public )。标识符如果以小写字母开头,对包外是不可见的,但是他们在整个包内是可以见并可用的(像面向对象语言的 private )。
大写字母可以来自整个Unicode编码范围像希腊文不仅ASCII中的大写字母
因此,导入一个包(只)能够使该包中的导出对象被访问。
假设在包 pack1 中我们有一个 thing (变量或函数)叫做 Thing以 T 开头,所以它是导出名),那么在当前包中导入 pack1 包Thing 可以被调用使用点标记像面向对象语言pack1.Thing pack1 是必需的!)
所以包也作为命名空间,可以帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于他们的包名,如:
___pack1.Thing 和 pack2.Thing___
一个包可以,也是有用的(为了更简短,名称冲突,...),也可以给予另一个名称(别名),如:`import fm "fmt"`。下面的代码使用了别名:
Example 4.2 [alias.go](examples/chapter_4/alias.go)
package main
import fm "fmt" // alias3
func main() {
fm.Println("hello, world")
}
_提醒_:导入一个包,但是没有在代码中使用会导致一个构建错误(例如:导入但补使用的: os 包)。这遵循 Go 的格言:“没有不必要的代码!“
包级声明和初始化:
import 语句之后可以声明 0 个或多个常量const变量var和类型type这些都是全局的在包范围内并且代码中所有的函数可以调用如 c 和 v 在下面的 gotemplate.go 中并且它们后面跟着一个或多个函数func
##4.2.2 函数
最简单的函数定义使用这样的格式func functionName()
小括号`()`中,没有,或者有一个或多个参数(使用`,`分离)可以作为该函数的输入。每个参数的变量名后,一定要是该参数的类型。
一个主函数作为程序启动所必需的通常是第一个函数否则会产生构建未定义错误main.main 发生。主函数没有参数和返回类型(与 C 语言家族相反),否则,将得到的构建错误:
主函数必须没有参数和返回值
当程序执行,初始化结束后,第一个调用(程序的入口点)的函数是 main.main()(如 C 语言)。程序在 main.main 结束后立即并成功退出。
方法里的代码(方法体)使用大括号:`{ }`包含。
第一个`{`必须与方法声明在同一行:这被编译器强制判断,否则 gofmt 会有build-error: syntax error: unexpected semicolon or newline before {)的错误提示。
(这是应为编译器执行 `func main() ;` 这是一个错误)
最后的`}`将被定位在函数代码下面;小功能是允许的一切都写在一行,例如:`func Sum(a, b int) int { return a + b }`
无论何时,使用`{ }`的规则都是相同的(如: if 语句等)
因此示意的通用函数应该像这样:
func functionName(parameter_list) (return_value_list) {
}
parameter_list 的形式为 (param1 type1, param2 type2, …)
return_value_list 的形式为 (ret1 type1, ret2 type2, …)
当函数需要被包外使用函数的名称必须以大写字母开头他们遵循Pascal命名规则 PascalCasing否则遵循砣峰命名规则 camelCasing首单词字母小写每一个新词的名称以大写字母开头。
行:`fmt.Println"hello, workd"`调用 fmt 包的 Println 函数,将输出字符串到控制台,接着换行字符`\n`
`fmt.Print("hello, world\n")` 可以得到相同的结果。
Print 和 Println 方法也支持变量fmt.Println(arr);他们使用默认的输出格式输出 arr 变量。
打印一个字符串或变量甚至可以使用预定义的方法 print 和 printlnprint("ABC") 或 println("ABC")或者(带一个变量 iprintln(i)
这些仅仅是用于在调试阶段;部署程序时会将它们替换 fmt 的相关函数。
当遇到结束符`}`或返回语句,程序根据方法的调用继续执行。
程序正常退出代码为 0 (程序以代码 0 退出);程序以异常中止退出的使用另外一个整数代码如 1 ;这可以用来测试成功执行一个程序。
##4.2.3 注释
Example 4.2 [hello_world2.go](examples/chapter_4/hello_world2.go)
package main
import "fmt" // Package implementing formatted I/O.
func main() {
fmt.Printf("Καλημέρα κόσμε; or こんにちは 世界\n")
}
这说明了国际化字符 Καλημέρακόσμε; or こんにちは世界,可以打印,也可以用作注释使用。
注释肯定不会被编译,但是他们被 godoc使用参见 § 3.6
行注释以`//`开始,可以在一行的开头或其他地方;这是最经常被使用的。多行注释也叫叫块注释以`/*`开始,以`*/`结束,不允许嵌套;这是用来包文档和注释代码。
每个包应该有包注释,注释块紧接 package 语句,介绍这个包并且提供相关信息,作整体功能介绍。一个包可以分散在多个文件中,但是注释只需要写在其中一个。当一个开发人员需要包的信息,使用 godoc这些注释将被显示。其后的句子和段落可以给出更多的细节。注释句子应适当的空行。
例子:
// 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...”开始。
例子:
// enterOrbit causes Superman to fly into low Earth orbit, a position
// that presents several possibilities for planet salvation.
func enterOrbit() error {
...
}
godoc-tool (参见 §3.6) 收集这些注释并产生一个技术文档。
##4.2.4 类型
变量(如常量)保持数据,数据可以是不同数据类型或者短整型。使用 var 声明的变量自动初始化为它的零值。类型定义了一系列值及操作,可以填充这些值。
类型可以是基本的(或原生的),如 intfloatboolstring或结构的或复合的如 structarrayslicemapchannel和insterface他们只描述了类型的行为。
结构化的类型没有真正的值时,它的值是 nil这是这些类型的默认值在 Objective-C 中是 nil在 Java 中是 null在 C 和 C++ 中是NULL或 0 。Go 中没有类型继承。
函数也可以是一个确定的类型,就是使用函数返回一个类型。返回类型写在函数名称和操作参数列表后面,如:
func FunctionName (a typea, b typeb) typeFunc
返回 typeFunc 类型的变量 var 可以在函数的某处声明:
return var
函数可以返回多个变量,并且返回类型使用逗号分隔和小括号`( )`包围func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
列子: 函数 Atoi (参见 § 4.7) func Atoi(s string) (i int, err error)
返回的形式为: return var1, var2
这是经常当成功(真/假)执行一个函数或错误信息与返回值一起返回时使用(见下面的多分配)。
使用 type 关键字定义你自己的类型。然后你可能像定义一个结构体(参见 第10章),但是也可能定义一个存在的类型的别名,如:
type IZ int
然后我们可以定义变量如var a IZ = 5
我们说 a 是 int 的基本类型,这使转型成为可能(参见 § 4.2.6)。
如果你有多个类型需要定义,可以使用多关键字形式,如:
type (
IZ int
FZ float
STR string
)
每个值经过编译后必须有类型(编译器必须能够推断出所有值的类型):
Go 语言是一种静态类型语言.
##4.2.5 Go 编程的一般结构
下面的程序可以编译,但并没有什么用处,但展示 Go 程序的首选结构。这种结构不是必须的编译器不关心main()或变量声明在最后,但一个标准的结构使代码从上到下有更好的可读性。
所有结构将在这接下来的章节进一步解释说,但总体思路是:
- import 之后: 定义常量,变量和类型
- 如果需要,然后是 init() 函数: 这是一个特殊的功能,每一个包可以包含并首先执行。
- 然后是 main() 函数(只在 package main 中)
- 然后是剩下的函数,首先是类型的方法,或是 main() 函数中先后顺序调用的函数;或方法和功能,按字母顺序的方法和函数排序很高的。
Example 4.4 [gotemplate.go](examples/chapter_4/gotemplate.go)
package main
import (
“fmt”
)
const c = “C”
var v int = 5
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 程序的执行(程序启动)顺序如下:
1在 package main 中的包全部导入,按标明的顺序:
2如果导入包1被这个包调用递归但是一个确定的包只会被导入一次
3然后每一个包以相反的顺序中所有的常量和变量都被评估如果它包含这个函数将执行 init() 方法。
4最后在 package main 中也一样,然后 main() 函数开始执行。
##4.2.6 转型
如果必要和可能的值可以转型(转换, 包裹成另一种类型的值。Go 语言没有隐式转换(自动),它必须明确说明,使用一个类似函数调用的语法(这里的类型可以看作是一种函数):
valueOfTypeB = typeB(valueOfTypeA)
类型B的值 = 类型B(类型A的值)
例子:
a := 5.0
b := int(a)
但这只能在定义正确的情况下成功例如从一个窄型转换到一个宽型例如int16 到 int32。当从一个宽型转换到窄型例如int32 到 int16 或 float32 到 int值丢失截断会发生。当无法转型并且编译器检测到了这一点会给出一个编译错误否则发生运行时错误。
变量是相同的基本类型可以相互转型:
var a IZ = 5
c := int(a)
d := IZ(c)
##4.2.7 关于 Go 语言命名
干净,可读的代码和简洁性是 Go 开发的主要目标。gofmt 命令强化了代码风格。在 Go 语言中命名应该是简短的,简洁的,令人回味的。长名称命名使用大小写混合和下划线,像在 Java 或 Python 代码中那样,往往阻碍了可读性。名称不应包含包名:包名已经足够说明了。一个方法或函数返回的对象,其中函数被作为一个名词的名字命名,不应使用 Get...。若要更改一个对象请使用“SetName”。如果有必要Go 语言使用大小写混合 MixedCaps 或 mixedCaps而不是下划线连接多个名称。
##链接
- [目录](directory.md)
- 上一部分:[文件名、关键字与标识符](04.1.md)
- 下一节:[常量](04.3.md)