命令行里也能记账:用 C++ 写了个待办清单工具

举报
茉莉风铃 发表于 2026/09/16 09:35:42 2026/09/16
【摘要】 学C++不能只刷算法题。本文用标准库 + 文件持久化,从零写一个能在终端跑的命令行待办工具,覆盖参数解析、数据结构、读写文件到 CMake 编译。

学 C++ 的人大多卡在"除了算法题不知道写啥"。其实终端里那些顺手的小工具,用 C++ 写起来特别合适——不需要界面、不需要框架,标准库就够。我前阵子写了个 todo 命令行工具,能加任务、列任务、标记完成,数据存本地文件,平时记点待办、临时的想法都靠它。

image.png

一、先定清楚要做什么

功能不用多,四个就够日常用:

  • todo add "写周报":新增一条任务
  • todo list:列出所有任务,完成的打勾
  • todo done 2:把第 2 条标记完成
  • todo rm 3:删除第 3 条

数据存成纯文本 tasks.txt,每行一条,格式 id|title|done,用 | 分隔。选纯文本而不是 JSON,是因为它可读、能直接 cat 看,排查也方便。

二、核心结构长什么样

先定义一个 Task 结构,再准备读写文件的两个函数:

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>

struct Task {
    int id;
    std::string title;
    bool done;
};

std::vector<Task> loadTasks(const std::string& path) {
    std::vector<Task> tasks;
    std::ifstream in(path);
    std::string line;
    while (std::getline(in, line)) {
        if (line.empty()) continue;
        std::stringstream ss(line);
        std::string idStr, title, doneStr;
        std::getline(ss, idStr, '|');
        std::getline(ss, title, '|');
        std::getline(ss, doneStr, '|');
        tasks.push_back({std::stoi(idStr), title, doneStr == "1"});
    }
    return tasks;
}

void saveTasks(const std::string& path, const std::vector<Task>& tasks) {
    std::ofstream out(path);
    for (const auto& t : tasks) {
        out << t.id << '|' << t.title << '|' << (t.done ? '1' : '0') << '\n';
    }
}

这里用 std::stringstream 按 | 切分,比手写 find + substr 清爽不少。loadTasks 读不到文件时会返回空 vector,所以第一次运行也不会崩。

三、解析参数把命令跑起来

main 里根据 argv[1] 分发命令,逻辑直来直去:

int main(int argc, char* argv[]) {
    const std::string path = "tasks.txt";
    if (argc < 2) {
        std::cout << "用法: todo add|list|done|rm ...\n";
        return 1;
    }
    std::string cmd = argv[1];
    auto tasks = loadTasks(path);

    if (cmd == "add") {
        std::string title = argv[2];
        int newId = tasks.empty() ? 1 : tasks.back().id + 1;
        tasks.push_back({newId, title, false});
        saveTasks(path, tasks);
        std::cout << "已添加 #" << newId << "\n";
    } else if (cmd == "list") {
        for (const auto& t : tasks) {
            std::cout << (t.done ? "[x] " : "[ ] ")
                      << t.id << ". " << t.title << "\n";
        }
    } else if (cmd == "done") {
        int id = std::stoi(argv[2]);
        for (auto& t : tasks) if (t.id == id) t.done = true;
        saveTasks(path, tasks);
    } else if (cmd == "rm") {
        int id = std::stoi(argv[2]);
        tasks.erase(std::remove_if(tasks.begin(), tasks.end(),
            [id](const Task& t){ return t.id == id; }), tasks.end());
        saveTasks(path, tasks);
    }
    return 0;
}

done 和 rm 用线性查找定位 id,任务量小完全够用。等哪天任务上千条了,再换成 std::map 索引也不迟——别提前优化。

四、编译与几个注意点

用 g++ 直接编:

g++ -std=c++17 -O2 todo.cpp -o todo
./todo add "交水电费"

想正经点就用 CMake:

cmake_minimum_required(VERSION 3.10)
project(todo)
set(CMAKE_CXX_STANDARD 17)
add_executable(todo todo.cpp)

几个可能遇到的小坑:

  • 中文参数:Windows 下 argv 默认是本地编码(GBK),直接 std::cout 可能乱码;Linux 一般是 UTF-8 没问题。跨平台要处理得用 std::filesystem 和宽字符那套,简单场景就只在自己机器上跑、别纠结。
  • 文件锁:这个工具是单用户的,没做并发锁。要是两个终端同时写,可能互相覆盖。真要多人用,得加 flock 之类的文件锁。
  • 参数校验:上面代码假定 argv[2] 一定存在。实际用别的命令漏传参数会崩,正式版记得先判 argc。
  • id 重用:删掉中间一条后,id 不会回收。这是有意为之——回收 id 反而容易引起歧义,留空更直观。

结语

一个不到一百行的 C++ 程序,就能在终端里踏踏实实帮你记账、记待办。它没法跟那些花哨的 App 比功能,但胜在零依赖、秒开、数据完全在自己手里。学一门语言,最好的练手方式从来不是再做一遍算法题,而是写个自己每天真用得上的小东西。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。