Go学习笔记-Packages

包(Package)是Go语言组织代码的基本单位。Go的包系统提供了模块化、封装性和代码重用的能力。理解包的概念对于构建大型Go应用程序至关重要。

包的基本概念

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// main.go
package main

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

func main() {
    // 1. 使用标准库包
    fmt.Println("Hello, Go Packages!")
    
    // math包
    fmt.Printf("圆周率: %.2f\n", math.Pi)
    fmt.Printf("平方根: %.2f\n", math.Sqrt(16))
    
    // strings包
    text := "Hello, World!"
    fmt.Printf("大写: %s\n", strings.ToUpper(text))
    fmt.Printf("包含'World': %t\n", strings.Contains(text, "World"))
    
    // time包
    now := time.Now()
    fmt.Printf("当前时间: %s\n", now.Format("2006-01-02 15:04:05"))
    
    // 2. 包的可见性规则
    // 大写字母开头的标识符是导出的(公开的)
    // 小写字母开头的标识符是未导出的(私有的)
    
    // 可以访问:fmt.Println(大写P)
    // 不能访问:fmt.println(如果存在,小写p)
    
    // 3. 包的别名
    // 在import时可以给包起别名
    // import f "fmt"
    // f.Println("使用别名")
    
    // 4. 点导入(不推荐)
    // import . "fmt"
    // Println("直接使用函数名")
    
    // 5. 空白导入(只执行包的init函数)
    // import _ "database/sql/driver"
}

创建自定义包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// utils/math.go
package utils

import "math"

// 导出的函数(首字母大写)
func Add(a, b int) int {
    return a + b
}

func Multiply(a, b int) int {
    return a * b
}

func Power(base, exp float64) float64 {
    return math.Pow(base, exp)
}

// 导出的常量
const (
    MaxInt = int(^uint(0) >> 1)
    MinInt = -MaxInt - 1
)

// 导出的变量
var (
    DefaultPrecision = 2
    Version         = "1.0.0"
)

// 未导出的函数(首字母小写)
func internalCalculation(x int) int {
    return x * 2
}

// 导出的结构体
type Calculator struct {
    precision int
}

// 构造函数(Go约定以New开头)
func NewCalculator(precision int) *Calculator {
    return &Calculator{precision: precision}
}

// 导出的方法
func (c *Calculator) Add(a, b float64) float64 {
    return roundToPrecision(a+b, c.precision)
}

func (c *Calculator) Subtract(a, b float64) float64 {
    return roundToPrecision(a-b, c.precision)
}

// 未导出的辅助函数
func roundToPrecision(value float64, precision int) float64 {
    multiplier := math.Pow(10, float64(precision))
    return math.Round(value*multiplier) / multiplier
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// utils/string.go
package utils

import (
    "strings"
    "unicode"
)

// 字符串工具函数
func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

func IsPalindrome(s string) bool {
    cleaned := strings.ToLower(removeNonAlphanumeric(s))
    return cleaned == Reverse(cleaned)
}

func TitleCase(s string) string {
    return strings.Title(strings.ToLower(s))
}

func WordCount(s string) map[string]int {
    words := strings.Fields(strings.ToLower(s))
    count := make(map[string]int)
    for _, word := range words {
        count[word]++
    }
    return count
}

// 未导出的辅助函数
func removeNonAlphanumeric(s string) string {
    var result strings.Builder
    for _, r := range s {
        if unicode.IsLetter(r) || unicode.IsDigit(r) {
            result.WriteRune(r)
        }
    }
    return result.String()
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// main.go - 使用自定义包
package main

import (
    "fmt"
    "./utils"  // 相对路径导入(不推荐)
    // 推荐使用模块路径:
    // "myproject/utils"
)

func main() {
    // 1. 使用导出的函数
    fmt.Printf("5 + 3 = %d\n", utils.Add(5, 3))
    fmt.Printf("4 * 6 = %d\n", utils.Multiply(4, 6))
    fmt.Printf("2^8 = %.0f\n", utils.Power(2, 8))
    
    // 2. 使用导出的常量和变量
    fmt.Printf("最大整数: %d\n", utils.MaxInt)
    fmt.Printf("默认精度: %d\n", utils.DefaultPrecision)
    fmt.Printf("版本: %s\n", utils.Version)
    
    // 3. 使用导出的结构体
    calc := utils.NewCalculator(3)
    fmt.Printf("计算器加法: %.3f\n", calc.Add(1.23456, 2.34567))
    fmt.Printf("计算器减法: %.3f\n", calc.Subtract(5.6789, 2.1234))
    
    // 4. 使用字符串工具函数
    text := "Hello, World!"
    fmt.Printf("原文: %s\n", text)
    fmt.Printf("反转: %s\n", utils.Reverse(text))
    fmt.Printf("标题格式: %s\n", utils.TitleCase("hello world"))
    
    // 5. 回文检查
    palindromes := []string{"racecar", "A man a plan a canal Panama", "hello"}
    for _, p := range palindromes {
        fmt.Printf("'%s' 是回文: %t\n", p, utils.IsPalindrome(p))
    }
    
    // 6. 词频统计
    sentence := "the quick brown fox jumps over the lazy dog the fox is quick"
    wordCount := utils.WordCount(sentence)
    fmt.Println("词频统计:")
    for word, count := range wordCount {
        fmt.Printf("  %s: %d\n", word, count)
    }
    
    // 注意:不能访问未导出的函数
    // utils.internalCalculation(5) // 编译错误
    // utils.removeNonAlphanumeric("test") // 编译错误
}

包的初始化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// config/config.go
package config

import (
    "fmt"
    "log"
    "os"
)

// 包级别变量
var (
    AppName    string
    Version    string
    Debug      bool
    ConfigFile string
)

// init函数在包被导入时自动执行
// 一个包可以有多个init函数,按照它们在源码中出现的顺序执行
func init() {
    fmt.Println("config包初始化 - init函数1")
    
    // 设置默认值
    AppName = "MyApp"
    Version = "1.0.0"
    Debug = false
    ConfigFile = "app.conf"
}

func init() {
    fmt.Println("config包初始化 - init函数2")
    
    // 从环境变量读取配置
    if env := os.Getenv("APP_NAME"); env != "" {
        AppName = env
    }
    
    if env := os.Getenv("APP_VERSION"); env != "" {
        Version = env
    }
    
    if env := os.Getenv("DEBUG"); env == "true" {
        Debug = true
    }
    
    if env := os.Getenv("CONFIG_FILE"); env != "" {
        ConfigFile = env
    }
}

func init() {
    fmt.Println("config包初始化 - init函数3")
    
    // 验证配置
    if AppName == "" {
        log.Fatal("应用名称不能为空")
    }
    
    // 打印配置信息
    PrintConfig()
}

// 导出的函数
func PrintConfig() {
    fmt.Printf("应用配置:\n")
    fmt.Printf("  名称: %s\n", AppName)
    fmt.Printf("  版本: %s\n", Version)
    fmt.Printf("  调试模式: %t\n", Debug)
    fmt.Printf("  配置文件: %s\n", ConfigFile)
}

func SetDebug(debug bool) {
    Debug = debug
    if Debug {
        fmt.Println("调试模式已启用")
    } else {
        fmt.Println("调试模式已禁用")
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// database/connection.go
package database

import (
    "fmt"
    "log"
)

// 模拟数据库连接
type Connection struct {
    host     string
    port     int
    database string
    connected bool
}

var defaultConnection *Connection

// init函数用于初始化默认连接
func init() {
    fmt.Println("database包初始化")
    
    defaultConnection = &Connection{
        host:     "localhost",
        port:     5432,
        database: "myapp",
        connected: false,
    }
    
    // 自动连接
    if err := defaultConnection.Connect(); err != nil {
        log.Printf("默认数据库连接失败: %v", err)
    }
}

func (c *Connection) Connect() error {
    if c.connected {
        return fmt.Errorf("已经连接到数据库")
    }
    
    fmt.Printf("连接到数据库: %s:%d/%s\n", c.host, c.port, c.database)
    c.connected = true
    return nil
}

func (c *Connection) Disconnect() error {
    if !c.connected {
        return fmt.Errorf("数据库未连接")
    }
    
    fmt.Printf("断开数据库连接: %s:%d/%s\n", c.host, c.port, c.database)
    c.connected = false
    return nil
}

func (c *Connection) IsConnected() bool {
    return c.connected
}

// 导出的函数,使用默认连接
func GetDefaultConnection() *Connection {
    return defaultConnection
}

func Query(sql string) ([]map[string]interface{}, error) {
    if !defaultConnection.connected {
        return nil, fmt.Errorf("数据库未连接")
    }
    
    fmt.Printf("执行查询: %s\n", sql)
    // 模拟查询结果
    return []map[string]interface{}{
        {"id": 1, "name": "张三"},
        {"id": 2, "name": "李四"},
    }, nil
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// main.go - 演示包初始化
package main

import (
    "fmt"
    
    // 导入包时会执行init函数
    "./config"
    "./database"
    
    // 空白导入,只执行init函数
    _ "./logger"  // 假设有一个logger包
)

func main() {
    fmt.Println("\n=== 主程序开始 ===")
    
    // 使用config包
    fmt.Printf("应用名称: %s\n", config.AppName)
    config.SetDebug(true)
    
    // 使用database包
    conn := database.GetDefaultConnection()
    fmt.Printf("数据库连接状态: %t\n", conn.IsConnected())
    
    results, err := database.Query("SELECT * FROM users")
    if err != nil {
        fmt.Printf("查询错误: %v\n", err)
    } else {
        fmt.Printf("查询结果: %v\n", results)
    }
    
    fmt.Println("=== 主程序结束 ===")
}

包的导入方式

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
package main

import (
    // 1. 标准导入
    "fmt"
    "strings"
    "time"
    
    // 2. 别名导入
    f "fmt"
    str "strings"
    
    // 3. 点导入(不推荐,会污染命名空间)
    . "math"
    
    // 4. 空白导入(只执行init函数)
    _ "image/png"  // 注册PNG格式支持
    _ "database/sql/driver"
    
    // 5. 分组导入(推荐)
    "encoding/json"
    "net/http"
    "os"
    
    // 第三方包
    "github.com/gorilla/mux"
    "github.com/jinzhu/gorm"
    
    // 本地包
    "myproject/config"
    "myproject/models"
    "myproject/utils"
)

func main() {
    // 1. 标准导入使用
    fmt.Println("标准导入")
    fmt.Println(strings.ToUpper("hello"))
    
    // 2. 别名导入使用
    f.Println("别名导入")
    f.Println(str.ToLower("WORLD"))
    
    // 3. 点导入使用(可以直接使用函数名)
    fmt.Printf("圆周率: %.2f\n", Pi)  // 直接使用math.Pi
    fmt.Printf("平方根: %.2f\n", Sqrt(16))  // 直接使用math.Sqrt
    
    // 4. 演示包的导入顺序
    // Go的导入顺序:
    // 1. 标准库包
    // 2. 第三方包
    // 3. 本地包
    
    // 5. 条件导入(通过build tags)
    // 在文件顶部使用:// +build linux
    // 或者:// +build !windows
    
    demonstrateImportBestPractices()
}

func demonstrateImportBestPractices() {
    fmt.Println("\n=== 导入最佳实践 ===")
    
    // 1. 避免循环导入
    // 包A导入包B,包B不能导入包A
    
    // 2. 最小化导入
    // 只导入需要的包,避免不必要的依赖
    
    // 3. 使用go mod管理依赖
    // go mod init myproject
    // go mod tidy
    
    // 4. 版本控制
    // go get github.com/gin-gonic/gin@v1.7.0
    
    // 5. 私有包导入
    // import "github.com/mycompany/private-repo/pkg"
    
    fmt.Println("导入最佳实践演示完成")
}

// 演示不同导入方式的使用场景
func demonstrateImportUseCases() {
    // 1. 标准导入 - 最常用
    data := map[string]interface{}{
        "name": "Go",
        "version": 1.18,
    }
    
    jsonData, _ := json.Marshal(data)
    fmt.Printf("JSON: %s\n", jsonData)
    
    // 2. 别名导入 - 避免命名冲突
    // 当有多个包有相同名称时使用
    // import (
    //     sqlDriver "database/sql/driver"
    //     mongoDriver "go.mongodb.org/mongo-driver/mongo"
    // )
    
    // 3. 空白导入 - 注册驱动或插件
    // import _ "github.com/go-sql-driver/mysql"
    // 这样MySQL驱动会注册到database/sql包中
    
    // 4. 点导入 - 测试包中常用
    // 在测试文件中可能会看到:
    // import . "github.com/onsi/ginkgo"
    // import . "github.com/onsi/gomega"
    
    fmt.Println("导入用例演示完成")
}

Go Modules

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// go.mod 文件示例
/*
module myproject

go 1.18

require (
    github.com/gin-gonic/gin v1.8.1
    github.com/gorilla/mux v1.8.0
    github.com/jinzhu/gorm v1.9.16
    golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
)

require (
    github.com/gin-contrib/sse v0.1.0 // indirect
    github.com/go-playground/locales v0.14.0 // indirect
    github.com/go-playground/universal-translator v0.18.0 // indirect
    github.com/go-playground/validator/v10 v10.10.0 // indirect
    github.com/goccy/go-json v0.9.7 // indirect
    github.com/json-iterator/go v1.1.12 // indirect
    github.com/leodido/go-urn v1.2.1 // indirect
    github.com/mattn/go-isatty v0.0.14 // indirect
    github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
    github.com/modern-go/reflect2 v1.0.2 // indirect
    github.com/pelletier/go-toml/v2 v2.0.1 // indirect
    github.com/ugorji/go/codec v1.2.7 // indirect
    golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
    golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
    golang.org/x/text v0.3.6 // indirect
    google.golang.org/protobuf v1.28.0 // indirect
    gopkg.in/yaml.v2 v2.4.0 // indirect
)
*/

// 常用的Go Modules命令演示
package main

import "fmt"

func main() {
    fmt.Println("Go Modules 命令演示")
    
    // 1. 初始化模块
    // go mod init myproject
    
    // 2. 添加依赖
    // go get github.com/gin-gonic/gin
    // go get github.com/gin-gonic/gin@v1.8.1  // 指定版本
    // go get github.com/gin-gonic/gin@latest   // 最新版本
    
    // 3. 更新依赖
    // go get -u github.com/gin-gonic/gin       // 更新到最新版本
    // go get -u ./...                          // 更新所有依赖
    
    // 4. 移除依赖
    // go mod tidy  // 移除未使用的依赖
    
    // 5. 下载依赖
    // go mod download
    
    // 6. 验证依赖
    // go mod verify
    
    // 7. 查看依赖图
    // go mod graph
    
    // 8. 查看可用版本
    // go list -m -versions github.com/gin-gonic/gin
    
    // 9. 使用replace指令(开发时常用)
    // replace github.com/mycompany/mypackage => ../mypackage
    
    // 10. 使用exclude指令
    // exclude github.com/problematic/package v1.0.0
    
    demonstrateModuleStructure()
}

func demonstrateModuleStructure() {
    fmt.Println("\n=== 模块结构演示 ===")
    
    // 推荐的项目结构:
    /*
    myproject/
    ├── go.mod
    ├── go.sum
    ├── main.go
    ├── cmd/
    │   └── server/
    │       └── main.go
    ├── internal/
    │   ├── config/
    │   ├── handler/
    │   ├── service/
    │   └── repository/
    ├── pkg/
    │   ├── utils/
    │   └── models/
    ├── api/
    │   └── v1/
    ├── web/
    │   ├── static/
    │   └── templates/
    ├── scripts/
    ├── docs/
    └── README.md
    */
    
    // internal/ 目录:只能被同一模块内的包导入
    // pkg/ 目录:可以被外部模块导入的包
    // cmd/ 目录:应用程序的入口点
    
    fmt.Println("模块结构说明:")
    fmt.Println("- internal/: 内部包,不能被外部导入")
    fmt.Println("- pkg/: 公共包,可以被外部导入")
    fmt.Println("- cmd/: 应用程序入口")
    fmt.Println("- api/: API定义")
    fmt.Println("- web/: Web资源")
}

总结

  1. 包的基本概念

    • 包是Go代码组织的基本单位
    • 每个Go文件都属于一个包
    • main包是程序的入口点
    • 包名通常与目录名相同
  2. 可见性规则

    • 大写字母开头:导出(公开)
    • 小写字母开头:未导出(私有)
    • 只有导出的标识符可以被其他包访问
  3. 包的导入

    • 标准导入:import "fmt"
    • 别名导入:import f "fmt"
    • 点导入:import . "fmt"(不推荐)
    • 空白导入:import _ "package"
  4. 包的初始化

    • init()函数在包导入时自动执行
    • 一个包可以有多个init()函数
    • 初始化顺序:常量 → 变量 → init()函数
  5. Go Modules

    • 现代Go依赖管理工具
    • go.mod文件定义模块
    • go.sum文件记录依赖校验和
    • 支持版本管理和依赖解析
  6. 最佳实践

    • 合理的包结构设计
    • 避免循环导入
    • 最小化依赖
    • 使用internal/目录保护内部包
    • 遵循Go的命名约定
updatedupdated2025-09-202025-09-20