目录
一、前期准备工作
文件的设置
(1)test.c —— 用于存放测试代码
(2)contact.c —— 用于具体的函数功能的实现
(3)contact.h —— 用于函数的声明以及相关类型的定义
为了表明每段代码具体存放在哪个文件中,以下每段代码第一行将表示为文件的名称,如下所示:
// contact.c
二、基础结构的实现
1.菜单的设置——方便用户对通讯录的功能进行选择
具体代码如下:
//test.c
void menu()
{
printf("*******************\n");
printf("**** 1.add ****\n"); // 增加联系人信息
printf("**** 2.del ****\n"); // 删除联系人信息
printf("**** 3.serach ****\n"); // 查找联系人信息
printf("**** 4.modify ****\n"); // 修改联系人信息
printf("**** 5.sort ****\n"); // 排序联系人信息
printf("**** 6.print ****\n"); // 打印联系人信息
printf("**** 0.exit ****\n"); // 退出通讯录
printf("*******************\n");
}
2.对用户输入的选择进行判断
为了方便用户使用,使用多组输入的判定形式,使用do while()循环
判断用户输入的内容,使用switch语句进行
代码如下:
// test.c
// 用户输入的选择
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
AddContact(&contact);
break;
case DEL:
DelContact(&contact);
break;
case SEARCH:
SearchContact(&contact);
break;
case MODIFY:
ModifyContact(&contact);
break;
case SORT:
SortContact(&contact);
break;
case PRINT:
PrintContact(&contact);
break;
case EXIT:
break;
default:
printf("选择错误,请重新选择:");
}
} while (input);
在switch中,判定的case内容没有设置用户输入的数据,而是使用了枚举类型对选项进行设置,如下:
// test.c
enum Menu
{
EXIT, // 默认为 整型的 0
ADD, // 1
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
在枚举类型中,第一个枚举常量默认为0,这里我们设置为退出通讯录的选项。
3.通讯录结构体的创建
为了调用方便,将结构体创建在创建的头文件contact.h中
(1)创建存放联系人信息的结构体
假设联系人的信息由姓名、性别、年龄、电话、地址五种信息构成。创建如下:
// contact.h
typedef struct PeoInfo
{
char name[MAX_NAME]; // 姓名
char sex[MAX_SEX]; // 性别
int age; // 年龄
char tel[MAX_TEL]; // 电话
char addr[MAX_ADDR]; // 地址
}PeoInfo;
(2)创建联系人列表
定义列表变量名为data
// test.c
// 已使用typedef进行类型定义,具体见下文
PeoInfo data[MAX];
(3)创建通讯录的结构体
这个结构体需要有存放联系人信息的结构体以及用来存放联系人数量的int类型变量sz
// contact.h
typedef struct Contact
{
PeoInfo data[MAX];
// 通讯录中具体的联系人数量
int sz;
}Contact;
在创建结构体时使用了typedef对结构体类型自定义为对应的类型名称,方便调用
(4)创建相关的类型定义
在创建结构体时,并没有使用直接的常量对大小和元素数量进行设置,使用的是define定义的相关类型进行设置,将define定义放在文件的开始也便于日后对于相应数量的修改,如下:
// contact.h
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TEL 15
#define MAX_ADDR 30
#define MAX 1000
(5)创建通讯录
这里不同于上文的创建结构体,这里需要的是存放通讯录信息的变量
// test.c
struct PeoInfo data[MAX];
4.通讯录的初始化
在使用通讯录执行预定的功能时,需要先对通讯录的具体内容进行初始化
我们创建InitContact函数对通讯录进行初始化,调用参数为通讯录contact,在功能实现中要求传递的参数为指针类型,我们使用&contact进行传参,如下:
(1)使用时样式如下:
// test.c
InitContact(&contact);
(2)函数的声明
传递的指针类型参数使用已定义的结构体类型Contact *进行设置,其他大部函数相同
我们将改函数的声明存放在contact.h文件中,之后函数声明不再提示.
声明举例如下:
// contact.h
void InitContact(Contact *pc);
(3)函数功能的实现
需要初始化的由两部分:联系人数量;联系人信息
联系人数量sz初始化为0
联系人信息使用内存设置函数memset()设置为0
// contact.c
void InitContact(Contact* pc)
{
pc->sz = 0;
// 要填充的空间为联系人列表,设置为0,大小为联系人列表的字节长度
memset(pc->data, 0, sizeof(pc->data));
}
使用memset函数需要引用头文件<string.h> 我们在contact.h中引用,如下:
// contact.h
#include<stdio.h>
#include<string.h>
之后直接源文件中引用该头文件即可:
// test.c
// contact.c
#Include "contact.h"
三、菜单中功能的实现
1.联系人的增加
函数的使用和声明样式已在上文中写明,可参考或移步此文对应文件的代码查看处
(1)对联系人数量进行判断
若通讯录满,则进行提示并退出该函数
使用return可提前退出函数
(2)通讯录未满则进行联系人信息的录入
(3)使联系人数量增加
使用++对联系人数量进行增加
如下:
// contact.c
void AddContact(Contact* pc)
{
if (pc->sz == MAX)
{
printf("通讯录已满,无法添加\n");
return;
}
// 增加联系人信息
printf("请输入姓名:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄:>");
scanf("%d", &pc->data[pc->sz].age);
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tel);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].addr);
// 使联系人数量+1
pc->sz++;
printf("增加成功\n");
}
需要注意的是:年龄为int类型的变量,需要使用&获得age的地址;除age外其他信息均为字符数组,数组名默认为首元素地址,无须添加&
2.删除指定的联系人
以通过查找姓名进行删除为例,其他需要用到查找相关的皆如此
(1)判断
首先我们判断通讯录中有无联系人信息,没有信息就提示无需进行删除并退出函数
(2)进行查找
a.创建一个字符数组用来存放用户输入的姓名信息,大小与创建结构体时一致,使用预定义的MAX_NAME;并读取用户输入的信息
b.创建FindName函数进行联系人的查找
由于此函数仅在当前文件内进行调用,可使用static定义当前函数对其他文件隐藏
调用的参数为通讯录,以及用户输入的查找姓名
通讯录在当前函数定义为指针pc,可直接调用
在FindName中,使用strcmp逐个将通讯录中联系人的姓名与用户输入的查找姓名进行比较,若存在,返回姓名一致时在联系人列表中的下标;不存在则返回-1,并在调用该函数的功能函数中对返回值进行判断并执行对应功能
// contact.c
static int FindName(const Contact* pc,const char input_name[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
// strcmp的结果为0时是找到了对应的姓名
if(strcmp(pc->data[i].name, input_name) == 0)
{
// 则返回对应的联系人信息下标
return i;
}
}
// 找不到
return -1;
}
(3)删除联系人
假设删除的信息下标为3,之后的信息的下标-1,对前一个信息进行覆盖,概念如下:
在删除完成后,将联系人数量减少1
// contact.c
void DelContact(Contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录为空,无需删除\n");
return;
}
char input_name[MAX_NAME];
printf("请输入要删除人的姓名:>");
scanf("%s", input_name);
// 设定ret判断返回结果
int ret = FindName(pc, input_name);
// 当查找失败时
if (ret == -1)
{
printf("要删除的人不存在\n");
return;
}
int i = 0;
for (i = ret; i < pc->sz; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
3.查找指定的联系人
这里我们也可以引用FindName函数进行查找
判断查找结果:不存在就提示并退出函数 / 存在就输出查找结果
// contact.c
void SearchContact(Contact* pc)
{
char find_name[MAX_NAME] = { 0 };
printf("请输入查找的联系人姓名:>");
scanf("%s", find_name);
int ret = FindName(pc, find_name);
if (ret == -1)
{
printf("查无此人\n");
return;
}
else
{
printf("%-10s\t%-10s\t%-10s\t%-10s\t%-10s\n", "姓名", "性别", "年龄", "电话", "地址");
printf("%-10s\t%-10s\t%-10d\t%-10s\t%-10s\n",
pc->data[ret].name,
pc->data[ret].sex,
pc->data[ret].age,
pc->data[ret].tel,
pc->data[ret].addr
);
printf("查找完毕\n");
}
}
使用了%-10s对打印结果进行对齐,%后加负数为左对齐,正数为右对齐,并打印了表头,方便信息的查看
4.修改联系人
依旧使用FindName函数进行查找
判断查找结果:不存在就无法进行修改,退出函数;存在就进行修改
修改使用覆盖法,使用户重新输入的信息覆盖到查找结果所在的位置上
// contact.c
void ModifyContact(Contact* pc)
{
char input_name[MAX_NAME] = { 0 };
printf("请输入要修改的联系人的姓名:>");
scanf("%s", input_name);
int ret = FindName(pc, input_name);
if (ret == -1)
{
printf("查无此人,无法修改\n");
return;
}
printf("请输入姓名:>");
scanf("%s", pc->data[ret].name);
printf("请输入性别:>");
scanf("%s", pc->data[ret].sex);
printf("请输入年龄:>");
scanf("%d", &pc->data[ret].age);
printf("请输入电话:>");
scanf("%s", pc->data[ret].tel);
printf("请输入地址:>");
scanf("%s", pc->data[ret].addr);
printf("修改完毕\n");
}
5.对联系人进行排序
以姓名作为排序标准为例
(1)先判断通讯录是否为空,为空就进行提示并退出函数
(2)排序
不为空就进行排序
使用qsort函数进行排序,通过查看,qsort的形式如下:
参数分别为 待排序列地址、代排关键字个数、关键字大小、比较函数地址
重要的是 比较函数地址,我们创建cmp_name函数作为比较函数
使用strcmp函数对字符串进行比较
如下:
// contact.c
int cmp_name(const void *p1, const void *p2)
{
return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
特别注意的是:qsort需要引用头文件<stdlib.h>,记得添加到contact.h中
使用qsort进行排序,如下:
// contact.c
void SortContact(Contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录为空,无需排序\n");
return;
}
qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_name);
printf("排序成功\n");
}
6.打印联系人列表
使用const 使得在打印时通讯录内容不会被改变,增强函数的健壮性
仍旧是判断联系人数量是否为0,为0则提示不用打印并退出函数
(1)打印表头
方便对联系人信息进行查看
(2)打印联系人信息
使用for循环对联系人列表进行遍历并打印联系人信息
代码如下:
// contact.c
void PrintContact(const Contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录中无联系人信息,无需打印\n");
return;
}
printf("%-10s\t%-10s\t%-10s\t%-10s\t%-10s\n", "姓名", "性别", "年龄", "电话", "地址");
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-10s\t%-10s\t%-10d\t%-10s\t%-10s\n",
pc->data[i].name,
pc->data[i].sex,
pc->data[i].age,
pc->data[i].tel,
pc->data[i].addr
);
}
printf("打印完成\n");
}
三、各文件代码内容
1.contact.h
#pragma once
// 相关函数的声明
// 一些类型的定义
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TEL 15
#define MAX_ADDR 30
#define MAX 1000
// 联系人的存放
// 使用typedef 定义结构体类型,方便调用
typedef struct PeoInfo
{
char name[MAX_NAME]; // 姓名
char sex[MAX_SEX]; // 性别
int age; // 年龄
char tel[MAX_TEL]; // 电话
char addr[MAX_ADDR]; // 地址
}PeoInfo;
// 通讯录
typedef struct Contact
{
PeoInfo data[MAX];
// 通讯录中具体的联系人数量
int sz;
}Contact;
// 初始化通讯录
void InitContact(Contact* pc);
// 增加联系人
void AddContact(Contact* pc);
// 打印联系人
void PrintContact(const Contact* pc);
// 删除联系人
void DelContact(Contact* pc);
// 查找联系人
void SearchContact(Contact* pc);
// 修改联系人
void ModifyContact(Contact* pc);
// 排序联系人
void SortContact(Contact* pc);
2.contact.c
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
// 具体的功能函数的实现
#include"contact.h"
// 初始化通讯录
void InitContact(Contact* pc)
{
// 初始化联系人数量为0
pc->sz = 0;
// 使用内存设置函数是联系人为0
// 内存设置——memset()
// 将pc->data指向的地址开始,初始化pc->data个字节数为0
memset(pc->data, 0, sizeof(pc->data));
}
// 增加联系人
void AddContact(Contact* pc)
{
// 判断通讯录是否已满(联系人是否大于MAX的值)
if (pc->sz == MAX)
{
printf("通讯录已满,无法添加\n");
return;
}
// 增加联系人信息
printf("请输入姓名:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄:>");
// 变量需要使用&
scanf("%d", &pc->data[pc->sz].age);
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tel);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].addr);
// 使联系人数量+1
pc->sz++;
printf("增加成功\n");
}
// 打印联系人——加上const确保在函数内部联系人信息不会被改变
void PrintContact(const Contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录中无联系人信息,无需打印\n");
return;
}
// 打印表头
printf("%-10s\t%-10s\t%-10s\t%-10s\t%-10s\n", "姓名", "性别", "年龄", "电话", "地址");
// 打印联系人信息
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-10s\t%-10s\t%-10d\t%-10s\t%-10s\n",
pc->data[i].name,
pc->data[i].sex,
pc->data[i].age,
pc->data[i].tel,
pc->data[i].addr
);
}
printf("打印完成\n");
}
// 通过姓名查找联系人
// 创建查找函数,方便被删除修改和查找时调用
// 使用static 使得此函数只能在本函数内部被调用和查看
static int FindName(const Contact* pc,const char input_name[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
// strcmp的结果为0时是找到了对应的姓名
if(strcmp(pc->data[i].name, input_name) == 0)
{
// 则返回对应的联系人信息下标
return i;
}
}
// 找不到
return -1;
}
// 删除联系人
void DelContact(Contact* pc)
{
// 判断通讯录是否为空
if (pc->sz == 0)
{
printf("通讯录为空,无需删除\n");
return;
}
// 查找要删除的人
char input_name[MAX_NAME];
printf("请输入要删除人的姓名:>");
scanf("%s", input_name);
// 设定ret判断返回结果
int ret = FindName(pc, input_name);
// 当查找失败时
if (ret == -1)
{
printf("要删除的人不存在\n");
return;
}
// 删除后,将删除联系人之后的联系人信息的下标-1
int i = 0;
for (i = ret; i < pc->sz; i++)
{
pc->data[i] = pc->data[i + 1];
}
// 删除成功后使联系人数量减1
pc->sz--;
printf("删除成功\n");
}
// 查找联系人
void SearchContact(Contact* pc)
{
char find_name[MAX_NAME] = { 0 };
printf("请输入查找的联系人姓名:>");
scanf("%s", find_name);
int ret = FindName(pc, find_name);
if (ret == -1)
{
printf("查无此人\n");
return;
}
else
{
printf("%-10s\t%-10s\t%-10s\t%-10s\t%-10s\n", "姓名", "性别", "年龄", "电话", "地址");
printf("%-10s\t%-10s\t%-10d\t%-10s\t%-10s\n",
pc->data[ret].name,
pc->data[ret].sex,
pc->data[ret].age,
pc->data[ret].tel,
pc->data[ret].addr
);
printf("查找完毕\n");
}
}
// 修改联系人
void ModifyContact(Contact* pc)
{
char input_name[MAX_NAME] = { 0 };
printf("请输入要修改的联系人的姓名:>");
scanf("%s", input_name);
int ret = FindName(pc, input_name);
if (ret == -1)
{
printf("查无此人,无法修改\n");
return;
}
printf("请输入姓名:>");
scanf("%s", pc->data[ret].name);
printf("请输入性别:>");
scanf("%s", pc->data[ret].sex);
printf("请输入年龄:>");
// 变量需要使用&
scanf("%d", &pc->data[ret].age);
printf("请输入电话:>");
scanf("%s", pc->data[ret].tel);
printf("请输入地址:>");
scanf("%s", pc->data[ret].addr);
printf("修改完毕\n");
}
// 创建比较函数
int cmp_name(const void *p1, const void *p2)
{
return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
// 排序联系人
void SortContact(Contact* pc)
{
// 判断通讯录是否为空
if (pc->sz == 0)
{
printf("通讯录为空,无需排序\n");
return;
}
// 以姓名为排序依据进行排序
// 使用qsort函数进行排序——调用了创建的cmp_name()
// 待排序列地址、待排关键字个数、关键字大小、比较函数地址
qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_name);
printf("排序成功\n");
}
3.test.c
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
// 通讯录
// 1.通讯录可以存放1000联系人信息
// 2.实现增删查改排序
// 每个联系人的信息构成:
// 姓名+性别+年龄+电话+地址
// 测试文件
#include"contact.h"
void menu()
{
printf("*******************\n");
printf("**** 1.add ****\n"); // 增加联系人信息
printf("**** 2.del ****\n"); // 删除联系人信息
printf("**** 3.serach ****\n"); // 查找联系人信息
printf("**** 4.modify ****\n"); // 修改联系人信息
printf("**** 5.sort ****\n"); // 排序联系人信息
printf("**** 6.print ****\n"); // 打印联系人信息
printf("**** 0.exit ****\n"); // 退出通讯录
printf("*******************\n");
}
// 使用枚举,使得switch选择时,更加明了
enum Menu
{
EXIT, // 默认为 0
ADD, // 1
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
int main() // 在静态模式下会因为数据过多报警告
{
int input = 0;
// 创建联系人列表
PeoInfo data[MAX];
// 创建通讯录
Contact contact;
// 初始化通讯录
InitContact(&contact);
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
AddContact(&contact);
break;
case DEL:
DelContact(&contact);
break;
case SEARCH:
SearchContact(&contact);
break;
case MODIFY:
ModifyContact(&contact);
break;
case SORT:
SortContact(&contact);
break;
case PRINT:
PrintContact(&contact);
break;
case EXIT:
break;
default:
printf("选择错误,请重新选择:");
}
} while (input);
return 0;
}
四、后期优化以及其他问题
1.后期优化
将会尽快提供通讯录的优化版本,分别为动态开辟版本以及将文件版本
2.查看代码时的注意事项
由于在编写代码时需要进行测试,故将打印联系人的函数放在了较前的位置
其他函数也根据实际需求进行编写,可能与本文中函数的顺序不符,见谅
3.查看方式
除本文内,也会将代码文件提交至gitee,可移步值gitee进行查看,当前版本通讯录文件存放链接如下:
shiwuqing-Contact 安全无毒,放心点击即可