mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-11-13 09:16:10 +08:00
4.2.2.md
This commit is contained in:
123
eBook/04.2.md
123
eBook/04.2.md
@@ -11,47 +11,44 @@ Example 4.1 [hello_world.go](examples/chapter_4/hello_world.go)
|
||||
fmt.Println("hello, world")
|
||||
}
|
||||
|
||||
##4.2.1 包,导入和可见性
|
||||
##4.2.1 包的概念、导入与可见性
|
||||
|
||||
包是结构化代码的一种方式:一个程序可能被其他包使用,被构建为一个“包”(通常简称为 pkg )。
|
||||
包是结构化代码的一种方式:每个程序都由包(通常简称为 pkg)的概念组成,可以使用自身的包或者从其它包中导入内容。
|
||||
|
||||
每个 go 文件属于(且只属于)一个包(想其他语言的类库或命名空间)。
|
||||
如同其它一些编程语言中的类库或命名空间的概念,每个 Go 文件都属于且仅属于一个包。一个包可以由许多以 `.go` 为扩展名的源文件组成,因此文件名和包名一般来说都是不相同的。
|
||||
|
||||
很多不同的 .go 文件可以属于同一个包,所以文件名和包名一般是不一样的。
|
||||
你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:`package main`。`package main`表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 `main` 的包。
|
||||
|
||||
在代码文件里,包必须在第一行标明,如:`package main`。`package main`表示一个独立可执行的程序。每个 Go 应用程序都包含一个包,包名为`main`。
|
||||
一个应用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代码都写在一个巨大的文件里:你可以用一些较小的文件,并且在每个文件非注释的第一行都使用 `package main` 来指明这些文件都属于 main 包。如果你打算编译包名不是为 main 的源文件,如 `pack1`,编译后产生的对象文件将会是 `pack1.a` 而不是可执行程序。另外要注意的是,所有的包名都应该使用小写字母。
|
||||
|
||||
一个应用程序可以包含不同的包,而且即使你只使用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 安装之后,包含一些可以直接使用的包,即标准库。
|
||||
在Windows上,标准库的位置在 Go 根目录下的子目录 pkg\windows_386中。在Linux中,标准库在 Go 根目录下的子目录 pkg\linux_amd64中(如果是安装的是32位,则在linux_386目录中)。一般情况下,标准包存放在 $GOROOT/pkg/$GOOS_$GOARCH/ 目录下。
|
||||
Go 的标准库包含了大量的包(如:fmt,os),但是你也可以创建自己的包(第 8 章)。
|
||||
|
||||
Go 的标准库包含了大量的包(如 fmt, os),但是你也可以创建自己的包(参见第 8 章)。
|
||||
如果想要构建一个程序,则包和包内的文件都必须以正确的顺序进行编译。包的依赖关系决定了其构建顺序。
|
||||
|
||||
构建一个程序,包和包内的文件,必须以正确的顺序进行编译。包的依赖关系决定了构建的顺序。
|
||||
属于同一个包的源文件必须全部被一起编译,一个包既是编译时的一个单元,因此根据惯例,每个目录都只包含一个包。
|
||||
|
||||
在一个包中,源文件都必须被编译在一起。包为编译的单位,按惯例每个目录为一个包。
|
||||
*如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。*
|
||||
|
||||
_如果对一个包进行更改或重新编译,所有的引用这个包的客户端程序必须也重新编译。_
|
||||
Go 中的包模型采用了显式依赖关系的机制来达到快速编译的目的,编译器会从后缀名为 `.o` 的对象文件(需要且只需要这个文件)中提取传递依赖类型的信息。
|
||||
|
||||
为了更快的构建,包原型使用显示依赖:Go 编译器会从 .o 的对象文件(只需要这个文件)中提取传递依赖类型的信息。
|
||||
如果 `A.go` 依赖 `B.go`,而 `B.go` 又依赖 `C.go`:
|
||||
|
||||
如果 A.go 依赖 B.go 依赖 C.go
|
||||
- 编译 `C.go`, `B.go`, 然后是 `A.go`.
|
||||
- 为了编译 `A.go`, 编译器读取的是 `B.o` 而不是 `C.o`.
|
||||
|
||||
- 编译 C.go, B.go, 然后是 A.go.
|
||||
- 为了编译 A.go, 编译器读取 B.o 而不是 C.o.
|
||||
这种机制对于编译大型的项目时可以显著地提升编译速度。
|
||||
|
||||
对于规模较大的程序,这样可以大大的加快编译速度。
|
||||
*每一段代码只会被编译一次*
|
||||
|
||||
_每一段代码只会被编译一次_
|
||||
一个 Go 程序是通过 `import` 关键字将一组包链接在一起。
|
||||
|
||||
一个 Go 程序是通过 import 关键字将一组包链接在一起。
|
||||
`import “fmt”` 告诉 Go 编译器这个程序需要使用 `fmt` 包(的函数,或其他元素),`fmt` 包实现了格式化 IO(输入/输出)的函数。包名被封闭在半角双引号 `""` 中。如果你打算从已编译的包中导入并加载公开声明的方法,不需要插入已编译包的源代码。
|
||||
|
||||
___import “fmt”___ 告诉 Go 编译器,这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。包名封闭在 `""` 中。从已编译的包中导入并加载公开声明方法,不用插入已编译包的源代码。
|
||||
|
||||
如果需要多个包,他们可以使用分隔声明,一个个的导入:
|
||||
如果需要多个包,它们可以被分别导入:
|
||||
|
||||
import “fmt”
|
||||
import “os”
|
||||
@@ -60,41 +57,38 @@ ___import “fmt”___ 告诉 Go 编译器,这个程序需要使用 fmt 包(
|
||||
|
||||
import “fmt”; import “os”
|
||||
|
||||
但是更短并且更优雅的方法(称为_factoring the keyword_,同样适用于const,var 和 type)也是有的:
|
||||
但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 const、var 和 type 的声明或定义):
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
(它还可以更短:`import ("fmt", "os")`但是对于分发版本,gofmt后会强制换行)
|
||||
(它甚至还可以更短:`import ("fmt", "os")` 但使用 gofmt 后将会被强制换行)
|
||||
|
||||
当需要导入多个包时,它会按字母顺序排列包名。
|
||||
当你导入多个包时,导入的顺序会按照字母排序。
|
||||
|
||||
包名如果不是以`.`或`/`开头,如"fmt"或者"container/list",Go 会在全局的 Go 树进行查找。包名如果以`./`开头,Go 会在实际的目录中搜索;以`/`开始(即使在Windows中),将会在系统(绝对)路径中搜索。
|
||||
如果包名不是以 `.` 或 `/` 开头,如 "fmt" 或者 "container/list" ,则 Go 会在全局文件进行查找;如果包名以 `./` 开头,则 Go 会在相对目录中查找;如果包名以 `/` 开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。
|
||||
|
||||
包已经包含了其他的所有引用的代码对象。
|
||||
导入包即等同于包含了这个包的所有的代码对象。
|
||||
|
||||
除了`_`,代码对象的标识符必须在包中唯一:这样就没有_名称冲突_了。但是相同的标识符可以用在不同的包中:_包名保证_了它们的不同。
|
||||
除了符号 `_`,包中所有代码对象的标识符必须是唯一的,以避免名称冲突。但是相同的标识符可以在不同的包中使用,因为可以使用包名来区分它们。
|
||||
|
||||
包暴露自己的代码对象到包外是根据以下的规则,
|
||||
这是由编译器执行:
|
||||
包通过下面这个被编译器强制执行的规则来决定是否将自身的代码对象暴露给外部文件:
|
||||
|
||||
___可见行规则___:
|
||||
**可见性规则**
|
||||
|
||||
当标识符(包括常量,变量,类型,函数名,结构体字段,...)以一个大写字母开头,如 Group1,那么‘对象’的这个方法在包外是可见的(对客户端程序可见,需要导入相应的包),这被叫做被导出(像面向对象语言的 public )。标识符如果以小写字母开头,对包外是不可见的,但是他们在整个包内是可以见并可用的(像面向对象语言的 private )。
|
||||
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。
|
||||
|
||||
(大写字母可以来自整个Unicode编码范围,像希腊文,不仅ASCII中的大写字母)。
|
||||
(大写字母可以使用任何 Unicode 编码的字符,比如希腊文,不仅仅是 ASCII 码中的大写字母)。
|
||||
|
||||
因此,导入一个包(只)能够使该包中的导出对象被访问。
|
||||
因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。
|
||||
|
||||
假设在包 pack1 中我们有一个 thing (变量或函数)叫做 Thing(以 T 开头,所以它是导出名),那么在当前包中导入 pack1 包,Thing 可以被调用,使用点标记,像面向对象语言:pack1.Thing( pack1 是必需的!)
|
||||
假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:`pack1.Thing`(pack1 在这里是不可以省略的)
|
||||
|
||||
所以包也作为命名空间,可以帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于他们的包名,如:
|
||||
因此包也可以作为命名空间使用,帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于他们的包名,例如 `pack1.Thing` 和 `pack2.Thing`。
|
||||
|
||||
___pack1.Thing 和 pack2.Thing___
|
||||
|
||||
一个包可以,也是有用的(为了更简短,名称冲突,...),也可以给予另一个名称(别名),如:`import fm "fmt"`。下面的代码使用了别名:
|
||||
你可以通过使用包的别名来解决包名之间的名称冲突,或者说根据你的个人喜好对包名进行重新设置,如:`import fm "fmt"`。下面的代码展示了如何使用包的别名:
|
||||
|
||||
Example 4.2 [alias.go](examples/chapter_4/alias.go)
|
||||
|
||||
@@ -105,60 +99,63 @@ Example 4.2 [alias.go](examples/chapter_4/alias.go)
|
||||
fm.Println("hello, world")
|
||||
}
|
||||
|
||||
_提醒_:导入一个包,但是没有在代码中使用会导致一个构建错误(例如:导入但补使用的: os 包)。这遵循 Go 的格言:“没有不必要的代码!“
|
||||
**注意事项** 如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如 `imported and not used: os`,这正是遵循了 Go 的格言:“没有不必要的代码!“
|
||||
|
||||
包级声明和初始化:
|
||||
**包的分级声明和初始化**
|
||||
|
||||
import 语句之后可以声明 0 个或多个常量(const),变量(var)和类型(type),这些都是全局的(在包范围内),并且代码中所有的函数可以调用(如 c 和 v 在下面的 gotemplate.go 中),并且它们后面跟着一个或多个函数(func)。
|
||||
你可以在使用 `import` 导入包之后定义或声明 0 个或多个常量(const)、变量(var)和类型(type),这些对象的作用域都是全局的(在本包范围内),所以可以被本包中所有的函数调用(如 [gotemplate.go](examples/chapter_4/gotemplate.go) 源文件中的 c 和 v),然后声明一个或多个函数(func)。
|
||||
|
||||
##4.2.2 函数
|
||||
|
||||
最简单的函数定义使用这样的格式:func functionName()
|
||||
这是定义一个函数最简单的格式:`func functionName()`
|
||||
|
||||
小括号`()`中,没有,或者有一个或多个参数(使用`,`分离)可以作为该函数的输入。每个参数的变量名后,一定要是该参数的类型。
|
||||
你可以在括号`()`中写入 0 个或多个函数的参数(使用`,`分离),每个参数的名称后面必须紧跟着该参数的类型。
|
||||
|
||||
一个主函数,作为程序启动所必需的(通常是第一个函数),否则会产生构建未定义错误:main.main 发生。主函数没有参数和返回类型(与 C 语言家族相反),否则,将得到的构建错误:
|
||||
main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数( ***译者注:如果有 init() 函数则会先执行该函数*** )。如果你的 main 包的源代码没有包含 main 函数,则会引发构建错误 `undefined: main.main`。main 函数即没有参数,也没有返回类型(与 C 家族中的其它语言恰好相反)。如果你不小心为 main 函数添加了参数或者返回类型,将会引发构建错误:
|
||||
|
||||
主函数必须没有参数和返回值
|
||||
func main must have no arguments and no return values results.
|
||||
|
||||
当程序执行,初始化结束后,第一个调用(程序的入口点)的函数是 main.main()(如 C 语言)。程序在 main.main 结束后立即并成功退出。
|
||||
在程序开始执行并完成初始化后,第一个调用(程序的入口点)的函数是 `main.main()`(如:C 语言),该函数一旦返回就表示程序已成功执行并立即退出。
|
||||
|
||||
方法里的代码(方法体)使用大括号:`{ }`包含。
|
||||
函数里的代码(函数体)使用大括号 `{ }` 括起来。
|
||||
|
||||
第一个`{`必须与方法声明在同一行:这被编译器强制判断,否则 gofmt 会有(build-error: syntax error: unexpected semicolon or newline before {)的错误提示。
|
||||
左大括号 `{` 必须与方法的声明放在同一行,这是编译器的强制规定,否则你在使用 gofmt 时就会出现 `build-error: syntax error: unexpected semicolon or newline before {` 这样的错误提示。
|
||||
|
||||
(这是应为编译器执行 `func main() ;` 这是一个错误)
|
||||
(这是因为编译器会产生 `func main() ;` 这样的结果,很明显这错误的)
|
||||
( ***Go 语言虽然看起来不使用分号作为语句的结束,但实际上这一过程是由编译器自动完成,因此才会引发向上面这样的错误*** )
|
||||
|
||||
最后的`}`将被定位在函数代码下面;小功能是允许的一切都写在一行,例如:`func Sum(a, b int) int { return a + b }`
|
||||
右大括号 `}` 需要被放在紧接着函数体的下一行。如果你的函数非常简短,你也可以将它们放在同一行:`func Sum(a, b int) int { return a + b }`。
|
||||
|
||||
无论何时,使用`{ }`的规则都是相同的(如: if 语句等)
|
||||
对于大括号 `{ }` 的使用规则在任何时候都是相同的(如:if 语句等)。
|
||||
|
||||
因此示意的通用函数应该像这样:
|
||||
因此符合规范的函数一般写成如下的形式:
|
||||
|
||||
func functionName(parameter_list) (return_value_list) {
|
||||
…
|
||||
}
|
||||
|
||||
其中:
|
||||
|
||||
parameter_list 的形式为 (param1 type1, param2 type2, …)
|
||||
return_value_list 的形式为 (ret1 type1, ret2 type2, …)
|
||||
|
||||
当函数需要被包外使用,函数的名称必须以大写字母开头;他们遵循Pascal命名规则 PascalCasing,否则遵循砣峰命名规则 camelCasing:首单词字母小写,每一个新词的名称以大写字母开头。
|
||||
只有当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。
|
||||
|
||||
行:`fmt.Println("hello, workd")`调用 fmt 包的 Println 函数,将输出字符串到控制台,接着换行字符`\n`。
|
||||
`fmt.Println("hello, workd")` 这一行调用了 `fmt` 包中的 `Println` 函数,可以将字符串输出到控制台,并在最后自动增加换行字符 `\n`。
|
||||
|
||||
`fmt.Print("hello, world\n")` 可以得到相同的结果。
|
||||
使用 `fmt.Print("hello, world\n")` 可以得到相同的结果。
|
||||
|
||||
Print 和 Println 方法也支持变量,如:fmt.Println(arr);他们使用默认的输出格式输出 arr 变量。
|
||||
`Print` 和 `Println` 这两个函数也支持使用变量,如:`fmt.Println(arr)`。如果没有特别指定,它们会以默认的打印格式将变量 `arr` 输出到控制台。
|
||||
|
||||
打印一个字符串或变量甚至可以使用预定义的方法 print 和 println:print("ABC") 或 println("ABC")或者(带一个变量 i):println(i)
|
||||
单纯地打印一个字符串或变量甚至可以使用预定义的方法来实现,如:`print`、`println:print("ABC")`、`println("ABC")`、`println(i)`(带一个变量 i)。
|
||||
|
||||
这些仅仅是用于在调试阶段;部署程序时会将它们替换 fmt 的相关函数。
|
||||
这些函数只可以用于调试阶段,在部署程序的时候务必将它们替换成 `fmt` 中的相关函数。
|
||||
|
||||
当遇到结束符`}`或返回语句,程序根据方法的调用继续执行。
|
||||
当被调用函数的代码执行到结束符 `}` 或返回语句时就会返回,然后程序继续执行调用该函数之后的代码。
|
||||
|
||||
程序正常退出代码为 0 (程序以代码 0 退出);程序以异常中止退出的使用另外一个整数代码如 1 ;这可以用来测试成功执行一个程序。
|
||||
程序正常退出的代码为 0 `Program exited with code 0`;如果程序因为异常而被终止,则会返回非零值,如:1。这个数值可以用来测试是否成功执行一个程序。
|
||||
|
||||
##4.2.3 注释
|
||||
##4.2.3 注释QUESTION
|
||||
Example 4.2 [hello_world2.go](examples/chapter_4/hello_world2.go)
|
||||
|
||||
package main
|
||||
|
||||
Reference in New Issue
Block a user