包与导入

Go 基础语法 中,我们已经用过 package mainimport "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 没有 publicprivate 这样的关键字,而是用一种非常简单的规则来控制可见性:首字母大写就是导出的,首字母小写就是包内私有的

// 其他包可以访问
func Factorial(n int) int { ... }
var MaxSize = 100
type Student struct { ... }

// 只有包内能访问
func helper() { ... }
var count = 0
type config struct { ... }

这个规则适用于所有东西:函数、变量、常量、类型、结构体字段。

所以你会发现标准库的函数都是大写开头的:fmt.Printlnsort.Intsstrings.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格式化输入输出PrintlnPrintfScanf
sort排序sort.Intssort.Slice
strings字符串操作SplitJoinContains
strconv字符串与数字互转AtoiItoa
math数学函数和常量MaxMinAbsMaxInt32
container/heap堆/优先队列实现 heap.Interface
bufio带缓冲的 I/OScanner(快读大数据量)

多文件组织

Go 的多文件组织比大多数语言都简单:同一个包(同一个目录)里的多个 .go 文件,可以直接互相调用,不需要任何 import

这意味着你可以把一个大文件拆成多个小文件,只要它们的 package 声明相同就行。比如把工具函数放到 utils.go,主逻辑放到 main.go,它们之间可以自由调用。

下面是一个多文件项目的示例:

了解了包和导入之后,下一篇我们来讲 函数与指针。Go 的函数可以返回多个值,指针的用法也比 C 简单不少,学完之后你就能写出更有结构的代码了。