Skip to content
目 录

GoLang - 代码结构化

可见性规则

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

INFO

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

因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。

假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:

go
pack1.Thing //(pack1 在这里是不可以省略的)

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

WARNING

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

包的概念

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

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

TIP

每个目录下面可以有多个.go文件,这些文件只能属于同一个包,否则编译时会报错。同一个包下的不同.go文件相互之间可以直接引用变量和函数,所以这些文件中定义的全局变量和函数不能重名。

你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main

package main 表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。package main 包下可以有多个文件,但所有文件中只能有一个 main() 方法,main() 方法代表程序入口。

简单地说,在含有 mian 包的目录下,你可以写多个文件,每个文件非注释的第一行都使用 package main 来指明这些文件都属于这个应用的 main 包,只有一个文件能有 main() 方法,也就是应用程序的入口。main 包不是必须的,只有在可执行的应用程序中需要。

包的导入

一个 Go 程序通过 import 关键字将一组包链接在一起。import 其实是导入目录,而不是定义的包名称,实际应用中我们一般都会保持一致。

例如:package big ,我们 import “math/big” ,其实是在 src 中的 src/math/big 目录。在代码中使用 big.Int 时,big 指的才是 Go 文件中定义的 package 名字。

导入多个包时,一般按照字母顺序排列包名称,像 GoLand 会在保存文件时自动完成这个动作。所谓导入包即等同于包含了这个包的所有的代码对象

为避免名称冲突,同一包中所有对象的标识符必须要求唯一。但是相同的标识符可以在不同的包中使用,因为可以使用包名来区分它们

import 语句一般放在包名定义的下一行,导入包示例如下:

go
package main
 
import  "context"  //加载context包

导入多个包的常见的方式是:

go
import  (
    "fmt"
    "net/http"
 )

调用导入的包函数的一般方式:

go
fmt.Println("Hello World!")

下面介绍三种特殊的 import 方式:

  • 点操作:import . "fmt"

    点操作的含义是某个包导入之后,在调用这个包的函数时,可以省略前缀的包名,如这里可以写成 Println("Hello World!"),而不是 fmt.Println("Hello World!")

  • 别名操作:import f "fmt"

    别名操作调用包函数时,前缀变成了别名,即 f.Println("Hello World!")。在实际项目中有时这样使用,但请谨慎使用,不要不加节制地采用这种形式。

  • _ 操作:import _ "github.com/go-sql-driver/mysql"

    _ 操作是引入某个包,但不直接使用包里的函数,而是调用该包里面的 init 函数,比如这个 mysql 包的导入。此外在开发中,由于某种原因某个原来导入的包现在不再使用,也可以采用这种方式处理。

标准库

在 Go 的安装文件里包含了一些可以直接使用的包,即标准库。在 Windows 下,标准库的位置在 Go 根目录下的子目录 pkg\windows_386 中;在 Linux 下,标准库在 Go 根目录下的子目录 pkg\linux_amd64 中(如果是安装的是 32 位,则在 linux_386 目录中)。Go 的标准库包含了大量的包(如:fmt 和 os), 在$GoROOT/src中可以看到源码,也可以根据情况自行重新编译。

完整列表可以在 Go Walker 查看。

    unsafe: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,
            可用在 C/C++ 程序的调用中。
    syscall-os-os/exec:
        os: 提供给我们一个平台无关性的操作系统功能接口,采用类Unix设计,
            隐藏了不同操作系统间差异,让不同的文件系统和操作系统对象表现一致。
        os/exec: 提供我们运行外部操作系统命令和程序的方式。
        syscall: 底层的外部包,提供了操作系统底层调用的基本接口。
    archive/tar 和 /zip-compress:压缩(解压缩)文件功能。
    fmt-io-bufio-path/filepath-flag:
        fmt: 提供了格式化输入输出功能。
        io: 提供了基本输入输出功能,大多数是围绕系统功能的封装。
        bufio: 缓冲输入输出功能的封装。
        path/filepath: 用来操作在当前系统中的目标文件名路径。
        flag: 对命令行参数的操作。  
    strings-strconv-unicode-regexp-bytes:
        strings: 提供对字符串的操作。
        strconv: 提供将字符串转换为基础类型的功能。
        unicode: 为 unicode 型的字符串提供特殊的功能。
        regexp: 正则表达式功能。
        bytes: 提供对字符型分片的操作。
    math-math/cmath-math/big-math/rand-sort:
        math: 基本的数学函数。
        math/cmath: 对复数的操作。
        math/rand: 伪随机数生成。
        sort: 为数组排序和自定义集合。
        math/big: 大数的实现和计算。   
    container-/list-ring-heap: 实现对集合的操作。
        list: 双链表。
        ring: 环形链表。
   time-log:
        time: 日期和时间的基本操作。
        log: 记录程序运行时产生的日志。
    encoding/Json-encoding/xml-text/template:
        encoding/Json: 读取并解码和写入并编码 Json 数据。
        encoding/xml:简单的 XML1.0 解析器。
        text/template:生成像 HTML 一样的数据与文本混合的数据驱动模板。
    net-net/http-html:
        net: 网络数据的基本操作。
        http: 提供了一个可扩展的 HTTP 服务器和客户端,解析 HTTP 请求和回复。
        html: HTML5 解析器。
    runtime: Go 程序运行时的交互操作,例如垃圾回收和协程创建。
    reflect: 实现通过程序运行时反射,让程序操作任意类型的变量。

包的初始化

Go 语言中 init() 函数常用于包的初始化,该函数是 Go 语言的一个重要特性,有下面的特征:

  • init 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
  • 每个包可以拥有多个 init 函数
  • 包的每个源文件也可以拥有多个 init 函数
  • 同一个包中多个 init() 函数的执行顺序不定
  • 不同包的 init() 函数按照包导入的依赖关系决定该函数的执行顺序
  • init() 函数不能被其他函数调用,其在 main 函数执行之前,自动被调用

Go 程序的执行(程序启动)顺序如下:

程序的初始化和执行都起始于 main 包。如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt 包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行 init 函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对 main 包中的包级常量和变量进行初始化,然后执行 main 包中的 init 函数(如果存在的话),最后执行 main 函数。