首页 > 后端开发 > C++ > 正文

C++如何实现命令行通讯录查询

P粉602998670
发布: 2025-09-04 08:50:01
原创
715人浏览过
答案:采用std::vector<Contact>存储联系人,结合文件I/O实现数据持久化,通过命令行菜单交互实现添加、查询、列出和保存功能。

c++如何实现命令行通讯录查询

在C++中实现命令行通讯录查询,核心在于合理的数据结构选择来存储联系人信息,利用标准输入输出进行用户交互,并结合文件I/O实现数据的持久化。这本质上是一个小型数据库应用,只不过我们自己来构建存储和查询逻辑。它考验的不仅是C++语法,更是对数据管理和用户体验的初步思考。

实现一个命令行通讯录查询,我们需要定义一个联系人结构体来存储姓名、电话等信息,然后用一个容器(比如

std::vector
登录后复制
)来管理这些联系人。为了让程序在关闭后数据不丢失,我们会将通讯录内容写入文件,并在程序启动时从文件中加载。用户通过输入命令(如“添加”、“查询”、“列出”)与程序互动,程序解析命令并执行相应操作。以下是一个基于文本文件的基本实现思路,它足够直观,也能满足日常的简单需求。

如何设计通讯录的数据结构,才能高效地进行查询?

对于命令行通讯录,数据结构的选择确实是第一步,也是决定后续操作效率的关键。我个人偏向于从最简单、最易理解的方案开始,然后根据实际需求逐步优化。

最初,一个

struct Contact
登录后复制
是必不可少的,它承载了联系人的基本信息:

立即学习C++免费学习笔记(深入)”;

struct Contact {
    std::string name;
    std::string phone;
    std::string email; // 也可以有更多字段,比如地址、备注等

    // 方便打印的函数
    void display() const {
        std::cout << "姓名: " << name << ", 电话: " << phone << ", 邮箱: " << email << std::endl;
    }

    // 用于文件读写的序列化/反序列化辅助函数
    // 假设以逗号分隔
    std::string serialize() const {
        return name + "," + phone + "," + email;
    }

    static Contact deserialize(const std::string& line) {
        Contact c;
        size_t pos1 = line.find(',');
        size_t pos2 = line.find(',', pos1 + 1);

        if (pos1 != std::string::npos && pos2 != std::string::npos) {
            c.name = line.substr(0, pos1);
            c.phone = line.substr(pos1 + 1, pos2 - (pos1 + 1));
            c.email = line.substr(pos2 + 1);
        } else {
            // 处理格式错误或不完整的情况,这里简单地赋空
            c.name = line; // 至少保留一部分信息
            c.phone = "";
            c.email = "";
        }
        return c;
    }
};
登录后复制

接下来,管理这些

Contact
登录后复制
对象。最直接、最符合C++习惯的容器是
std::vector<Contact>
登录后复制
。它提供了动态数组的功能,添加、遍历都很方便。对于小规模通讯录(比如几十到几百个联系人),线性搜索(遍历
vector
登录后复制
中的每个元素进行匹配)的性能完全可以接受。

std::vector<Contact> contacts; // 存储所有联系人
登录后复制

如果你对查询效率有更高的要求,尤其是在按姓名进行精确查找时,可以考虑

std::map<std::string, Contact>
登录后复制
或者
std::unordered_map<std::string, Contact>
登录后复制
map
登录后复制
会按键(通常是姓名)排序,提供O(log N)的查找速度;
unordered_map
登录后复制
则提供平均O(1)的查找速度。但它们的问题在于,如果你需要按电话号码查询,或者进行模糊查询(比如输入“张”查所有姓张的),
map
登录后复制
unordered_map
登录后复制
的优势就不明显了,可能仍然需要遍历。

所以,我的建议是,先用

std::vector<Contact>
登录后复制
,它最简单直观,也最容易实现文件读写。如果未来发现查询性能成为瓶颈,再考虑引入
std::map
登录后复制
作为辅助索引,或者干脆切换到更复杂的结构。不过,对于命令行工具,多数情况下用户不会有海量的联系人,
vector
登录后复制
的简单性往往胜过
map
登录后复制
的理论性能优势。

命令行交互中,如何处理用户输入并提供友好的操作界面?

一个好用的命令行工具,用户交互体验至关重要。这不仅仅是技术实现的问题,更是对用户习惯和心理的把握。我通常会设计一个清晰的菜单,并通过循环让用户可以持续操作,直到他们选择退出。

  1. 显示菜单: 每次操作完成后,或者在程序启动时,显示当前可用的命令。这能让用户一目了然。

    void displayMenu() {
        std::cout << "\n--- 通讯录管理系统 ---\n";
        std::cout << "1. 添加联系人\n";
        std::cout << "2. 查询联系人\n";
        std::cout << "3. 列出所有联系人\n";
        std::cout << "4. 保存并退出\n";
        std::cout << "请输入您的选择: ";
    }
    登录后复制
  2. 获取用户输入: 使用

    std::cin
    登录后复制
    读取用户的选择,但对于需要包含空格的输入(如姓名、邮箱),务必使用
    std::getline(std::cin >> std::ws, variable)
    登录后复制
    std::ws
    登录后复制
    会跳过之前的空白字符,避免
    getline
    登录后复制
    读到空行。

    std::string choice_str;
    std::getline(std::cin >> std::ws, choice_str); // 读取整个行,包括可能的空格
    int choice = std::stoi(choice_str); // 转换为整数
    登录后复制

    这里有个小坑,如果用户输入非数字,

    std::stoi
    登录后复制
    会抛异常,所以最好用
    try-catch
    登录后复制
    块包裹,或者先用
    stringstream
    登录后复制
    尝试转换,增加程序的健壮性。

  3. 命令解析与执行: 使用

    if-else if
    登录后复制
    或者
    switch
    登录后复制
    语句根据用户输入执行相应的功能。

    switch (choice) {
        case 1: addContact(); break;
        case 2: searchContact(); break;
        case 3: listAllContacts(); break;
        case 4: running = false; break; // 退出循环
        default: std::cout << "无效的选择,请重新输入。\n"; break;
    }
    登录后复制
  4. 输入验证: 这是提升用户体验的关键。例如,添加电话号码时,可以简单检查是否全为数字;添加邮箱时,检查是否有

    @
    登录后复制
    符号。虽然命令行程序通常不会做太复杂的正则验证,但基础的格式检查能避免录入明显错误的数据。

    MagicStudio
    MagicStudio

    图片处理必备效率神器!为你的图片提供神奇魔法

    MagicStudio 102
    查看详情 MagicStudio
  5. 反馈信息: 每次操作后,给用户一个明确的反馈,比如“联系人添加成功!”、“未找到匹配联系人。”这让用户知道程序是否成功执行了他们的指令。

一个持续运行的循环是必不可少的,它让用户可以连续执行多个操作:

bool running = true;
while (running) {
    displayMenu();
    // 获取并处理用户输入
    // ...
}
登录后复制

这样的结构,加上清晰的提示和适当的错误处理,就能构建一个相对友好且功能完整的命令行交互界面。

通讯录数据如何持久化,确保程序关闭后信息不丢失?

数据的持久化是任何有状态应用程序的必备功能,对于通讯录来说,这意味着我们希望下次启动程序时,之前添加的联系人依然存在。在C++中,最常见的做法就是利用文件I/O,将数据写入硬盘

我倾向于使用简单的文本文件,因为它可读性强,方便调试,也容易与其他工具集成(比如用文本编辑器直接查看或修改)。

  1. 选择文件格式:

    • CSV (Comma Separated Values) 格式: 这是最简单、最常用的文本格式。每行代表一个联系人,字段之间用逗号分隔。例如:
      张三,13800138000,zhangsan@example.com
      登录后复制
      。这种格式易于读写,而且很多电子表格软件都能直接打开。
    • JSON 或 XML: 如果数据结构更复杂,或者需要更强的可扩展性,可以考虑这些结构化数据格式。但它们需要额外的解析库(如
      nlohmann/json
      登录后复制
      ),会增加项目的复杂性。对于简单的通讯录,CSV足够了。
  2. 保存数据:

    • 使用
      std::ofstream
      登录后复制
      (输出文件流)来写入文件。
    • 遍历存储联系人的
      std::vector
      登录后复制
      ,将每个
      Contact
      登录后复制
      对象序列化成一行字符串,然后写入文件。
    void saveContactsToFile(const std::string& filename, const std::vector<Contact>& contacts) {
        std::ofstream outFile(filename);
        if (!outFile.is_open()) {
            std::cerr << "错误: 无法打开文件 " << filename << " 进行写入。\n";
            return;
        }
        for (const auto& contact : contacts) {
            outFile << contact.serialize() << std::endl;
        }
        outFile.close();
        std::cout << "通讯录已保存到 " << filename << "。\n";
    }
    登录后复制

    这里,

    contact.serialize()
    登录后复制
    就是前面
    Contact
    登录后复制
    结构体中定义的那个辅助函数。

  3. 加载数据:

    • 使用
      std::ifstream
      登录后复制
      (输入文件流)来读取文件。
    • 逐行读取文件内容,将每行字符串反序列化成一个
      Contact
      登录后复制
      对象,然后添加到
      std::vector
      登录后复制
      中。
    std::vector<Contact> loadContactsFromFile(const std::string& filename) {
        std::vector<Contact> loadedContacts;
        std::ifstream inFile(filename);
        if (!inFile.is_open()) {
            std::cerr << "注意: 无法打开文件 " << filename << ",将创建一个新的通讯录。\n";
            return loadedContacts; // 返回空向量
        }
        std::string line;
        while (std::getline(inFile, line)) {
            if (!line.empty()) {
                loadedContacts.push_back(Contact::deserialize(line));
            }
        }
        inFile.close();
        std::cout << "已从 " << filename << " 加载通讯录。\n";
        return loadedContacts;
    }
    登录后复制

    这里的

    Contact::deserialize(line)
    登录后复制
    Contact
    登录后复制
    结构体中的静态反序列化函数。

关于错误处理: 文件I/O操作尤其需要注意错误处理。文件可能不存在、权限不足、磁盘空间已满等。检查

is_open()
登录后复制
是一个好习惯,
std::cerr
登录后复制
用于输出错误信息,这比
std::cout
登录后复制
更适合报告问题。

在程序启动时调用

loadContactsFromFile
登录后复制
,在程序退出前(或者用户选择“保存并退出”时)调用
saveContactsToFile
登录后复制
,这样就能确保数据的完整性。这种简单而直接的持久化方式,对于大多数命令行工具而言,既实用又足够。

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <limits> // For numeric_limits

// Contact 结构体定义
struct Contact {
    std::string name;
    std::string phone;
    std::string email;

    void display() const {
        std::cout << "姓名: " << name << ", 电话: " << phone << ", 邮箱: " << email << std::endl;
    }

    std::string serialize() const {
        // 简单地用逗号分隔,如果字段本身包含逗号,这里需要更复杂的转义逻辑
        return name + "," + phone + "," + email;
    }

    static Contact deserialize(const std::string& line) {
        Contact c;
        size_t pos1 = line.find(',');
        size_t pos2 = line.find(',', pos1 + 1);

        if (pos1 != std::string::npos && pos2 != std::string::npos) {
            c.name = line.substr(0, pos1);
            c.phone = line.substr(pos1 + 1, pos2 - (pos1 + 1));
            c.email = line.substr(pos2 + 1);
        } else if (pos1 != std::string::npos) { // 只有姓名和电话
            c.name = line.substr(0, pos1);
            c.phone = line.substr(pos1 + 1);
            c.email = "";
        } else { // 只有姓名
            c.name = line;
            c.phone = "";
            c.email = "";
        }
        return c;
    }
};

// 全局的联系人列表
std::vector<Contact> contacts;
const std::string FILENAME = "contacts.txt"; // 数据文件

// 函数声明
void displayMenu();
void addContact();
void searchContact();
void listAllContacts();
void saveContactsToFile();
void loadContactsFromFile();

int main() {
    loadContactsFromFile(); // 程序启动时加载数据

    bool running = true;
    while (running) {
        displayMenu();
        std::cout << "请输入您的选择: ";
        std::string choice_str;
        std::getline(std::cin >> std::ws, choice_str); // 使用 >> std::ws 跳过前一个换行符

        try {
            int choice = std::stoi(choice_str);
            switch (choice) {
                case 1: addContact(); break;
                case 2: searchContact(); break;
                case 3: listAllContacts(); break;
                case 4:
                    saveContactsToFile();
                    running = false;
                    std::cout << "程序已退出。再见!\n";
                    break;
                default:
                    std::cout << "无效的选择,请重新输入。\n";
                    break;
            }
        } catch (const std::invalid_argument& e) {
            std::cerr << "输入无效,请确保输入的是数字。\n";
        } catch (const std::out_of_range& e) {
            std::cerr << "输入的数字太大或太小,请重新输入。\n";
        }
        std::cout << std::endl; // 每次操作后换行,美观
    }

    return 0;
}

void displayMenu() {
    std::cout << "--- 通讯录管理系统 ---\n";
    std::cout << "1. 添加联系人\n";
    std::cout << "2. 查询联系人\n";
    std::cout << "3. 列出所有联系人\n";
    std::cout << "4. 保存并退出\n";
}

void addContact() {
    Contact newContact;
    std::cout << "请输入姓名: ";
    std::getline(std::cin >> std::ws, newContact.name);
    std::cout << "请输入电话: ";
    std::getline(std::cin >> std::ws, newContact.phone);
    std::cout << "请输入邮箱 (可选,留空请直接回车): ";
    std::getline(std::cin >> std::ws, newContact.email); // 允许为空

    // 简单验证,确保姓名不为空
    if (newContact.name.empty()) {
        std::cout << "姓名不能为空,添加失败。\n";
        return;
    }

    contacts.push_back(newContact);
    std::cout << "联系人 '" << newContact.name << "' 添加成功!\n";
}

void searchContact() {
    std::cout << "请输入要查询的姓名或电话 (支持模糊查询): ";
    std::string query;
    std::getline(std::cin >> std::ws, query);

    bool found = false;
    for (const auto& contact : contacts) {
        // 简单的模糊查询,判断姓名或电话是否包含查询字符串
        if (contact.name.find(query) != std::string::npos ||
            contact.phone.find(query) != std::string::npos) {
            contact.display();
            found = true;
        }
    }

    if (!found) {
        std::cout << "未找到匹配的联系人。\n";
    }
}

void listAllContacts() {
    if (contacts.empty()) {
        std::cout << "通讯录为空。\n";
        return;
    }
    std::cout << "--- 所有联系人 ---\n";
    for (const auto& contact : contacts) {
        contact.display();
    }
    std::cout << "-------------------\n";
}

void saveContactsToFile() {
    std::ofstream outFile(FILENAME);
    if (!outFile.is_open()) {
        std::cerr << "错误: 无法打开文件 " << FILENAME << " 进行写入。\n";
        return;
    }
    for (const auto& contact : contacts) {
        outFile << contact.serialize() << std::endl;
    }
    outFile.close();
    std::cout << "通讯录已保存到 " << FILENAME << "。\n";
}

void loadContactsFromFile() {
    std::ifstream inFile(FILENAME);
    if (!inFile.is_open()) {
        std::cerr << "注意: 无法打开文件 " << FILENAME << ",将创建一个新的通讯录。\n";
        return; // 返回空向量,表示从头开始
    }
    std::string line;
    while (std::getline(inFile, line)) {
        if (!line.empty()) {
            contacts.push_back(Contact::deserialize(line));
        }
    }
    inFile.close();
    std::cout << "已从 " << FILENAME << " 加载通讯录。\n";
}
登录后复制

以上就是C++如何实现命令行通讯录查询的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号