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,适合纯数据):
3. 功能拆分:把 main 变成“调度中心”¶
建议把功能拆成这些函数:
addContactremoveContactfindContactlistContacts
每个函数只做一件事,main 负责菜单与调用(见 02 章“函数让代码更好改”)。
4. 最小可用版本(可运行)¶
输入规则(示例):
add name phonedel nameget namelistexit
下面是一份完整可运行代码,你可以直接编译运行:
#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;
}
设计解释(帮你把前面知识串起来):
book用vector保存联系人列表(见 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 path、load path - 输入/输出:输出 ok 或 error
- 约束:文件格式每行两个字段
- 提示:用 ifstream/ofstream,利用 RAII 自动关闭
- 目标:把“资源管理”用到真实场景
小结¶
你现在已经能把前面知识串成一个完整程序:
- 用 struct/class 建模
- 用 vector 管理列表
- 用函数拆分功能
- 用算法排序与查找
- (可选)用文件做持久化
到这里,你已经具备独立写出中小型 C++ 控制台程序的能力。后续如果你要接 Qt/C++ 或者更进阶的数据结构,都有坚实基础了。