go中包的概念、导入与可见性

包是结构化代码的一种方式:每一个程序都由包(一般简称为 pkg)的概念组成,可使用自身的包或者从其它包中导入内容。linux

如同其它一些编程语言中的类库或命名空间的概念,每一个 Go 文件都属于且仅属于一个包。一个包能够由许多以 .go 为扩展名的源文件组成,所以文件名和包名通常来讲都是不相同的。git

你必须在源文件中非注释的第一行指明这个文件属于哪一个包,如:package mainpackage main表示一个可独立执行的程序,每一个 Go 应用程序都包含一个名为 main 的包。github

一个应用程序能够包含不一样的包,并且即便你只使用 main 包也没必要把全部的代码都写在一个巨大的文件里:你能够用一些较小的文件,而且在每一个文件非注释的第一行都使用 package main 来指明这些文件都属于 main 包。若是你打算编译包名不是为 main 的源文件,如 pack1,编译后产生的对象文件将会是 pack1.a 而不是可执行程序。另外要注意的是,全部的包名都应该使用小写字母。编程

标准库windows

在 Go 的安装文件里包含了一些能够直接使用的包,即标准库。在 Windows 下,标准库的位置在 Go 根目录下的子目录 pkg\windows_386 中;在 Linux 下,标准库在 Go 根目录下的子目录 pkg\linux_amd64 中(若是是安装的是 32 位,则在 linux_386 目录中)。通常状况下,标准包会存放在 $GOROOT/pkg/$GOOS_$GOARCH/ 目录下。编程语言

Go 的标准库包含了大量的包(如:fmt 和 os),可是你也能够建立本身的包。函数

若是想要构建一个程序,则包和包内的文件都必须以正确的顺序进行编译。包的依赖关系决定了其构建顺序。编码

属于同一个包的源文件必须所有被一块儿编译,一个包便是编译时的一个单元,所以根据惯例,每一个目录都只包含一个包。spa

若是对一个包进行更改或从新编译,全部引用了这个包的客户端程序都必须所有从新编译。code

Go 中的包模型采用了显式依赖关系的机制来达到快速编译的目的,编译器会从后缀名为 .o 的对象文件(须要且只须要这个文件)中提取传递依赖类型的信息。

若是 A.go 依赖 B.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"

可是还有更短且更优雅的方法(被称为因式分解关键字,该方法一样适用于 const、var 和 type 的声明或定义):

import (
   "fmt" "os" )

它甚至还能够更短的形式,但使用 gofmt 后将会被强制换行:

import ("fmt"; "os")

当你导入多个包时,导入的顺序会按照字母排序。

若是包名不是以 ./ 开头,如 "fmt" 或者 "container/list",则 Go 会在全局文件进行查找;若是包名以 ./ 开头,则 Go 会在相对目录中查找;若是包名以 / 开头(在 Windows 下也能够这样使用),则会在系统的绝对路径中查找。

导入包即等同于包含了这个包的全部的代码对象。

除了符号 _,包中全部代码对象的标识符必须是惟一的,以免名称冲突。可是相同的标识符能够在不一样的包中使用,由于可使用包名来区分它们。

包经过下面这个被编译器强制执行的规则来决定是否将自身的代码对象暴露给外部文件:

可见性规则

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就能够被外部包的 代码所使用(客户端程序须要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符若是以小写字母开头,则对包外是不可见的,可是他们在整个包的内部是可见而且可用的(像面向对象语言中的 private )。

(大写字母可使用任何 Unicode 编码的字符,好比希腊文,不只仅是 ASCII 码中的大写字母)。

所以,在导入一个外部包后,可以且只可以访问该包中导出的对象。

假设在包 pack1 中咱们有一个变量或函数叫作 Thing(以 T 开头,因此它可以被导出),那么在当前包中导入 pack1 包,Thing 就能够像面向对象语言那样使用点标记来调用:pack1.Thing(pack1 在这里是不能够省略的)。

所以包也能够做为命名空间使用,帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于他们的包名,例如 pack1.Thingpack2.Thing

你能够经过使用包的别名来解决包名之间的名称冲突,或者说根据你的我的喜爱对包名进行从新设置,如:import fm "fmt"。下面的代码展现了如何使用包的别名:

示例 4.2 alias.go

package main

import fm "fmt" // alias3 func main() { fm.Println("hello, world") }

注意事项

若是你导入了一个包却没有使用它,则会在构建程序时引起错误,如 imported and not used: os,这正是遵循了 Go 的格言:“没有没必要要的代码!“。

包的分级声明和初始化

你能够在使用 import 导入包以后定义或声明 0 个或多个常量(const)、变量(var)和类型(type),这些对象的做用域都是全局的(在本包范围内),因此能够被本包中全部的函数调用(如 gotemplate.go 源文件中的 c 和 v),而后声明一个或多个函数(func)。

 

Go 程序的通常结构

下面的程序能够被顺利编译但什么都作不了,不过这很好地展现了一个 Go 程序的首选结构。这种结构并无被强制要求,编译器也不关心 main 函数在前仍是变量的声明在前,但使用统一的结构可以在从上至下阅读 Go 代码时有更好的体验。

全部的结构将在这一章或接下来的章节中进一步地解释说明,但整体思路以下:

  • 在完成包的 import 以后,开始对常量、变量和类型的定义或声明。
  • 若是存在 init 函数的话,则对该函数进行定义(这是一个特殊的函数,每一个含有该函数的包都会首先执行这个函数)。
  • 若是当前包是 main 包,则定义 main 函数。
  • 而后定义其他的函数,首先是类型的方法,接着是按照 main 函数中前后调用的顺序来定义相关函数,若是有不少函数,则能够按照字母顺序来进行排序。

示例 4.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. 按顺序导入全部被 main 包引用的其它包,而后在每一个包中执行以下流程:
  2. 若是该包又导入了其它的包,则从第一步开始递归执行,可是每一个包只会被导入一次。
  3. 而后以相反的顺序在每一个包中初始化常量和变量,若是该包含有 init 函数的话,则调用该函数。
  4. 在完成这一切以后,main 也执行一样的过程,最后调用 main 函数开始执行程序。
相关文章
相关标签/搜索