当前位置: 首页 > news >正文

通讯录详解(静态版,动态版,文件版)


  • 💓博客主页:江池俊的博客
  • ⏩收录专栏:C语言进阶之路
  • 👉专栏推荐:✅C语言初阶之路 ✅数据结构探索✅C语言刷题专栏
  • 💻代码仓库:江池俊的代码仓库
  • 🎉欢迎大家点赞👍评论📝收藏⭐

在这里插入图片描述

文章目录

      • 前言
  • ♨️一、静态通讯录
    • 🚀1.1 通讯录的前期准备
      • 🍁1.1.1 创建菜单
      • 🍁1.1.2 创建结构体
      • 🍁1.1.3 头文件的包含和#define定义常量
      • 🍁1.1.4 接口函数的声明
      • 🍁1.1.5 实现通讯录菜单选项的对接
    • 🚀1.2 通讯录函数功能的实现
      • 📌1.2.1 初始化通讯录
      • 📌1.2.2 添加联系人
      • 📌1.2.3 删除联系人
      • 📌1.2.4 查找联系人
      • 📌1.2.5 修改联系人信息
      • 📌1.2.6 打印通讯录信息
      • 📌1.2.7 排序通讯录
    • 🚀1.3 静态通讯录源代码
      • 🧩1.3.1 Contact.h 文件
      • 🧩1.3.2 Contact.c 文件
      • 🧩1.3.3 test.c 文件
  • ♨️二、通讯录优化之`动态通讯录`
    • 🚀2.1 通讯录结构体的优化
    • 🚀2.2 通讯录初始化函数的优化
    • 🚀2.3 添加扩容函数
    • 🚀2.4 释放动态开辟的内存
    • 🚀2.5 动态通讯录源代码
      • 🧩2.5.1 Contact.h 文件
      • 🧩2.5.2 Contact.c 文件
      • 🧩2.5.3 test.c 文件
  • ♨️三、通讯录优化之`文件版通讯录`
    • 🚀3.1 保存通讯录信息到文件
    • 🚀3.2 在初始化时加载文件信息到通讯录
    • 🚀3.3 文件版通讯录源代码
      • 🧩3.3.1 Contact.h 文件
      • 🧩3.3.2 Contact.c 文件
      • 🧩3.3.3 test.c 文件


前言

在现代社会中,通讯录已经成为了我们生活中不可或缺的一部分。无论是工作还是生活,我们都需要一个可靠的通讯录来记录和管理我们的联系人信息。

本文将介绍用C语言来实现一个通讯录管理系统,其中主要存储了若干联系人的信息,每个人的信息包括他们的姓名、年龄、性别、电话号码、住址等。并且该通讯录包括以下功能:

  1. 增加联系人
  2. 删除联系人
  3. 查找联系人
  4. 修改联系人
  5. 打印通讯录
  6. 排序通讯录
  7. 退出程序

在写通讯录前,我们需要创建工程,这里为了让大家养成模块化的好习惯,我们尽量将代码分成三个文件来写。这里我打开的编译器是 vs 2022,在资源管理器的 头文件 中创建 Contact.h 文件,此文件作用主要是为了存储各种头文件和通讯录各个功能的函数的声明以及联系人信息和通讯录结构体的创建;在源文件中创建 Contact.c 文件用来实现通讯录各大功能的函数,Test.c 文件用来测试通讯录的功能。具体如下图所示:

在这里插入图片描述


♨️一、静态通讯录

🚀1.1 通讯录的前期准备

🍁1.1.1 创建菜单

再创建通讯录之前,我们需要先建立一个完整的菜单,从而来实现用户与计算机的交互,方便用户快速查找和访问通讯录中的联系人信息,以实现快捷拨号、发送短信、查看联系人信息等功能。

代码:

void menu()
{printf("------------------------------\n");printf("----     1.添加联系人     ----\n");printf("----     2.删除联系人     ----\n");printf("----     3.查找联系人     ----\n");printf("----     4.修改联系人     ----\n");printf("----     5.打印通讯录     ----\n");printf("----     6.排序通讯录     ----\n");printf("----     0.退出通讯录     ----\n");printf("------------------------------\n");
}

在这里插入图片描述

🍁1.1.2 创建结构体

//类型声明
typedef struct PeoInfo
{char name[NAME_MAX];int age;char sex[SEX_MAX];char tele[TELE_MAX];char addr[ADDR_MAX];
}PeoInfo; //一个联系人的信息typedef struct Contact
{PeoInfo data[MAX];//存放联系人的信息int sz;//记录当前通讯录存放的联系人个数
}Contact;//通讯录

定义两个结构体类型:PeoInfoContact
PeoInfo结构体类型包含了5个成员变量,分别是:

  • name:一个字符数组,用于存储联系人姓名,其最大长度为NAME_MAX
  • age:一个整型变量,用于存储联系人年龄。
  • sex:一个字符数组,用于存储联系人性别,其最大长度为SEX_MAX
  • tele:一个字符数组,用于存储联系人电话号码,其最大长度为TELE_MAX
  • addr:一个字符数组,用于存储联系人地址,其最大长度为ADDR_MAX

这个结构体类型代表了一个联系人的信息。

Contact结构体类型包含了两个成员变量:

  • data:一个PeoInfo类型的数组,用于存储多个联系人的信息,其最大长度为MAX
  • sz:一个整型变量,用于记录当前通讯录中存放的联系人个数。

这个结构体类型代表了一个通讯录,其中可以存储多个联系人的信息,并且记录了当前通讯录中联系人的个数。

通过这两个结构体类型,我们可以方便地存储和管理联系人的信息和通讯录的状态。

🍁1.1.3 头文件的包含和#define定义常量

#include<stdio.h>
#include<string.h>//memset、strcmp
#include<assert.h>//assert
#include<stdlib.h>//qsort函数、malloc、realloc#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
#define MAX 100 //通讯录中可记录联系人数目的最大值

🍁1.1.4 接口函数的声明

声明实现通讯录各个功能的函数,如初始化通讯录,增加联系人,显示联系人,删除联系人,查找联系人等功能的函数。

//初始化通讯录
void InitContact(Contact* pc);
//增加联系人
void AddContact(Contact* pc);
//显示联系人信息
void ShowContact(Contact* pc);//这里形参也可以是结构体变量,因为打印不改变结构体内容
//删除联系人
void DelContact(Contact* pc);
//查找联系人
void SearchContact(Contact* pc);
//修改指定联系人
void ModifyContact(Contact* pc);
//排序通讯录
void SortContact(Contact* pc);
//按姓名排序
void SortContact_by_name(Contact* pc);
//按年龄排序
void SortContact_by_age(Contact* pc);

🍁1.1.5 实现通讯录菜单选项的对接

创建一个通讯录程序的框架,为了增加代码的可读性和维护性,使用枚举类型定义各个功能函数的选择。在主函数中,首先创建了一个通讯录对象,并初始化该对象。然后通过一个循环来显示菜单并获取用户的输入,根据用户的选择调用相应的函数来执行相应的操作。

enum Option //枚举常量对应各个函数的选择
{EXIT, //0ADD, //1DEL, //2SEARCH, //3MODIFY, //4SHOW, //5SORT //6  
};int main()
{int input = 0;//创建通讯录Contact con;//通讯录//初始化通讯录InitContact(&con);int choice = 0;//排序方式的选择do{menu();printf("请输入你的选择 ->:");scanf("%d", &input);switch (input){case ADD:AddContact(&con);break;case DEL:DelContact(&con);break;case SEARCH:SearchContact(&con);break;case MODIFY:ModifyContact(&con);break;case SHOW:ShowContact(&con);break;case SORT:SortContact(&con);break;case EXIT: printf("退出通讯录\n"); break;default:printf("选择错误,请重新选择!\n");break;}} while (input);return 0;
}

🚀1.2 通讯录函数功能的实现

📌1.2.1 初始化通讯录

//初始化通讯录
void InitContact(Contact* pc)
{assert(pc);pc->sz = 0;memset(pc->data, 0, sizeof(pc->data));
}
  1. 首先使用 assert 宏来检查传入的指针是否为非空。如果指针为空,则程序会在这里终止并输出错误信息。
  2. 接下来,将结构体的 sz 成员变量初始化为 0,这是表示通讯录中联系人数目的变量。
  3. 然后,使用 memset 函数将结构体的 data 成员数组(该数组存放的是联系人的信息)初始化为 0sizeof(pc->data) 确定了要清零的字节数,确保整个数组都被清零。

📌1.2.2 添加联系人

//增加联系人
void AddContact(Contact* pc)
{assert(pc);if (pc->sz == MAX){printf("通讯录已满,无法增加\n");return;}//增加信息printf("请输入增加联系人的名字:");scanf("%s", pc->data[pc->sz].name);printf("请输入增加联系人的年龄:");scanf("%d", &(pc->data[pc->sz].age));printf("请输入增加联系人的性别:");scanf("%s", pc->data[pc->sz].sex);printf("请输入增加联系人的电话:");scanf("%s", pc->data[pc->sz].tele);printf("请输入增加联系人的地址:");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("增加联系人成功!\n");
}
  1. 首先,使用断言(assert)确保传入的指针不为空。
  2. 然后,检查通讯录是否已满(即 sz 等于 MAX)。如果已满,则打印一条错误消息并返回,不执行后续操作。
  3. 如果通讯录未满,则提示用户输入要增加的联系人的姓名、年龄、性别、电话和地址。
  4. 使用 scanf 函数从标准输入读取用户的输入,并将其存储在 Contact 结构体中相应的字段中。
  5. 最后,将 sz 的值加 1,表示成功增加了一个联系人,并打印一条成功消息。

在这里插入图片描述

📌1.2.3 删除联系人

//删除联系人
void DelContact(Contact* pc)
{char name[NAME_MAX];assert(pc);if (pc->sz == 0){printf("通讯录为空,无法删除\n");return;}printf("请输入要删除的人的名字:");scanf("%s", name);//找到名字为name的人int ret = FindByName(pc, name);if (ret == -1){printf("要删除的人不存在\n");return;}//删除这个人的信息for (int i = ret; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf("删除成功!\n");
}
  1. 首先,声明一个字符数组name,用于存储要删除的人的名字。
  2. 使用断言(assert)确保传入的指针不为空。
  3. 如果通讯录为空(即sz等于0),则打印一条错误消息并返回,不执行后续操作。
  4. 提示用户输入要删除的人的名字,并将其存储在name数组中。
  5. 调用FindByName函数来查找名字为name的人的位置,将结果存储在变量ret中。
  6. 如果ret等于-1,表示要删除的人不存在,则打印一条错误消息并返回。
  7. 如果找到了要删除的人,则通过循环将后面的联系人信息向前移动一位,覆盖掉要删除的人的信息。
  8. 将通讯录的大小减1,表示成功删除了一个联系人。
  9. 打印一条成功消息,表示删除成功。

在这里插入图片描述

📌1.2.4 查找联系人

在查找联系人并打印信息之前我们需要先找到联系人的下标,以便后续对于该联系人信息的获取和修改等操作。所以先创建一个FindByName 的函数,该函数的作用是查找联系人,若找到返回联系人的下标,否者,返回 -1

(1)FindByName 函数

//查找联系人并返回下标
int FindByName(Contact* pc, char name[])
{assert(pc);for (int i = 0; i < pc->sz; i++){if (strcmp(name, pc->data[i].name) == 0){return i;//找到了,返回下标}}return -1;//没找到,返回-1
}
  1. 首先,使用assert(pc)确保传入的联系人列表指针不为空。
  2. 然后,使用一个循环遍历联系人列表中的每个元素。循环变量i0 开始,直到pc->sz - 1(联系人列表的大小)。
  3. 在循环中,使用strcmp(name, pc->data[i].name) == 0判断当前联系人的名称是否与要查找的名称相同。这里使用了strcmp函数进行字符串比较。
  4. 如果找到了匹配的联系人,即strcmp的结果为 0,则返回当前下标i
  5. 如果循环结束后仍未找到匹配的联系人,则返回 -1

(2)SearchContact 函数

//查找联系人并打印信息
void SearchContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("请输入要查找的人的姓名:");scanf("%s", &name);int ret = FindByName(pc, name);if (ret == -1){printf("要查找的人不存在\n");return;}//显示找到的人的信息printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",ret + 1,pc->data[ret].name,pc->data[ret].age,pc->data[ret].sex,pc->data[ret].tele,pc->data[ret].addr);
}
  1. 此函数接受一个指向Contact 结构体的指针作为参数,然后提示用户输入要查找的人的姓名。
  2. 接着,调用 FindByName 函数来查找该联系人,并将结果存储在变量ret中。
  3. 如果找到了联系人,它会显示该联系人的信息;否则,它会打印一条错误消息。

在这里插入图片描述

📌1.2.5 修改联系人信息

由于联系人有多个信息,我们无法确定要修改的信息是哪一项,所以在修改联系人信息前需要先创建一个修改联系人信息的菜单,从而供用户选择对相应的信息进行修改。

(1)ModifyMenu 函数

void ModifyMenu()
{printf("------------------------\n");printf("----   0.退出修改   ----\n");printf("----   1.修改姓名   ----\n");printf("----   2.修改年龄   ----\n");printf("----   3.修改性别   ----\n");printf("----   4.修改电话   ----\n");printf("----   5.修改地址   ----\n");printf("------------------------\n");
}

在这里插入图片描述

(2)ModifyContact 函数

//修改指定联系人
void ModifyContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("请输入要修改的人的姓名:");scanf("%s", &name);int ret = FindByName(pc, name);if (ret == -1){printf("要修改的人不存在\n");return;}int input = 0;do{ModifyMenu();//修改信息的菜单printf("请选择你要修改的信息->:");scanf("%d", &input);switch (input){case 0:printf("退出修改!\n");break;case 1:printf("请输入修改后的姓名:");scanf("%s", pc->data[ret].name);break;case 2:printf("请输入修改后的年龄:");scanf("%d", &(pc->data[ret].age));break;case 3:printf("请输入修改后的性别:");scanf("%s", pc->data[ret].sex);break;case 4:printf("请输入修改后的电话:");scanf("%s", pc->data[ret].tele);break;case 5:printf("请输入修改后的地址:");scanf("%s", pc->data[ret].addr);break;default:printf("选择错误,请重新选择!\n");break;}} while (input);printf("修改成功!\n");
}
  1. 首先通过输入要修改的人的姓名来查找该联系人
  2. 如果找到了就进入一个循环,显示修改信息的菜单并让用户选择要修改的信息,然后根据用户的选择进行相应的修改操作。
  3. 最后输出修改成功的提示信息。

在这里插入图片描述

📌1.2.6 打印通讯录信息

遍历通讯录,逐个打印出联系人的序号、姓名、年龄、性别、电话和地址信息。如果通讯录为空,则则打印提示信息并结束函数。

//显示联系人信息
void ShowContact(Contact* pc)//这里形参也可以是结构体变量,因为打印不改变结构体内容
{assert(pc);if (pc->sz == 0){printf("通讯录为空,无需打印\n");return;}int i = 0;//序号  名字  年龄  性别  电话    地址//xxx   xxx   xxx   xxx    xxx    xxxprintf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");for (i = 0; i < pc->sz; i++){//打印每个人的信息printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);}
}

在这里插入图片描述

📌1.2.7 排序通讯录

这里可以使用两种方式进行排序,第一种是按照联系人姓名排序,第二种则是按照联系人年龄来排序。两种排序都是基于 qsort 函数来实现。qsort 函数的具体使用方法见《深入理解回调函数qsort:从入门到模拟实现》
qsort 函数的关键是第四个参数,该参数是一个函数指针,用来指向一个比较函数,故排序的方式也是由它来决定的,比如:想要按姓名来排序就要写一个按姓名来比较的函数,如果想要按年龄来排序就要写一个按年龄来比较的函数。
(1)SortMenu 函数
该函数打印的是通讯录排序的方式,方便与用户进行交互,让用户来选择排序通讯录的方式。

void SortMenu()
{printf("-------------------------\n");printf("----   0.退出排序    ----\n");printf("----   1.按姓名排序  ----\n");printf("----   2.按年龄排序  ----\n");printf("-------------------------\n");
}

在这里插入图片描述

(2)比较函数 + 排序函数

//按名字比较
int cmp_by_name(const void* e1, const void* e2)
{return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}//按年龄比较
int cmp_by_age(const void* e1, const void* e2)
{return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}//按姓名排序 --- qsort
void SortContact_by_name(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
}
//按年龄排序 --- qsort
void SortContact_by_age(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);
}

这段代码是用于对联系人进行排序的。它包含了两个比较函数(cmp_by_namecmp_by_age)以及两个排序函数(SortContact_by_nameSortContact_by_age)。

  1. cmp_by_name 函数用于按名字比较两个联系人。它接受两个 const void* 类型的参数 e1e2,然后将它们转换为 PeoInfo 类型的指针,并使用 strcmp 函数比较它们的 name 成员。如果 e1 的名字在字母顺序上排在 e2 之前,则返回负数;如果相等,则返回零;如果 e1 的名字在字母顺序上排在 e2 之后,则返回正数。
  2. cmp_by_age 函数用于按年龄比较两个联系人。它接受两个 const void* 类型的参数 e1e2,然后将它们转换为 PeoInfo 类型的指针,并计算它们的 age 成员之差。如果 e1 的年龄小于 e2的年龄,则返回负数;如果相等,则返回零;如果 e1 的年龄大于 e2 的年龄,则返回正数。
  3. SortContact_by_name 函数用于按姓名对联系人数组进行排序。它接受一个 Contact* 类型的参数 pc,然后使用 qsort 函数对 pc->data 数组进行排序。qsort 函数的第四个参数是一个比较函数指针,这里传入的是 cmp_by_name 函数。这样,当 qsort函数需要比较两个元素时,就会调用 cmp_by_name 函数来进行比较。
  4. SortContact_by_age 函数用于按年龄对联系人数组进行排序。它与 SortContact_by_name 函数类似,只是比较函数改为了 cmp_by_age 函数。

(3)SortContact 函数

//排序通讯录
void SortContact(Contact* pc)
{int inpuct = 0;if (pc->sz == 0){printf("通讯录为空,不用排序!\n");return;}do{SortMenu();//排序通讯录菜单printf("请选择排序方式 ->:");scanf("%d", &inpuct);switch (inpuct){case 0:printf("退出排序!\n");break;case 1:SortContact_by_name(pc);printf("按姓名排序后:\n");ShowContact(pc);break;case 2:SortContact_by_age(pc);printf("按年龄排序后:\n");ShowContact(pc);break;default:printf("输入错误,请重新输入");break;}} while (inpuct);
}
  1. 首先,代码检查联系人数组是否为空,如果为空则打印提示信息并返回。
  2. 接下来,进入一个循环,显示排序菜单并等待用户输入选择。根据用户的输入,执行相应的排序操作:
    • 如果用户选择0,退出排序;
    • 如果用户选择1,调用 SortContact_by_name 函数按姓名对联系人进行排序,并打印排序后的结果;
    • 如果用户选择2,调用 SortContact_by_age 函数按年龄对联系人进行排序,并打印排序后的结果。
  3. 如果用户输入的选择不在有效范围内,会打印错误提示并重新显示排序菜单。

在这里插入图片描述

🚀1.3 静态通讯录源代码

🧩1.3.1 Contact.h 文件

#pragma once#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>//qsort函数、malloc、realloc#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
#define MAX 100 //通讯录中可记录联系人数目的最大值//类型声明
typedef struct PeoInfo
{char name[NAME_MAX];int age;char sex[SEX_MAX];char tele[TELE_MAX];char addr[ADDR_MAX];
}PeoInfo; //一个联系人的信息typedef struct Contact
{PeoInfo data[MAX];//存放联系人的信息int sz;//记录当前通讯录存放的联系人个数
}Contact;//通讯录//初始化通讯录
void InitContact(Contact* pc);//增加联系人
void AddContact(Contact* pc);//显示联系人信息
void ShowContact(Contact* pc);//这里形参也可以是结构体变量,因为打印不改变结构体内容//删除联系人
void DelContact(Contact* pc);//查找联系人
void SearchContact(Contact* pc);//修改指定联系人
void ModifyContact(Contact* pc);//排序通讯录
void SortContact(Contact* pc);//按姓名排序
void SortContact_by_name(Contact* pc);//按年龄排序
void SortContact_by_age(Contact* pc);

🧩1.3.2 Contact.c 文件

#define _CRT_SECURE_NO_WARNINGS 1#include "Contact.h"void ModifyMenu()
{printf("------------------------\n");printf("----   0.退出修改   ----\n");printf("----   1.修改姓名   ----\n");printf("----   2.修改年龄   ----\n");printf("----   3.修改性别   ----\n");printf("----   4.修改电话   ----\n");printf("----   5.修改地址   ----\n");printf("------------------------\n");
}void SortMenu()
{printf("-------------------------\n");printf("----   0.退出排序    ----\n");printf("----   1.按姓名排序  ----\n");printf("----   2.按年龄排序  ----\n");printf("-------------------------\n");
}//初始化通讯录
void InitContact(Contact* pc)
{assert(pc);pc->sz = 0;memset(pc->data, 0, sizeof(pc->data));
}//增加联系人
void AddContact(Contact* pc)
{assert(pc);if (pc->sz == MAX){printf("通讯录已满,无法增加\n");return;}//增加信息printf("请输入增加联系人的名字:");scanf("%s", pc->data[pc->sz].name);printf("请输入增加联系人的年龄:");scanf("%d", &(pc->data[pc->sz].age));printf("请输入增加联系人的性别:");scanf("%s", pc->data[pc->sz].sex);printf("请输入增加联系人的电话:");scanf("%s", pc->data[pc->sz].tele);printf("请输入增加联系人的地址:");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("增加联系人成功!\n");
}//显示联系人信息
void ShowContact(Contact* pc)//这里形参也可以是结构体变量,因为打印不改变结构体内容
{assert(pc);if (pc->sz == 0){printf("通讯录为空,无需打印\n");return;}int i = 0;//序号  名字  年龄  性别  电话    地址//xxx   xxx   xxx   xxx    xxx    xxxprintf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");for (i = 0; i < pc->sz; i++){//打印每个人的信息printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);}
}//查找联系人并返回下标
int FindByName(Contact* pc, char name[])
{assert(pc);for (int i = 0; i < pc->sz; i++){if (strcmp(name, pc->data[i].name) == 0){return i;//找到了,返回下标}}return -1;//没找到,返回-1
}//删除联系人
void DelContact(Contact* pc)
{char name[NAME_MAX];assert(pc);if (pc->sz == 0){printf("通讯录为空,无法删除\n");return;}printf("请输入要删除的人的名字:");scanf("%s", name);//找到名字为name的人int ret = FindByName(pc, name);if (ret == -1){printf("要删除的人不存在\n");return;}//删除这个人的信息for (int i = ret; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf("删除成功!\n");
}//查找联系人并打印信息
void SearchContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("请输入要查找的人的姓名:");scanf("%s", &name);int ret = FindByName(pc, name);if (ret == -1){printf("要查找的人不存在\n");return;}//显示找到的人的信息printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",ret + 1,pc->data[ret].name,pc->data[ret].age,pc->data[ret].sex,pc->data[ret].tele,pc->data[ret].addr);
}//修改指定联系人
void ModifyContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("请输入要修改的人的姓名:");scanf("%s", &name);int ret = FindByName(pc, name);if (ret == -1){printf("要修改的人不存在\n");return;}int input = 0;do{ModifyMenu();//修改信息的菜单printf("请选择你要修改的信息->:");scanf("%d", &input);switch (input){case 0:printf("退出修改!\n");break;case 1:printf("请输入修改后的姓名:");scanf("%s", pc->data[ret].name);break;case 2:printf("请输入修改后的年龄:");scanf("%d", &(pc->data[ret].age));break;case 3:printf("请输入修改后的性别:");scanf("%s", pc->data[ret].sex);break;case 4:printf("请输入修改后的电话:");scanf("%s", pc->data[ret].tele);break;case 5:printf("请输入修改后的地址:");scanf("%s", pc->data[ret].addr);break;default:printf("选择错误,请重新选择!\n");break;}} while (input);printf("修改成功!\n");
}//按名字比较
int cmp_by_name(const void* e1, const void* e2)
{return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}//按年龄比较
int cmp_by_age(const void* e1, const void* e2)
{return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}//按姓名排序 --- qsort
void SortContact_by_name(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
}
//按年龄排序 --- qsort
void SortContact_by_age(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);
}//排序通讯录
void SortContact(Contact* pc)
{int inpuct = 0;if (pc->sz == 0){printf("通讯录为空,不用排序!\n");return;}do{SortMenu();//排序通讯录菜单printf("请选择排序方式 ->:");scanf("%d", &inpuct);switch (inpuct){case 0:printf("退出排序!\n");break;case 1:SortContact_by_name(pc);printf("按姓名排序后:\n");ShowContact(pc);break;case 2:SortContact_by_age(pc);printf("按年龄排序后:\n");ShowContact(pc);break;default:printf("输入错误,请重新输入");break;}} while (inpuct);
}

🧩1.3.3 test.c 文件

#define _CRT_SECURE_NO_WARNINGS 1
//测试通讯录基本功能#include "Contact.h"void menu()
{printf("------------------------------\n");printf("----     1.添加联系人     ----\n");printf("----     2.删除联系人     ----\n");printf("----     3.查找联系人     ----\n");printf("----     4.修改联系人     ----\n");printf("----     5.打印通讯录     ----\n");printf("----     6.排序通讯录     ----\n");printf("----     0.退出通讯录     ----\n");printf("------------------------------\n");
}enum Option //枚举常量对应各个函数的选择
{EXIT, //0ADD, //1DEL, //2SEARCH, //3MODIFY, //4SHOW, //5SORT //6  
};int main()
{int input = 0;//创建通讯录Contact con;//通讯录//初始化通讯录InitContact(&con);int choice = 0;//排序方式的选择do{menu();printf("请输入你的选择 ->:");scanf("%d", &input);switch (input){case ADD:AddContact(&con);break;case DEL:DelContact(&con);break;case SEARCH:SearchContact(&con);break;case MODIFY:ModifyContact(&con);break;case SHOW:ShowContact(&con);break;case SORT:SortContact(&con);break;case EXIT: printf("退出通讯录\n"); break;default:printf("选择错误,请重新选择!\n");break;}} while (input);return 0;
}

♨️二、通讯录优化之动态通讯录

上述静态通讯录我们不难发现它的一个致命的缺点,在通讯录结构体的创建时,我们将通讯录的大小定义为100,当通讯录存满100个人的信息时我们如果想要继续存储,则会发生越界,程序报错,因此,我们需要优化通讯录使得我们能够手动增加通讯录的大小,于是就需要利用动态内存分配来定义通讯录结构体内的联系人数组的大小。

🚀2.1 通讯录结构体的优化

typedef struct Contact
{PeoInfo* data;//存放联系人的信息int sz;//当前通讯录存放的联系人的个数int capacity;//当前通讯录的最大容量
}Contact;

定义了一个名为Contact的结构体,用于表示通讯录。该结构体包含以下成员:

  • PeoInfo* data;:指向存放联系人信息的指针。
  • int sz;:当前通讯录中存放的联系人的个数。
  • int capacity;:当前通讯录的最大容量。

🚀2.2 通讯录初始化函数的优化

使用 malloc 动态开辟联系人数组的空间,初始空间大小为 INIT_DATA,再使用 memset 将数组中的值都置为0

//初始化通讯录
void InitContact(Contact* pc)
{assert(pc);pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INIT_DATA);if (pc->data == NULL){perror("InitContact->malloc");return;}memset(pc->data, 0, sizeof(pc->data));pc->sz = 0;pc->capacity = INIT_DATA;
}

🚀2.3 添加扩容函数

创建一个用于检查并扩容通讯录的函数。它接受一个指向Contact结构体的指针作为参数,并根据当前容量和最大容量进行判断是否需要扩容。

如果当前容量等于最大容量,那么就会执行以下操作:

  1. 使用realloc函数对data成员进行重新分配内存空间。新的内存空间大小为原容量加上一个常量ADD_DATA
  2. 如果重新分配失败,会打印错误信息并返回。
  3. 如果重新分配成功,将新分配的内存地址赋值给data成员,并将最大容量增加ADD_DATA
  4. 打印"增容成功"的消息。

如果当前容量没有达到最大容量,则不执行任何操作。

//扩容
void CheckCapacity(Contact* pc)
{//1.如果满了,就增容if (pc->sz == pc->capacity){Contact* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + ADD_DATA) * sizeof(PeoInfo));if (ptr == NULL){perror("CheckCapacity->realloc");return;}pc->data = ptr;pc->capacity += ADD_DATA;printf("增容成功\n");}//2.如果没满,啥也不干
}

🚀2.4 释放动态开辟的内存

创建一个销毁通讯录的函数(其目的是在程序结束前释放动态开辟的内存空间)。它接受一个指向Contact结构体的指针作为参数,并执行以下操作:

  1. 使用free函数释放data成员所指向的内存空间。
  2. data成员设置为NULL,以避免悬挂指针。
  3. sz成员设置为0,表示当前通讯录中没有联系人。
  4. capacity成员设置为0,表示通讯录的最大容量为0。
  5. 使用printf函数输出"销毁通讯录成功"的消息。

通过调用这个函数,可以释放通讯录所占用的内存空间,并将相关成员变量重置为初始状态。

//销毁通讯录
void DestroyContact(Contact* pc)
{free(pc->data);pc->data = NULL;pc->sz = 0;pc->capacity = 0;printf("销毁通讯录成功\n"); 
}

🚀2.5 动态通讯录源代码

🧩2.5.1 Contact.h 文件

#pragma once#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>//qsort函数、malloc、realloc#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
//#define MAX 100 //通讯录记录的人的信息的最大值#define INIT_DATA 4 //通讯录的初始大小
#define ADD_DATA 2 //每次通讯录增加的容量大小//类型声明
typedef struct PeoInfo
{char name[NAME_MAX];int age;char sex[SEX_MAX];char tele[TELE_MAX];char addr[ADDR_MAX];
}PeoInfo; //一个人的信息//typedef struct Contact
//{
//	PeoInfo data[MAX];//存放信息
//	int sz;//记录当前通讯录存放的人的信息个数
//}Contact;typedef struct Contact
{PeoInfo* data;//存放联系人的信息int sz;//当前通讯录存放的联系人的个数int capacity;//当前通讯录的最大容量
}Contact;//初始化通讯录
void InitContact(Contact* pc);//增加联系人
void AddContact(Contact* pc);//显示联系人信息
void ShowContact(Contact* pc);//这里形参也可以是结构体变量,因为打印不改变结构体内容//删除联系人
void DelContact(Contact* pc);//查找联系人
void SearchContact(Contact* pc);//修改指定联系人
void ModifyContact(Contact* pc);//排序通讯录
void SortContact(Contact* pc);//按姓名排序
void SortContact_by_name(Contact* pc);//按年龄排序
void SortContact_by_age(Contact* pc);//销毁通讯录
void DestroyContact(Contact* pc);

🧩2.5.2 Contact.c 文件

#define _CRT_SECURE_NO_WARNINGS 1#include "Contact.h"void CheckCapacity(Contact* pc);void ModifyMenu()
{printf("------------------------\n");printf("----   0.退出修改   ----\n");printf("----   1.修改姓名   ----\n");printf("----   2.修改年龄   ----\n");printf("----   3.修改性别   ----\n");printf("----   4.修改电话   ----\n");printf("----   5.修改地址   ----\n");printf("------------------------\n");
}void SortMenu()
{printf("-------------------------\n");printf("----   0.退出排序    ----\n");printf("----   1.按姓名排序  ----\n");printf("----   2.按年龄排序  ----\n");printf("-------------------------\n");
}//初始化通讯录
void InitContact(Contact* pc)
{assert(pc);pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INIT_DATA);if (pc->data == NULL){perror("InitContact->malloc");return;}memset(pc->data, 0, sizeof(pc->data));pc->sz = 0;pc->capacity = INIT_DATA;
}//扩容
void CheckCapacity(Contact* pc)
{//1.如果满了,就增容if (pc->sz == pc->capacity){Contact* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + ADD_DATA) * sizeof(PeoInfo));if (ptr == NULL){perror("CheckCapacity->realloc");return;}pc->data = ptr;pc->capacity += ADD_DATA;printf("增容成功\n");}//2.如果没满,啥也不干
}//增加联系人
void AddContact(Contact* pc)
{assert(pc);//检查通讯录当前的容量//1.如果满了,就增容//2.如果没满,啥也不干CheckCapacity(pc);printf("请输入增加联系人的名字:");scanf("%s", pc->data[pc->sz].name);printf("请输入增加联系人的年龄:");scanf("%d", &(pc->data[pc->sz].age));printf("请输入增加联系人的性别:");scanf("%s", pc->data[pc->sz].sex);printf("请输入增加联系人的电话:");scanf("%s", pc->data[pc->sz].tele);printf("请输入增加联系人的地址:");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("增加联系人成功!\n");
}//显示联系人信息
void ShowContact(Contact* pc)//这里形参也可以是结构体变量,因为打印不改变结构体内容
{assert(pc);if (pc->sz == 0){printf("通讯录为空,无需打印\n");return;}int i = 0;printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");for (i = 0; i < pc->sz; i++){//打印每个人的信息printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);}
}//查找联系人并返回下标
int FindByName(Contact* pc, char name[])
{assert(pc);for (int i = 0; i < pc->sz; i++){if (strcmp(name, pc->data[i].name) == 0){return i;//找到了,返回下标}}return -1;//没找到,返回-1
}//删除联系人
void DelContact(Contact* pc)
{char name[NAME_MAX];assert(pc);if (pc->sz == 0){printf("通讯录为空,无法删除\n");return;}printf("请输入要删除的人的名字:");scanf("%s", name);//找到名字为name的人int ret = FindByName(pc, name);if (ret == -1){printf("要删除的人不存在\n");return;}//删除这个人的信息for (int i = ret; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf("删除成功!\n");
}//查找联系人并打印信息
void SearchContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("请输入要查找的人的姓名:");scanf("%s", &name);int ret = FindByName(pc, name);if (ret == -1){printf("要查找的人不存在\n");return;}//显示找到的人的信息printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",ret + 1, pc->data[ret].name, pc->data[ret].age, pc->data[ret].sex, pc->data[ret].tele, pc->data[ret].addr);
}//修改指定联系人
void ModifyContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("请输入要修改的人的姓名:");scanf("%s", &name);int ret = FindByName(pc, name);if (ret == -1){printf("要修改的人不存在\n");return;}int input = 0;do{ModifyMenu();printf("请选择你要修改的信息->:");scanf("%d", &input);switch (input){case 0:printf("退出修改!\n");break;case 1:printf("请输入修改后的姓名:");scanf("%s", pc->data[ret].name);break;case 2:printf("请输入修改后的年龄:");scanf("%d", &(pc->data[ret].age));break;case 3:printf("请输入修改后的性别:");scanf("%s", pc->data[ret].sex);break;case 4:printf("请输入修改后的电话:");scanf("%s", pc->data[ret].tele);break;case 5:printf("请输入修改后的地址:");scanf("%s", pc->data[ret].addr);break;default:printf("选择错误,请重新选择!\n");break;}} while (input);printf("修改成功!\n");
}//按名字比较
int cmp_by_name(const void* e1, const void* e2)
{return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}//按年龄比较
int cmp_by_age(const void* e1, const void* e2)
{return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}//按姓名排序
void SortContact_by_name(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
}
//按年龄排序
void SortContact_by_age(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);
}//排序通讯录
void SortContact(Contact* pc)
{int inpuct = 0;if (pc->sz == 0){printf("通讯录为空,不用排序!\n");return;}do{SortMenu();printf("请选择排序方式 ->:");scanf("%d", &inpuct);switch (inpuct){case 0:printf("退出排序!\n");break;case 1:SortContact_by_name(pc);printf("按姓名排序后:\n");ShowContact(pc);break;case 2:SortContact_by_age(pc);printf("按年龄排序后:\n");ShowContact(pc);break;default:printf("输入错误,请重新输入");break;}} while (inpuct);
}//销毁通讯录
void DestroyContact(Contact* pc)
{free(pc->data);pc->data = NULL;pc->sz = 0;pc->capacity = 0;printf("销毁通讯录成功\n"); 
}

🧩2.5.3 test.c 文件

#define _CRT_SECURE_NO_WARNINGS 1
//测试通讯录基本功能#include "Contact.h"void menu()
{printf("------------------------\n");printf("----     1.add      ----\n");printf("----     2.del      ----\n");printf("----     3.search   ----\n");printf("----     4.modify   ----\n");printf("----     5.show     ----\n");printf("----     6.sorts    ----\n");printf("----     0.exit     ----\n");printf("------------------------\n");
}enum Option
{EXIT, //0ADD,DEL,SEARCH,MODIFY,SHOW,SORT
};int main()
{int input = 0;//创建通讯录Contact con;//通讯录//初始化通讯录InitContact(&con);int choice = 0;//排序方式的选择do{menu();printf("请输入你的选择 ->:");scanf("%d", &input);switch (input){case EXIT:DestroyContact(&con);printf("退出通讯录\n");break;case ADD:AddContact(&con);break;case DEL:DelContact(&con);break;case SEARCH:SearchContact(&con);break;case MODIFY:ModifyContact(&con);break;case SHOW:ShowContact(&con);break;case SORT:SortContact(&con);break;default:printf("选择错误,请重新选择!\n");break;}} while (input);return 0;
}

♨️三、通讯录优化之文件版通讯录

在实现动态通讯录后,我们发现虽然解决了通讯录空间不足的问题,但是对于已经加入通讯录中的信息在退出程序后无法保存来,即退出程序后之前添加的联系人信息消失不见,无法找到。这是因为程序运行后信息是存储在内存上的,退出程序后,计算机会清除内存上的信息,但是文件中的信息是存储在磁盘上的,即使程序退出,磁盘上的信息仍然存在,不会消失。

所以为了使通讯录中的信息保存下来我们需要采用文件操作的方法实现。

🚀3.1 保存通讯录信息到文件

创建一个函数 SaveContact,将通讯录中的数据保存到名为 "Contact.text" 的文件中。

//保存通讯录中的数据到文件中
void SaveContact(Contact* pc)
{FILE* pf = fopen("Contact.text", "wb");if (pf == NULL){perror("SaveContact");return;}//写信息到文件int i = 0;for (i = 0; i < pc->sz; i++){//fwrite(&(pc->data[i]), sizeof(PeoInfo), 1, pf);fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);}//关闭文件fclose(pf);pf = NULL; 
}
  1. 首先,使用 fopen 函数以二进制模式打开文件,并将文件指针存储在变量 pf 中。如果无法打开文件(即 pfNULL),则会调用 perror 函数打印错误信息,并直接返回。
  2. 接下来,代码使用一个循环来遍历通讯录中的每个联系人。循环变量 i0 开始,直到 pc->sz(通讯录的大小)减 1。在每次循环中,通过 fwrite 函数将当前联系人的信息写入文件中。这里使用了指针运算符 + 来获取当前联系人的地址,并将其作为第一个参数传递给 fwrite 函数。第二个参数是每个联系人信息的大小(单位是字节),第三个参数是要写入的次数,第四个参数是文件指针。
  3. 最后,代码使用 fclose 函数关闭文件,并将文件指针设置为 NULL,以确保不会再次使用该指针。

🚀3.2 在初始化时加载文件信息到通讯录

创建一个 LoadContact 函数,将文件中的信息加载到通讯录中。在初始化时调用此函数即可将文件 "Contact.text" 中的信息加载到通讯录中。

//将文件信息加载到通讯录
void LoadContact(Contact* pc)
{FILE* pf = fopen("Contact.text", "rb");if (pf == NULL){perror("LoadContact");return;}//加载文件信息(读文件)PeoInfo temp = { 0 }; //临时存储从文件中读取的数据while (fread(&temp, sizeof(PeoInfo), 1, pf)) //读取成功返回1,否则返回0{//检查容量,如果没满才能加载文件中的数据到通讯录CheckCapacity(pc);pc->data[pc->sz++] = temp;}//关闭文件fclose(pf);pf = NULL;
}
  1. 首先,使用fopen函数以二进制只读模式打开文件,并将文件指针保存在变量pf中。如果文件打开失败,函数会调用perror函数打印错误信息,并直接返回。
  2. 接下来,函数创建一个临时变量temp,并将其初始化为全零。然后,它进入一个循环,使用fread函数从文件中读取数据,每次读取一个PeoInfo结构体的大小。如果读取成功,fread函数返回1,否则返回0。在循环中,函数首先调用CheckCapacity函数来检查通讯录是否还有空间容纳新的联系人信息。如果有空间,就将读取到的联系人信息存储到通讯录的相应位置,并将通讯录的大小加
    1
  3. 最后,函数使用fclose函数关闭文件,并将文件指针设置为 NULL,以避免后续操作时出现错误。

🚀3.3 文件版通讯录源代码

🧩3.3.1 Contact.h 文件

#pragma once#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h> //qsort函数、malloc、realloc#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
//#define MAX 100 //通讯录记录的人的信息的最大值#define INIT_DATA 4 //通讯录的初始大小
#define ADD_DATA 2 //每次通讯录增加的容量大小//类型声明
typedef struct PeoInfo
{char name[NAME_MAX];int age;char sex[SEX_MAX];char tele[TELE_MAX];char addr[ADDR_MAX];
}PeoInfo; //一个人的信息//typedef struct Contact
//{
//	PeoInfo data[MAX];//存放信息
//	int sz;//记录当前通讯录存放的人的信息个数
//}Contact;typedef struct Contact
{PeoInfo* data;//存放联系人的信息int sz;//当前通讯录存放的联系人的个数int capacity;//当前通讯录的最大容量
}Contact;//初始化通讯录
void InitContact(Contact* pc);//增加联系人
void AddContact(Contact* pc);//显示联系人信息
void ShowContact(Contact* pc);//这里形参也可以是结构体变量,因为打印不改变结构体内容//删除联系人
void DelContact(Contact* pc);//查找联系人
void SearchContact(Contact* pc);//修改指定联系人
void ModifyContact(Contact* pc);//排序通讯录
void SortContact(Contact* pc);//按姓名排序
void SortContact_by_name(Contact* pc);//按年龄排序
void SortContact_by_age(Contact* pc);//销毁通讯录
void DestroyContact(Contact* pc);//保存通讯录中的数据到文件中
void SaveContact(Contact* pc);

🧩3.3.2 Contact.c 文件

#define _CRT_SECURE_NO_WARNINGS 1#include "Contact.h"//检查通讯录的容量是否已满
void CheckCapacity(Contact* pc);void ModifyMenu()
{printf("------------------------\n");printf("----   0.退出修改   ----\n");printf("----   1.修改姓名   ----\n");printf("----   2.修改年龄   ----\n");printf("----   3.修改性别   ----\n");printf("----   4.修改电话   ----\n");printf("----   5.修改地址   ----\n");printf("------------------------\n");
}void SortMenu()
{printf("-------------------------\n");printf("----   0.退出排序    ----\n");printf("----   1.按姓名排序  ----\n");printf("----   2.按年龄排序  ----\n");printf("-------------------------\n");
}//将文件信息加载到通讯录
void LoadContact(Contact* pc)
{FILE* pf = fopen("Contact.text", "rb");if (pf == NULL){perror("LoadContact");return;}//加载文件信息(读文件)PeoInfo temp = { 0 }; //临时存储从文件中读取的数据while (fread(&temp, sizeof(PeoInfo), 1, pf)) //读取成功返回1,否则返回0{//检查容量,如果没满才能加载文件中的数据到通讯录CheckCapacity(pc);pc->data[pc->sz++] = temp;}//关闭文件fclose(pf);pf = NULL;
}//文件版本的初始化通讯录
void InitContact(Contact* pc)
{assert(pc);pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INIT_DATA);if (pc->data == NULL){perror("InitContact->malloc");return;}memset(pc->data, 0, sizeof(pc->data));pc->sz = 0;pc->capacity = INIT_DATA;//加载文件中的信息到通讯录LoadContact(pc); 
}动态版本的初始化通讯录
//void InitContact(Contact* pc)
//{
//	assert(pc);
//	pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INIT_DATA);
//	if (pc->data == NULL)
//	{
//		perror("InitContact->malloc");
//		return;
//	}
//	memset(pc->data, 0, sizeof(pc->data));
//	pc->sz = 0;
//	pc->capacity = INIT_DATA; 
//}//扩容
void CheckCapacity(Contact* pc)
{//1.如果满了,就增容if (pc->sz == pc->capacity) {Contact* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + ADD_DATA) * sizeof(PeoInfo)); if (ptr == NULL){perror("CheckCapacity->realloc");return;}pc->data = ptr;pc->capacity += ADD_DATA;printf("增容成功\n");}//2.如果没满,啥也不干
}//增加联系人
void AddContact(Contact* pc)
{assert(pc);//检查通讯录当前的容量//1.如果满了,就增容//2.如果没满,啥也不干CheckCapacity(pc);printf("请输入增加联系人的名字:");scanf("%s", pc->data[pc->sz].name);printf("请输入增加联系人的年龄:");scanf("%d", &(pc->data[pc->sz].age));printf("请输入增加联系人的性别:");scanf("%s", pc->data[pc->sz].sex);printf("请输入增加联系人的电话:");scanf("%s", pc->data[pc->sz].tele);printf("请输入增加联系人的地址:");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("增加联系人成功!\n");
}//显示联系人信息
void ShowContact(Contact* pc)//这里形参也可以是结构体变量,因为打印不改变结构体内容
{assert(pc);if (pc->sz == 0){printf("通讯录为空,无需打印\n");return;}int i = 0;printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");for (i = 0; i < pc->sz; i++){//打印每个人的信息printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);}
}//查找联系人并返回下标
int FindByName(Contact* pc, char name[])
{assert(pc);for (int i = 0; i < pc->sz; i++){if (strcmp(name, pc->data[i].name) == 0){return i;//找到了,返回下标}}return -1;//没找到,返回-1
}//删除联系人
void DelContact(Contact* pc)
{char name[NAME_MAX];assert(pc);if (pc->sz == 0){printf("通讯录为空,无法删除\n");return;}printf("请输入要删除的人的名字:");scanf("%s", name);//找到名字为name的人int ret = FindByName(pc, name);if (ret == -1){printf("要删除的人不存在\n");return;}//删除这个人的信息for (int i = ret; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf("删除成功!\n");
}//查找联系人并显示联系人的信息
void SearchContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("请输入要查找的人的姓名:");scanf("%s", &name);int ret = FindByName(pc, name);if (ret == -1){printf("要查找的人不存在\n");return;}//显示找到的人的信息printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",ret + 1,pc->data[ret].name,pc->data[ret].age,pc->data[ret].sex,pc->data[ret].tele,pc->data[ret].addr);
}//修改指定联系人
void ModifyContact(Contact* pc)
{char name[NAME_MAX];assert(pc);printf("请输入要修改的人的姓名:");scanf("%s", &name);int ret = FindByName(pc, name);if (ret == -1){printf("要修改的人不存在\n");return;}int input = 0;do{ModifyMenu();printf("请选择你要修改的信息->:");scanf("%d", &input);switch (input){case 0:printf("退出修改!\n");break;case 1:printf("请输入修改后的姓名:");scanf("%s", pc->data[ret].name);break;case 2:printf("请输入修改后的年龄:");scanf("%d", &(pc->data[ret].age));break;case 3:printf("请输入修改后的性别:");scanf("%s", pc->data[ret].sex);break;case 4:printf("请输入修改后的电话:");scanf("%s", pc->data[ret].tele);break;case 5:printf("请输入修改后的地址:");scanf("%s", pc->data[ret].addr);break;default:printf("选择错误,请重新选择!\n");break;}} while (input);printf("修改成功!\n");
}//销毁通讯录
void DestroyContact(Contact* pc)
{free(pc->data);pc->data = NULL;pc->sz = 0;pc->capacity = 0;
}//按名字比较
int cmp_by_name(const void* e1, const void* e2)
{return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}//按年龄比较
int cmp_by_age(const void* e1, const void* e2)
{return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}//按姓名排序
void SortContact_by_name(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
}
//按年龄排序
void SortContact_by_age(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);
}//排序通讯录
void SortContact(Contact* pc)
{int inpuct = 0;if (pc->sz == 0){printf("通讯录为空,不用排序!\n");return;}do{SortMenu();printf("请选择排序方式 ->:");scanf("%d", &inpuct);switch (inpuct){case 0:printf("退出排序!\n");break;case 1:SortContact_by_name(pc);printf("按姓名排序后:\n");ShowContact(pc);break;case 2:SortContact_by_age(pc);printf("按年龄排序后:\n");ShowContact(pc);break;default:printf("输入错误,请重新输入");break;}} while (inpuct);
}//保存通讯录中的数据到文件中
void SaveContact(Contact* pc)
{FILE* pf = fopen("Contact.text", "wb");if (pf == NULL){perror("SaveContact");return;}//写信息到文件int i = 0;for (i = 0; i < pc->sz; i++){//fwrite(&(pc->data[i]), sizeof(PeoInfo), 1, pf);fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);}//关闭文件fclose(pf);pf = NULL; 
}

🧩3.3.3 test.c 文件

#define _CRT_SECURE_NO_WARNINGS 1
//测试通讯录基本功能#include "Contact.h"void menu()
{printf("------------------------\n");printf("----     1.add      ----\n");printf("----     2.del      ----\n");printf("----     3.search   ----\n");printf("----     4.modify   ----\n");printf("----     5.show     ----\n");printf("----     6.sorts    ----\n");printf("----     0.exit     ----\n");printf("------------------------\n");
}enum Option
{EXIT, //0ADD,DEL,SEARCH,MODIFY,SHOW,SORT
};int main()
{int input = 0;//创建通讯录Contact con;//通讯录//初始化通讯录InitContact(&con);int choice = 0;//排序方式的选择do{menu();printf("请输入你的选择 ->:");scanf("%d", &input);switch (input){case EXIT://保存通讯录中的数据到文件中SaveContact(&con);//销毁通讯录DestroyContact(&con);printf("退出通讯录\n");break;case ADD:AddContact(&con);break;case DEL:DelContact(&con);break;case SEARCH:SearchContact(&con);break;case MODIFY:ModifyContact(&con);break;case SHOW:ShowContact(&con);break;case SORT:SortContact(&con);break;default:printf("选择错误,请重新选择!\n");break;}} while (input);return 0;
}

💖今天的分享就到此结束了,如有疑问或是文章有不足之处,还请大家在评论区与我讨论或者私信我,创作不易,如果文章对你有帮助的话,还请三连支持一下咯🔥

在这里插入图片描述

相关文章:

通讯录详解(静态版,动态版,文件版)

&#x1f493;博客主页&#xff1a;江池俊的博客⏩收录专栏&#xff1a;C语言进阶之路&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅数据结构探索✅C语言刷题专栏&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f389;欢迎大家点赞&#x1f44d;评论&#x…...

在windows中搭建vue开发环境

1.环境搭建 具体环境搭建步骤参考链接 注意该博客中初始化命令&#xff1a; vue init webpack MyPortalProject需改为小写&#xff1a; vue init webpack myportalproject不然会报错 Warning: name can no longer contain capital letters2.创建第一个vueelement ui项目 …...

数字化转型:云表低代码开发助力制造业腾飞

数字化转型已成为制造业不可避免的趋势。为了应对市场快速变化、提高运营效率以及降低成本&#xff0c;制造业企业积极追求更加智能化、敏捷的生产方式。在这个转型过程中&#xff0c;低代码技术作为一种强大的工具&#xff0c;正逐渐崭露头角&#xff0c;有望加速制造业的数字…...

Linux学习之vim跳转到特定行数

参考的博客&#xff1a;《Vim跳到最后一行的方法》 《oeasy教您玩转vim - 14 - # 行头行尾》 《Linux&#xff1a;vim 中跳到首行和最后一行》 想要跳到特定行的话&#xff0c;可以在命令模式和正常模式进行跳转。要是对于vim的四种模式不太熟的话&#xff0c;可以到博客《Linu…...

详解基于Android的Appium+Python自动化脚本编写

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…...

【马蹄集】—— 百度之星 2023

百度之星 2023 目录 BD202301 公园⭐BD202302 蛋糕划分⭐⭐⭐BD202303 第五维度⭐⭐ BD202301 公园⭐ 难度&#xff1a;钻石    时间限制&#xff1a;1秒    占用内存&#xff1a;64M 题目描述 今天是六一节&#xff0c;小度去公园玩&#xff0c;公园一共 N N N 个景点&am…...

大数据毕业设计选题推荐-无线网络大数据平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…...

【jvm】虚拟机之本地方法接口与本地方法库

目录 一、本地方法1.1 说明1.2 代码示例1.3 为什么要使用native method 二、现状 一、本地方法 1.1 说明 1.一个Native Method就是一个Java调用非Java代码的接口。 2.一个Native Method是这样一个Java方法&#xff1a;该方法的实现由非Java语言实现&#xff0c;比如C。 3.这个…...

HDFS系统操作命令大全

一&#xff0c;前言 HDFS作为分布式存储的文件系统&#xff0c;有其对数据的路径表达方式 HDFS同linux系统一样&#xff0c;均是以/作为根目录的组织形式 linux&#xff1a;/usr/local/hello.txt HDFS&#xff1a;/usr/local/hello.txt 二&#xff0c;如何区分呢&#xff1f; L…...

雷尼绍探头编程 9810

9810 ​ 安全移动 使用参数 参数含义#9移动速度 F#117移动速度 F#148#24X 移动 终点绝对坐标#25Y 移动 终点绝对坐标#26Z 移动 终点绝对坐标#123机床移动到终点的绝对坐标 与 终点的理论值 的 差#5041当前绝对坐标 X 值#5042当前绝对坐标 Y 值#5043当前绝对坐标 Z 值#116刀具…...

el-table 列分页

<template><div><el-table:data"tableData":key"tampTime"style"width: 100%"><el-table-columnprop"name"label"姓名"width"180"></el-table-column><el-table-columnprop&quo…...

APP攻防--ADB基础

进入app包 先使用 adb devices查看链接状态 手机连接成功的 adb shell 获取到手机的一个shell 此时想进入app包时没有权限的&#xff0c;APP包一般在data/data/下。没有执行权限&#xff0c;如图 Permission denied 权限被拒绝 此时需要手机root&#xff0c;root后输入 su …...

【Linux】第十站:git和gdb的基本使用

文章目录 一、git的基本操作1.gitee新建仓库注意事项2.git的安装3.git的克隆4.git的add5.git的commit6.git的push7.git log8.git status9. .gitignore 二、Linux调试器---gdb1.背景2.gdb安装、进入与退出3.list/l4.r/run运行程序5. break/b 打断点6.info/i b 查看断点7.delete/…...

Single Image Haze Removal Using Dark Channel Prior(暗通道先验)

去雾算法都会依赖于很强的先验以及假设&#xff0c;并结合相应的物理模型&#xff0c;完成去雾过程。本文作者何凯明及其团队通过大量的无雾图像和有雾图像&#xff0c;归纳总结出无雾图像在其对应的暗通道图像上具有极低的强度值&#xff08;趋近于0&#xff09;&#xff0c;并…...

力扣382.链表随机节点(java利用数组随机返回节点值)

Problem: 382. 链表随机节点 文章目录 思路解题方法复杂度Code 思路 注意链表与数组的特性&#xff0c;对于随机访问读取的操作利用数组可以较方便实现&#xff0c;所以我们可以将链表中的节点值先存入到数组中最后再取出随机生成节点位置的值。 解题方法 1.生成List集合与Rand…...

在jupyter中使用R

如果想在Jupyter Notebook中使用R语言&#xff0c;以下几个步骤操作可行&#xff1a; 1、启动Anaconda Prompt 2、进入R的安装位置&#xff0c;切换到R的安装位置&#xff1a;D:\Program Files\R\R-3.4.3\bin&#xff0c;启动R&#xff0c;具体代码操作步骤如下&#xff0c;在…...

2023(第四届)江西开放数据创新应用大赛等你来挑战!

邀请函 这是一个友好的邀请。无论你是数据领域的专家、学生还是爱好者&#xff0c;我们都欢迎你加入这个平台。这不仅仅是一场比赛&#xff0c;更是一个交流、学习和展示自己的机会。 丰厚奖金&#xff1a;我们为参赛者准备了总计15W的奖金池&#xff0c;期待你的才华在这里得…...

2023-mac rz sz 安装

之前安装过一次&#xff0c;没问题&#xff0c;这次按照之前教程装了就不管上传下载都会卡住&#xff1b; step1: brew install lrzsz step2&#xff1a;在/usr/local/bin 路径下配置两个sh,之前从网上找到的直接用都不对&#xff0c;下面这个是调试过的正式可用的 iterm2…...

使用Matplotlib绘画3D图时运行不出结果,也不报错,图片是空白 !!

1.问题&#xff1a; 我使用如下代码运用matplotlib中的Axes3D绘画3D图&#xff0c;但是运行出来的结果是空白。 import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D #导入3D包 fig plt.figure() #窗口 #ax Axes3D(fig) # X, Y …...

Matlab函数——find

介绍 当你需要返回某个数组中符合指定条件的所有元素的索引时&#xff0c;可以使用 MATLAB 中的 find 函数。 find 函数语法&#xff1a; indices find(X) indices find(X, k) indices find(X, k, first) indices find(X, k, last) 其中&#xff0c;X 是一个数组&#xf…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...