🐸 青蛙编程网
首页 / Go / Go 基础 / 8. 综合练习

8. 综合练习:命令行小工具

这是什么:命令行小工具 = 敲一行命令就能用的程序;本章主角是一个带「并发测算」玩法的待办清单(todo)。

为什么用:前七章的变量、struct、接口、错误、并发是散着学的,不拼一次就还是碎片。这一章把它们全拧成一个能跑的小项目——Go 生态的第一块砖。

如何用:照着骨架把 add、list、done 三个命令写出来,再用 goroutine + channel 加上 bench 并发小测。

8.1 功能

go run . add 完成教程写作
go run . list
go run . done 1
go run . bench     ← 并发快乐一下

8.2 数据与命令骨架

// main.go
package main

import (
    "fmt"
    "os"
    "strconv"
)

type Task struct {
    ID   int
    Text string
    Done bool
}

var tasks []Task
var nextID = 1

func main() {
    if len(os.Args) < 2 {          // os.Args[0] 是程序名本身
        usage()
        return
    }

    switch os.Args[1] {
    case "add":
        add(os.Args[2])
    case "list":
        list()
    case "done":
        id, _ := strconv.Atoi(os.Args[2])   // 字符串转 int
        done(id)
    case "bench":
        bench()
    default:
        usage()
    }
}

func usage() {
    fmt.Println("用法: go run . add|list|done <id>|bench")
}

8.3 各命令实现

func add(text string) {
    tasks = append(tasks, Task{ID: nextID, Text: text})
    nextID++
    fmt.Println("已添加:", text)
}

func list() {
    for _, t := range tasks {
        mark := " "
        if t.Done { mark = "✔" }
        fmt.Printf("[%s] %d %s\n", mark, t.ID, t.Text)
    }
}

func done(id int) {
    for i := range tasks {
        if tasks[i].ID == id {
            tasks[i].Done = true
            return
        }
    }
    fmt.Println("未找到任务", id)
}

你会看到:

$ go run . list
[✔] 1 完成教程写作
[ ] 2 复习接口

8.4 bench:并发小测

func bench() {
    var wg sync.WaitGroup
    results := make(chan int)

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            total := 0
            for j := 0; j < n*1_000_000; j++ { total += j }
            results <- total
        }(i)
    }

    go func() { wg.Wait(); close(results) }()

    for r := range results {
        fmt.Println("单线合计:", r)
    }
}

运行体验:5 个 goroutine 并行算完,总体耗时 ≈ 最慢一条,而不是 5 倍相加。

8.5 拓展:数据持久化(选做)

用标准库 encoding/json 把 tasks 写进本地文件(JSON 结构直接对上 struct 标签),下次启动读取:

// 保存:os.WriteFile + json.MarshalIndent
// 加载:os.ReadFile  + json.Unmarshal(&tasks)

完成后你的 CLI 就有了「真数据」,可以写进简历第一个工具了。

8.6 知识对照表

用到 学自
struct + 切片 + range 第 3、5 章
switch 命令分发 第 3 章
os.Args / strconv 第 3、4 章
多返回值习惯、错误检查 第 4、6 章
goroutine + channel + WaitGroup 第 7 章

8.7 小结

恭喜完成 Go 基础全部 8 章!你已经会:

  • 写模块化 Go 程序(包、方法、接口)
  • 用 error 习惯「直线式」错误流
  • 用 goroutine + channel 做并发设计

打个起点——下一课我们用 Gin 把它变成 Web 后端(进入 Go 板块后续)。去 HTML/CSS/JS 修行过的你,转 Go 只是「换一种写法的舞蹈」。

Go 基础完成。下一站回到 Web:开发工具板块总览 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。