前言
通讯录的实现综合了C语言的不少基本语法、编程思想和好的编程习惯,深入理解此项目的实现有助于提高语言的实际应用能力。
这个通讯录可以保存联系人的姓名、年龄、性别、电话和地址基本信息,实现基本的增、删、查、改和排序功能,并最终将联系人信息存储到文件中。
测试模块
这部分主要进行各个接口的组合和与用户的交互。
static void menu()
{
printf("**------- CONTACT --------**\n");
printf("**---1.Add-------2.Del----**\n");
printf("**---3.Search---4.Modify--**\n");
printf("**---5.Show------6.sort---**\n");
printf("**------0.save&exit-------**\n");
printf("***----------------------***\n");
}
void test(void)
{
struct Contact con;//创建通讯录con
InitContact(&con);//初始化通讯录
int input = 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:
SaveContact(&con);//将信息保存到文件中
DestoryContact(&con);//free开辟的动态内存空间
printf("退出通讯录\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
通讯录模块
声明模块
这部分主要是通讯录的定义和各个函数接口的声明。为了尽量合理利用空间,这里使用动态顺序表临时存储联系人的信息,也可以使用链表存储。
与以往不同的是,这次测试模块中switch
中的case
不再使用1,2,3...0的分支匹配方法,而是定义了一个枚举变量进行替换,这样可以增加代码的可读性和可维护性。
#pragma once
#include<stdio.h>
#include<string.h>
#include<Windows.h>
#include<stdbool.h>
#include<stdlib.h>
#include<errno.h>
#define NAME_MAX 20
#define SEX_MAX 5
#define TEL_MAX 20
#define ADRR_MAX 30
#define DEFAULT_CAP 5//设置初始容量为5
//为了增强可读性和可维护性,用枚举表示各个选项
enum option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT
};
//包含联系人信息的结构体
struct PeopInfo
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char tel[TEL_MAX];
char adrr[ADRR_MAX];
};
//用动态顺序表存放信息
struct Contact
{
struct PeopInfo *data;//存放信息
int size;//记录当前元素个数
int capacity;//记录通讯录当前的容量
};
void InitContact(struct Contact* ps);
void AddContact(struct Contact* ps);
void ShowContact(const struct Contact* ps);
void DelContact(struct Contact* ps);
void SearchContact(const struct Contact* ps);
void ModifyContact(struct Contact* ps);
void SortContact(struct Contact* ps);
void DestoryContact(struct Contact* ps);
void SaveContact(struct Contact* ps);
void LoadContact(struct Contact* ps);
实现模块
这部分进行各个函数接口的实现。
一些小组件
在实现下面的函数时,会多次用到一些小接口。
添加单个元素
信息添加、信息修改会用到这个组件。
static void AddConOnce(struct Contact* ps,int aim)
{
printf("请输入姓名>: \b");
scanf("%s", ps->data[aim].name);
printf("请输入年龄>: \b");
scanf("%d", &(ps->data[aim].age));
printf("请输入性别>: \b");
scanf("%s", ps->data[aim].sex);
printf("请输入电话>: \b");
scanf("%s", ps->data[aim].tel);
printf("请输入地址>: \b");
scanf("%s", ps->data[aim].adrr);
printf("添加成功__\n");
}
打印单个元素
信息打印、信息查找会用到这个组件。
void PrintOnce(const struct Contact* ps,int i)
{
printf("%-20s\t%-3d\t%-5s\t%-20s\t%-25s\n",
ps->data[i].name,
ps->data[i].age,
ps->data[i].sex,
ps->data[i].tel,
ps->data[i].adrr);
}
查找姓名
信息查找、信息修改会用到这个组件。
static SearchByName(const struct Contact* ps)
{
char name[NAME_MAX];
printf("请输入要查找的姓名>:");
scanf("%s", name);
int ret = CheckByName(ps, name);//查找信息
if (ret == -1)
{
printf("要查找的数据不存在\n");
}
else
{
printf("%-20s\t%-3s\t%-5s\t%-20s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
PrintOnce(ps, ret);//打印
}
ScreePauAndCl();
}
查找电话
信息查找会用到这个组件。
static int CheckByTel(const struct Contact* ps, const char* tel, int* ReleTel, int* count)
{
_Bool flag = false;//记录相关信息是否存在
for (int i = 0; i < ps->size; i++)
{
//返回值不为NULL,说明该条信息具有相关性
if (strstr(ps->data[i].tel, tel) != NULL)
{
flag = true;
ReleTel[(*count)++] = i;//将该条信息的下标放入数组中
}
}
//不存在,返回-1
if (flag == false)
{
return -1;
}
}
初始化通讯录
初始化通讯录就是为顺序表开辟空间,并将文件中的信息加载到顺序表中。加载信息时要时刻注意顺序表的容量,随时进行扩容。
void InitContact(struct Contact* ps)
{
//开辟初始容量大小的空间
ps->data = (struct PeopInfo*)malloc(DEFAULT_CAP * sizeof(struct PeopInfo));
ps->size = 0;
ps->capacity = DEFAULT_CAP;
//将文件中的信息加载到通讯录
LoadContact(ps);
}
void LoadContact(struct Contact* ps)
{
struct PeopInfo tmp = { 0 };//临时存储文件中的信息
FILE* pfRead = fopen("Contact.data", "rb");
if (pfRead==NULL)
{
printf("LoadContact:%s\n", strerror(errno));
return;
}
//无数据时fread返回0,load停止
while (fread(&tmp, sizeof(struct PeopInfo), 1, pfRead))
{
CheckCap(ps);//对顺序表当前的容量进行判断和扩容
ps->data[ps->size] = tmp;//将数据交给顺序表
ps->size++;
}
fclose(pfRead);
pfRead = NULL;
}
信息添加
对顺序表进行操作之前首先对容量进行判断和扩容。为了便于后面的函数对顺序表内容进行操作,这里将添加单个元素的功能单独封装。
void AddContact(struct Contact* ps)
{
CheckCap(ps);//判断和扩容
//添加单个元素
AddConOnce(ps, ps->size);
ps->size++;
ScreePauAndCl();
}
信息删除
首先提示提示输入姓名,按姓名查找目标信息,并将目标信息删除。若目标不存在,则报出提示。这里按姓名查找信息的功能单独封装成一个函数以供其他接口使用。
void DelContact(struct Contact* ps)
{
//通过姓名查找
char name[NAME_MAX];
printf("请输入要删除的人的姓名>:");
scanf("%s", name);
//查找对应的数据
int ret = CheckByName(ps, name);
if (ret == -1)
{
printf("要查找的数据不存在\n");
ScreePauAndCl();
}
//删除
else
{
for (int j = ret; j < ps->size-1; j++)
{
ps->data[j] = ps->data[j + 1];
}
ps->size--;
printf("删除成功\n");
ScreePauAndCl();
}
}
信息查找
支持两种查找方式:姓名查找和电话查找。其中电话查找支持模糊查找,输入电话片段,输出所有含此电环片段的相关信息。
void SearchContact(const struct Contact* ps)
{
/*
可以支持姓名查找和电话查找
其中电话查找可以支持模糊查找
*/
OptionList();//展示查找方式
again:
printf("请选择查找方式>:");
int input = 0;
scanf("%d", &input);
switch (input)
{
case SEARCH_NAME:
SearchByName(ps);//姓名查找
break;
case SEARCH_TEL:
SearchByTel(ps,ps->capacity);//电话查找
break;
case SEARCH_EXIT://退出查找
return;
default:
printf("选择错误,请重新选择\n");
goto again;
break;
}
}
姓名查找模块:
static SearchByName(const struct Contact* ps)
{
char name[NAME_MAX];
printf("请输入要查找的姓名>:");
scanf("%s", name);
int ret = CheckByName(ps, name);//查找信息
if (ret == -1)
{
printf("要查找的数据不存在\n");
}
else
{
printf("%-20s\t%-3s\t%-5s\t%-20s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
PrintOnce(ps, ret);//打印
}
ScreePauAndCl();
}
电话查找模块:
这里使用临时空间RelevantTel
存储所有相关的信息,函数SheckByTel()
负责查找相关信息并将其放入RelevantTel
中。输出信息后注意要释放RelevantTel
的空间。
static SearchByTel(const struct Contact* ps,int cap)
{
//临时存储所有相关信息
int* RelevantTel = (int*)malloc(cap * sizeof(int));
int count = 0;//记录数组元素个数
char tel[TEL_MAX];
printf("请输入要查找的电话号码>:");
scanf("%s", tel);
int ret = CheckByTel(ps,tel,RelevantTel,&count);
if (ret == -1)
{
printf("要查找的数据不存在\n");
}
else
{
//打印所有相关元素
system("cls");
printf("%-20s\t%-3s\t%-5s\t%-20s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
for (int i = 0; i < count; i++)
{
PrintOnce(ps, RelevantTel[i]);//打印单个元素
}
}
//释放临时空间
free(RelevantTel);
RelevantTel = NULL;
system("pause");
}
信息修改
修改信息就是将原来的信息覆盖掉。
void ModifyContact(struct Contact* ps)
{
char name[NAME_MAX];
printf("请输入要修改的人的姓名>:");
scanf("%s", name);
int ret = CheckByName(ps, name);
if (ret == -1)
{
printf("相关信息不存在\n");
ScreePauAndCl();
}
else
{
AddConOnce(ps, ret);//覆盖掉原来的信息
ScreePauAndCl();
}
}
信息打印
void ShowContact(const struct Contact* ps)
{
if (ps->size == 0)
{
printf("通讯录为空\n");
ScreePauAndCl();
}
else
{
system("cls");
printf("%-20s\t%-3s\t%-5s\t%-20s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
for (int i = 0; i < ps->size; i++)
{
PrintOnce(ps,i);//打印单个元素
}
}
}
信息排序
直接使用库函数进行快速排序。
int CompByName(const void* e1,const void* e2)
{
return strcmp(((struct PeopInfo*)e1)->name, ((struct PeopInfo*)e2)->name);
}
void SortContact(struct Contact* ps)
{
//按姓名排序
qsort(ps->data, ps->size, sizeof(ps->data[0]), CompByName);
}
退出和保存
用户退出通讯录时,将顺序表中的信息写入到文件中以实现保存,并销毁顺序表的空间。
void DestoryContact(struct Contact* ps)标签:ps,struct,实现,void,C语言,查找,Contact,通讯录,printf From: https://blog.51cto.com/u_15752114/5966305
{
//销毁顺序表
free(ps->data);
ps->data = NULL;
}
void SaveContact(struct Contact* ps)
{
//将顺序表中的数据写入到文件中
FILE* pfWrite = fopen("Contact.data", "wb");
if (pfWrite==NULL)
{
printf("SaveContact:%s\n", strerror(errno));
return;
}
for (int i = 0; i < ps->size; i++)//逐个写入文件
{
fwrite(&(ps->data[i]), sizeof(struct PeopInfo), 1, pfWrite);
}
fclose(pfWrite);
pfWrite = NULL;
}