一、数据结构升级:更完善的联系人模型

为满足新需求,我们需要扩展联系人结构体,增加电话和住址字段:

#define MAX_NAME 20
#define MAX_PHONE 12
#define MAX_ADDR 50
#define MAX_CONTACTS 1000

typedef struct Person {
    char name[MAX_NAME];
    char gender[5];     // "男"/"女"/"保密"
    int age;
    char phone[MAX_PHONE];
    char address[MAX_ADDR];
} Person;

typedef struct AddressBook {
    Person contacts[MAX_CONTACTS];
    int count;          // 当前联系人数量
} AddressBook;

优化点:

  1. 使用typedef简化类型名
  2. 字符串长度使用宏定义便于维护
  3. 性别改用字符串存储更灵活

二、功能实现详解

  1. 初始化与清空功能
// 初始化通讯录
void initAddressBook(AddressBook *ab) {
    ab->count = 0;
    // 加载文件数据(实现略,参考上节课)
}

// 清空通讯录
void clearAddressBook(AddressBook *ab) {
    if (confirm("确定要清空所有联系人吗?")) {
        ab->count = 0;
        saveToFile(ab);  // 保存空文件
        printf("通讯录已清空!\n");
    }
}

关键点:

  • 清空操作需二次确认
  • 同步更新文件确保数据一致性
  1. 添加联系人(带输入验证)
void addContact(AddressBook *ab) {
    if (ab->count >= MAX_CONTACTS) {
        printf("通讯录已满!\n");
        return;
    }

    Person p;
    printf("请输入姓名:");
    scanf("%s", p.name);
    
    // 性别验证
    while (1) {
        printf("请输入性别(男/女/保密):");
        scanf("%s", p.gender);
        if (strcmp(p.gender, "男")==0 || strcmp(p.gender, "女")==0 || 
            strcmp(p.gender, "保密")==0) break;
        printf("输入无效,请重新输入!\n");
    }

    // 年龄验证
    while (1) {
        printf("请输入年龄:");
        scanf("%d", &p.age);
        if (p.age > 0 && p.age < 150) break;
        printf("年龄无效,请重新输入!\n");
    }

    // 电话验证(11位数字)
    while (1) {
        printf("请输入电话:");
        scanf("%s", p.phone);
        if (strlen(p.phone) == 11 && isdigitString(p.phone)) break;
        printf("电话号码格式错误,请重新输入!\n");
    }

    printf("请输入住址:");
    scanf("%s", p.address);

    ab->contacts[ab->count++] = p;
    saveToFile(ab);
    printf("联系人添加成功!\n");
}

输入验证技巧:

  • 使用循环确保输入符合格式
  • 自定义isdigitString()函数检查全数字字符串
  1. 删除与查找功能
// 查找联系人(返回索引,未找到返回-1)
int findContact(const AddressBook *ab, const char *name) {
    for (int i = 0; i < ab->count; i++) {
        if (strcmp(ab->contacts[i].name, name) == 0) {
            return i;
        }
    }
    return -1;
}

// 删除联系人
void deleteContact(AddressBook *ab) {
    char name[MAX_NAME];
    printf("请输入要删除的联系人姓名:");
    scanf("%s", name);

    int index = findContact(ab, name);
    if (index == -1) {
        printf("未找到该联系人!\n");
        return;
    }

    // 前移覆盖删除
    for (int i = index; i < ab->count - 1; i++) {
        ab->contacts[i] = ab->contacts[i + 1];
    }
    ab->count--;
    saveToFile(ab);
    printf("联系人已删除!\n");
}

删除优化:

  • 使用数组前移实现逻辑删除
  • 同步更新联系人数量
  1. 修改与显示功能
// 修改联系人信息
void modifyContact(AddressBook *ab) {
    char name[MAX_NAME];
    printf("请输入要修改的联系人姓名:");
    scanf("%s", name);

    int index = findContact(ab, name);
    if (index == -1) {
        printf("未找到该联系人!\n");
        return;
    }

    // 选择性修改字段(示例略)
    // 提示用户选择要修改的字段,重新输入对应内容
    saveToFile(ab);
    printf("联系人信息已更新!\n");
}

// 显示所有联系人
void displayAll(const AddressBook *ab) {
    if (ab->count == 0) {
        printf("通讯录为空!\n");
        return;
    }

    // 格式化输出表头
    printf("%-10s%-6s%-6s%-13s%-20s\n", 
           "姓名", "性别", "年龄", "电话", "住址");
    printf("--------------------------------------------\n");
    
    // 循环输出所有联系人
    for (int i = 0; i < ab->count; i++) {
        printf("%-10s%-6s%-6d%-13s%-20s\n",
               ab->contacts[i].name,
               ab->contacts[i].gender,
               ab->contacts[i].age,
               ab->contacts[i].phone,
               ab->contacts[i].address);
    }
}

显示优化:

  • 使用固定宽度格式化输出
  • 添加表头提高可读性
  1. 排序功能实现
// 比较函数(用于qsort)
int compareByName(const void *a, const void *b) {
    return strcmp(((Person*)a)->name, ((Person*)b)->name);
}

// 按姓名排序
void sortByName(AddressBook *ab) {
    if (ab->count <= 1) return;
    
    qsort(ab->contacts, ab->count, sizeof(Person), compareByName);
    saveToFile(ab);
    printf("通讯录已按姓名排序!\n");
}

排序关键点:

  • 使用标准库qsort函数
  • 自定义比较函数实现字典序排序

三、文件操作升级

为保证数据安全,我们采用二进制文件存储,并添加版本控制:

// 文件头结构(添加版本信息)
typedef struct {
    int version;       // 文件版本号
    int contactCount;  // 联系人数量
} FileHeader;

// 保存到文件
void saveToFile(const AddressBook *ab) {
    FILE *fp = fopen("contacts.dat", "wb");
    if (fp == NULL) {
        perror("无法保存文件");
        return;
    }

    // 写入文件头
    FileHeader header = {1, ab->count};
    fwrite(&header, sizeof(FileHeader), 1, fp);
    
    // 写入所有联系人
    fwrite(ab->contacts, sizeof(Person), ab->count, fp);
    fclose(fp);
}

// 从文件加载
void loadFromFile(AddressBook *ab) {
    FILE *fp = fopen("contacts.dat", "rb");
    if (fp == NULL) return;  // 文件不存在,创建新通讯录

    // 读取文件头
    FileHeader header;
    fread(&header, sizeof(FileHeader), 1, fp);
    
    // 检查版本兼容性
    if (header.version != 1) {
        printf("文件版本不兼容!\n");
        fclose(fp);
        return;
    }

    // 读取联系人
    int count = header.contactCount;
    if (count > MAX_CONTACTS) count = MAX_CONTACTS;
    
    fread(ab->contacts, sizeof(Person), count, fp);
    ab->count = count;
    fclose(fp);
}

文件优化:

  • 添加版本号支持未来扩展
  • 限制最大读取数量防止溢出
  • 错误处理更完善

四、用户界面优化

主菜单增加颜色和分隔线,提升用户体验:

void showMenu() {
    system("cls");  // 清屏(Windows)
    printf("\033[32m");  // 设置绿色文字
    printf("========================================\n");
    printf("        高级通讯录管理系统 v1.0\n");
    printf("========================================\n");
    printf("\033[34m");  // 设置蓝色文字
    printf(" 1. 添加联系人    2. 删除联系人\n");
    printf(" 3. 查找联系人    4. 修改联系人\n");
    printf(" 5. 显示所有      6. 清空通讯录\n");
    printf(" 7. 按姓名排序    8. 退出系统\n");
    printf("\033[0m");   // 恢复默认颜色
    printf("----------------------------------------\n");
    printf("请选择操作(1-8):");
}

界面美化:

  • 使用ANSI转义序列实现彩色输出
  • 添加清屏功能保持界面整洁
  • 菜单布局更合理

五、项目扩展建议

  1. 动态内存管理:改用链表结构,彻底解决容量限制
  2. 数据加密:文件存储时对敏感信息加密(如电话号码)
  3. 高级查询:支持按地址、年龄范围等多条件查询
  4. 备份功能:自动创建历史版本备份,防止数据丢失
  5. 导入导出:支持CSV、JSON等格式,方便数据交换

总结:

通过本节课的学习,我们掌握了一个完整的C语言项目开发流程,包括:

  1. 复杂数据结构设计
  2. 模块化函数开发
  3. 输入验证与错误处理
  4. 文件持久化存储
  5. 排序算法应用
  6. 用户界面优化

通讯录项目不仅是C语言基础的综合实践,更是培养编程思维的重要环节。建议大家动手实现这个系统,并尝试添加自己的特色功能,进一步提升编程能力!


久远寺门邸
1 声望0 粉丝