跳转至

12-综合练习小项目:通讯录

目录页:见 C++ 小白教程目录

目标

  • class/struct 组织数据模型
  • std::vector 保存列表,用函数拆分功能
  • std::string 处理输入
  • 做一个能用的小程序:增删改查 + 保存/加载(可选)

先决知识

  • 类与对象(见 05 章)
  • vector 与 map(见 07 章)
  • 算法(find/sort)(见 08 章)

1. 项目需求:我们要做什么

做一个命令行通讯录(先做最核心的功能):

  • 添加联系人:name、phone
  • 删除联系人:按 name
  • 查找联系人:按 name
  • 列出所有联系人:按 name 排序输出

可选加分:

  • 保存到文件、从文件加载
  • 支持模糊查找(包含子串)

2. 数据模型:Contact

  • 专业名称:数据模型(data model)
  • 类比:一张联系人卡片
  • 作用:把一条联系人的字段固定下来,后面所有操作围绕它展开
  • 规则/坑点:字段尽量使用合适的类型(文本就用 string)

我们用 struct(默认 public,适合纯数据):

#include <string>

struct Contact {
    std::string name;
    std::string phone;
};

3. 功能拆分:把 main 变成“调度中心”

建议把功能拆成这些函数:

  • addContact
  • removeContact
  • findContact
  • listContacts

每个函数只做一件事,main 负责菜单与调用(见 02 章“函数让代码更好改”)。


4. 最小可用版本(可运行)

输入规则(示例):

  • add name phone
  • del name
  • get name
  • list
  • exit

下面是一份完整可运行代码,你可以直接编译运行:

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

struct Contact {
    std::string name;
    std::string phone;
};

int findIndexByName(const std::vector<Contact>& book, const std::string& name) {
    for (std::size_t i = 0; i < book.size(); i++) {
        if (book[i].name == name) {
            return static_cast<int>(i);
        }
    }
    return -1;
}

void addContact(std::vector<Contact>& book, const std::string& name, const std::string& phone) {
    int idx = findIndexByName(book, name);
    if (idx != -1) {
        book[static_cast<std::size_t>(idx)].phone = phone;
        return;
    }
    book.push_back(Contact{name, phone});
}

bool removeContact(std::vector<Contact>& book, const std::string& name) {
    int idx = findIndexByName(book, name);
    if (idx == -1) {
        return false;
    }
    book.erase(book.begin() + static_cast<std::ptrdiff_t>(idx));
    return true;
}

const Contact* findContact(const std::vector<Contact>& book, const std::string& name) {
    int idx = findIndexByName(book, name);
    if (idx == -1) {
        return nullptr;
    }
    return &book[static_cast<std::size_t>(idx)];
}

void listContacts(std::vector<Contact> book) {
    std::sort(book.begin(), book.end(), [](const Contact& a, const Contact& b) {
        return a.name < b.name;
    });

    for (const auto& c : book) {
        std::cout << c.name << " " << c.phone << '\n';
    }
}

int main() {
    std::vector<Contact> book;

    while (true) {
        std::string cmd;
        if (!(std::cin >> cmd)) {
            break;
        }

        if (cmd == "add") {
            std::string name;
            std::string phone;
            std::cin >> name >> phone;
            addContact(book, name, phone);
            std::cout << "ok\n";
        } else if (cmd == "del") {
            std::string name;
            std::cin >> name;
            bool ok = removeContact(book, name);
            std::cout << (ok ? "ok" : "not found") << '\n';
        } else if (cmd == "get") {
            std::string name;
            std::cin >> name;
            const Contact* p = findContact(book, name);
            if (p == nullptr) {
                std::cout << "not found\n";
            } else {
                std::cout << p->name << " " << p->phone << '\n';
            }
        } else if (cmd == "list") {
            listContacts(book);
        } else if (cmd == "exit") {
            break;
        } else {
            std::cout << "unknown\n";
        }
    }

    return 0;
}

设计解释(帮你把前面知识串起来):

  • bookvector 保存联系人列表(见 07 章)
  • 查找函数返回下标或返回指针(见 04 章:指针可表示“找不到”)
  • list 为了不破坏原顺序,用“传值拷贝一份”再排序(见 06 章:拷贝的直觉)
  • 排序用 std::sort + lambda(见 08 章)

5. 保存与加载(可选加分)

你可以把通讯录保存到文本文件,每行:name phone

你需要用到:

  • std::ofstream 写文件(见 06 章:文件对象是 RAII)
  • std::ifstream 读文件

建议接口:

  • bool save(const std::vector<Contact>& book, const std::string& path)
  • bool load(std::vector<Contact>& book, const std::string& path)

实现时注意:

  • 读文件前清空 book
  • 文件格式不对就返回 false

常见坑

  • 错误:删除元素后继续用旧下标/旧指针
    结果:越界或指向错误对象
    正确:删除后不要复用旧位置;必要时重新查找

  • 错误:把 listContacts 写成引用参数然后直接排序
    结果:原顺序被破坏
    正确:想保留原顺序就拷贝一份再排;想修改原顺序就明确说明并直接排序

  • 错误:用 cin >> 读取带空格的名字
    结果:被截断
    正确:需要带空格就用 getline 并设计更清晰的输入格式(可后续升级)


小练习(项目升级方向)

练习 1:模糊查找

  • 题目:输入关键字 key,输出 name 包含 key 的联系人
  • 输入/输出:输出匹配的联系人列表
  • 约束:不区分大小写为加分项
  • 提示:string 的查找可以用 find
  • 目标:练字符串处理

练习 2:按电话查找

  • 题目:支持 getp phone
  • 输入/输出:输出对应 name,找不到输出 not found
  • 约束:可以用 map 做 phone->name(见 07 章)
  • 提示:维护两份结构要注意同步更新
  • 目标:练 map 的价值

练习 3:保存/加载

  • 题目:实现 save/load,并加入命令 save pathload path
  • 输入/输出:输出 ok 或 error
  • 约束:文件格式每行两个字段
  • 提示:用 ifstream/ofstream,利用 RAII 自动关闭
  • 目标:把“资源管理”用到真实场景

小结

你现在已经能把前面知识串成一个完整程序:

  • 用 struct/class 建模
  • 用 vector 管理列表
  • 用函数拆分功能
  • 用算法排序与查找
  • (可选)用文件做持久化

到这里,你已经具备独立写出中小型 C++ 控制台程序的能力。后续如果你要接 Qt/C++ 或者更进阶的数据结构,都有坚实基础了。