纯C语言(无算法)的静态版通讯录
一、总括
1.1、通讯录的简述
小编讲解的通讯录是以C语言语法为基准的但不涉及动态内存开辟、文件操作以及数据结构与算法的知识。该通讯录的主要目的是巩固函数,分支,指针,循环以及结构体的知识。
1.2、函数的分文件处理
虽然小编的通讯录的主体逻辑比较简单,但是仍然将函数的定义和声明分离,这样使得代码的可阅读性显著提升,降低了程序的复杂度。
针对通讯录可以创建三个文件,分别是main.c、address_list.c、address_list.h。通俗地讲,就是将函数的声明、函数的调用、函数的定义分别放在三个文件,这样做使得源代码分成更小、更易于管理的文件,大大提高了程序的可维护性。
源文件main.c用于实现通讯录的主体逻辑,简单点说就是在主函数中调用函数。
在头文件address_list.h中,进行函数声明,定义符号常量,声明全局变量和结构体变量。
在address_list.c文件中,针对通讯录的不同功能用函数进行封装,所有的函数定义都放在该源文件中。
二、通讯录各模块分析
2.1、符号声明的模块(address_list.h)
解读:
-
#define _CRT_SECURE_NO_WARNINGS 1。它是C语言预处理指令,用于禁用某些被微软C运行库认为不安全的函数的相关警告。比如scanf函数,strcpy函数。
-
结构体类型重命名:当自定义的结构体类型名较长并且在代码中频繁使用时,可以对结构体类型名进行重命名操作,这样可以简化代码,使得代码更易读。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h> // 使用strcmp函数
#include <stdbool.h> // 使用bool类型
#include <stdlib.h> // 使用动态内存开辟的函数
#define NAME_MAX 20 // 结构体成员name的字符数组的可容纳最大字符数
#define SEX_MAX 3
#define PHONE_MAX 12
#define ADDRESS_MAX 25
#define MAX 100 // 通讯录可以容纳的最大人数
// 结构体类型重命名:使用具有描述性的名称,使得代码的含义更清晰明了
// 创建记录通讯人员各种信息的结构体
typedef struct Peopleinfo // 类型重命名
{
// 符号常量用作数组的大小。如果需要更改数组大小,只需更改符号常量的值即可,不需要
// 在代码中寻找和更改每个使用该值的实例。
char name[NAME_MAX];// 姓名
char sex[SEX_MAX]; // 性别
char phone[PHONE_MAX]; // 电话号码
char address[ADDRESS_MAX]; // 住址
int age; // 年龄
}Peoinfo, *pPeoinfo;
// 创建通讯录结构体
typedef struct ptContact
{
// 建立一个存储联系人信息的结构体数组
Peoinfo data[MAX];
// 通讯录的有效人数
size_t count;
}*pContact, Contact;
// 函数声明
void Menu(); // 显示菜单
void InitContact(Contact* pcon);// 初始化通讯录
void AddContact(Contact* pcon);// 添加联系人
void DelContact(Contact* pcon);// 删除联系人
void ShowContact(const Contact* pcon);//显示联系人
void SEARCHContact(const Contact* pcon);// 查找指定联系人
// 修改联系人
void MofifyContact(Contact* pcon);
// 排序联系人
void SortContact(Contact* pcon);
2.2、 实现主体逻辑的模块(main.c)
菜单模块的源代码写在main.c文件中。
- 在菜单模块中,使用枚举常量代替0~6。比如,在菜单中表示添加联系人的函数名时,是用ADD表示而不是用数字1表示,使得代码更易于理解和阅读。
- 在调用函数时使用地址传参,节省内存空间,改变形参的同时改变函数实参。
enum Option
{
EXIT,// 数字零代表退出函数
ADD,
DELETE,
SEARCH,
MODIFY,
SHOW,
SORT
};
int main()
{
size_t input = 0;
// 创建通讯录结构体
Contact con;
// 如果不初始化结构体,那么通讯录结构体数组中存放的都是随机值
InitContact(&con);
do
{
Menu();
printf("请输入选项:");
// 若编译器不支持%zd,可以使用%u
scanf("%zd", &input);
switch (input)
{
// 地址传参
case ADD:
// 增加联系人
AddContact(&con);
break;
case DELETE:
// 删除联系人
DelContact(&con);
break;
case SEARCH:
// 查找联系人
SEARCHContact(&con);
break;
case MODIFY:
// 修改联系人
MofifyContact(&con);
break;
case SHOW:
// 展示所有的联系人
ShowContact(&con);
break;
case SORT:
// 排序联系人
SortContact(&con);
break;
case EXIT:
printf("已退出通讯录!\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
2.3、实现函数定义的模块(address_list.c)
2.3.1、初始化通讯录
解读memset函数
//定义在头文件string.h
void* memset(void* s, int c, size_t count);
该函数的作用是将一段内存区域的每个字节都设定为指定的值。void* s指向要设置指定值的内存区域的起始地址。整型数据c代表要设定的值,无符号整型数据count代表要在内存中设置多少字节。
// 初始化通讯录
void InitContact(Contact* pcon)
{
// 初始化通讯录,人数为零
pcon->count = 0;
// sizeof(con->peoinfo)表示整个结构体的大小
memset(pcon, 0, sizeof(pcon->data));
}
2.3.2、添加联系人
解读:在添加联系人之前,需要判断通讯录的人数是否已满。如何确定要添加的联系人在通讯录数组中的位置呢?pcon->count表示通讯录当前的有效人数,那么(pcon->count - 1)代表有效人数中最后一个人在通讯录数组中下标。因此,pcon->count表示将要添加的联系人在数组中的下标。
// 添加联系人
void AddContact(Contact* pcon)
{
// 添加联系人前,判断通讯录是否已满,其中MAX为通讯录的最大容量
if (MAX == pcon->count)
{
printf("通讯录已满,无法添加联系人!\n");
// 提前终止函数的执行
return;
}
else
{
// 添加联系人的信息
printf("输入联系人姓名:");
scanf("%s", pcon->data[pcon->count].name);// 姓名
printf("输入联系人年龄:");
while (1)
{
// 排除非整型数据的输入
while (1 != scanf("%d", &(pcon->data[pcon->count].age)))
{
while ('\n' != getchar())
;
printf("请输入一个正整数:");
}
// 排除非正数输入
if (pcon->data[pcon->count].age <= 0)
{
printf("请输入一个正整数:");
}
else
{
// 满足上述则条件强制跳出循环
break;
}
}
printf("性别:男 女\n");
printf("输入联系人性别:");
while (1 == scanf("%s", pcon->data[pcon->count].sex))
{
// 吸收缓冲区中的错误输入
while ('\n' != getchar());
if ( 0 == strcmp("男", pcon->data[pcon->count].sex))
{
break;
}
else if (0 == strcmp("女", pcon->data[pcon->count].sex))
{
break;
}
else
{
printf("输入错误,请重新输入:");
}
// 输入错误时,sex数组清零
memset(pcon->data[pcon->count].sex, 0, 3);
}
printf("输入联系人电话号码:");
scanf("%s", pcon->data[pcon->count].phone);//电话号码
printf("输入联系人住址:");
scanf("%s", (pcon->data[pcon->count]).address);//住址
// 通讯录联系人的总数增加一个
pcon->count++;
printf("添加成功!\n");
}
}
2.3.3、删除联系人
解读思路:执行通讯录的删除功能时,首先判断当前通讯录的总人数是否为零:若总人数为零,则提示用户无法删除;若总人数不为零,则执行删除功能。当执行删除功能时,需要判断要删除的联系人是否存在:若不存在,则提示用户查无此人。当要删除的联系人存在时,找到要删除的数据在数组中的下标,然后进行删除。
// 删除联系人
void DelContact(Contact* pcon)
{
// 判断通讯录联系人的总数是否为零
if (0 == pcon->count)
{
printf("当前通讯录人数为零,无法删除\n");
// 提前终止函数的执行
}
else
{
// 当前通讯录的人数不为零可以进行删除操作
// 判断是否通讯录中是否存在该联系人
char name[NAME_MAX] = { 0 };// 记录用户输入的姓名
printf("输入要删除的联系人姓名:");
scanf("%s", name);
int pos = FindByName(pcon, name);
// 判断是否存在联系人
if (-1 == pos)
{
printf("该联系人在通讯录中不存在,无法删除!\n");
}
else
{
// 删除联系人
for (int i = pos; i < pcon->count; i++)
{
// 数据后移
pcon->data[i] = pcon->data[i + 1];
}
// 删除联系人的另一种实现方式
/*memmove(&(pcon->data[pos]),
&(pcon->data[pos]) + 1,
(pcon->count - pos - 1) * sizeof(*pcon->data));
printf("删除成功!\n");*/
// 每删除一次,联系人的总人数减一
pcon->count--;
}
}
}
2.3.4、查找指定联系人
// 通过姓名查找联系人:用static修饰该函数改变函数,仅源文件内可用
// 使用const修饰是为了防止在该函数你内部修改通讯录结构体指针pcon和用户输入的姓名name
static int FindByName(const Contact* pcon, const char* name)
{
int pos = -1;
for (int i = 0; i < pcon->count; i++)
{
// 使用strcmp函数比较两个字符串的内容
if (!strcmp(pcon->data[i].name, name))
{
// 暂时不考虑联系人重名的情况
pos = i;
break;
}
}
return pos;
}
// 查找指定联系人
void SEARCHContact(const Contact* pcon)
{
// 首先通讯录中联系人的总人数是否为零
if (0 == pcon->count)
{
printf("当前通讯录没有联系人,无法查找!\n");
}
else
{
char name[NAME_MAX] = { 0 };
printf("请输入要查找联系人的姓名:");
scanf("%s", name);
int ret = FindByName(pcon, name);
if (-1 == ret)
{
// 当返回值为负数时,说明找不到指定联系人
printf("查无此人\n");
}
else
{
// 显示标题
printf("%-8s %-4s %-5s %-15s %-25s\n",
"姓名", "年龄", "性别", "电话号码", "家庭住址");
// 显示对应的联系人
printf("%-8s %-4d %-5s %-15s %-25s\n",
pcon->data[ret].name,
pcon->data[ret].age,
pcon->data[ret].sex,
pcon->data[ret].phone,
pcon->data[ret].address
);
}
}
}
注意:标题的格式和打印联系人信息的格式必须严格对齐,这样做在运行程序时可以显得打印样式对齐美观。
2.3.5、修改指定联系人
// 通过姓名修改联系人
void MofifyContact(Contact* pcon)
{
if (!pcon->count)
{
printf("通讯录当前人数为零,无法修改!\n");
}
else
{
// 判断联系人是否存在
char name[NAME_MAX] = { 0 };
printf("请输入要修改联系人的姓名:");
scanf("%s", name);
int pos = FindByName(pcon, name);
if (-1 == pos)
{
printf("查无此人,修改失败!\n");
}
else
{
// 修改联系人的信息
printf("输入联系人姓名:");
scanf("%s", pcon->data[pos].name);// 姓名
printf("输入联系人年龄:");
while (1)
{
// 排除错误类型的输入
while (1 != scanf("%d", &(pcon->data[pos].age)))
{
while ('\n' != getchar())
;
printf("请输入一个正整数:");
}
// 排除非正数的输入
if (pcon->data[pos].age <= 0)
{
printf("请输入一个正整数:");
}
else
{
// 符合条件强制跳出循环
break;
}
}
printf("性别:男 女\n");
printf("输入联系人性别:");
while (1 == scanf("%s", pcon->data[pos].sex))
{
// 吸收缓冲区中的错误输入
while ('\n' != getchar());
if (0 == strcmp("男", pcon->data[pos].sex))
{
break;
}
else if (0 == strcmp("女", pcon->data[pos].sex))
{
break;
}
else
{
printf("输入错误,请重新输入:");
}
// 输入错误时,sex数组清零
memset(pcon->data[pos].sex, 0, 3);
}
printf("输入联系人电话号码:");
scanf("%s", pcon->data[pos].phone);//电话号码
printf("输入联系人住址:");
scanf("%s", (pcon->data[pos]).address);//住址
printf("修改成功!\n");
}
}
}
2.3.6、排序联系人
C语言提供了一个名为qsort的函数,这个函数可以对同类型元素进行快速排序。它的声明在头文件stdlib.h中,具体如下:
void qsort(
void* Base,
size_t count,
size_t size,
int (*cmpar)(const void* p1,const void*));
Base指向待排序元素的序列的起始地址
count是元素个数
size是每个元素在内存中占用多少字节。注意:每个元素必须是相同类型的。
cmpar是指向比较两个元素大小的函数的指针。如果首个函数参数比第二个大,则函数返回一个正整数;如果首个函数参数比第二个小,则函数返回一个负整数;如果两个函数参数相等,则返回整数零。
每当使用qsort函数时,需要使用者提供比较两个元素大小的函数。
// 通过名字进行比较的函数
static int cmp_by_name(const void* p1, const void* p2)
{
return strcmp(((Peoinfo*)p1)->name, ((Peoinfo*)p2)->name);
}
// 排序联系人
void SortContact(Contact* pcon)
{
// 当通讯录只有一个人或没有人时,不需要排序
if (0 == pcon->count)
{
printf("当前通讯录无联系人,无法排序!\n");
}
else if (1 == pcon->count)
{
printf("当前通讯录仅有一人,无法排序。\n");
}
else
{
char name[NAME_MAX] = { 0 };
// 使用
qsort(pcon->data, pcon->count, sizeof(Peoinfo), cmp_by_name);
printf("排序成功!\n");
ShowContact(pcon);// 展示排序成功的通讯录
}
}
三、完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#define NAME_MAX 20 // 结构体成员name的字符数组的可容纳最大数
#define SEX_MAX 3
#define PHONE_MAX 12
#define ADDRESS_MAX 25
#define MAX 100 // 通讯录可以容纳的最大人数
// 结构体类型重命名:使用描述性结构体类型名称,使得代码的含义更清晰明了
// 创建记录通讯人员各种信息的结构体
typedef struct Peopleinfo // 类型重命名
{
char name[NAME_MAX];// 姓名
char sex[SEX_MAX]; // 性别
char phone[PHONE_MAX]; // 电话号码
char address[ADDRESS_MAX]; // 住址
int age; // 年龄
}Peoinfo;
// 创建通讯录结构体
typedef struct pContact
{
Peoinfo data[MAX];
// 通讯录的有效人数
int count;
}*pContact, Contact;
// 函数声明
void Menu(); // 显示菜单
void InitContact(Contact* pcon);// 初始化通讯录
void AddContact(Contact* pcon);// 添加联系人
void DelContact(Contact* pcon);// 删除联系人
void ShowContact(const Contact* pcon);//显示联系人
void SEARCHContact(const Contact* pcon);// 查找指定联系人
// 修改联系人
void MofifyContact(Contact* pcon);
// 排序联系人
void SortContact(Contact* pcon);
enum Option
{
EXIT,
ADD,
DELETE,
SEARCH,
MODIFY,
SHOW,
SORT
};
int main()
{
int input = 0;
Contact con;
InitContact(&con);
do
{
Menu();
printf("请输入选项:");
scanf("%d", &input);
switch (input)
{
case ADD:
AddContact(&con);
break;
case DELETE:
DelContact(&con);
break;
case SEARCH:
SEARCHContact(&con);
break;
case MODIFY:
MofifyContact(&con);
break;
case SHOW:
ShowContact(&con);
break;
case SORT:
SortContact(&con);
break;
case EXIT:
printf("已退出通讯录!\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
// 初始化通讯录
void InitContact(Contact* pcon)
{
// 初始化通讯录,人数为零
pcon->count = 0;
// sizeof(con->peoinfo)表示整个结构体的大小
memset(pcon, 0, sizeof(pcon->data));
}
// 通过姓名查找联系人:用static修饰该函数改变函数,仅源文件内可用
static int FindByName(const Contact* pcon, const char* name)
{
int pos = -1;
for (int i = 0; i < pcon->count; i++)
{
if (0 == strcmp(pcon->data[i].name, name))
{
// 暂时不考虑联系人重名的情况
pos = i;
break;
}
}
return pos;
}
// 显示菜单
void Menu()
{
printf("***********通讯录菜单**********\n");
printf("***** 1.add 2.delete ******\n");
printf("***** 3.search 4.modify ******\n");
printf("***** 5.show 6.sort ********\n");
printf("***** 0.exit ******************\n");
printf("***********通讯录菜单**********\n");
}
//显示联系人
void ShowContact(const Contact* pcon)
{
if (0 == pcon->count)
{
printf("当前通讯录无联系人可显示!\n");
return;
}
else
{
printf("%-8s %-4s %-5s %-15s %-25s\n",
"姓名", "年龄", "性别", "电话号码", "家庭住址");
for (int i = 0; i < pcon->count; i++)
{
printf("%-8s %-4d %-5s %-15s %-25s\n",
pcon->data[i].name,
pcon->data[i].age,
pcon->data[i].sex,
pcon->data[i].phone,
pcon->data[i].address
);
}
}
}
// 添加联系人
void AddContact(Contact* pcon)
{
// 添加联系人前,判断通讯录是否已满
if (MAX == pcon->count)
{
printf("通讯录已满,无法添加联系人!\n");
// 提前终止函数的执行
return;
}
else
{
// 添加联系人的信息
printf("输入联系人姓名:");
scanf("%s", pcon->data[pcon->count].name);// 姓名
printf("输入联系人年龄:");
while (1)
{
// 排除错误类型输入
while (1 != scanf("%d", &(pcon->data[pcon->count].age)))
{
while ('\n' != getchar())
;
printf("请输入一个正整数:");
}
// 排除非正数输入
if (pcon->data[pcon->count].age <= 0)
{
printf("请输入一个正整数:");
}
else
{
break;
}
}
printf("性别:男 女\n");
printf("输入联系人性别:");
while (1 == scanf("%s", pcon->data[pcon->count].sex))
{
// 吸收缓冲区中的错误输入
while ('\n' != getchar());
if ( 0 == strcmp("男", pcon->data[pcon->count].sex))
{
break;
}
else if (0 == strcmp("女", pcon->data[pcon->count].sex))
{
break;
}
else
{
printf("输入错误,请重新输入:");
}
// 输入错误时,sex数组清零
memset(pcon->data[pcon->count].sex, 0, 3);
}
printf("输入联系人电话号码:");
scanf("%s", pcon->data[pcon->count].phone);//电话号码
printf("输入联系人住址:");
scanf("%s", (pcon->data[pcon->count]).address);//住址
// 通讯录联系人的总数增加一个
pcon->count++;
printf("添加成功!\n");
}
}
// 删除联系人
void DelContact(Contact* pcon)
{
// 判断通讯录联系人的总数是否为零
if (0 == pcon->count)
{
printf("当前通讯录人数为零,无法删除\n");
// 提前终止函数的执行
}
else
{
// 当前通讯录的人数不为零可以进行删除操作
// 判断是否通讯录中是否存在该联系人
char name[NAME_MAX] = { 0 };// 记录用户输入的姓名
printf("输入要删除的联系人姓名:");
scanf("%s", name);
int pos = FindByName(pcon, name);
// 判断是否存在联系人
if (-1 == pos)
{
printf("该联系人在通讯录中不存在,无法删除!\n");
}
else
{
// 删除联系人
/*for (int i = pos; i < pcon->count; i++)
{
pcon->data[i] = pcon->data[i + 1];
}*/
// 删除联系人的另一种实现方式
memmove(&(pcon->data[pos]),
&(pcon->data[pos]) + 1,
(pcon->count - pos - 1) * sizeof(*pcon->data));
printf("删除成功!\n");
pcon->count--;
}
}
}
// 查找指定联系人
void SEARCHContact(const Contact* pcon)
{
if (0 == pcon->count)
{
printf("当前通讯录没有联系人,无法查找!\n");
}
else
{
char name[NAME_MAX] = { 0 };
printf("请输入要查找联系人的姓名:");
scanf("%s", name);
int ret = FindByName(pcon, name);
if (-1 == ret)
{
printf("查无此人\n");
}
else
{
printf("%-8s %-4s %-5s %-15s %-25s\n",
"姓名", "年龄", "性别", "电话号码", "家庭住址");
printf("%-8s %-4d %-5s %-15s %-25s\n",
pcon->data[ret].name,
pcon->data[ret].age,
pcon->data[ret].sex,
pcon->data[ret].phone,
pcon->data[ret].address
);
}
}
}
// 通过姓名修改联系人
void MofifyContact(Contact* pcon)
{
if (!pcon->count)
{
printf("通讯录当前人数为零,无法修改!\n");
}
else
{
// 判断联系人是否存在
char name[NAME_MAX] = { 0 };
printf("请输入要修改联系人的姓名:");
scanf("%s", name);
int pos = FindByName(pcon, name);
if (-1 == pos)
{
printf("查无此人,修改失败!\n");
}
else
{
// 修改联系人的信息
printf("输入联系人姓名:");
scanf("%s", pcon->data[pos].name);// 姓名
printf("输入联系人年龄:");
while (1)
{
// 排除错误类型的输入
while (1 != scanf("%d", &(pcon->data[pos].age)))
{
while ('\n' != getchar())
;
printf("请输入一个正整数:");
}
// 排除非正数的输入
if (pcon->data[pos].age <= 0)
{
printf("请输入一个正整数:");
}
else
{
// 符合条件强制跳出循环
break;
}
}
printf("性别:男 女\n");
printf("输入联系人性别:");
while (1 == scanf("%s", pcon->data[pos].sex))
{
// 吸收缓冲区中的错误输入
while ('\n' != getchar());
if (0 == strcmp("男", pcon->data[pos].sex))
{
break;
}
else if (0 == strcmp("女", pcon->data[pos].sex))
{
break;
}
else
{
printf("输入错误,请重新输入:");
}
// 输入错误时,sex数组清零
memset(pcon->data[pos].sex, 0, 3);
}
printf("输入联系人电话号码:");
scanf("%s", pcon->data[pos].phone);//电话号码
printf("输入联系人住址:");
scanf("%s", (pcon->data[pos]).address);//住址
printf("修改成功!\n");
}
}
}
// 通过名字进行比较的函数
static int cmp_by_name(const void* p1, const void* p2)
{
return strcmp(((Peoinfo*)p1)->name, ((Peoinfo*)p2)->name);
}
// 排序联系人
void SortContact(Contact* pcon)
{
if (0 == pcon->count)
{
printf("当前通讯录无联系人,无法排序!\n");
}
else if (1 == pcon->count)
{
printf("当前通讯录仅有一人,无法排序。\n");
}
else
{
char name[NAME_MAX] = { 0 };
qsort(pcon->data, pcon->count, sizeof(Peoinfo), cmp_by_name);
printf("排序成功!\n");
ShowContact(pcon);
}
}
敬请各位读者多多指教!