首页 / Go 语言入门教程 / 包与导入基础

Go 语言入门教程

包与导入基础

本教程共 80 篇 · 第 6 篇 · 更新于 2026-07-27 · 约 8 分钟阅读

GoGo 入门教程packageimport可见性main 包Go 包

6. 包与导入基础

本节目标:搞懂 Go 的包机制,学会声明包、导入包、控制可见性,写出能被别人复用的代码。

什么是包

Go 用**包(Package)**来组织代码。一个包就是同一个目录下的一组 .go 文件,它们共享同一个 package 声明。

你可以把包理解成一个文件夹里的所有代码文件。这些文件里的函数、变量、类型,互相之间可以直接访问,不需要导入。

来看个例子:

myproject/
  go.mod
  main.go
  utils/
    helper.go
    math.go

这里 main.go 属于 main 包,utils/ 目录下的两个文件属于 utils 包。helper.gomath.go 里的代码互相可以直接用,不需要 import。

package 声明

每个 .go 文件的第一行(注释除外)必须是 package 声明:

package main    // 可执行程序
package utils   // 库包

包名通常和目录名一致。比如 utils/ 目录下的文件写 package utils。但也有例外,比如 main 包的目录名通常不叫 main。

main 包的特殊地位

main 包是 Go 程序的入口。只有 package main 里才能有 func main(),这是程序运行的起点。

package main

import "fmt"

func main() {
    fmt.Println("程序从这里开始执行")
}

可执行程序必须有且只有一个 main 包和 main 函数。库包不需要 main 函数。

import 导入

要用别的包里的东西,就得先导入。

单个导入

import "fmt"

导入标准库的 fmt 包,之后就能用 fmt.Println 这些函数了。

批量导入

多个包建议用括号括起来,这是 Go 推荐的风格:

import (
    "fmt"
    "math"
    "strings"
)

你也可以每个都单独写一行 import,但不如这种写法整洁。

导入路径

导入路径分两种情况:

标准库:直接写包名

import "fmt"
import "math/rand"

第三方或自己的包:写完整的模块路径

import "github.com/google/uuid"
import "example/hello/utils"

导入路径 = 模块路径 + 包在模块里的子目录路径。

别名导入

如果两个包名一样,或者包名太长,可以起别名:

import (
    f "fmt"           // 别名为 f
    "math/rand"       // 包名是 rand
    crand "crypto/rand"  // 别名为 crand,避免和 math/rand 冲突
)

用了别名之后,只能用别名来调用,不能用原名:

f.Println("hello")  // 正确
fmt.Println("hello")  // 错误,fmt 未定义

匿名导入

如果你只需要执行某个包的初始化代码(init 函数),但不需要用它的导出内容,可以用下划线 _ 做匿名导入:

import (
    _ "github.com/lib/pq"  // 注册 PostgreSQL 驱动,但不直接调用
)

这在数据库驱动注册的场景很常见。驱动包的 init 函数会把自己注册进去,你不需要手动调用它。

可见性规则

Go 控制可见性的方式特别简单:看首字母大小写

  • 大写字母开头:导出的(Exported),包外可以访问
  • 小写字母或下划线开头:未导出的(Unexported),只有包内能访问

没有 publicprivateprotected 这些关键字。

package utils

const PublicName = "Go"    // 包外可以访问
const privateName = "golang" // 只有 utils 包内能访问

func PublicFunc() {}    // 包外可以调用
func privateFunc() {}   // 只有 utils 包内能调用

type User struct {       // 包外可以访问
    Name  string        // 包外可以访问
    email string        // 只有 utils 包内能访问
}
Note

这个规则适用于所有名称:函数、变量、常量、类型、结构体字段。大写就是公开的,小写就是私有的,就这么简单。

来写一个完整的例子

我们创建一个简单的模块,包含 main 包和一个 greeting 包。

项目结构:

greet/
  go.mod
  main.go
  greeting/
    greeting.go

初始化模块

mkdir greet
cd greet
go mod init example/greet

写 greeting 包

创建 greeting/greeting.go

package greeting

import "fmt"

// Hello 返回问候语,大写开头,包外可访问
func Hello(name string) string {
    msg := fmt.Sprintf("Hello, %s!", name)
    return msg
}

// help 是小写的,包外访问不了
func help() string {
    return "this is private"
}

写 main 包

创建 main.go

package main

import (
    "fmt"

    "example/greet/greeting"
)

func main() {
    msg := greeting.Hello("Go")
    fmt.Println(msg)

    // greeting.help()  // 编译报错:help 没有导出
}

运行

go run .

输出:

Hello, Go!

如果你取消注释 greeting.help() 那行,编译会报错,因为 help 是小写的,外部访问不了。

包的初始化顺序

Go 在导入包时会执行初始化:

  1. 先初始化导入的包(按依赖顺序,被依赖的先初始化)
  2. 执行包级别变量的声明
  3. 执行 init 函数(如果有的话)
  4. 最后执行 main 函数

每个包只会被初始化一次,不管被导入多少次。

init 函数是一种特殊的函数,不需要调用,Go 会在包初始化时自动执行:

package greeting

func init() {
    // 这个函数在包被导入时自动执行
    fmt.Println("greeting 包初始化了")
}

一个文件里可以写多个 init 函数,它们按出现顺序执行。

内部包

Go 有一个特殊约定:路径中包含 /internal/ 的包,只能被 /internal/ 的父目录下的包导入。

比如模块 example/myapp 的结构:

myapp/
  go.mod
  main.go
  internal/
    config/
      config.go
  handler/
    handler.go
  • main.go 可以导入 example/myapp/internal/config
  • example/myapp/handler 可以导入 example/myapp/internal/config
  • 但外部模块不能导入 example/myapp/internal/config

这让你可以放心地把不想对外暴露的实现细节放在 internal/ 目录下。

常见问题

循环导入

Go 不允许循环导入。如果 A 导入了 B,B 就不能再导入 A。

// packageA/a.go
package packageA
import "example/mod/packageB"  // A 导入了 B

// packageB/b.go
package packageB
import "example/mod/packageA"  // 报错!B 不能导入 A

遇到循环导入,通常的解决办法是:

  • 把公共部分提取到第三个包
  • 用接口解耦
  • 重新审视包的职责划分

导入了但没用

Go 编译器会报错:导入了包却没使用。这是 Go 的设计选择,强制你保持代码整洁。

import (
    "fmt"
    "math"  // 编译报错:imported and not used
)

包名和导入路径不一致

大多数情况下包名和导入路径最后一段一致。但有时候不一致,比如 gopkg.in/yaml.v3 的包名是 yaml。这时候你要注意实际包名是 yaml,不是 yaml.v3

Tip

如果不确定一个包的实际包名,导入之后看编辑器的提示,或者用 go doc 查一下。

学完这节你能做什么

  • 理解 Go 包的组织方式
  • 声明包、导入包、使用别名和匿名导入
  • 用大小写控制可见性
  • 写一个包含多个包的小项目
  • 知道循环导入怎么避免

下一节进入基础语法的学习,从 token、标识符、关键字开始。