15·工程化入门

包与模块

packagego-modimportexport

第15课 - 包与模块

学习目标

  1. 理解包(Package)的基本概念和作用,明白为什么需要将代码组织成包。
  2. 掌握使用Go Modules进行依赖管理,理解go.modgo.sum文件的作用。
  3. 学会如何创建、导入和使用自定义包,实现代码的模块化。
  4. 深刻理解Go的导出(Export)规则,掌握哪些标识符可以被其他包访问。
  5. 能够初始化项目模块,并管理第三方依赖

核心概念

1. 什么是包(Package)?

想象一下你的工具箱。你会把螺丝刀、扳手等放在不同的格子里,而不是全部扔在一起。在Go中,就是这样的“格子”。它是一组相关的Go源文件(.go文件)的集合,放在同一个目录下。主要作用是代码组织复用

  • 每个Go文件都必须以package <包名>声明开头。
  • 同一个目录下的所有.go文件必须属于同一个包(除了main包)。
  • 一个可执行程序必须有一个main包,并包含一个main函数作为入口。

2. 什么是模块(Module)与Go Modules?

模块是Go 1.11引入的包管理工具,用于解决依赖版本问题。你可以把模块理解为一个或多个包的集合,拥有独立的版本号。

  • go.mod文件:模块的“身份证”和“购物清单”。它记录了模块名、Go版本、依赖的第三方包及其版本。
  • go.sum文件:记录每个依赖包的哈希值,确保代码一致性(安全校验)。

3. 导出规则

这是Go非常重要的一个设计。要让一个函数、变量、类型等可以被其他包使用(即“导出”),它的名字必须以大写字母开头

  • 大写开头func Add() -> 可以被其他包导入使用(package.Add())。
  • 小写开头func add() -> 仅在定义它的包内部可见(私有)。

代码示例

我们将创建一个简单的计算器程序,演示如何将加法功能抽离到一个独立的包中。

项目结构:

my-calculator/
├── go.mod
├── main.go
└── calc/
    └── calculator.go

步骤1:初始化模块 在项目根目录 my-calculator/ 下打开终端,执行:

go mod init my-calculator

这会生成go.mod文件。

步骤2:创建calc 创建 calc/calculator.go 文件:

// 文件:calc/calculator.go
// 这个文件属于 `calc` 包
package calc

// Add 函数:将两个整数相加并返回结果。
// 注意:函数名 `Add` 首字母大写,所以它是导出的,可以被其他包(如main包)使用。
func Add(a, b int) int {
    return a + b
}

// add 函数:同上,但未导出(小写开头)。
// 这个函数只能在 calc 包内部被调用。
func add(a, b int) int {
    return a + b
}

步骤3:在main包中使用calc 创建 main.go 文件:

// 文件:main.go
// 这个文件属于 `main` 包,是程序的入口
package main

import (
    "fmt"
    // 导入我们自己的 calc 包。
    // 导入路径是相对于 `go.mod` 文件中定义的模块名的路径。
    // 模块名是 `my-calculator`,calc包在其下的`calc`目录,所以路径是 `my-calculator/calc`。
    "my-calculator/calc"
)

func main() {
    result := calc.Add(10, 20) // 正确:调用导出的 Add 函数
    fmt.Println("10 + 20 =", result)

    // 以下代码会报错,因为 `add` 函数是未导出的(小写开头)
    // result2 := calc.add(5, 3) // 编译错误:calc.add undefined
}

运行结果:

$ go run main.go
10 + 20 = 30

实践练习

练习1:导入标准库包

要求: 编写一个程序,同时使用 fmtmath 包,计算一个圆的面积(π * r²)并打印结果。输入半径 r5预期输出: 圆的面积: 78.53981633974483

练习2:创建并使用自定义包

要求: 在项目中创建一个名为 stringutil 的包,其中包含一个导出的函数 Reverse(s string) string,该函数返回字符串 s 的反转形式(例如 "hello" -> "olleh")。然后在 main 包中调用此函数。 提示: 模块名沿用上一个例子的 my-calculator

练习3:处理导入错误

要求: 假设你的模块名是 my-project,你有一个包位于 my-project/utils 目录下。写出在 main.go 中正确导入该包的语句。如果导入路径写成了 utils(没有模块名前缀),会发生什么?请尝试并描述错误信息。

常见错误

  1. 忘记初始化模块:在项目目录下直接运行go run,没有go.mod文件,导致无法识别自定义包路径。

    • 错误信息示例package my-calculator/calc is not in GOROOT ...
    • 解决:先执行 go mod init <模块名>
  2. 目录名与包名不一致:Go文件中的package声明必须与文件所在目录名一致(main包除外)。

    • 错误信息示例expected 'package' keyword
    • 解决:确保 calc/calculator.go 文件顶部是 package calc,而不是其他名称。
  3. 导入未使用的包:Go编译器不允许导入未使用的包,这会导致编译失败。

    • 错误信息示例imported and not used: "fmt"
    • 解决:要么使用它,要么删除导入语句,或使用 _ 作为空标识符(如 _ "fmt")进行副作用导入。
  4. 试图访问未导出的标识符:尝试从外部包调用小写字母开头的函数或变量。

    • 错误信息示例undefined: calc.add
    • 解决:将需要外部访问的标识符首字母改为大写。

小结

  • 包(Package) 是Go代码的基本组织单元,实现了代码的封装复用
  • 模块(Module) 是Go的依赖管理方案,由 go.mod 文件定义,是包的集合。
  • 使用 go mod init <模块名> 初始化新项目模块。
  • 导入路径:标准库直接写包名(如"fmt");自定义包和第三方包使用模块名/子目录路径(如"my-calculator/calc")。
  • 导出规则大写开头的标识符(函数、变量、类型等)是公开的,可被其他包访问;小写开头的是私有的,仅限包内使用。
  • 良好的包组织能使你的项目结构清晰,易于维护和协作。接下来,我们将学习如何对这些包进行测试。

练习编辑器

go
Loading...

继续学习

完成本课后,建议继续学习下一课「单元测试」 以巩固所学知识。