前言:今天我们开辟一个新的系列【手把手带你做项目系列】,带大家一起写一个通讯录,力争通过三种方式“静态版本、动态版本、文件存储版本”带领大家领略到项目的规范与严谨,不断的进行优化和完善,提高自己的编程素养。
目录
项目要求:
1 静态通讯录的概要
2 静态通讯录接口函数实现
2.1 通讯录的基本结构
2.2 简易菜单
2.3 初始化通讯录
2.4 增加成员
2.5 打印通讯录
2.6 删除联系人
2.7 查找联系人
2.8 修改联系人
2.9 排序联系人(按名字)
2.10 完善与改进
3 完整代码
通讯录可以用来存储1000个人的信息,每个人的信息包括:姓名、性别、年龄、电话、住址
基本要求如下:
- 添加联系人信息
- 删除指定联系人信息
- 查找指定联系人信息
- 修改指定联系人信息
- 显示所有联系人信息
- 清空所有联系人
- 以名字排序所有联系人
静态通讯录:使用的是定长数组,即数组的长度不能发生改变。我们可以设置通讯录可以记录的成员个数为1000个。
文件名 | 功能 |
Contact.c | 通讯录函数接口的实现 |
Contact.h | 通讯录相关的宏定义,头文件,接口函数的声明 |
test.c | 函数接口测试 |
通讯录是一个结构体。
包含:1.通讯录成员数组,每一个通讯录成员(姓名、地址等)又是一个结构体。
2.标志通讯录成员个数的变量。
注意:为了方便后序更改通讯录的各种大小,推荐使用宏定义
#define MAX 100
#define MAX_NAME 20 //名字最大长度
#define MAX_SEX 10 //性别最大长度
#define MAX_TELE 12 //电话最大长度
#define MAX_ADDR 30 //地址最大长度
//人的信息
struct PeoInfo
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
};
注意:未来如果你还想用这个类型,上面的写法就比较麻烦了,可以用typedef重定义
typedef struct PeoInfo
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
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");
printf("*********************************\n");
}
int main()
{
int input = 0;
PeoInfo data[1000]; //存1000人信息,仅仅这样可以吗?
do
{
menu();
printf("请选择>");
scanf("%d", &input);
switch (input)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 0:
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
在上面代码段中,仅仅 PeoInfo data[1000] ;是不够的,因为通讯录涉及增删查改,其剩余变量在发生变化。因此我们使用的时候需要一个变量来记录实际有效的人数信息。
PeoInfo data[1000];
int count = 0; //记录当前人数信息
如果我们把上面这两个放在main函数实现,是不是会显得有些散乱且麻烦?那我们想是不是可以把这两个封装在一起?
//通讯录
typedef struct Contact
{
PeoInfo data[MAX]; //存放人的信息
int count; //记录当前通讯录中实际人的个数
}Contact;
诶有办法了,把它俩都放在一个结构体,找起来也会方便不少。
好,我们再回到刚刚的主函数,此时我们可以这样改写
Contact con; //通讯录
创建好之后,我们是不是应该给它初始化一下?毕竟一个人的信息都没有,那我们给它初始化为全0吧。
Contact con = { 0 } //初始化通讯录,这样对吗?
理论上可行,但是我们不推荐这么写,为什么呢?
1、我们应该尽量把代码封装成函数的形式,模块化的去工作。
2、如果我们想对它初始化的内容不能通过大括号的方式初始化,或者说想初始化指定的内容不方便,那我们有函数的方式初始化会方便很多。
InitContact(&con); //初始化通讯录正确方式,注意我们要改内容应该传地址哦
结构体传参尽量传地址,这样效率更高。
我们准备使用 InitContact 这个函数,就去 contact.h 处声明一下。
void InitContact(Contact* pc); //初始化通讯录
好,接下来我们去 contact.c 处实现一下函数。
Tip:到 contact.c 处我们发现它不认识 InitContact 函数,我们要记得在前面添加头文件#include"contact.h" 这样它就记住了。
void InitContact(Contact* pc)
{
assert(pc); //断言一下,不能为空指针
pc->count = 0;
memset(pc->data, 0, sizeof(pc->data)); //设置内存大小
//pc->data拿到了通讯录中PenInfo数组的数组名
//数组名单独放在sizeof内部,计算的是整个数组的大小
//memset(ps->data, 0, sizeof(PeoInfo) * MAX); //(写法2)通讯录成员数组初始化为0
}
我们希望 data 里面全是0,怎么做呢?此时我们就要请出 memset 函数,有小伙伴可能会感到陌生,这里再简述一下 memset 函数。
void * memset ( void * ptr, int value, size_t num ); 功能介绍:内存设置(memory set)是将某一块内存中的内容全部设置为指定的值,这个函数通常为新申请的内存做初始化工作。(以字节为单位初始化) 需要的头文件:#include
memset 函数来设置我们的内存非常方便,如果不用 memset 函数,那就要考虑循环来初始化,这样就会比较繁琐。
写到这里,我们来调试一下看看效果。
初始化成功了,我们增加一个人的信息试试看吧。
AddContact(&con); //封装函数
void AddContact(Contact* pc); //增加联系人到通讯录
谨记:我们一定要清楚一段代码是怎么从无到有产生的,千万不敢从第一行开始一行一行的“抄代码”,千万不敢一个文件一个文件的写(写完.h 之后写.c),代码是哪里需要去哪里写!
//写法1:直接对通讯录成员数组进行操作
void AddContact(Contact* pc)
{
assert(pc);
if (pc->count == 1000)
{
printf("通讯录已满,无法增加\n");
return;
}
printf("请输入姓名:\n");
scanf("%s", pc->data[pc->count].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pc->count].age)); //需要取地址,数组不用
printf("请输入性别:\n");
scanf("%s", pc->data[pc->count].sex);
printf("请输入电话:\n");
scanf("%s", pc->data[pc->count].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[pc->count].addr);
pc->count++;
printf("增加成功\n");
}
//写法2:先创建一个临时结构体成员,然后赋值后,赋给通讯录结构体数组中ps->size位置
void AddContact(Contact* ps)
{
PeoInfo tmp = { 0 };
//先判断通讯录是否满了
if (ps->size == MAX)
{
printf("通讯录已满\n");
}
else
{
printf("请输入名字:");
scanf("%s", tmp.name); //数组名不用&
printf("请输入年龄: ");
scanf("%d", &(tmp.age));
printf("请输入地址:");
scanf("%s", tmp.addr);
printf("请输入号码:");
scanf("%s", tmp.tele);
printf("请输入性别:");
scanf("%s", tmp.sex);
ps->data[ps->size] = tmp;
printf("添加成功\n");
ps->size++;
}
}
ShowContact(&con); //封装函数
void ShowContact(const Contact* pc); //打印通讯录的信息
//这一次只是打印,不会更改,因此我们使用const来保护指针。
void ShowContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
// -是左对齐的意思,\t是空3格
for (i = 0; i < pc->count; i++)
{
printf("%-20s\t%-3d\t%-5s\t%-12s\t%-30s\n", pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
小习惯:当遇到长代码时我们可以回车一下增加可读性。
删除代码封装与前面相同,此处仅展示接口函数。
void DelContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
assert(pc);
int i = 0;
if(pc->count == 0)
{
printf("通讯录为空,没有信息可以删除\n");
return;
}
printf("请输入要删除人的名字:\n");
scanf("%s", name);
//删除
//1.查找
int pos = FindByName(pc, name); //封装查找函数,pos是位置
if (pos == -1)
{
printf("要删除的人不存在\n");
return;
}
//2.删除
for (i = pos; i < pc->count - 1; i++)
{
pc->data[i] = pc->data[i + 1]; //从该位置开始后面的数据往前覆盖
}
pc->count--;
printf("删除成功\n");
}
我们思考一下,删除一个联系人是不是先要查找联系人?那查找、修改联系人是不是也要先查找联系人?既然查找这个功能用的比较多。我们不妨把查找封装成一个函数。同时我们不希望这个函数在.h文件中声明,只在contact.c 文件中给删除、修改函数提供支持,于是加上static修饰。
static int FindByName(Contact* pc, char name[]) //static代表只给本文件的函数用
{
assert(pc);
int i = 0;
for (i = 0; i < pc->count; i++)
{
if (0 == strcmp(pc->data[i].name, name)) //字符串比较函数strcmp
{
return i; //返回下标
}
}
return -1;
}
补充知识: int strcmp ( const char * str1, const char * str2 ); 功能介绍:字符串比较(string compare) 当s1<s2时,返回为负数;当s1=s2时,返回值= 0;当s1>s2时,返回正数。 即:两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止。如:1."A"<"B" 2."A"<"AB" 3."compare"<"computer" 注意事项:该函数只能比较字符串,即可用于比较两个字符串常量,或比较数组和字符串常量,不能比较数字等其他形式的参数。
void SearchContact(Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要查找人的名字:");
scanf("%s", name);
//1.查找
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要查找的人不存在\n");
return;
}
//2.打印
printf("%-10s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
printf("%-10s\t%-3d\t%-5s\t%-12s\t%-30s\n", pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
} //注意下标变为pos了
void ModifyContact(Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要修改人的名字:");
scanf("%s", name);
//1.查找
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要修改的人不存在\n");
return;
}
printf("已查找到要修改人的姓名,接下来开始修改\n");
//2.修改(这个人所有信息重新录入一遍)
printf("请输入姓名:\n");
scanf("%s", pc->data[pos].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pos].age)); //需要取地址,数组不用
printf("请输入性别:\n");
scanf("%s", pc->data[pos].sex);
printf("请输入电话:\n");
scanf("%s", pc->data[pos].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[pos].addr);
printf("修改成功\n");
}
void SortContact(Contact* pc)
{
assert(pc);
qsort(pc->data, pc->count, sizeof(PeoInfo), cmp_peo_by_name);
printf("排序成功\n");
}
我们之前学过利用qsort函数排序,下面简单回顾一下。
void qsort (void* base, size_t num, size_t size, int (*compare)(const void*,const void*)); 需要的头文件:#include功能介绍: qsort()函数(quick sort)是八大排序算法中的快速排序,能够排序任意数据类型的数组其中包括整形,浮点型,字符串甚至还有自定义的结构体类型。 参数介绍:1.首元素地址base:2.元素个数num 3.一个元素大小size 4.自定义比较函数compare compare分析:我们需要告诉qsort函数我们希望数据按照怎么的方式进行比较,比如对于几个字符串,我们可以比较字符串的大小(strcmp),也可以比较字符串的长度(strlen),因此我们要告诉qsort函数我们希望的比较方式,我们就需要传入一个比较函数compar就简写为cmp吧。
int cmp_peo_by_name(const void* e1, const void* e2)
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
} //strcmp的返回值恰好和cmp需要的返回值相同
运行截图:
排序前:
排序后:
在前面主函数的switch语句中,每一个case对应一个数字比较简单。但是从工程性的角度,这样是不够规范的,因此我们引入枚举这个概念对代码进行优化,增加代码的可读性与可维护性。
enum Option
{
EXIT, //从0开始,每次+1
ADD,
DEL,
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 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;
}
关于枚举变量的进一步了解,详见博主的另一篇文章:自定义类型详解
//contact.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#include
#define MAX 100
#define MAX_NAME 10
#define MAX_SEX 10
#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;
//通讯录
typedef struct Contact
{
PeoInfo data[MAX]; //存放人的信息
int count; //记录当前通讯录中实际人的个数
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增加联系人到通讯录
void AddContact(Contact* pc);
//打印通讯录的信息
void ShowContact(const Contact* pc);
//删除指定联系人
void DelContact(Contact* pc);
//查找指定联系人
void SearchContact(Contact* pc);
//修改指定联系人
void ModifyContact(Contact* pc);
//排序通讯录中的内容
void SortContact(Contact* pc);
//contact.c
#include"contact.h"
void InitContact(Contact* pc)
{
assert(pc);
pc->count = 0;
memset(pc->data, 0, sizeof(pc->data)); //设置内存大小
}
void AddContact(Contact* pc)
{
assert(pc);
if (pc->count == 100)
{
printf("通讯录已满,无法增加\n");
return;
}
printf("请输入姓名:\n");
scanf("%s", pc->data[pc->count].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pc->count].age)); //需要取地址,数组不用
printf("请输入性别:\n");
scanf("%s", pc->data[pc->count].sex);
printf("请输入电话:\n");
scanf("%s", pc->data[pc->count].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[pc->count].addr);
pc->count++;
printf("增加成功\n");
}
void ShowContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-10s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->count; i++)
{
printf("%-10s\t%-3d\t%-5s\t%-12s\t%-30s\n", pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
static int FindByName(Contact* pc, char name[]) //只给本文件的函数用
{
assert(pc);
int i = 0;
for (i = 0; i < pc->count; i++)
{
if (0 == strcmp(pc->data[i].name, name))
{
return i;
}
}
return -1;
}
void DelContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
assert(pc);
int i = 0;
if(pc->count == 0)
{
printf("通讯录为空,没有信息可以删除\n");
return;
}
printf("请输入要删除人的名字:\n");
scanf("%s", name);
//删除
//1.查找
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要删除的人不存在\n");
return;
}
//2.删除
for (i = pos; i < pc->count - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->count--;
printf("删除成功\n");
}
void SearchContact(Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要查找人的名字:");
scanf("%s", name);
//1.查找
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要查找的人不存在\n");
return;
}
//2.打印
printf("%-10s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
printf("%-10s\t%-3d\t%-5s\t%-12s\t%-30s\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("请输入要修改人的名字:");
scanf("%s", name);
//1.查找
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要修改的人不存在\n");
return;
}
printf("已查找到要修改人的姓名,接下来开始修改\n");
//2.修改
printf("请输入姓名:\n");
scanf("%s", pc->data[pos].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pos].age)); //需要取地址,数组不用
printf("请输入性别:\n");
scanf("%s", pc->data[pos].sex);
printf("请输入电话:\n");
scanf("%s", pc->data[pos].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[pos].addr);
printf("修改成功\n");
}
int cmp_peo_by_name(const void* e1, const void* e2)
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
//按照名字来排序
void SortContact(Contact* pc)
{
assert(pc);
qsort(pc->data, pc->count, sizeof(PeoInfo), cmp_peo_by_name);
printf("排序成功\n");
}
//test.c
#include"contact.h"
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT
};
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");
printf("*********************************\n");
}
int main()
{
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);
return 0;
}