写一个通讯录程序,能完成以下目标:
1、能够任意添加联系人,并且通讯录的容量会随着你添加的联系人的数量动态增大。
2、根据指定联系人的姓名找到该联系人,并将其信息打印出来。
3、能根据指定联系人的姓名找到并删除该联系人,删除后通讯录中联系人的相对顺序不发生改变。
4、根据指定联系人的姓名找到并修改该联系人的信息。
5、根据联系人的姓名排序通讯录中的信息。
6、清空通讯录、
7、退出通讯录之前,先将信息保存到文件。
8、在下次启动通讯录时,先将保存在文件中的信息加载到通讯录,做到数据的长久使用。
我们先要定义一个基础的联系人类型,已保存联系人的各种信息,我这里定义的联系人主要包括编号、姓名、性别等信息:
typedef struct liaison {
int id;
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char telephone[MAX_TELE];
char address[MAX_ADDR];
} Liaison;
这里的MAX_NAME、MAX_SEX……这些都是一些用define定义的数值常量,表示的是这些成员最大的长度。主要为的就是一个方便,可以通过具体情况具体定值。
再定义一个通讯录类型,以保存多个联系人的信息与信息的条数等:
typedef struct contact {
Liaison* data;
int num; // 通讯录中联系人的个数
int capacity; // 当前通讯录的容量
} Contact;
我这里的data没有直接写成数组形式是为了将通讯录形成动态的而设计的,因为直接写成数组的话,就只能固定写死了。
协程指针的话就可以在未来动态申请空间,并让data指针指向这块空间了。
我们先假设data已经指向了一块合法的空间,并且num是为0的,所以我们就可以向通讯录中添加联系人的信息了:
// 添加联系人到通讯录
void Add(Contact* pc) {
assert(pc);
// 判断是否需要增容
int update = check_capacity(pc);
if (1 == update) {
printf("增容成功……当前容量为:%d\n", pc->capacity);
}
printf("请输入待添加人编号:");
scanf("%d", &((pc->data[pc->num]).id));
printf("请输入待添加人姓名:");
scanf("%s", ((pc->data[pc->num]).name));
printf("请输入待添加人性别:");
scanf("%s", ((pc->data[pc->num]).sex));
printf("请输入待添加人年龄:");
scanf("%d", &((pc->data[pc->num]).age));
printf("请输入待添加人电话:");
scanf("%s", ((pc->data[pc->num]).telephone));
printf("请输入待添加人地址:");
scanf("%s", ((pc->data[pc->num]).address));
pc->num++;
printf("添加成功!\n");
}
添加信息的功能其实很简单,把各种信息录入一遍就行了,但在添加之前一定要判断一下当前通讯录是否已满,满了就要进行增容。
我们来看一下增容函数:
// 检查是否需要增容,增容成功返回1,不需要增容则返回0
int check_capacity(Contact* pc) {
assert(pc);
if (if_full(pc)) {
// 进行扩容
Liaison* temp = (Liaison*)realloc(pc->data, (pc->capacity + UPDATE_SIZE) * sizeof(Liaison));
if (NULL == temp) {
perror("check_capacity");
return -1;
}
else {
pc->data = temp;
pc->capacity += UPDATE_SIZE; // 增容成功,要更新capacity
return 1;
}
}
return 0;
}
如果函数is_full的返回值为1,表示当前通讯录已满,要进行增容。
由于是动态开辟的空间所以我们需要用realloc函数来帮我们扩大空间,而realloc函数有可能申请失败,所以要对其返回结果进行判断。
其中is_full函数的逻辑也很简单,当pc->num == pc->capacity时,则说明当前通讯录已满:
int if_full(Contact* pc) {
assert(pc);
if (pc->num == pc->capacity) {
return 1;
}
return 0;
}
当我们添加成功后,就可以打印出来看看,所以我们先写一个打印函数:
void show(const Contact* pc) {
assert(pc);
if (0 == pc->num) {
printf("通讯录为空,没有信息可显示\n");
return;
}
// 先打印个标题(这些都是为了美观)
printf(" ------------------------------------------------------------------------------------\n");
printf("| %-3s\t|%-10s\t|%-4s\t|%-3s\t|%-15s\t|%-20s|\n", "编号", "名字", "性别", "年龄", "电话", "地址");
printf("|------------------------------------------------------------------------------------|\n");
// 再打印信息
int i = 0;
for (i = 0; i < pc->num; i++) {
printf("| %-3d\t|%-10s\t|%-4s\t|%-3d\t|%-15s\t|%-20s|\n", pc->data[i].id, pc->data[i].name,
pc->data[i].sex, pc->data[i].age,
pc->data[i].telephone, pc->data[i].address);
printf("|------------------------------------------------------------------------------------|\n");
}
}
很多功能都需要用到查找,所以我们可以将查找功能单独写成一个小函数:
// 根据名字查找某个指定联系人,找到返回其下标,未找到返回-1
int find_by_name(const Contact* pc, char* name) {
assert(pc && name);
int i = 0;
for (i = 0; i < pc->num; i++) {
if (strcmp(pc->data[i].name, name) == 0) {
return i;
}
}
return -1;
}
那我们写起查询函数也就水到渠成了:
// 查找指定人信息并显示
void search(const Contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入待查找人姓名:");
scanf("%s", name);
int index = find_by_name(pc, name);
if (-1 == index) {
printf("待查找人不存在……\n");
return;
}
printf("查找成功,信息如下:\n");
printf(" ------------------------------------------------------------------------------------\n");
printf("| %-3s\t|%-10s\t|%-4s\t|%-3s\t|%-15s\t|%-20s|\n", "编号", "名字", "性别", "年龄", "电话", "地址");
printf("|------------------------------------------------------------------------------------|\n");
printf("| %-3d\t|%-10s\t|%-4s\t|%-3d\t|%-15s\t|%-20s|\n", pc->data[index].id, pc->data[index].name,
pc->data[index].sex, pc->data[index].age,
pc->data[index].telephone, pc->data[index].address);
printf("|------------------------------------------------------------------------------------|\n");
}
一进到函数就需要先输入一个姓名,然后调用查找函数查找该名字,如果找不到就直接返回。找到了就根据返回的下标index打印出对应的信息即可。
其实我们的删除并不需要真的将该联系人的信息销毁,我们只需要让其后面的联系人的信息覆盖到他的位置上即可。而且我们让其后面的联系人全都向前移动一位,也正好满足删除后个联系人相对顺序不变的要求:
void delete(Contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入待删除人姓名:");
scanf("%s", name);
int index = find_by_name(pc, name);
if (-1 == index) {
printf("待删除人不存在……\n");
return;
}
int i = 0;
for (i = index; i < pc->num - 1; i++) {
pc->data[i] = pc->data[i + 1]; // 让其后面的联系人全都向前移动一位
}
printf("删除成功!\n");
pc->num--; // 删除成功,需要将人数减1
}
比如我们现在通讯录中已经有了一些信息:
我们可以将张三删除掉:
我们可以看到,删除后的个联系人的相对顺序还是不变的。
修改功能当然有很多的实现方式,最好的实现方式就是只修改指定信息,但我这里想偷个小懒,就直接把所有信息在重录一遍了:
// 修改指定联系人信息
void modify(Contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入待修改人的姓名:");
scanf("%s", name);
int index = find_by_name(pc, name);
if (-1 == index) {
printf("待修改人不存在……\n");
return;
}
printf("请输入要修改的编号:");
scanf("%d", &((pc->data[index]).id));
printf("请输入要修改的姓名:");
scanf("%s", ((pc->data[index]).name));
printf("请输入要修改的年龄:");
scanf("%d", &((pc->data[index]).age));
printf("请输入要修改的性别:");
scanf("%s", ((pc->data[index]).sex));
printf("请输入要修改的电话:");
scanf("%s", ((pc->data[index]).telephone));
printf("请输入要修改的地址:");
scanf("%s", ((pc->data[index]).address));
printf("修改成功!\n");
}
清空的功能也非常的简单,只需要把capacitynum全都置为0,并且将原来的空间内容全部清零即可:
void clear(Contact* pc) {
assert(pc);
memset(pc, 0, sizeof(*pc));
pc->capacity;
pc->num = 0;
printf("通讯录已清空……\n");
}
当我们想让通讯录里的信息能够长久使用时,就需要将这些信息保存起来,那么将这些信息保存到文件中就是我们最好的选择:
void save_contact(Contact* pc) {
assert(pc);
FILE* pf = fopen("contact.txt", "w");
if (NULL == pf) {
perror("save_contact");
return;
}
// 写文件
int i = 0;
for (i = 0; i < pc->num; i++) {
fwrite(pc->data + i , sizeof(Liaison), 1, pf);
}
// 关闭文件
fclose(pf);
pf = NULL;
}
其中关于fwrite函数的官方解释是:
翻译成中国话就是:
说人话就是从ptr指向的空间中取出size * count个字节大小的数据,写入到stream流中。
所以这里的操作就是依次取出data[i]中的内容,写入文件contact.txt中。
比如说我们现在有下面这4个联系人的信息:
当我们选择退出后会显示:
我们就可以到文件目录下找到contact.txt文件,就会发现里面是有内容的:
当然啦,这个函数是以二进制的形式写入的,所以有很多信息我们都是看不懂的,但没关系,计算机看得懂就行了。
有了文件来保存我们的数据,我们就可以在下一次打开通讯录时候,优先将保存好的数据加载到我们的data空间中了,这些功能放在一个函数init_contact中完成:
void init_contact(Contact* pc) {
assert(pc);
pc->data = (Liaison*)malloc(DEFAULT_SIZE * sizeof(Liaison));
pc->num = 0;
pc->capacity = DEFAULT_SIZE;
// 从文件中加在信息到通讯录
download_contact(pc);
}
在初始化函数中,我们先初始化通讯录的默认大小然后再将文件中的数据加载到通讯录中:
// 从文件中加载信息到通讯录
void download_contact(Contact* pc) {
assert(pc);
// 打开文件
FILE* pf = fopen("contact.txt", "r");
if (NULL == pf) {
perror("download_contact");
return;
}
// 读文件
Liaison temp = { 0 };
while (fread(&temp, sizeof(Liaison), 1, pf)) {
// 检查是否需要增容
check_capacity(pc);
pc->data[pc->num] = temp;
pc->num++;
}
// 关闭文件
fclose(pf);
pf = NULL;
}
因为文件中存储的数据可能超过了已初始化的默认容量的大小,所以我们在每次写入之前都要先检查是否需要增容,如果已满则增容后在写入。
其中函数fread与函数,fwrite是很相似的,只不过是函数fread是将文件流中读取到的数据写到ptr所指向的空间中去。它们两是成对使用的。
该文件存放各种函数和符号的声明和定义,以及各种头文件的包含:
#include
#include
#include
#include
#include
#define MAX 1000
#define MAX_NAME 10
#define MAX_SEX 4
#define MAX_ADDR 20
#define MAX_TELE 15
#define DEFAULT_SIZE 2
#define UPDATE_SIZE 2
// 定义一个联系人类型
typedef struct liaison {
int id;
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char telephone[MAX_TELE];
char address[MAX_ADDR];
} Liaison;
// 再创建一个通讯录类型
typedef struct contact {
Liaison* data;
int num;
int capacity;
} Contact;
// 初始化通讯录
void init_contact(Contact* pc);
// 添加联系人到通讯录
void Add(Contact* pc);
// 显示所有联系人信息
void show(const Contact* pc);
// 根据名字查找某个指定联系人,找到返回其下标,未找到返回-1
int find_by_name(const Contact* pc, char* name);
// 删除指定联系人信息
void delete(Contact* pc);
// 修改指定联系人信息
void modify(Contact* pc);
// 查找指定人信息并显示
void search(const Contact* pc);
// 比较两个联系人的名字
int cmp_by_name(const void* p1, const void* p2);
// 根据名字排序通讯录中的信息,默认升序
void sort(Contact* pc);
// 清空通讯录
void clear(Contact* pc);
// 判断通讯录是否已满,满了就返回1,否则返回0
int if_full(Contact* pc);
// 保存信息到文件
void save_contact(Contact *pc);
// 从文件中加在信息到通讯录
void download_contact(Contact *pc);
// 检查是否需要增容,增容成功返回1,不需要增容则返回0
int check_capacity(Contact *pc);
该文件存放各种函数的定义:
#include "contact.h"
// 初始化通讯录
void init_contact(Contact* pc) {
assert(pc);
pc->data = (Liaison*)malloc(DEFAULT_SIZE * sizeof(Liaison));
pc->num = 0;
pc->capacity = DEFAULT_SIZE;
// 从文件中加在信息到通讯录
download_contact(pc);
}
// 添加联系人到通讯录
void Add(Contact* pc) {
assert(pc);
// 判断是否需要增容
int update = check_capacity(pc);
if (1 == update) {
printf("增容成功……当前容量为:%d\n", pc->capacity);
}
printf("请输入待添加人编号:");
scanf("%d", &((pc->data[pc->num]).id));
printf("请输入待添加人姓名:");
scanf("%s", ((pc->data[pc->num]).name));
printf("请输入待添加人性别:");
scanf("%s", ((pc->data[pc->num]).sex));
printf("请输入待添加人年龄:");
scanf("%d", &((pc->data[pc->num]).age));
printf("请输入待添加人电话:");
scanf("%s", ((pc->data[pc->num]).telephone));
printf("请输入待添加人地址:");
scanf("%s", ((pc->data[pc->num]).address));
pc->num++;
printf("添加成功!\n");
}
// 显示所有联系人信息
void show(const Contact* pc) {
assert(pc);
if (0 == pc->num) {
printf("通讯录为空,没有信息可显示\n");
return;
}
// 先打印个标题
printf(" ------------------------------------------------------------------------------------\n");
printf("| %-3s\t|%-10s\t|%-4s\t|%-3s\t|%-15s\t|%-20s|\n", "编号", "名字", "性别", "年龄", "电话", "地址");
printf("|------------------------------------------------------------------------------------|\n");
// 再打印信息
int i = 0;
for (i = 0; i < pc->num; i++) {
printf("| %-3d\t|%-10s\t|%-4s\t|%-3d\t|%-15s\t|%-20s|\n", pc->data[i].id, pc->data[i].name,
pc->data[i].sex, pc->data[i].age,
pc->data[i].telephone, pc->data[i].address);
printf("|------------------------------------------------------------------------------------|\n");
}
}
// 根据名字查找某个指定联系人,找到返回其下标,未找到返回-1
int find_by_name(const Contact* pc, char* name) {
assert(pc && name);
int i = 0;
for (i = 0; i < pc->num; i++) {
if (strcmp(pc->data[i].name, name) == 0) {
return i;
}
}
return -1;
}
// 删除指定联系人信息
void delete(Contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入待删除人姓名:");
scanf("%s", name);
int index = find_by_name(pc, name);
if (-1 == index) {
printf("待删除人不存在……\n");
return;
}
int i = 0;
for (i = index; i < pc->num - 1; i++) {
pc->data[i] = pc->data[i + 1]; // 让其后面的联系人全都向前移动一位
}
printf("删除成功!\n");
pc->num--; // 删除成功,需要将人数减1
}
// 修改指定联系人信息
void modify(Contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入待修改人的姓名:");
scanf("%s", name);
int index = find_by_name(pc, name);
if (-1 == index) {
printf("待修改人不存在……\n");
return;
}
printf("请输入要修改的编号:");
scanf("%d", &((pc->data[index]).id));
printf("请输入要修改的姓名:");
scanf("%s", ((pc->data[index]).name));
printf("请输入要修改的年龄:");
scanf("%d", &((pc->data[index]).age));
printf("请输入要修改的性别:");
scanf("%s", ((pc->data[index]).sex));
printf("请输入要修改的电话:");
scanf("%s", ((pc->data[index]).telephone));
printf("请输入要修改的地址:");
scanf("%s", ((pc->data[index]).address));
printf("修改成功!\n");
}
// 查找指定人信息并显示
void search(const Contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入待查找人姓名:");
scanf("%s", name);
int index = find_by_name(pc, name);
if (-1 == index) {
printf("待查找人不存在……\n");
return;
}
printf("查找成功,信息如下:\n");
printf(" ------------------------------------------------------------------------------------\n");
printf("| %-3s\t|%-10s\t|%-4s\t|%-3s\t|%-15s\t|%-20s|\n", "编号", "名字", "性别", "年龄", "电话", "地址");
printf("|------------------------------------------------------------------------------------|\n");
printf("| %-3d\t|%-10s\t|%-4s\t|%-3d\t|%-15s\t|%-20s|\n", pc->data[index].id, pc->data[index].name,
pc->data[index].sex, pc->data[index].age,
pc->data[index].telephone, pc->data[index].address);
printf("|------------------------------------------------------------------------------------|\n");
}
// 比较两个联系人的名字
int cmp_by_name(const void* p1, const void* p2) {
assert(p1 && p2);
return strcmp(((Liaison*)p1)->name, ((Liaison*)p2)->name);
}
// 根据名字排序通讯录中的信息,默认升序
void sort(Contact* pc) {
assert(pc);
qsort(pc->data, pc->num, sizeof(pc->data[0]), cmp_by_name);
printf("排序成功!\n");
show((const Contact*)pc);
}
// 清空通讯录
void clear(Contact* pc) {
assert(pc);
memset(pc, 0, sizeof(*pc));
pc->capacity;
pc->num = 0;
printf("通讯录已清空……\n");
}
// 判断通讯录是否已满,满了就返回1,否则返回0
int if_full(Contact* pc) {
assert(pc);
if (pc->num == pc->capacity) {
return 1;
}
return 0;
}
// 保存信息到文件
void save_contact(Contact* pc) {
assert(pc);
FILE* pf = fopen("contact.txt", "w");
if (NULL == pf) {
perror("save_contact");
return;
}
// 写文件
int i = 0;
for (i = 0; i < pc->num; i++) {
fwrite(pc->data + i , sizeof(Liaison), 1, pf);
}
// 关闭文件
fclose(pf);
pf = NULL;
}
// 从文件中加载信息到通讯录
void download_contact(Contact* pc) {
assert(pc);
// 打开文件
FILE* pf = fopen("contact.txt", "r");
if (NULL == pf) {
perror("download_contact");
return;
}
// 读文件
Liaison temp = { 0 };
while (fread(&temp, sizeof(Liaison), 1, pf)) {
// 检查是否需要增容
check_capacity(pc);
pc->data[pc->num] = temp;
pc->num++;
}
// 关闭文件
fclose(pf);
pf = NULL;
}
// 检查是否需要增容,增容成功返回1,不需要增容则返回0
int check_capacity(Contact* pc) {
assert(pc);
if (if_full(pc)) {
// 进行扩容
Liaison* temp = (Liaison*)realloc(pc->data, (pc->capacity + UPDATE_SIZE) * sizeof(Liaison));
if (NULL == temp) {
perror("check_capacity");
return -1;
}
else {
pc->data = temp;
pc->capacity += UPDATE_SIZE;
return 1;
}
}
return 0;
}
该文件用来运行通讯录:
#include "contact.h"
// 先写一个简易的菜单
void menu() {
printf("*******************menu********************\n");
printf("********1、Add 2、Delete*********\n");
printf("********3、Search 4、Modify*********\n");
printf("********5、Show 6、Sort***********\n");
printf("********7、Clear 0、Exit***********\n");
printf("*******************menu********************\n");
}
enum Option {
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT,
CLEAR
};
int main() {
// 先创建我们的一个通讯录
Contact con;
// 初始化通讯录
init_contact(&con);
int input = 0;
do {
menu();
printf("请选择:");
scanf("%d", &input);
switch (input) {
case ADD:
Add(&con);
break;
case DEL:
delete(&con);
break;
case SEARCH:
search(&con);
break;
case MODIFY:
modify(&con);
break;
case SHOW:
show(&con);
break;
case SORT:
sort(&con);
break;
case CLEAR:
clear(&con);
break;
case EXIT:
printf("正在保存信息……\n");
Sleep(2000);
// 保存信息到文件
save_contact(&con);
printf("保存信息成功!\n");
printf("正在退出程序……\n");
Sleep(1000);
free(con.data);
con.data = NULL;
printf("退出程序成功\n");
break;
default:
printf("输入有误,请重新输入……\n");
break;
}
} while (input);
return 0;
}