联系人数据结构应定义在独立的Contact.h头文件中,仅包含Contact类及其纯数据操作,不掺杂管理逻辑、业务校验或IO功能,确保职责单一与低耦合。

联系人数据结构该定义在哪个头文件里
联系人信息必须独立成类(或结构体),且只管自身字段和基础操作,不掺杂任何管理逻辑。常见错误是把 Contact 类塞进 AddressBook.h 里,结果导致头文件耦合、编译依赖爆炸。
正确做法是新建 Contact.h,仅包含:
class Contact {
public:
std::string name;
std::string phone;
std::string email;
// 只提供构造、打印、序列化等纯数据操作
void print() const;
std::string to_string() const;
};
- 所有成员变量设为
public或用get_*方法暴露——别加一堆业务相关的validate_phone()这类函数 - 不要在
Contact里持有索引、ID 或指向通讯录的指针,它不该知道自己被谁管理 - 如果要用
std::vector<Contact>存储,那是管理模块的事,不是Contact的责任
通讯录管理类怎么设计接口才不越界
管理类(比如 AddressBook)只负责增删查改容器、持久化、排序等“容器级”操作,绝不能侵入联系人语义。典型越界行为:在 AddressBook::add() 里做手机号格式校验、自动补区号、去重合并逻辑。
推荐接口签名保持窄而明确:
立即学习“C++免费学习笔记(深入)”;
class AddressBook {
public:
void add(const Contact& c);
bool remove(const std::string& name); // 仅按姓名删(单一维度)
std::vector<Contact> search(const std::string& keyword) const;
void save_to_file(const std::string& path) const;
void load_from_file(const std::string& path);
private:
std::vector<Contact> contacts_;
};
- 搜索返回
std::vector<Contact>,而不是int索引或指针——避免暴露内部存储细节 - 所有修改操作(
add/remove)不返回状态码,失败时抛std::runtime_error(例如文件打不开、重复添加等非业务错误) - 不提供
get_contact_by_index(int i)这类下标访问——外部应通过搜索或遍历获取,否则管理类成了裸容器包装器
main.cpp 里怎么组织调用链才不乱
入口文件只做三件事:解析用户输入、调用管理类接口、输出结果。绝不出现 Contact c; c.name = "xxx"; 这种直接构造和赋值——那属于测试或初始化逻辑,应抽到单独函数里。
- 用户输入解析建议用
std::map<std::string, std::function<void(AddressBook&)>>映射命令字符串到操作函数,避免大段if-else - 每条命令对应一个独立函数(如
handle_add(AddressBook& ab)),里面只做输入读取 + 调用ab.add(...)+ 异常捕获 - 禁止在
main()里直接操作contacts_成员——哪怕只是ab.contacts_.size()也不行;所有状态查询走ab.size()这类封装接口(需在AddressBook中补充)
为什么 operator<< 不该写在 Contact 类里
流输出是展示层逻辑,和数据模型无关。把 operator<< 塞进 Contact 头文件,等于强制所有包含它的代码都依赖 <iostream>,破坏可移植性(比如将来想把 Contact 用在嵌入式无 IO 环境)。
正确位置是单独的 io_utils.h:
// io_utils.h #include <iostream> #include "Contact.h" std::ostream& operator<<(std::ostream& os, const Contact& c);
- 这样
Contact.h保持干净,不引入任何 IO 头 - 如果需要不同格式输出(JSON / CSV),只需新增
to_json_string(const Contact&)等函数,不影响原有结构 - 单元测试时也方便 mock 输出行为——你测的是数据,不是怎么打印它


















