目录
前言:
对于这个项目,是前面数组基础版本的通讯录的升级版本,大家如果一步理解有困难,一定先点击我的主页了解一下前几篇博客的内容做一个铺垫,我这里罗列一下关联最大的几篇:
1.项目功能需求分析
实现一个通讯录:
通讯录保存个人信息名字,年龄 性别 电话 住址 用保存人的信息的结构体实现
1.通讯录空间不固定,可以先存放一部分的信息,当用户还要存入信息的时候可以自动增加容量。(比如默认先存放3个联系人的信息,不够了,就每次增加两个联系人信息的空间给用户)
2.添加联系人
3.删除指定联系人
4.查找指定联系人
5.显示所有人的信息
6.排序功能7.可以修改指定联系人的信息
8.退出程序后,录入的信息可以保存在硬盘文件中,下次打开通讯录程序还可以查询到。
2.文件框架说明
test.c 主菜单文件,用于功能测试
contact.c 函数具体实现文件
contact.h 存放函数和类型的声明和必要头文件
3.程序主框架实现
我们希望在程序中实现不同的操作,比如上述的增删查改联系人的信息,而不是执行一次程序就结束,用户可以选择退出。接下来我们就实现一下主体框架,包含主页面菜单显示。
void menu() { printf("----------------------------------\n"); printf("|***0.Exit 1.ADD***********|\n"); printf("|***2.DEL 3.SEARCH*********|\n"); printf("|***4.MODIFY 5.SHOW***********|\n"); printf("|***6.SORT ****************|\n"); printf("----------------------------------\n"); } void test() { int input = 0; 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); } int main() { test(); return 0; }
case后面的选项是1,2,3,4,5,那么我们想可不可以利用枚举,让case后面的选项既能表示我们的功能含义又能表示功能选择数字,这样的代码可读性就会更高一些,我们就可以修改如下 :
首先定义选择枚举类型:
enum OPtion { EXIT,//0 ADD,//1 DEL,//2 SEARCH, MODIFY, SHOW, SORT };
这个依然是放在我们的头文件中去定义,这样代码的整体美观些,接下来的结构修改为:
void menu() { printf("----------------------------------\n"); printf("|***0.Exit 1.ADD***********|\n"); printf("|***2.DEL 3.SEARCH*********|\n"); printf("|***4.MODIFY 5.SHOW***********|\n"); printf("|***6.SORT ****************|\n"); printf("----------------------------------\n"); } void test() { int input = 0; Contact con; InitContact(&con); do { menu(); printf("请选择:》"); scanf("%d", &input); switch (input) { case ADD: break; case DEL: break; case SEARCH: break; case MODIFY: break; case SHOW: break; case SORT: break; case EXIT: printf("退出程序成功\n"); break; default: printf("选择错误,请根据菜单选项进行功能选择,谢谢配合\n"); break; } } while (input); } int main() { test(); return 0; }
4.创建联系人结构体类型和通讯录结构体类型
由于通讯录存储的是每一个联系人的基本信息,名字、电话、性别、住址等,所以我们可以封装一个联系人结构体类型用于描述每一个联系人。
typedef struct PeoInfo { char name[20]; int age; char sex[5]; char telepnumber[12]; char addr[30]; } PeoInfo;
为了后续书写的方便将struct PeoInfo类型重定义为PeoInfo
我们现在需要一块空间来存储我们联系人的信息,这块空间的起始地址交给我们的一个联系人结构体类型的指针。
PeoInfo* data;//指向了存放数据的空间
由于,我们后续会对通讯录进行增删操作,就会改变数组的大小,为了方便查看或者显示或者为了方便操作我们可以定义一个SZ来记录数组的大小,增加一个联系人,sz+1.......
int sz;//记录的是当前有效元素的个数
这,当我们起始有三个联系人类型那么大的空间的时候,还没有存入联系人的信息,此时我们的sz=0,那么我们什么时候开始扩大我们的空间呢,所以我们专门设计一个变量来记录我们通讯录的数据空间容量,起始容量为3,当sz 等于我们的容量的时候,就可以扩容了。
int capacity;//记录当前存放数据空间的容量
数据区,容量和联系人数目都是通讯录的属性,我们可以设计一下通讯录类型结构体:
typedef struct Contact { PeoInfo* data;//指向了存放数据的空间 int sz;//记录的是当前有效元素的个数 int capacity;//记录当前存放数据空间的容量 }Contact;
为了以后书写方便,用typedef重定义通讯录结构体类型为:Contact类型。
4.1创建通讯录
当我们的通讯录类型有了过后,我们就可以创建一个通讯录:
5.程序功能实现--封装功能函数实现不同功能
5.1通讯录初始化
初始化函数,首先要为我们的data数据区申请3个PeoINfo类型的空间,将sz也初始化为0.将容量初始化3,起始的容量不一定设置为3,方便不同的伙伴来设置,我们将容量的大小也设置为宏.我们传参使用了结构体地址传参,接收参数使用了结构体指针,内存占用少。
void Initcontact(Contact* pc) { assert(pc); pc->data = (PeoInfo*)malloc(CAPICITY * sizeof(PeoInfo)); if (pc->data == NULL) { perror("Initcontact"); } pc->sz = 0; pc->capacity = CAPICITY; }
②由于我们的数据后续是要存储到我们的文件中的,那么就意味着当我们启动程序的时候是不是应该先把文件中的数据加载到程序中来,所以我们还需要实现一个加载函数,将文件中的数据读到我们的程序中来。
int ChekCapacity(Contact* pc);//提前声明我们的检查增容函数 void LoadContact(Contact* pc) { //首先用二进制读的方式打开文件 FILE* pf = fopen("contact.dat", "rb"); if (pf == NULL) { //打开失败就不继续了 perror("loadcontact"); return; } //读文件 PeoInfo tmp = { 0 };//创建一个联系人结构体来存储读到的信息 while (fread(&tmp, sizeof(PeoInfo), 1, pf))//fred返回的是读到的个数,读完返回0 { if (0 == ChekCapacity(pc))//这里因为要把读到的信息放到通讯录中,就要考虑增容 return;//增容失败就不往下执行了 //增容成功,将数据加载到通讯录 pc->data[pc->sz] = tmp; pc->sz++;//加载一个联系人信息,sz++ } //关闭文件 fclose(pf); pf = NULL; } void Initcontact(Contact* pc)
放到最后将通讯录信息保存写完后一起测试。
5.2增加联系人
在动态版本中,我们不用考虑通讯录满没满。而是应该考虑通讯录申请的空间是否够了,不够就要扩容,每录入一个联系的信息,sz就+1,当我们的sz与我们的容量相等的时候,就要考虑扩容,所以我们进入增加函数第一步就应该先判断是否要增容。
为了代码方便,我们将检查是否要增容这一步封装为一个函数:
int ChekCapacity(Contact* pc) { if (pc->sz == pc->capacity) { PeoInfo* ptr =(PeoInfo * )realloc(pc->data, (pc->capacity + 2 * sizeof(PeoInfo))); if (ptr == NULL) { perror("ChekCapaticy"); return 0; } else { pc->data = ptr; pc->capacity += 2; } } return 1; }
函数返回1,说明增容成功,函数返回0说明增容失败。当然,也不一定每次只增加两个空间,我们还是将增加的空间数目定义为宏,方便伙伴们修改:
#define ADDC 2
void Initcontact(Contact* pc) { assert(pc); pc->data = (PeoInfo*)malloc(CAPICITY * sizeof(PeoInfo)); if (pc->data == NULL) { perror("Initcontact"); } pc->sz = 0; pc->capacity = CAPICITY; } int ChekCapacity(Contact* pc) { if (pc->sz == pc->capacity) { PeoInfo* ptr =(PeoInfo * )realloc(pc->data, (pc->capacity + ADDC * sizeof(PeoInfo))); if (ptr == NULL) { perror("ChekCapaticy"); return 0; } else { pc->data = ptr; pc->capacity += ADDC; printf("增容成功\n"); return1; } } return 1; }
我们的增加联系人的函数为:
void AddContact(Contact* pc) { assert(pc);//断言防止传入空指针 //首先判断通讯录满每满 if (0 == ChekCapacity(pc)) { return; } printf("请输入联系人的名字>\n"); scanf("%s", pc->data[pc->sz].name);//由于name是数组名这里就不要&符号了 printf("请输入联系人的年龄>\n"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入联系人的性别>\n"); scanf("%s", pc->data[pc->sz].sex); printf("请输入联系人的电话>\n"); scanf("%s", pc->data[pc->sz].telepnumber); printf("请输入联系人的地址>\n"); scanf("%s", pc->data[pc->sz].addr); //添加联系人成功,我们sz++ pc->sz++; printf("添加联系人成功\n"); }
测试一下:
先往通讯录里面放入三个联系人信息:
下一步增容:
增容成功,我们展现一下通讯录然后我们查找一下2号联系人:
功能正常。
5.3显示所有联系人的信息
为了看一下我们添加联系人或者删除联系人的效果,我们这里就先写显示函数
用于我们只是显示通讯录信息,并不将参数内容进行修改所以参数最好使用const进行修饰,防止出错,这一步不理解的伙伴可以点击我的主页看一下我讲解const的博文。
这个函数我们只用循环打印我们data数组内容就行
void ShowContact(const Contact* pc) { assert(pc);//断言一下防止传入空指针 int i = 0; printf("————————————————————————————————————————————————————————————————————————————————————————\n"); printf("|%-20s\t|%-4s\t|%-5s\t|%-12s\t|%-30s|\n", "名字", "年龄", "性别", "电话", "地址"); for (i = 0; i < pc->sz; i++) { printf("|%-20s\t|%-4d\t|%-5s\t|%-12s\t|%-30s|\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].telepnumber, pc->data[i].addr); printf("————————————————————————————————————————————————————————————————————————————————————————\n"); } printf("————————————————————————————————————————————————————————————————————————————————————————\n");
我们来看一下前两个函数执行的效果:
5.4删除指定联系人的信息
首先,如果通讯录为空,我们就没有删除的东西
第二,要删除对应联系人的信息,实现思想是先找到对应联系人的名字匹配相同将后面的内容前移覆盖完成删除。
void DelCotact(Contact* pc) { if (pc->sz == 0) { printf("通讯录为空,无法删除\n"); return; } char name[DATAMAX] = { 0 }; assert(pc);//断言,防止传入空指针 printf("请输入要删除的人的名字:》\n"); scanf("%s", name); //遍历找到要删除的人 int i = 0; int del = 0; int flag = 0; for (i = 0; i < pc->sz; i++) { if (strcmp(pc->data[i].name, name) == 0) { del = i; flag = 1; break; } } if (flag == 0) { printf("找不到要删除的人,请检查名字是否输入正确\n"); return; } //找到了,删除联系人 for (i = del; i < pc->sz - 1; i++) { pc->data[i] = pc->data[i + 1]; } pc->sz--; printf("成功删除联系人\n"); }
由于我们后面修改呀等等都会用到查找这个函数,所以我们将其封装为一个查找函数,那我们就可以改造代码如下:
int Findbyname(Contact* pc, char name[]) { assert(pc); int i = 0; for (i = 0; i < pc->sz; i++) { if (strcmp(pc->data[i].name, name) == 0) { return i;//找到了 } } return -1;//找不到 } void DelCotact(Contact* pc) { if (pc->sz == 0) { printf("通讯录为空,无法删除\n"); return; } char name[DATAMAX] = { 0 }; assert(pc);//断言,防止传入空指针 printf("请输入要删除的人的名字:》\n"); scanf("%s", name); //遍历找到要删除的人 int i = 0; int del = 0; del = Findbyname(pc, name); if (del == -1) { printf("找不到要删除的人,请检查名字是否输入正确\n"); return; } //找到了,删除联系人 for (i = del; i < pc->sz - 1; i++) { pc->data[i] = pc->data[i + 1]; } pc->sz--; printf("成功删除联系人\n"); }
我们来看一下效果:
5.5查找指定联系人
这里我们就实现一下通过名字查找,之所以单独写一个查找函数是因为在功能中,查找到过后还要显示出来。
看实现:
void SearchContact(Contact* pc) { assert(pc); char name[DATAMAX] = { 0 }; printf("请输入要查找的人的名字:》\n"); scanf("%s", name); int pos = Findbyname(pc, name); if (pos == -1) { printf("要查找的人不存在,请检查名字是否输入正确\n"); } else { printf("————————————————————————————————————————————————————————————————————————————————————————\n"); printf("|%-20s\t|%-4s\t|%-5s\t|%-12s\t|%-30s|\n", "名字", "年龄", "性别", "电话", "地址"); printf("————————————————————————————————————————————————————————————————————————————————————————\n"); printf("|%-20s\t|%-4d\t|%-5s\t|%-12s\t|%-30s|\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].telepnumber, pc->data[pos].addr); printf("————————————————————————————————————————————————————————————————————————————————————————\n"); } }
这里查找也可以根据其他的比如号码等,可以利用Switch来实现。看一下效果:
5.6修改联系人的信息
通过名字来找到要修改信息的联系人,然后重新录入信息
void ModifyContact(Contact* pc) { assert(pc); char name[DATAMAX] = { 0 }; printf("请输入要修改人的名字:>\n"); scanf("%s", name); int pos = Findbyname(pc, name); if (pos == -1) { printf("要修改的人不存在,请检查名字是否输入正确\n"); } else { printf("请输入要修改人的名字>\n"); scanf("%s", pc->data[pos].name);//由于name是数组名这里就不要&符号了 printf("请输入要修改人的年龄>\n"); scanf("%d", &(pc->data[pos].age)); printf("请输入要修改人的性别>\n"); scanf("%s", pc->data[pos].sex); printf("请输入要修改人的电话>\n"); scanf("%s", pc->data[pos].telepnumber); printf("请输入要修改人的地址>\n"); scanf("%s", pc->data[pos].addr); \ printf("修改成功\n"); } }
我们看一下效果:
6.释放通讯录
由于我们的通讯录空间是malloc来的,所以在最后程序结束应该将空间释放,防止造成内存泄漏。
那我们就在退出选项后面增加一个销毁函数:
void DestoryContact(Contact* pc) { free(pc->data); pc->data = NULL; pc->capacity = 0; pc->sz = 0; }
7.保存通讯录信息
在销毁通讯录前,通过文件读写的方式保存通讯录的信息
void Savecontact(Contact* pc) { FILE* pf = fopen("contact.dat", "wb"); if (pf == NULL) { perror("SaveContact"); return; } //写数据 int i = 0; for (i = 0; i < pc->sz; i++) { fwrite(pc->data + i, sizeof(PeoInfo), 1, pf); } //关闭文件 fclose(pf); pf = NULL; }
看一下效果:
由于是二进制存储我们使用编译器来查看:
成功写入,我们搭配加载看一下是不是我们上一次添加的信息:
当通讯录再次启动就有我们上次写入的信息了。
8.源码及结语
这是一个综合C语言百分之五六十的知识的一个项目,大家可以配合我之前的文章进行理解,希望这篇文章对大家的期末有所帮助。下面附上源码,创作不易,如果可以大家可以点赞三连收藏,对于项目有问题欢迎一起交流。谢谢大家的关注。
contact.h
#pragma once #include<stdio.h> #include<string.h> #include<assert.h> #include<stdlib.h> #define DATAMAX 100 #define CAPICITY 3 #define ADDC 2 enum OPtion { EXIT,//0 ADD,//1 DEL,//2 SEARCH, MODIFY, SHOW, SORT }; //类型的声明 typedef struct PeoInfo { char name[20]; int age; char sex[5]; char telepnumber[12]; char addr[30]; } PeoInfo; //typedef struct Contact //{ // PeoInfo data[DATAMAX]; // int sz; //}Contact; typedef struct Contact { PeoInfo* data;//指向了存放数据的空间 int sz;//记录的是当前有效元素的个数 int capacity;//记录当前存放数据空间的容量 }Contact; //初始化函数 void Initcontact(Contact* pc);//函数声明 void AddContact(Contact* pc);//增加联系人函数 void ShowContact(const Contact* pc);//显示通讯录信息 void DelCotact(Contact* pc);//删除联系人信息 void SearchContact(Contact* pc);//查找联系人的信息 void ModifyContact(Contact* pc);//修改联系人的信息 void DestoryContact(Contact* pc);//销毁通讯录 void Savecontact(Contact* pc);//保存通讯录到文件
test.c
#include"contact.h" //通讯录空间不固定,大小1可以调整 // 默认放3个人的信息,不够,每次增加两个 void menu() { printf("----------------------------------\n"); printf("|***0.Exit 1.ADD***********|\n"); printf("|***2.DEL 3.SEARCH*********|\n"); printf("|***4.MODIFY 5.SHOW***********|\n"); printf("|***6.SORT ****************|\n"); printf("----------------------------------\n"); } void test() { int input = 0; Contact con; Initcontact(&con); do { menu(); printf("请选择:》"); scanf("%d", &input); switch (input) { case ADD: AddContact(&con); break; case DEL: DelCotact(&con); break; case SEARCH: SearchContact(&con); break; case MODIFY: ModifyContact(&con); break; case SHOW: ShowContact(&con); break; case SORT: break; case EXIT: Savecontact(&con); DestoryContact(&con); printf("退出程序成功\n"); break; default: printf("选择错误,请根据菜单选项进行功能选择,谢谢配合\n"); break; } } while (input); } int main() { test(); return 0; }
contact.c
#include"contact.h" int Findbyname(Contact* pc, char name[]) { assert(pc); int i = 0; for (i = 0; i < pc->sz; i++) { if (strcmp(pc->data[i].name, name) == 0) { return i;//找到了 } } return -1;//找不到 } int ChekCapacity(Contact* pc);//提前声明我们的检查增容函数 void LoadContact(Contact* pc) { //首先用二进制读的方式打开文件 FILE* pf = fopen("contact.dat", "rb"); if (pf == NULL) { //打开失败就不继续了 perror("loadcontact"); return; } //读文件 PeoInfo tmp = { 0 };//创建一个联系人结构体来存储读到的信息 while (fread(&tmp, sizeof(PeoInfo), 1, pf))//fred返回的是读到的个数,读完返回0 { if (0 == ChekCapacity(pc))//这里因为要把读到的信息放到通讯录中,就要考虑增容 return;//增容失败就不往下执行了 //增容成功,将数据加载到通讯录 pc->data[pc->sz] = tmp; pc->sz++;//加载一个联系人信息,sz++ } //关闭文件 fclose(pf); pf = NULL; } void Initcontact(Contact* pc) { assert(pc); pc->data = (PeoInfo*)malloc(CAPICITY * sizeof(PeoInfo)); if (pc->data == NULL) { perror("Initcontact"); } pc->sz = 0; pc->capacity = CAPICITY; LoadContact(pc); } int ChekCapacity(Contact* pc) { if (pc->sz == pc->capacity) { PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + ADDC * sizeof(PeoInfo))); if (ptr == NULL) { perror("ChekCapaticy"); return 0; } else { pc->data = ptr; pc->capacity += ADDC; printf("增容成功\n"); return 1; } } return 1; } void AddContact(Contact* pc) { assert(pc);//断言防止传入空指针 //首先判断通讯录满每满 if (0 == ChekCapacity(pc)) { return; } printf("请输入联系人的名字>\n"); scanf("%s", pc->data[pc->sz].name);//由于name是数组名这里就不要&符号了 printf("请输入联系人的年龄>\n"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入联系人的性别>\n"); scanf("%s", pc->data[pc->sz].sex); printf("请输入联系人的电话>\n"); scanf("%s", pc->data[pc->sz].telepnumber); printf("请输入联系人的地址>\n"); scanf("%s", pc->data[pc->sz].addr); //添加联系人成功,我们sz++ pc->sz++; printf("添加联系人成功\n"); } void ShowContact(const Contact* pc) { assert(pc);//断言一下防止传入空指针 int i = 0; printf("————————————————————————————————————————————————————————————————————————————————————————\n"); printf("|%-20s\t|%-4s\t|%-5s\t|%-12s\t|%-30s|\n", "名字", "年龄", "性别", "电话", "地址"); printf("————————————————————————————————————————————————————————————————————————————————————————\n"); for (i = 0; i < pc->sz; i++) { printf("|%-20s\t|%-4d\t|%-5s\t|%-12s\t|%-30s|\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].telepnumber, pc->data[i].addr); printf("————————————————————————————————————————————————————————————————————————————————————————\n"); } printf("————————————————————————————————————————————————————————————————————————————————————————\n"); } void DelCotact(Contact* pc) { if (pc->sz == 0) { printf("通讯录为空,无法删除\n"); return; } char name[DATAMAX] = { 0 }; assert(pc);//断言,防止传入空指针 printf("请输入要删除的人的名字:》\n"); scanf("%s", name); //遍历找到要删除的人 int i = 0; int del = 0; del = Findbyname(pc, name); if (del == -1) { printf("找不到要删除的人,请检查名字是否输入正确\n"); return; } //找到了,删除联系人 for (i = del; i < pc->sz - 1; i++) { pc->data[i] = pc->data[i + 1]; } pc->sz--; printf("成功删除联系人\n"); } void SearchContact(Contact* pc) { assert(pc); char name[DATAMAX] = { 0 }; printf("请输入要查找的人的名字:》\n"); scanf("%s", name); int pos = Findbyname(pc, name); if (pos == -1) { printf("要查找的人不存在,请检查名字是否输入正确\n"); } else { printf("————————————————————————————————————————————————————————————————————————————————————————\n"); printf("|%-20s\t|%-4s\t|%-5s\t|%-12s\t|%-30s|\n", "名字", "年龄", "性别", "电话", "地址"); printf("————————————————————————————————————————————————————————————————————————————————————————\n"); printf("|%-20s\t|%-4d\t|%-5s\t|%-12s\t|%-30s|\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].telepnumber, pc->data[pos].addr); printf("————————————————————————————————————————————————————————————————————————————————————————\n"); } } void ModifyContact(Contact* pc) { assert(pc); char name[DATAMAX] = { 0 }; printf("请输入要修改人的名字:>\n"); scanf("%s", name); int pos = Findbyname(pc, name); if (pos == -1) { printf("要修改的人不存在,请检查名字是否输入正确\n"); } else { printf("请输入要修改人的名字>\n"); scanf("%s", pc->data[pos].name);//由于name是数组名这里就不要&符号了 printf("请输入要修改人的年龄>\n"); scanf("%d", &(pc->data[pos].age)); printf("请输入要修改人的性别>\n"); scanf("%s", pc->data[pos].sex); printf("请输入要修改人的电话>\n"); scanf("%s", pc->data[pos].telepnumber); printf("请输入要修改人的地址>\n"); scanf("%s", pc->data[pos].addr); \ printf("修改成功\n"); } } void DestoryContact(Contact* pc) { free(pc->data); pc->data = NULL; pc->capacity = 0; pc->sz = 0; } void Savecontact(Contact* pc) { FILE* pf = fopen("contact.dat", "wb"); if (pf == NULL) { perror("SaveContact"); return; } //写数据 int i = 0; for (i = 0; i < pc->sz; i++) { fwrite(pc->data + i, sizeof(PeoInfo), 1, pf); } //关闭文件 fclose(pf); pf = NULL; }