这学期 C++ 课设要求做一个完整的桌面应用。我选择做一个通讯录管理系统, 从命令行版本做起,逐步加入了多态架构、文件持久化、CSV 导入导出等功能。 这篇文章记录整个设计和实现过程。

需求梳理

在动手之前,先把核心需求列清楚:

类体系设计 — 多态是核心

如果只是存姓名和电话,一个 struct 就够了。但不同联系人类型有不同字段—— 同学有"学校",同事有"公司",亲戚有"关系"——这就需要用继承 + 多态来设计。

// 抽象基类
class Person {
protected:
    string name, birthDate, phone, email, customGroup;
public:
    virtual void display() const = 0;        // 纯虚函数
    virtual void saveToStream(ostream&) const = 0;
};

// 派生类
class Classmate : public Person {
    string school;           // 同学特有字段
};

class Colleague : public Person {
    string company;          // 同事特有字段
};

class Friend : public Person {
    string hobby;            // 朋友特有字段
};

class Relative : public Person {
    string relationship;     // 亲戚特有字段
};
💡 设计要点:
基类 Person 持有所有类型的共性问题(姓名、电话等), 派生类各自扩展特有问题(学校、公司等)。 display()saveToStream() 声明为纯虚函数, 强制每个派生类实现自己的展示和存储逻辑——这就是多态的核心。

数据存储 — 文件持久化

不能让数据在程序关闭后消失。最简单的持久化方案就是按类型分文件存储: 每种联系人类型各自一个文本文件,格式统一。

// 文件格式(以 Classmate 为例)
// classmates.txt
张三 | 2003-05-12 | 13800138000 | zhang@mail.com | 深圳大学 | 同学群
李四 | 2002-11-08 | 13900139000 | li@mail.com   | 清华大学 | 项目组

用管道符 | 分隔字段,简单直接。 启动时自动读取所有文件,退出时自动保存——用户完全感知不到文件操作。

CSV 导入导出 — 打通外部数据

如果只能手动录入,这个系统的实用性就大打折扣。 加了 CSV 导入导出后,可以从 Excel 导出的通讯录直接导入,也可以把数据导出去备份。

void AddressBook::exportToCSV(const string& filename) const {
    ofstream f(filename);
    f << "类型,姓名,生日,电话,邮箱,分组,特有信息\n";
    for (auto* p : contacts) {
        f << p->getCategory() << ",";
        f << p->getName() << ",";
        f << p->getBirthDate() << ",";
        // ... 各字段
        p->saveToStream(f);  // 多态调用
    }
}

功能模块一览

整个系统共有 13 个功能选项:

  1. 添加联系人 — 选择类型,逐字段输入,自动校验格式
  2. 删除联系人 — 按姓名精确删除
  3. 修改联系人 — 找到后逐字段修改
  4. 搜索联系人 — 支持模糊匹配,输入"张"能找到所有张姓联系人
  5. 显示全部 — 列表形式展示所有联系人
  6. 按分类显示 — 分别展示同学、同事、朋友、亲戚
  7. 按姓名排序 — 升序排列
  8. 按生日排序 — 按月份和日期排序,方便看谁快过生日了
  9. 生日提醒 — 自动检测未来 5 天内过生日的联系人,支持发邮件祝福
  10. 月度统计 — 统计每个月有多少人生日
  11. 按分组显示 — 支持自定义分组标签(如"项目组"、"跑友群")
  12. 导出 CSV — 一键备份所有数据
  13. 导入 CSV — 批量导入,自动识别联系人类型

控制台美化 — 彩色界面

纯黑白控制台太单调了,加了一个简单的颜色工具:

// ConsoleUtils.h
void colorPrint(int color, const string& text) {
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
    cout << text;
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
}

菜单边框用青色绘制,选项用黄色,错误提示用红色——虽然只是控制台程序, 但视觉上的区分能大大提升使用体验。

遇到的坑

坑 1:内存管理

vector<Person*> 存联系人指针,必须在析构时手动释放所有对象。 最早忘了写 AddressBook 的析构函数,导致内存泄漏。 Valgrind 跑一遍才发现漏了几十个对象。

坑 2:CSV 中文字段

Windows 下导出的 CSV 用 GBK 编码,而我的程序默认用 UTF-8 写入。 导致导入 Excel 导出的 CSV 时中文全是乱码。解决方法是在读取时检测 BOM 头并做编码转换。

坑 3:cin 缓冲区残留

混用 cin >>getline() 时,缓冲区里的换行符经常捣乱, 导致 getline 读到空字符串。解决方案是每次 cin >> 后 加一句 cin.ignore() 清掉缓冲。

这个项目让我真正理解了面向对象的三大特性——封装、继承、多态,不是背概念,而是在写代码的过程中自然体会到的。

后续改进方向

一、图形化界面(GUI)方案

命令行虽然高效,但对普通用户不够友好。将系统迁移到图形界面,有三种主流选择:

方案 A:Qt(C++ 原生,推荐)

Qt 是 C++ 最成熟的 GUI 框架,天然适合这个项目——不需要改业务逻辑,只替换 UI 层。

// 用 Qt 的 Model/View 架构重构
class ContactTableModel : public QAbstractTableModel {
    AddressBook* book;  // 复用现有逻辑
public:
    int rowCount(...) const { return book->getSize(); }
    QVariant data(...) const { /* 从 Person 读取字段 */ }
};

// 主窗口
class MainWindow : public QMainWindow {
    QTableView* tableView;
    ContactTableModel* model;
    // 工具栏:添加、删除、搜索、导出...
};

方案 B:Electron + Web 前端

用 HTML/CSS/JS 做界面,C++ 编译成后端服务或 Node.js 插件。界面漂亮且开发快。

方案 C:imgui(轻量级)

Dear ImGui 是一个即时模式 GUI 库,几行代码就能出界面,适合工具类软件。

🎯 推荐路线:
先上 Qt —— 用 Qt Designer 拖出主界面,用 TableView 展示联系人列表, 用 Form 做编辑弹窗。现有的 AddressBookPerson 类可以完整复用, 只在 UI 层做替换。预计 2-3 天能出第一版。

二、数据层升级

SQLite 替代文本文件

当前每个联系人类型一个 txt 文件,查询和搜索效率低。换成 SQLite:

// SQLite 表设计
CREATE TABLE contacts (
    id        INTEGER PRIMARY KEY AUTOINCREMENT,
    type      TEXT NOT NULL,        -- 'classmate'/'colleague'/'friend'/'relative'
    name      TEXT NOT NULL,
    birthdate TEXT,
    phone     TEXT,
    email     TEXT,
    group_tag TEXT,
    extra     TEXT                 -- JSON: 存每种类型的特有字段
);

// 搜索变得简单
SELECT * FROM contacts WHERE name LIKE '%张%' ORDER BY name;

三、功能扩展

1. 通讯录合并与去重

用户可能从手机、微信、QQ、Excel 多个来源导入联系人,难免有重复。实现智能去重:

2. 标签系统

当前只有单一"分组"字段。升级为多标签体系:一个联系人可以有多个标签——如 #大学 #羽毛球 #前同事,支持按标签筛选和统计。

3. 生日提醒升级

4. 名片识别

拍一张名片,OCR 识别姓名、电话、公司、邮箱,自动填充到通讯录中。可以用 Tesseract OCR 或百度 OCR API。

5. 数据可视化面板

Qt 有 QtCharts 模块可以原生实现,也可以导出数据用前端图表库展示。

四、网络同步

1. WebDAV / 云盘同步

把 SQLite 数据库文件存到坚果云、OneDrive 等同步盘,多台电脑自动同步。实现简单,零成本。

2. 自建同步服务器

用 Go 或 Python 写一个简单的 REST API 服务:

GET    /api/contacts          # 获取全部联系人
POST   /api/contacts          # 新增联系人
PUT    /api/contacts/:id      # 更新联系人
DELETE /api/contacts/:id      # 删除联系人
POST   /api/sync              # 增量同步(传时间戳,只返回变更)

客户端每次启动时拉取最新数据,修改后推送到服务器。

3. 手机端配套 App

用 Flutter 做手机端,通过同步服务器与桌面端共享同一份通讯录。Flutter 一套代码同时出 Android 和 iOS。

五、工程化改进

"从命令行到 GUI,从文本文件到数据库,从单机到多端同步——这就是一个课程作业进化成真正产品的路线图。每一步都是技术跃迁。"

如果你也在学 C++,强烈建议动手做一个小项目而不是只看教程。 写这个通讯录的过程比我刷 100 道语法题学到的都多。选择合适的下一步,继续打磨它。