文章目录
号称揽括c语言百分之九十基础知识的小项目——数据持久化通讯录,非常适合大一学生巩固和加强c语言,提高我们的代码能力,一起来看看吧。
该通讯录具备了一般通讯录的功能,例如添加,删除,修改,查找,排序(按名字字母大小排序),显示通讯录。
除此之外,该通讯录所占空间大小不是一成不变的,而是调用了c语言中的内存函数,随着你添加的人数越多,会自动开辟扩容空间,节省了空间上的浪费;
值得一提的是,该通讯录是数据持久化的,数据不会随着程序结束而消失,而是调用文件函数,生成文件,把数据保存起来;下一次运行程序,也会继承生成文件原数据,继续使用通讯录。因此,该通讯录除了无页面,基本上与手机上的通讯录无异。
当然,也不可能尽善尽美,有什么不好的地方,请大家多多指教哈·。
通讯录实现过程逻辑
我们上来就先浅创建两个结构体,一个是个人信息,另一个则是通讯录,注释如下
typedef struct peo
{
char name[NAME_MAX];//姓名
int age; //年龄
char sex[SEX_MAX]; //性别
char tele[TELE_MAX];//电话
char addr[ADDR__MAX];//地址
}peo;
typedef struct contact
{
peo* data;
//创建一个指向结构体(个人信息)指针;
//后面可以以结构体数组的形式使用
int sz;
//通讯录当前人数
int moren;
//通讯录默认人数,可开辟空间增加
}contact;
变量的大小我们都是先define定义好,以便我们后期可能修改大小,这样就不需要在整个代码里进行修改。
#define MAX 1000
#define NAME_MAX 15
#define ADDR__MAX 15
#define SEX_MAX 10
#define TELE_MAX 15
#define MOREN_SE 2
整个通讯录的实现逻辑也是比较简单易懂的,使用do…while 语句,上来就给菜单进行选择,选择则使用switch语句;选择0就退出程序,其余选择就进入一个函数,使用通讯录的对应功能。代码注释如下,包括函数作用。
int main()
{
int input;
contact con = { 0 };//创建一个通讯录结构体变量
InitContact(&con);
//初始化通讯录,包括开辟空间和继承上次运行数据
do
{
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
addinput(&con);
//添加联系人函数
break;
case 2:
delcontact(&con);
//删除联系人函数
break;
case 3:
searchcontact(&con);
//查找联系人函数
break;
case 4:
modifycontact(&con);
//修改联系人信息函数
break;
case 5:
paixucontact_byname(&con);
//对联系人进行排序函数
break;
case 6:
show(&con);
break;
case 0:
SaveContact(&con);
DestroyContact(&con);
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
}
} while (input);
}
通讯录不同功能的函数实现
一.开辟空间和继承上次运行数据
InitContact(&con);这是初始化通讯录,包括开辟空间和继承上次运行数据,函数参数是一个结构体指针,指向我们刚才创建的那个通讯录结构体 con;
void InitContact(contact*pc)
{
assert(pc);
//判断传进来的指针是否为空,空指针的话整个程序会崩溃
pc->moren = MOREN_SE;
//初始化通讯录默认人数
peo* tmp = (peo*)malloc(sizeof(peo) * pc->moren);
//malloc是库函数,用于开辟空间,下面我会再介绍
if (NULL != tmp)
{
//空间如果开辟失败,会返回一个空指针;
pc->data = tmp;
//空间开辟成功,我们则把通讯录指针指向这个空间
}
else
{
printf("%s", strerror(errno));
//报出空间失败原因
}
LoadContact(pc);//继承上次运行得到的数据
}
malloc开辟空间函数,参数是要开辟空间的大小,单位为字节,返回一个指针,指向这个刚开辟的空间。
LoadContact(pc);这个函数用于继承上次运行得到的数据,这里先不介绍,等会和保存数据到文件一起介绍。
二.添加联系人及动态内存分配
addinput(&con);添加联系人函数,同样传过去一个结构体指针,指向我们刚才创建的那个通讯录结构体 con;添加联系人之前我们得先检查当前通讯录人数是否等于默认人数,如果是,就得扩容空间,是通讯录空间变大,所以我们再创建一个函数进行检查。
void addinput(contact* pc)
{
int flag = 0;
cheak_sz(pc);
//检查人数函数
printf("请输入名字:>");
scanf("%s", pc->data[pc->sz].name);
//con指针指向peo指针所对应内容
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
//增加联系人成功,通讯录当前人数sz+1
printf("增加联系人成功\n");
}
cheak_sz(pc);检查是否需要扩容空间,如果通讯录人数等于默认人数,则对通讯录空间进行扩容,这涉及到动态内存分配,我们调用realloc库函数对通讯录空间进行扩容
void cheak_sz(contact* pc)
{
assert(pc);
if (pc->sz == pc->moren)
{
//如果通讯录人数等于默认人数,则对通讯录空间进行扩容
peo*tmp = (peo*)realloc(pc->data, sizeof(peo) * (pc->moren + 2));
//对通讯录空间进行扩容,realloc函数下面会介绍
if (tmp != NULL)
{
pc->data= tmp;
pc->moren += 2;//默认人数+2
//
printf("通讯录人数已满,正在扩容...增容成功\n");
}
else
{
printf("%s", strerror(errno));
}
}
}
realloc(pc->data, sizeof(peo) * (pc->moren + 2))
这个函数第一个参数是指针,你想为谁扩容,指针就指向谁;第二个参数则是空间要该改变成多少,单位也是字节;返回值是一个void指针,我们用一个结构体指针接收,返回值也要强制类型转换成对应的指针
三.删除联系人
输入联系人名字进行查找,然后删除对应联系人,删除结束后,我们得把通讯录后面的内容,一个一个往前挪移回来。所谓删除,其实也就是把后面的内容往前覆盖。
void delcontact(contact* pc)
{
assert(pc);
printf("请输入联系人名字进行删除\n");
char name[20];
int i = 0;
scanf("%s", name);
int ret=search(pc, name);//查找联系人函数
if (ret != -1)
{
for (i = ret; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
//
}
pc->sz--;
printf("删除联系人成功\n");
}
else
{
printf("不存在该联系人\n");
}
}
查找联系人函数也很简单,对所有联系人名字遍历查找一下,找到返回对应下标
int search(contact* pc,char name[])
{
int i;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(name, pc->data[i].name) == 0)
{
return i;
}
}
return -1;
}
四.修改联系人信息
同样输入联系人名字进行查找,查找联系人函数在上面已介绍,如果找到则进行修改。这些都相对比较简单。
void modifycontact(contact* pc)
{
assert(pc);
char name[20];
printf("请输入联系人名字进行修改\n");
scanf("%s", name);
int ret = search(pc, name);
if (ret != -1)
{
printf("请修改名字:>");
scanf("%s", pc->data[ret].name);
printf("请修改年龄:>");
scanf("%d", &(pc->data[ret].age));
printf("请修改性别:>");
scanf("%s", pc->data[ret].sex);
printf("请修改电话:>");
scanf("%s", pc->data[ret].tele);
printf("请修改地址:>");
scanf("%s", pc->data[ret].addr);
printf("修改联系人成功\n");
}
else
{
printf("不存在该联系人\n");
}
}
五.查找联系人信息
这个也相对简单,同样输入名字,调用查找联系人,找到联系人就打印出全部信息。
void searchcontact(contact* pc)
{
assert(pc);
char name[20];
printf("请输入联系人名字进行查找\n");
scanf("%s", name);
int ret = search(pc, name);
if (ret != -1)
{
printf("%-15s\t%-5d\t%-10s\t%-15s\t%-15s\n",
pc->data[ret].name, pc->data[ret].age, pc->data[ret].sex,
pc->data[ret].tele, pc->data[ret].addr);
}
else
{
printf("通讯录不存在该联系人\n");
}
}
六.按名字字母对联系人进行排序显示
这个排序使用了c语言中的排序函数qsort,不知道你们对这个函数了解?
qsort排序函数有四个函数,第一个是指针,你想对什么类型变量进行排序,就传该类型的指针指向对应内容空间;第二个是进行排序的变量有多少个;第三个是每个变量所占内存空间大小,单位字节;比较麻烦的是第四个参数,其实是传过去一个比较参数,这个函数定义了两个变量之间的比较方式,我这里其实是对字符串进行比较,所以直接调用strcmp函数,这里就不介绍strcmp函数了。
int compare(const void* arg1, const void* arg2)
{
return strcmp((*(peo*)arg1).name,(*(peo*)arg2).name);
}
void paixucontact_byname(contact* pc)
{
qsort(pc->data, pc->sz, sizeof(pc->data[1]), compare);
show(pc);//排序后会展示一下当前通讯录内容
}
七.将通讯录内容打印显示
这一步也不难,把所有人的信息遍历一下,然后打印出来
void show(contact* pc)
{
int i = 0;
printf("%-15s\t%-5s\t%-10s\t%-15s\t%-15s\n",
"名字", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++)
{
printf("%-15s\t%-5d\t%-10s\t%-15s\t%-15s\n",
pc->data[i].name, pc->data[i].age, pc->data[i].sex,
pc->data[i].tele, pc->data[i].addr );
}
}
八.程序结束后保存数据
我们先打开一个文件,如果不存在该文件,则会先创建一个文件,我们以二进制写文件的方式先打开文件。
fopen(“contact.txt”, “wb”),第一个函数参数是文件名或者文件地址,第二个函数参数是以什么方式打开。这里’wb’是指用二进制的方式打开进行导入数据。
void SaveContact(contact* pc)
{
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
printf("SaveContact:: %s\n", strerror(errno));
//判断文件打开是否成功
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(peo), 1, pf);
//一次循环导入一个联系人的信息
}
fclose(pf);
pf = NULL;
}
如果文件打开成功,我们则调用fwrite函数把联系人的数据一个一个写进去,这里是以二进制的方式导入数据,我们打开生成的文件也是乱码的。
简单介绍一下fwrite函数
fwrite(pc->data + i, sizeof(peo), 1, pf);
该函数有四个参数,第一个参数是指针,你想给文件写入什么内容,就把一个指向该内容的指针传过去;第二个是每次要写入的空间大小,单位是字节;第三个参数要读入多少个这样的空间。第四个是一个文件指针,指向需要写入内容的文件
需要注意的是,保存数据后整个函数已接近运行结束,结束之前我们最好把开辟的动态内存先手动释放,还给系统。这里我也有这么做,可以参考一下
void DestroyContact(contact* pc)
{
assert(pc);
free(pc->data);
//free函数释放指针指向开辟的动态内存空间
pc->data = NULL;
pc->sz = 0;
pc->moren = 0;
}
九.程序开始运行时下载导入上次的数据
LoadContact(pc);这个函数用于继承上次运行得到的数据,内嵌于初始化函数void InitContact(contact*pc);中。
同样我们先打开一个文件,'rb’是以二进制的方式访问文件,打开成功后,则把文件内容加载进程序运行内存中。
这里调用fread库函数把文件内容导入
void LoadContact(contact* pc)
{
FILE* pf = fopen("contact.txt", "rb");
if (pf == NULL)
{
printf("InitContact:: open for reading : %s\n", strerror(errno));
return;
}
peo buf = { 0 };
while (fread(&buf, sizeof(peo), 1, pf))
{
cheak_sz(pc);
pc->data[pc->sz] = buf;
pc->sz++;
}
fclose(pf);
pf = NULL;
}
这里也简单介绍一下fread函数;
fread(&buf, sizeof(peo), 1, pf)
该函数有四个参数,第一个参数是指针,你想从文件导入什么内容,就把一个指针传过去用于接收数据;第二个是每次要从文件下载内容的空间大小,单位是字节;第三个参数是要下载多少个这样的空间;第四个参数是文件指针,你想从哪个文件下载数据,就给对应的文件指针。
通讯录完整代码
contact.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
#include<windows.h>
#include<assert.h>
#include<errno.h>
#define MAX 1000
#define NAME_MAX 15
#define ADDR__MAX 15
#define SEX_MAX 10
#define TELE_MAX 15
#define MOREN_SE 2
typedef struct peo
{
char name[NAME_MAX];//姓名
int age; //年龄
char sex[SEX_MAX]; //性别
char tele[TELE_MAX];//电话
char addr[ADDR__MAX];//地址
}peo;
typedef struct contact
{
peo* data;
//创建一个结构体(个人信息)指针;
int sz;
//通讯录当前人数
int moren;
//通讯录默认人数,可开辟空间增加
}contact;
void InitContact(contact* pc);
void cheak_sz(contact* pc);
void addinput(contact* pc);
void show(contact* pc);
void SaveContact(contact* pc);
void LoadContact(contact* pc);
void DestroyContact(contact* pc);
void delcontact(contact* pc);
int search(contact* pc, char name[]);
void modifycontact(contact* pc);
void searchcontact(contact* pc);
int compare(const void* arg1, const void* arg2);
void paixucontact_byname(contact* pc);
test.c
#include"contact.h"
void menu()
{
printf("\n*******************************************\n");
printf("********* 1.添加 2.删除 *******\n");
printf("********* 3.查找 4.修改 *******\n");
printf("********* 5.排序 6.显示 *******\n");
printf("********* 0.退出并保存 *******\n");
printf("********************************************\n");
}
int main()
{
int input;
contact con = { 0 };
InitContact(&con);
//初始化通讯录,包括开辟空间和继承上次运行数据
do
{
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
addinput(&con);
//添加联系人函数
break;
case 2:
delcontact(&con);
//删除联系人函数
break;
case 3:
searchcontact(&con);
//查找联系人函数
break;
case 4:
modifycontact(&con);
//修改联系人信息函数
break;
case 5:
paixucontact_byname(&con);
//对联系人进行排序函数
break;
case 6:
show(&con);
break;
case 0:
SaveContact(&con);
DestroyContact(&con);
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
}
} while (input);
}
contact.c
#include"contact.h"
void LoadContact(contact* pc)
{
FILE* pf = fopen("contact.txt", "rb");
if (pf == NULL)
{
printf("InitContact:: open for reading : %s\n", strerror(errno));
return;
}
peo buf = { 0 };
while (fread(&buf, sizeof(peo), 1, pf))
{
cheak_sz(pc);
pc->data[pc->sz] = buf;
pc->sz++;
}
fclose(pf);
pf = NULL;
}
void InitContact(contact*pc)
{
assert(pc);
pc->moren = MOREN_SE;
peo* tmp = (peo*)malloc(sizeof(peo) * pc->moren);
if (NULL != tmp)
{
pc->data = tmp;
}
else
{
printf("%s", strerror(errno));
}
LoadContact(pc);
}
void cheak_sz(contact* pc)
{
assert(pc);
if (pc->sz == pc->moren)
{
peo*tmp = (peo*)realloc(pc->data, sizeof(peo) * (pc->moren + 2));
if (tmp != NULL)
{
pc->data= tmp;
pc->moren += 2;
printf("通讯录人数已满,正在扩容...增容成功\n");
}
else
{
printf("%s", strerror(errno));
}
}
}
void addinput(contact* pc)
{
int flag = 0;
cheak_sz(pc);
printf("请输入名字:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("增加联系人成功\n");
/*printf("请选择\n");
printf("1:继续添加联系人\n");
printf("2:退出添加联系人\n");
scanf("%d", &flag);
if (flag == 1)
{
addinput(pc);
}
else
{
return;
}*/
}
void show(contact* pc)
{
int i = 0;
printf("%-15s\t%-5s\t%-10s\t%-15s\t%-15s\n",
"名字", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++)
{
printf("%-15s\t%-5d\t%-10s\t%-15s\t%-15s\n",
pc->data[i].name, pc->data[i].age, pc->data[i].sex,
pc->data[i].tele, pc->data[i].addr );
}
}
void SaveContact(contact* pc)
{
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
printf("SaveContact:: %s\n", strerror(errno));
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(peo), 1, pf);
}
fclose(pf);
pf = NULL;
}
void searchcontact(contact* pc)
{
assert(pc);
char name[20];
printf("请输入联系人名字进行查找\n");
scanf("%s", name);
int ret = search(pc, name);
if (ret != -1)
{
printf("%-15s\t%-5d\t%-10s\t%-15s\t%-15s\n",
pc->data[ret].name, pc->data[ret].age, pc->data[ret].sex,
pc->data[ret].tele, pc->data[ret].addr);
}
else
{
printf("通讯录不存在该联系人\n");
}
}
int search(contact* pc,char name[])
{
int i;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(name, pc->data[i].name) == 0)
{
return i;
}
}
return -1;
}
void modifycontact(contact* pc)
{
assert(pc);
char name[20];
printf("请输入联系人名字进行修改\n");
scanf("%s", name);
int ret = search(pc, name);
if (ret != -1)
{
printf("请修改名字:>");
scanf("%s", pc->data[ret].name);
printf("请修改年龄:>");
scanf("%d", &(pc->data[ret].age));
printf("请修改性别:>");
scanf("%s", pc->data[ret].sex);
printf("请修改电话:>");
scanf("%s", pc->data[ret].tele);
printf("请修改地址:>");
scanf("%s", pc->data[ret].addr);
printf("修改联系人成功\n");
}
else
{
printf("不存在该联系人\n");
}
}
void delcontact(contact* pc)
{
assert(pc);
printf("请输入联系人名字进行删除\n");
char name[20];
int i = 0;
scanf("%s", name);
int ret=search(pc, name);
if (ret != -1)
{
for (i = ret; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除联系人成功\n");
}
else
{
printf("不存在该联系人\n");
}
}
int compare(const void* arg1, const void* arg2)
{
return strcmp((*(peo*)arg1).name,(*(peo*)arg2).name);
}
void paixucontact_byname(contact* pc)
{
qsort(pc->data, pc->sz, sizeof(pc->data[1]), compare);
show(pc);
}
void DestroyContact(contact* pc)
{
assert(pc);
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->moren = 0;
}
最后 求鼓励 哈哈哈
这个通讯还是存在蛮多不足的,还请大家多多指教,评论区不啬赐教,谢谢您了;
码字不易,也请动动小手点点赞!一起提升技术能力!