目录
前言
思路
开始菜单
联系人与通讯录:
通讯录框架:
实现初始化 增删改查 排序 打印:
初始化函数:
增加联系人函数:
显示所有联系人信息:
查找name函数:
删除指定联系人:
查找指定联系人:
修改指定联系人:
排序指定联系人信息:
动态分配联系人个数:
完整版:
大家对通讯录都不陌生,比如手机里的通讯录,我们可以存储联系人在通讯录里,也可以对已有联系人进行删除、查找、修改和排序。
那么如何通过C语言实现和手机上功能一样的通讯录呢?下面我开始一步步构建我们的通讯录。 (文章最后有完整版存放在Gitee)
通讯录中要包含联系人的一些必要信息,比如:名字、年龄、性别、电话和住址等,这次我们的通讯录就包含上述五种信息,其他相关信息可以自行添加,根据需要修改代码。
这次我们的通讯录容量设定存放100个联系人。
通讯录功能:
- 实现增加联系人,
- 删除指定联系人,
- 查找指定联系人,
- 修改指定联系人,
- 查找指定联系人,
- 对已有联系人相关信息排序,
- 除此之外,我们还需要显示所有联系人的信息(也就是打印输出)。
我们创建两个源文件和一个头文件:
将头文件contact.c包含到text.c和contact.c中,这样可以在源文件中使用被包含的头文件中定义的函数、变量、宏、头文件等。
好的,思路已经差不多了,现在开始写代码吧 !!!
将头文件
首先写一个开始菜单menu函数:
void menu()
{
printf("***************************************\n");
printf("******** 1.add 2.del ********\n");
printf("******** 3.search 4.modify ********\n");
printf("******** 5.show 6.sort ********\n");
printf("******** 0.exit ********\n");
printf("***************************************\n");
}
我们的联系人和通讯录都在contact.h中创建。
创建保存联系人信息的结构体PeoInfo,(名字、年龄、性别、电话和住址)
使用 typedef 将 struct PeoInfo 类型自定义成 PeoInfo 更加直观。
typedef struct PeoInfo
{
char name[20];
int age;
char sex[5];
char tele[12];
char addr[30];
}PeoInfo;
在头文件中定义一个值为100的常量MAX。
#define MAX 100
创建存放联系人的通讯录Contact,定义PeoInfo类型的数组data,存放MAX个联系人对应的信息。
sz用于记录当前储存联系人的数量,在之后筛选联系人时会用到。
typedef struct Contact
{
PeoInfo data[MAX];
int sz;
}Contact;
也可以将联系人各种信息定义为常量,方便日后修改其所需空间。
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
typedef struct PeoInfo
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
我们将通讯录框架封装成一个 test 函数,通过do-while语句实现不同功能。
首先定义input变量,由用户输入的input的值判断进行什么操作。
定义结构体Contact类型变量 con ,在这个通讯录框架中con就是通讯录。
然后初始化通讯录,将通讯录的成员赋值为零,函数内部改变改变外部变量通讯录con,需要传递地址给初始化函数,结构体传参最好传地址,节约空间。初始化函数后续进行讲解。
void test()
{
int input = 0;
Contact con;
InitContact(&con);
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);
}
通讯录运行通过do-while实现,内部调用menu函数打印开始菜单,然后提示用户选择功能。
接下来通过swich-case语句判断输入不同的 input 值对应实现不同功能。
我们都知道case后只能加整数,但我们可以创建枚举类型替换这些整数,枚举类型成员的值从0开始递增1,适用于代替case后的整数,使用枚举可以更直观了解函数功能。
将enum OPPION在contact.h中声明。
enum OPPION
{
EXIT, //退出
ADD, //添加
DEL, //删除
SEARCH, //查找
MODIFY, //修改
SHOW, //打印
SORT //排序
};
接下来我们创建switch-case中实现通讯录增删改查排序对应的函数:
将他们声明在头文件中,这些函数我们都写在contact.c中。
//初始化函数声明
void InitContact(Contact* pc);
//增加联系人
void AddContact(Contact* pc);
//显示所有联系人的信息
void ShowContact(const Contact* pc);//不需要修改加const
//删除指定联系人
void DelContact(Contact* pc);
//查找指定联系人
void SearchContact(const Contact* pc);
//修改指定联系人
void ModifyContact(Contact* pc);
//排序指定联系人信息
void SortContact(Contact* pc);
void InitContact(Contact* pc)
{
assert(pc);
memset(pc->data, 0, sizeof(pc->data));
pc->sz = 0;
}
宏assert进行断言, 需包含 assert
会引发一个错误,并向标准错误流输出一条消息,通常包括断言失败的信息和文件名/行号等信息,程序会终止执行。
memset函数需要包含头文件
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");
}
判断当前联系人是否已满,已满则执行return;中止增加联系人。
void ShowContact(const Contact* pc)
{
assert(pc);
//打印列标题
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-30s\t\n", "名字", "年龄", "性别", "电话", "地址");
for (int i = 0; i < pc->sz; i++)
{
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-30s\t\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
打印一行标题方便查看。
int FindByName(const Contact* pc, char name[])
{
int i;
for (i = 0; i < pc->sz; i++)
{
//比较char类型字符串name需要strcmp
if (strcmp(pc->data[i].name, name) == 0) {
return i;
}
}
return -1;
}
该函数用来支持函数内部的查找功能,返回值为联系人在数组的下标,可以不需要让别人知道,
所以可以不在头文件里声明,在这个文件内函数能调用即可,加上static也可以。
比较字符串只能用strcmp函数进行比较,需要包含
这个函数将服务于后续各种需要指定联系人的函数。
void DelContact(Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要删除的人的名字:>");
scanf("%s", name);
int del = FindByName(pc, name);
if (del == -1) {
printf("要删除的人不存在\n");
return;
}
//删除坐标为del的联系人
int i;
for (i = del; i < pc->sz - 1; i++) {
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("成功删除联系人\n");
}
创建一个name数组,大小为自定义常量MAX_NAME,大小为20,name数组用于存放要删除的联系人,然后定义del变量接受FindByName的返回值。
该函数每次只删除一个name,比如:传入的结构体指针包含二十个name,即sz=20。 删除一个则该name后续的元素都要往前移动一位,最后一位元素的下标为19,需要移动sz-1次,则 i 要小于 sz-1,保证下标为del到最后一个元素下标为sz向前移动一位。
删除之后 sz 需要减一。
void SearchContact(const Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要查找人的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
printf("要查找的人不存在\n");
else
{
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-30s\t\n",
"名字", "年龄", "性别", "电话", "地址");
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-30s\t\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
}
查找到联系人则打印出相关信息,否则则提示”要查找的人不存在“。
void ModifyContact(Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要修改人的名字\n");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
printf("要修改的人不存在\n");
else {
printf("请输入名字:>");
scanf("%s", pc->data[pos].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pos].age));
printf("请输入性别:>");
scanf("%s", pc->data[pos].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pos].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pos].addr);
printf("修改完成\n");
}
}
如果要修改的联系人存在,则通过FindByName函数获得到该联系人在data中的下标,然后通过pos进行修改相关信息。
int CompareByName(const void* a, const void* b) {
return strcmp(((PeoInfo*)a)->name, ((PeoInfo*)b)->name);
}
int CompareByAge(const void* a, const void* b) {
return ((PeoInfo*)a)->age - ((PeoInfo*)b)->age;
}
int CompareByAddr(const void* a, const void* b) {
return strcmp(((PeoInfo*)a)->addr, ((PeoInfo*)b)->addr);
}
void SortContact(Contact* pc)
{
assert(pc);
char del[10];
printf("请输入想要排序的项目\n");
scanf("%s", del);
if (strcmp(del, "name") == 0)
{
qsort(pc->data, pc->sz, sizeof(PeoInfo), CompareByName);
}
else if (strcmp(del, "age") == 0)
{
qsort(pc->data, pc->sz, sizeof(PeoInfo), CompareByAge);
}
else if (strcmp(del, "addr") == 0)
{
qsort(pc->data, pc->sz, sizeof(PeoInfo), CompareByAddr);
}
else
{
printf("无效的排序选项\n");
return;
}
// 输出排序后的结果
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-30s\t\n", "名字", "年龄", "性别", "电话", "地址");
for (int i = 0; i < pc->sz; i++)
{
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-30s\t\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
运用qsort函数排序指定联系人的信息,在联系人信息中我只写了姓名、年龄和地址这些能进行排序对应的函数。
如果你忘记qsort函数了,希望这篇文章的qsort函数部分对你有帮助详解C语言—进阶指针(二)
这篇《动态分配内存》讲解了相关知识,如果你忘记了,可以复习一下。
可以当以上正常版本的通讯录学懂之后,回过头来学习动态版本的通讯录。
我们做出以下修改,将con定义为指针类型,方便修改同时动态分配内存。
Contact* con;
在头文件contact.h中增加两个自定义常量,
初始化存储联系人个数的数组data的值为 DEFAULT_SZ也就是 3。
后续通过增容函数,使其每次是存放联系人个数的上限增加两个联系人。
#define DEFAULT_SZ 3 //默认值3
#define INC_SZ 2 //每次增加两个联系人
对结构体Contact进行调整:
typedef struct Contact
{
PeoInfo* data;
int sz;//当前存放的有效元素的个数
int capacity;//通讯录当前最大容量
}Contact;
修改初始化函数:
void InitContact(Contact* pc)
{
assert(pc);
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (pc->data == NULL) {
perror("InitContact");
return;
}
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
}
增加增容函数 :
int CheckCapacity(Contact* pc)
{
if (pc->sz == pc->capacity)//判断是否需要增容
{
PeoInfo* ptr=(PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
if (ptr == NULL) {
perror("CheckCapacity");
return 0;
}
else {
pc->data = ptr;
pc->capacity += INC_SZ;
printf("成功增容\n");
return 1;
}
}
return 1;
}
如果增容失败则停止增加联系人:
void AddContact(Contact* pc)
{
assert(pc);
if (CheckCapacity(pc) == 0)//增容失败,停止添加联系人
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");
}
对switch-case中EXIT添加对动态分配内存进行释放的DestroyContact函数。
case EXIT:
DestroyContact(&con);
printf("退出通讯录\n");
break;
void DestroyContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
}
完整版我放在Gitee上了,
请自行查阅: 正常版本 and 动态版本