# 第7章:包和模块
学习目标
通过本章学习,你将掌握: - Go语言包的概念和组织方式 - 模块系统的使用和管理 - 依赖管理的最佳实践 - 包的发布和版本控制 - 工作区和多模块开发
包的基础概念
什么是包
包的定义和作用
package main
import (
"fmt"
"math"
"strings"
"time"
)
func main() {
fmt.Println("=== Go包的基础概念 ===")
// 包是Go代码组织的基本单位
fmt.Println("\n1. 包的作用:")
fmt.Println(" - 代码组织和模块化")
fmt.Println(" - 命名空间管理")
fmt.Println(" - 访问控制")
fmt.Println(" - 代码复用")
fmt.Println("\n2. 标准库包的使用:")
// 使用math包
fmt.Printf(" math.Pi = %.6f\n", math.Pi)
fmt.Printf(" math.Sqrt(16) = %.2f\n", math.Sqrt(16))
// 使用strings包
text := "Hello, Go Programming!"
fmt.Printf(" strings.ToUpper(\"%s\") = %s\n", text, strings.ToUpper(text))
fmt.Printf(" strings.Contains(\"%s\", \"Go\") = %t\n", text, strings.Contains(text, "Go"))
// 使用time包
now := time.Now()
fmt.Printf(" 当前时间: %s\n", now.Format("2006-01-02 15:04:05"))
fmt.Println("\n3. 包的命名规则:")
fmt.Println(" - 小写字母")
fmt.Println(" - 简短且有意义")
fmt.Println(" - 避免下划线和驼峰")
fmt.Println(" - 通常是单数形式")
fmt.Println("\n4. 包的可见性:")
fmt.Println(" - 大写字母开头:公开(exported)")
fmt.Println(" - 小写字母开头:私有(unexported)")
demonstratePackageVisibility()
}
go run package_basics.go
创建自定义包
# 创建一个简单的数学工具包
mkdir -p mathutil
echo 'package mathutil
import "math"
// 公开函数:计算两个数的最大值
func Max(a, b int) int {
if a > b {
return a
}
return b
}
// 公开函数:计算两个数的最小值
func Min(a, b int) int {
if a < b {
return a
}
return b
}
// 公开函数:计算平方根
func Sqrt(x float64) float64 {
return math.Sqrt(x)
}
// 私有函数:内部使用
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
// 公开函数:计算绝对值
func Abs(x int) int {
return abs(x)
}
// 公开常量
const (
Pi = 3.14159265359
E = 2.71828182846
)
// 公开变量
var (
Version = "1.0.0"
Author = "Go Tutorial"
)' > mathutil/mathutil.go
# 创建字符串工具包
mkdir -p stringutil
echo 'package stringutil
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 {
s = strings.ToLower(s)
cleaned := ""
for _, r := range s {
if unicode.IsLetter(r) || unicode.IsDigit(r) {
cleaned += string(r)
}
}
return cleaned == Reverse(cleaned)
}
// 统计单词数量
func WordCount(s string) int {
return len(strings.Fields(s))
}
// 首字母大写
func Capitalize(s string) string {
if len(s) == 0 {
return s
}
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
}
// 私有函数:清理字符串
func cleanString(s string) string {
return strings.TrimSpace(strings.ToLower(s))
}
// 比较字符串(忽略大小写和空格)
func EqualIgnoreCase(a, b string) bool {
return cleanString(a) == cleanString(b)
}' > stringutil/stringutil.go
# 使用自定义包
echo 'package main
import (
"fmt"
"./mathutil"
"./stringutil"
)
func main() {
fmt.Println("=== 使用自定义包 ===")
fmt.Println("\n--- 数学工具包 ---")
fmt.Printf("Max(10, 20) = %d\n", mathutil.Max(10, 20))
fmt.Printf("Min(10, 20) = %d\n", mathutil.Min(10, 20))
fmt.Printf("Abs(-15) = %d\n", mathutil.Abs(-15))
fmt.Printf("Sqrt(25) = %.2f\n", mathutil.Sqrt(25))
fmt.Printf("Pi = %.6f\n", mathutil.Pi)
fmt.Printf("Version = %s\n", mathutil.Version)
fmt.Println("\n--- 字符串工具包 ---")
text := "Hello, World!"
fmt.Printf("原字符串: %s\n", text)
fmt.Printf("反转: %s\n", stringutil.Reverse(text))
fmt.Printf("首字母大写: %s\n", stringutil.Capitalize("hello world"))
fmt.Printf("单词数量: %d\n", stringutil.WordCount("Go is a great programming language"))
// 测试回文
palindromes := []string{"racecar", "A man a plan a canal Panama", "hello"}
for _, p := range palindromes {
fmt.Printf("\"%s\" 是回文: %t\n", p, stringutil.IsPalindrome(p))
}
// 测试字符串比较
fmt.Printf("\"Hello\" == \" HELLO \" (忽略大小写): %t\n",
stringutil.EqualIgnoreCase("Hello", " HELLO "))
fmt.Println("\n=== 包的可见性演示 ===")
fmt.Println("可以访问 mathutil.Max (公开函数)")
fmt.Println("可以访问 mathutil.Pi (公开常量)")
fmt.Println("不能访问 mathutil.abs (私有函数)")
// fmt.Println(mathutil.abs(-5)) // 这行会编译错误
}' > use_custom_packages.go && go run use_custom_packages.go
包的初始化
init函数的使用
# 创建一个演示init函数的包
mkdir -p initdemo
echo 'package initdemo
import (
"fmt"
"time"
)
// 包级别变量
var (
startTime time.Time
counter int
)
// init函数在包被导入时自动执行
func init() {
fmt.Println("initdemo包的第一个init函数执行")
startTime = time.Now()
counter = 0
}
// 可以有多个init函数
func init() {
fmt.Println("initdemo包的第二个init函数执行")
fmt.Printf("包初始化时间: %s\n", startTime.Format("15:04:05.000"))
}
// 公开函数
func GetStartTime() time.Time {
return startTime
}
func IncrementCounter() int {
counter++
return counter
}
func GetCounter() int {
return counter
}
// 包级别的常量和变量初始化
const PackageName = "initdemo"
var PackageVersion = "1.0.0"' > initdemo/init.go
# 创建另一个包演示依赖初始化
mkdir -p logger
echo 'package logger
import (
"fmt"
"os"
"time"
)
var logFile *os.File
func init() {
fmt.Println("logger包初始化开始")
// 创建日志文件
var err error
logFile, err = os.Create("app.log")
if err != nil {
fmt.Printf("创建日志文件失败: %v\n", err)
return
}
fmt.Println("logger包初始化完成")
Log("Logger initialized")
}
func Log(message string) {
if logFile != nil {
timestamp := time.Now().Format("2006-01-02 15:04:05")
logLine := fmt.Sprintf("[%s] %s\n", timestamp, message)
logFile.WriteString(logLine)
logFile.Sync()
fmt.Print(logLine)
}
}
func Close() {
if logFile != nil {
Log("Logger closing")
logFile.Close()
}
}' > logger/logger.go
# 演示包初始化顺序
echo 'package main
import (
"fmt"
"time"
"./initdemo"
"./logger"
)
// main包的init函数
func init() {
fmt.Println("main包的init函数执行")
}
func main() {
fmt.Println("\n=== main函数开始执行 ===")
// 使用initdemo包
fmt.Printf("initdemo包启动时间: %s\n",
initdemo.GetStartTime().Format("15:04:05.000"))
for i := 0; i < 3; i++ {
count := initdemo.IncrementCounter()
logger.Log(fmt.Sprintf("计数器值: %d", count))
time.Sleep(100 * time.Millisecond)
}
fmt.Println("\n=== 包初始化顺序说明 ===")
fmt.Println("1. 依赖包先初始化(logger)")
fmt.Println("2. 然后是被导入的包(initdemo)")
fmt.Println("3. 最后是main包")
fmt.Println("4. 每个包内的init函数按声明顺序执行")
fmt.Println("5. 同一个包的多个init函数按出现顺序执行")
// 清理
logger.Close()
fmt.Println("\n=== 查看日志文件 ===")
}' > init_demo.go && go run init_demo.go && echo "\n日志文件内容:" && cat app.log
Go模块系统
模块的基本概念
创建和初始化模块
package main
import "fmt"
func main() {
fmt.Println("=== Go模块系统介绍 ===")
fmt.Println("\n1. 什么是Go模块:")
fmt.Println(" - Go 1.11引入的依赖管理系统")
fmt.Println(" - 替代GOPATH模式")
fmt.Println(" - 版本化的依赖管理")
fmt.Println(" - 可重现的构建")
fmt.Println("\n2. 模块的组成:")
fmt.Println(" - go.mod文件:模块定义和依赖声明")
fmt.Println(" - go.sum文件:依赖的校验和")
fmt.Println(" - 源代码文件")
fmt.Println("\n3. 模块的优势:")
fmt.Println(" - 版本管理")
fmt.Println(" - 依赖隔离")
fmt.Println(" - 可重现构建")
fmt.Println(" - 更好的安全性")
fmt.Println("\n4. 常用命令:")
fmt.Println(" go mod init - 初始化模块")
fmt.Println(" go mod tidy - 整理依赖")
fmt.Println(" go mod download - 下载依赖")
fmt.Println(" go mod verify - 验证依赖")
fmt.Println(" go mod graph - 显示依赖图")
}
go run module_intro.go
创建第一个模块
# 创建一个新的模块项目
mkdir -p myproject
cd myproject
# 初始化模块
go mod init github.com/example/myproject
echo '模块初始化完成,查看go.mod文件:'
cat go.mod
# 创建主程序
echo 'package main
import (
"fmt"
"github.com/example/myproject/calculator"
"github.com/example/myproject/utils"
)
func main() {
fmt.Println("=== 我的第一个Go模块 ===")
// 使用calculator包
result := calculator.Add(10, 20)
fmt.Printf("10 + 20 = %d\n", result)
result = calculator.Multiply(5, 6)
fmt.Printf("5 * 6 = %d\n", result)
// 使用utils包
numbers := []int{1, 2, 3, 4, 5}
max := utils.FindMax(numbers)
fmt.Printf("数组 %v 的最大值: %d\n", numbers, max)
text := "Hello, Go Modules!"
reversed := utils.ReverseString(text)
fmt.Printf("反转 \"%s\": %s\n", text, reversed)
}' > main.go
# 创建calculator包
mkdir -p calculator
echo 'package calculator
// Add 计算两个整数的和
func Add(a, b int) int {
return a + b
}
// Subtract 计算两个整数的差
func Subtract(a, b int) int {
return a - b
}
// Multiply 计算两个整数的积
func Multiply(a, b int) int {
return a * b
}
// Divide 计算两个整数的商
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}' > calculator/calculator.go
# 修复calculator包的导入
echo 'package calculator
import "fmt"
// Add 计算两个整数的和
func Add(a, b int) int {
return a + b
}
// Subtract 计算两个整数的差
func Subtract(a, b int) int {
return a - b
}
// Multiply 计算两个整数的积
func Multiply(a, b int) int {
return a * b
}
// Divide 计算两个整数的商
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}' > calculator/calculator.go
# 创建utils包
mkdir -p utils
echo 'package utils
// FindMax 找到整数切片中的最大值
func FindMax(numbers []int) int {
if len(numbers) == 0 {
return 0
}
max := numbers[0]
for _, num := range numbers[1:] {
if num > max {
max = num
}
}
return max
}
// FindMin 找到整数切片中的最小值
func FindMin(numbers []int) int {
if len(numbers) == 0 {
return 0
}
min := numbers[0]
for _, num := range numbers[1:] {
if num < min {
min = num
}
}
return min
}
// ReverseString 反转字符串
func ReverseString(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)
}
// Sum 计算整数切片的和
func Sum(numbers []int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}' > utils/utils.go
# 运行程序
echo '\n运行模块程序:'
go run main.go
# 查看模块信息
echo '\n查看go.mod文件:'
cat go.mod
echo '\n模块结构:'
find . -name "*.go" -o -name "go.mod" | sort
cd ..
依赖管理
添加外部依赖
# 创建一个使用外部依赖的项目
mkdir -p webdemo
cd webdemo
# 初始化模块
go mod init github.com/example/webdemo
# 创建一个简单的Web服务器
echo 'package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
type Response struct {
Message string `json:"message"`
Timestamp time.Time `json:"timestamp"`
Status string `json:"status"`
}
func main() {
// 配置logrus
logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.Info("启动Web服务器")
// 创建路由器
r := mux.NewRouter()
// 定义路由
r.HandleFunc("/", homeHandler).Methods("GET")
r.HandleFunc("/api/status", statusHandler).Methods("GET")
r.HandleFunc("/api/time", timeHandler).Methods("GET")
// 添加中间件
r.Use(loggingMiddleware)
fmt.Println("服务器运行在 http://localhost:8080")
fmt.Println("可用端点:")
fmt.Println(" GET /")
fmt.Println(" GET /api/status")
fmt.Println(" GET /api/time")
log.Fatal(http.ListenAndServe(":8080", r))
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
response := Response{
Message: "欢迎使用Go模块Web演示",
Timestamp: time.Now(),
Status: "success",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func statusHandler(w http.ResponseWriter, r *http.Request) {
response := Response{
Message: "服务器运行正常",
Timestamp: time.Now(),
Status: "healthy",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func timeHandler(w http.ResponseWriter, r *http.Request) {
now := time.Now()
response := Response{
Message: fmt.Sprintf("当前时间: %s", now.Format("2006-01-02 15:04:05")),
Timestamp: now,
Status: "success",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
logrus.WithFields(logrus.Fields{
"method": r.Method,
"url": r.URL.Path,
"duration": time.Since(start),
"remote": r.RemoteAddr,
}).Info("HTTP请求")
})
}' > main.go
# 添加依赖
echo '添加gorilla/mux依赖...'
go get github.com/gorilla/mux
echo '添加logrus依赖...'
go get github.com/sirupsen/logrus
# 查看依赖
echo '\n查看go.mod文件:'
cat go.mod
echo '\n查看go.sum文件:'
cat go.sum
# 整理依赖
go mod tidy
echo '\n整理后的go.mod文件:'
cat go.mod
cd ..
依赖版本管理
package main
import (
"fmt"
"os/exec"
"strings"
)
func main() {
fmt.Println("=== Go模块依赖版本管理 ===")
fmt.Println("\n1. 语义化版本控制 (SemVer):")
fmt.Println(" 格式: MAJOR.MINOR.PATCH")
fmt.Println(" - MAJOR: 不兼容的API变更")
fmt.Println(" - MINOR: 向后兼容的功能新增")
fmt.Println(" - PATCH: 向后兼容的问题修复")
fmt.Println("\n2. 版本选择规则:")
fmt.Println(" - 最小版本选择 (MVS)")
fmt.Println(" - 选择满足所有约束的最小版本")
fmt.Println(" - 确保构建的可重现性")
fmt.Println("\n3. 版本约束语法:")
fmt.Println(" v1.2.3 - 精确版本")
fmt.Println(" v1.2 - 最新的v1.2.x")
fmt.Println(" v1 - 最新的v1.x.x")
fmt.Println(" latest - 最新版本")
fmt.Println(" upgrade - 升级到兼容的最新版本")
fmt.Println(" patch - 升级到最新的补丁版本")
fmt.Println("\n4. 常用版本管理命令:")
commands := []struct {
cmd string
desc string
}{
{"go list -m all", "列出所有依赖及其版本"},
{"go list -m -versions github.com/gorilla/mux", "列出包的所有可用版本"},
{"go get github.com/gorilla/mux@v1.8.0", "获取特定版本"},
{"go get github.com/gorilla/mux@latest", "获取最新版本"},
{"go get github.com/gorilla/mux@upgrade", "升级到兼容的最新版本"},
{"go get github.com/gorilla/mux@patch", "升级到最新补丁版本"},
{"go mod why github.com/gorilla/mux", "解释为什么需要这个依赖"},
{"go mod graph", "显示依赖关系图"},
}
for _, cmd := range commands {
fmt.Printf(" %-45s - %s\n", cmd.cmd, cmd.desc)
}
fmt.Println("\n5. 依赖升级策略:")
fmt.Println(" - 定期检查依赖更新")
fmt.Println(" - 优先升级安全补丁")
fmt.Println(" - 谨慎升级主版本")
fmt.Println(" - 充分测试后再升级")
fmt.Println("\n6. 版本冲突解决:")
fmt.Println(" - Go使用最小版本选择")
fmt.Println(" - 手动指定版本解决冲突")
fmt.Println(" - 使用replace指令临时替换")
demonstrateVersionCommands()
}
func demonstrateVersionCommands() {
fmt.Println("\n=== 演示版本管理命令 ===")
// 检查是否在模块目录中
if !isInModule() {
fmt.Println("注意:以下命令需要在Go模块目录中执行")
return
}
// 执行一些安全的命令
commands := []string{
"go list -m all",
"go mod graph",
}
for _, cmd := range commands {
fmt.Printf("\n执行: %s\n", cmd)
output, err := exec.Command("sh", "-c", cmd).Output()
if err != nil {
fmt.Printf("错误: %v\n", err)
} else {
lines := strings.Split(string(output), "\n")
for i, line := range lines {
if i < 10 && line != "" { // 只显示前10行
fmt.Printf(" %s\n", line)
}
}
if len(lines) > 10 {
fmt.Printf(" ... (还有 %d 行)\n", len(lines)-10)
}
}
}
}
func isInModule() bool {
_, err := exec.Command("go", "list", "-m").Output()
return err == nil
}
go run version_management.go
模块发布和版本控制
准备发布模块
# 创建一个可发布的模块
mkdir -p publishdemo
cd publishdemo
# 初始化模块
go mod init github.com/example/publishdemo
# 创建README文件
echo '# PublishDemo
一个演示Go模块发布的示例项目。
## 功能
- 字符串处理工具
- 数学计算工具
- 时间处理工具
## 安装
```bash
go get github.com/example/publishdemo
使用示例
package main
import (
"fmt"
"github.com/example/publishdemo/stringtools"
"github.com/example/publishdemo/mathtools"
)
func main() {
// 使用字符串工具
reversed := stringtools.Reverse("Hello")
fmt.Println(reversed) // olleH
// 使用数学工具
result := mathtools.Factorial(5)
fmt.Println(result) // 120
}
版本历史
- v1.0.0: 初始版本
- v1.1.0: 添加数学工具
- v1.2.0: 添加时间工具
许可证
MIT License’ > README.md
创建许可证文件
echo ‘MIT License
Copyright © 2024 Example
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.’ > LICENSE
创建字符串工具包
mkdir -p stringtools echo ‘package stringtools
import ( “strings” “unicode” )
// Reverse 反转字符串 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) }
// IsPalindrome 检查是否为回文 func IsPalindrome(s string) bool { cleaned := “” for _, r := range strings.ToLower(s) { if unicode.IsLetter® || unicode.IsDigit® { cleaned += string® } } return cleaned == Reverse(cleaned) }
// WordCount 统计单词数量 func WordCount(s string) int { return len(strings.Fields(s)) }
// Capitalize 首字母大写 func Capitalize(s string) string { if len(s) == 0 { return s } return strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) }’ > stringtools/stringtools.go
创建数学工具包
mkdir -p mathtools echo ‘package mathtools
import “errors”
// Factorial 计算阶乘 func Factorial(n int) int { if n < 0 { return 0 } if n <= 1 { return 1 } return n * Factorial(n-1) }
// GCD 计算最大公约数 func GCD(a, b int) int { for b != 0 { a, b = b, a%b } return a }
// LCM 计算最小公倍数 func LCM(a, b int) int { return a * b / GCD(a, b) }
// IsPrime 检查是否为质数 func IsPrime(n int) bool { if n < 2 { return false } for i := 2; i*i <= n; i++ { if n%i == 0 { return false } } return true }
// Power 计算幂 func Power(base, exp int) (int, error) { if exp < 0 { return 0, errors.New(“指数不能为负数”) } result := 1 for i := 0; i < exp; i++ { result *= base } return result, nil }’ > mathtools/mathtools.go
创建时间工具包
mkdir -p timetools echo ‘package timetools
import ( “fmt” “time” )
// FormatDuration 格式化持续时间 func FormatDuration(d time.Duration) string { if d < time.Minute { return fmt.Sprintf(“%.1f秒”, d.Seconds()) } else if d < time.Hour { return fmt.Sprintf(“%.1f分钟”, d.Minutes()) } else if d < 24*time.Hour { return fmt.Sprintf(“%.1f小时”, d.Hours()) } else { return fmt.Sprintf(“%.1f天”, d.Hours()/24) } }
// IsWeekend 检查是否为周末 func IsWeekend(t time.Time) bool { weekday := t.Weekday() return weekday == time.Saturday || weekday == time.Sunday }
// NextWorkday 获取下一个工作日 func NextWorkday(t time.Time) time.Time { next := t.AddDate(0, 0, 1) for IsWeekend(next) { next = next.AddDate(0, 0, 1) } return next }
// DaysBetween 计算两个日期之间的天数 func DaysBetween(start, end time.Time) int { duration := end.Sub(start) return int(duration.Hours() / 24) }’ > timetools/timetools.go
创建示例程序
echo ‘package main
import ( “fmt” “time”
"github.com/example/publishdemo/stringtools"
"github.com/example/publishdemo/mathtools"
"github.com/example/publishdemo/timetools"
)
func main() { fmt.Println(“=== PublishDemo 模块示例 ===”)
fmt.Println("\n--- 字符串工具 ---")
text := "Hello, World!"
fmt.Printf("原文: %s\n", text)
fmt.Printf("反转: %s\n", stringtools.Reverse(text))
fmt.Printf("单词数: %d\n", stringtools.WordCount(text))
fmt.Printf("首字母大写: %s\n", stringtools.Capitalize("hello world"))
palindromes := []string{"racecar", "hello", "A man a plan a canal Panama"}
for _, p := range palindromes {
fmt.Printf("\"%s\" 是回文: %t\n", p, stringtools.IsPalindrome(p))
}
fmt.Println("\n--- 数学工具 ---")
fmt.Printf("5的阶乘: %d\n", mathtools.Factorial(5))
fmt.Printf("12和18的最大公约数: %d\n", mathtools.GCD(12, 18))
fmt.Printf("12和18的最小公倍数: %d\n", mathtools.LCM(12, 18))
numbers := []int{17, 18, 19, 20, 21}
for _, n := range numbers {
fmt.Printf("%d 是质数: %t\n", n, mathtools.IsPrime(n))
}
if power, err := mathtools.Power(2, 10); err == nil {
fmt.Printf("2的10次方: %d\n", power)
}
fmt.Println("\n--- 时间工具 ---")
now := time.Now()
fmt.Printf("当前时间: %s\n", now.Format("2006-01-02 15:04:05"))
fmt.Printf("是否为周末: %t\n", timetools.IsWeekend(now))
nextWorkday := timetools.NextWorkday(now)
fmt.Printf("下一个工作日: %s\n", nextWorkday.Format("2006-01-02"))
duration := 2*time.Hour + 30*time.Minute
fmt.Printf("格式化持续时间: %s\n", timetools.FormatDuration(duration))
start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC)
days := timetools.DaysBetween(start, end)
fmt.Printf("2024-01-01 到 2024-01-15 相差 %d 天\n", days)
}’ > example/main.go
创建example目录
mkdir -p example mv example/main.go example/
创建测试文件
echo ‘package stringtools
import “testing”
func TestReverse(t *testing.T) { tests := []struct { input string expected string }{ {“hello”, “olleh”}, {“world”, “dlrow”}, {“”, “”}, {“a”, “a”}, {“Go语言”, “言语oG”}, }
for _, test := range tests {
result := Reverse(test.input)
if result != test.expected {
t.Errorf("Reverse(%q) = %q, expected %q", test.input, result, test.expected)
}
}
}
func TestIsPalindrome(t *testing.T) { tests := []struct { input string expected bool }{ {“racecar”, true}, {“hello”, false}, {“A man a plan a canal Panama”, true}, {“”, true}, {“Madam”, true}, }
for _, test := range tests {
result := IsPalindrome(test.input)
if result != test.expected {
t.Errorf("IsPalindrome(%q) = %t, expected %t", test.input, result, test.expected)
}
}
}’ > stringtools/stringtools_test.go
运行测试
echo ‘\n运行测试:’ go test ./…
查看模块结构
echo ‘\n模块结构:’ find . -type f | grep -E ‘.(go|md|mod|sum)$’ | sort
echo ‘\n=== 模块发布准备清单 ===” echo ‘✓ README.md - 项目说明’ echo ‘✓ LICENSE - 许可证’ echo ‘✓ go.mod - 模块定义’ echo ‘✓ 源代码文件’ echo ‘✓ 测试文件’ echo ‘✓ 示例代码’ echo ‘✓ 文档注释’
echo ‘\n发布步骤:’ echo ‘1. 创建Git仓库并推送代码’ echo ‘2. 创建版本标签 (git tag v1.0.0)’ echo ‘3. 推送标签 (git push origin v1.0.0)’ echo ‘4. Go模块代理会自动索引’ echo ‘5. 用户可以通过 go get 安装’
cd ..
## 工作区和多模块开发
### Go工作区
#### 创建和使用工作区
```bash
# 创建工作区演示
mkdir -p workspace-demo
cd workspace-demo
# 初始化工作区
go work init
echo '工作区初始化完成,查看go.work文件:'
cat go.work
# 创建第一个模块
mkdir -p module-a
cd module-a
go mod init example.com/module-a
echo 'package main
import (
"fmt"
"example.com/module-a/greetings"
)
func main() {
fmt.Println("=== 模块A ===")
message := greetings.Hello("World")
fmt.Println(message)
messages := greetings.HelloMultiple([]string{"Alice", "Bob", "Charlie"})
for _, msg := range messages {
fmt.Println(msg)
}
}' > main.go
mkdir -p greetings
echo 'package greetings
import (
"fmt"
"math/rand"
"time"
)
// Hello 返回问候语
func Hello(name string) string {
messages := []string{
"Hi, %s! Welcome!",
"Great to see you, %s!",
"Hail, %s! Well met!",
}
rand.Seed(time.Now().UnixNano())
return fmt.Sprintf(messages[rand.Intn(len(messages))], name)
}
// HelloMultiple 返回多个问候语
func HelloMultiple(names []string) []string {
var messages []string
for _, name := range names {
message := Hello(name)
messages = append(messages, message)
}
return messages
}' > greetings/greetings.go
cd ..
# 创建第二个模块
mkdir -p module-b
cd module-b
go mod init example.com/module-b
echo 'package main
import (
"fmt"
"example.com/module-b/calculator"
"example.com/module-b/utils"
)
func main() {
fmt.Println("\n=== 模块B ===")
// 使用计算器
result := calculator.Add(10, 20)
fmt.Printf("10 + 20 = %d\n", result)
result = calculator.Multiply(5, 6)
fmt.Printf("5 * 6 = %d\n", result)
// 使用工具函数
numbers := []int{1, 5, 3, 9, 2, 8}
fmt.Printf("数组: %v\n", numbers)
fmt.Printf("最大值: %d\n", utils.Max(numbers))
fmt.Printf("最小值: %d\n", utils.Min(numbers))
fmt.Printf("平均值: %.2f\n", utils.Average(numbers))
}' > main.go
mkdir -p calculator
echo 'package calculator
// Add 加法
func Add(a, b int) int {
return a + b
}
// Subtract 减法
func Subtract(a, b int) int {
return a - b
}
// Multiply 乘法
func Multiply(a, b int) int {
return a * b
}
// Divide 除法
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}' > calculator/calculator.go
# 修复导入
echo 'package calculator
import "fmt"
// Add 加法
func Add(a, b int) int {
return a + b
}
// Subtract 减法
func Subtract(a, b int) int {
return a - b
}
// Multiply 乘法
func Multiply(a, b int) int {
return a * b
}
// Divide 除法
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}' > calculator/calculator.go
mkdir -p utils
echo 'package utils
// Max 找到最大值
func Max(numbers []int) int {
if len(numbers) == 0 {
return 0
}
max := numbers[0]
for _, num := range numbers[1:] {
if num > max {
max = num
}
}
return max
}
// Min 找到最小值
func Min(numbers []int) int {
if len(numbers) == 0 {
return 0
}
min := numbers[0]
for _, num := range numbers[1:] {
if num < min {
min = num
}
}
return min
}
// Average 计算平均值
func Average(numbers []int) float64 {
if len(numbers) == 0 {
return 0
}
sum := 0
for _, num := range numbers {
sum += num
}
return float64(sum) / float64(len(numbers))
}' > utils/utils.go
cd ..
# 将模块添加到工作区
go work use ./module-a
go work use ./module-b
echo '\n查看更新后的go.work文件:'
cat go.work
# 创建使用两个模块的主程序
echo 'package main
import (
"fmt"
"example.com/module-a/greetings"
"example.com/module-b/calculator"
"example.com/module-b/utils"
)
func main() {
fmt.Println("=== 工作区演示:使用多个模块 ===")
// 使用模块A的功能
fmt.Println("\n--- 来自模块A的问候 ---")
greeting := greetings.Hello("工作区用户")
fmt.Println(greeting)
// 使用模块B的功能
fmt.Println("\n--- 来自模块B的计算 ---")
sum := calculator.Add(15, 25)
fmt.Printf("15 + 25 = %d\n", sum)
numbers := []int{10, 20, 30, 40, 50}
fmt.Printf("数组 %v 的最大值: %d\n", numbers, utils.Max(numbers))
fmt.Printf("数组 %v 的平均值: %.2f\n", numbers, utils.Average(numbers))
fmt.Println("\n=== 工作区的优势 ===")
fmt.Println("1. 同时开发多个相关模块")
fmt.Println("2. 本地模块间的依赖解析")
fmt.Println("3. 统一的构建和测试")
fmt.Println("4. 简化的开发工作流")
}' > main.go
# 运行工作区程序
echo '\n运行工作区程序:'
go run main.go
# 运行各个模块
echo '\n运行模块A:'
cd module-a && go run main.go && cd ..
echo '\n运行模块B:'
cd module-b && go run main.go && cd ..
# 显示工作区结构
echo '\n工作区结构:'
find . -name "*.go" -o -name "go.work" -o -name "go.mod" | sort
cd ..
总结
本章详细介绍了Go语言的包和模块系统,包括:
- 包的基础概念 - 包的定义、命名规则和可见性
- 自定义包创建 - 如何创建和使用自定义包
- 包的初始化 - init函数的使用和执行顺序
- Go模块系统 - 现代化的依赖管理方式
- 依赖管理 - 版本控制和依赖解析
- 模块发布 - 如何准备和发布Go模块
- 工作区 - 多模块开发的最佳实践
关键要点
- 包是Go代码组织的基本单位
- 使用大小写控制包成员的可见性
- Go模块提供了版本化的依赖管理
- 语义化版本控制确保兼容性
- 工作区支持多模块协同开发
- 良好的文档和测试是模块发布的关键
下一步
下一章将学习Go语言的错误处理,了解如何优雅地处理程序中的错误情况。