在 Go 基础语法 中,我们已经用过 package main 和 import "fmt" 了。这篇文章来更深入地讲讲 Go 的包机制:可见性规则是怎么回事,init() 函数有什么用,以及多文件项目怎么组织。
Go 的包(package)
Go 用包(package)来组织代码,每个 .go 文件开头必须声明自己属于哪个包。
package main 是特殊的,它告诉编译器"这是一个可执行程序"。程序运行时会找到 main 包里的 main() 函数作为入口。
同一个目录下的所有 .go 文件必须属于同一个包,包名通常和目录名保持一致。
import 导入
用 import 引入其他包后,就能用 包名.函数名 的方式调用了。前面已经介绍过基本写法,这里补充几个细节。
引入多个包时推荐用分组写法,更清晰:
import (
"fmt"
"sort"
"strings"
)Go 有一条很严格的规则:导入了但没有使用的包会编译报错。这不是警告,是直接编不过。这是 Go 的设计哲学:不留死代码。
如果你确实只想触发包的初始化而不调用它的函数(后面会讲到 init()),可以用下划线 _ 忽略:
import _ "net/http/pprof"可见性规则(大小写)
Go 没有 public、private 这样的关键字,而是用一种非常简单的规则来控制可见性:首字母大写就是导出的,首字母小写就是包内私有的。
// 其他包可以访问
func Factorial(n int) int { ... }
var MaxSize = 100
type Student struct { ... }
// 只有包内能访问
func helper() { ... }
var count = 0
type config struct { ... }这个规则适用于所有东西:函数、变量、常量、类型、结构体字段。
所以你会发现标准库的函数都是大写开头的:fmt.Println、sort.Ints、strings.ToUpper。因为小写开头的函数只是包内的辅助函数,外面根本调不到。
init() 初始化函数
每个包可以定义 init() 函数,它会在包被导入时自动执行,而且在 main() 之前。你不需要也不能手动调用它。
执行顺序是:被导入包的 init() → 当前包的 init() → main()。
刷题中基本不会用到 init(),但在实际工程中它很常见。最经典的场景是数据库驱动注册。
Go 的 database/sql 包提供了统一的数据库接口,各种驱动(MySQL、PostgreSQL 等)通过 init() 把自己注册进去。你只需要加一行 import _ "github.com/go-sql-driver/mysql",驱动的 init() 就会自动完成注册,不需要你手动调用任何函数。
下面用一个简单的例子演示 init() 的执行时机:
运行这段代码你会看到 init() 的输出在 main() 前面。
刷题常用标准库包一览
下面列出刷题中最常用的标准库包,让你知道"有哪些东西可以用"。具体的用法和示例会在后续的 常用数据结构 和 算法题常用技巧 章节中详细介绍。
| 包名 | 用途 | 典型函数/类型 |
|---|---|---|
fmt | 格式化输入输出 | Println、Printf、Scanf |
sort | 排序 | sort.Ints、sort.Slice |
strings | 字符串操作 | Split、Join、Contains |
strconv | 字符串与数字互转 | Atoi、Itoa |
math | 数学函数和常量 | Max、Min、Abs、MaxInt32 |
container/heap | 堆/优先队列 | 实现 heap.Interface |
bufio | 带缓冲的 I/O | Scanner(快读大数据量) |
多文件组织
Go 的多文件组织比大多数语言都简单:同一个包(同一个目录)里的多个 .go 文件,可以直接互相调用,不需要任何 import。
这意味着你可以把一个大文件拆成多个小文件,只要它们的 package 声明相同就行。比如把工具函数放到 utils.go,主逻辑放到 main.go,它们之间可以自由调用。
下面是一个多文件项目的示例:
了解了包和导入之后,下一篇我们来讲 函数与指针。Go 的函数可以返回多个值,指针的用法也比 C 简单不少,学完之后你就能写出更有结构的代码了。