说明:本次项目主要内容是通讯录的实现,通过项目发现代码存在的一些bug,笔者以此为出发点进行一些随笔,文中有不足之处请读者朋友们海涵,另特别感谢读者朋友愿意花费宝贵时间阅览本文。
项目准备
在项目开始实施之前,先对项目进行预估,大体构想如何实现等问题。一般而言,在C语言开发过程中我们通常会引用到自定义头文件,因此需要进行相关设置。以下是本次项目准备内容
- 项目开发环境 ,win10-64位机,VScode软件,主要编译器是gcc(关于mingw64的下载我会附上链接,官网下载巨慢的!本人花了好久时间才完成下载);
- 关于项目自定义头文件的引入 本次开发分开为三个文件,分别为声明函数的文件,实现函数的文件及主函数调用的问文件,当然在一个文件及里进行也可,就是来回翻代码有点麻烦。引入自定义头文件格式为#define “xxx.h”,当然,这样会存在一个问题,在vscode软件中进行多文件编译时,用run code运行好像不太可,找了很多法子都没解决,所以我选择用shell终端指令,具体为 g++ -g .\文件1路径 .\文件2路径 -o xxx(这是需要生成的可执行文件名,最后会生成一个xxx.exe文件,记住,在写指令的时候不需要加上后缀!),按回车,而后继续输入.\xxx.exe进行就可以了;
- 项目涉及的算法 数组遍历,冒泡排序(重要!),对于冒泡排序算法涉及到在结构体中需要整体变动问题!
项目开始
根据项目需要先定义好结构体,为方便我把第三个结构体给注释了,详情见代码!
// 代码中参数在#define中设置
struct PeoInfo
{
/* data */
char name[MAX_NAME];
int age;
//struct DATE birthday;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
};
/*struct DATE
{
int year;
int month;
int day;
};*/
struct Contact
{
struct PeoInfo data[MAX];//用于存放单个人员信息
int size;//记录当前已经有的元素个数
}con;//在此直接创建通讯录con
项目实现的功能
本次对于功能实现外的其他代码均省略,如有需要可私信
- 添加人员信息 ,在添加通讯录的实现过程需要注意的是,传参问题,以及结构体指针指向问题。以输入名字为例:见代码
// 省略部分代码
//在主函数中传参,进行地址引用
AddCon(&con);
//函数实现,用指针进行接收地址值
AddCon(struct Contact *ps)
{
//先判断通讯录是否已满的情况,而后进行下一步操作
if (ps->size == MAX)
printf("通讯录已满,无法添加\n");
else
{
printf("请输入名字:>");
scanf("%s",ps -> data[ps -> size].name);
//.......
ps -> size++;//每输入一个记录,通讯录里的人员自
//......
}
}
- 显示已添加人员信息 在进行人员信息显示时,往往涉及到判断通讯录人员是否存在等问题。同时在这一环中极易出现bug,如输入数据后,出现显示时自动终止执行,出现这类情况在于数据类型匹配等问题,并且读者感兴趣可以了解栈溢出相关资料。具体实现见代码!
void DisplayCon(const struct Contact* ps)
{
// 判断通讯录是否为空,即说明ps->size是否为0
if (ps -> size == 0)
printf("通讯录为空:\n");
else
{
int i = 0;
printf("%-8s\t%-4s\t%-5s\t%-110s\t%-8s\n","姓名","年龄","性别","电话","地址"); //左对齐
for (i = 0;i < ps -> size ;i++) //遍历结构体
{
printf("%-8s\t%-4d\t%-5s\t%-10s\t%-8s\n",
ps -> data[i].name,
ps -> data[i].age,//这里需要注意的是,数据类型一定一定要对应得上!age是int型,需用%d来显示和获取,否则容易出现问题!
//......
}
}
}
- 修改信息 当我们需要对某一信息进行增删改查等操作时,第一步对其数据存在与否进行判断是必要的。所以在进行以下操作之前,先封装一个函数Find用来查询数据。Find()函数用于接收两个参数,分别是结构体地址以及需要查询的数据内容。具体详情如下:
// 封装Find函数
static int Find(struct Contact* ps,char name[MAX_NAME])
{
for (int i = 0;i < ps->size;i++)
{
//判断输入的数据信息是否与通讯录中已存的信息匹配,若匹配则返回所在位置,否则返回-1,表示找不到该人员。
if (strcmp(ps -> data[i].name,name) == 0)
{
return i;
}
}
return -1;
}
//进行修改操作
void ModCon(struct Contact* ps)
{
char name[MAX_NAME];//预先声明name数组用来存放输入的
printf("请输入修改人的名字:>");
scanf("%s",name);
int pos = Find(ps,name);//声明 int型变量pos用以接收返回值
if (pos == -1)
{
printf("需要修改人的信息不存在:\n");
}
else
{
printf("请输入名字:>");
scanf("%s",ps -> data[pos].name);
printf("请输入年龄:>");
scanf("%d",&(ps -> data[pos].age));
//......
printf("修改完成\n");
}
}
- 删除信息 删除功能的实现在我们查找相应数据后进行,相当于一次遍历赋值操作,而后将原有通讯录大小进行相应缩减即可,即把后一个元素往前挪!代码如下
int pos = Find(ps,name);//通过终端输入数据后调用Find函数进行查找
//删除数据
int j = 0;
for (j = pos;j < ps -> size - 1;j++)
{
ps -> data[j] = ps ->data[j + 1]; //后面元素往前挪1个单位
}
ps -> size--;
printf("删除成功!\n");;
这里可能有人会疑问,为什么for循环里的j终止条件是ps->size-1而不是ps->size呢。因为ps->size表示总元素个数,比如现在有6个元素(下标从0开始),那么最后一元素对应的下标是5,假设j的终止条件是ps->size,那么当j5时,jsize,成立,此时应该有 ps -> data[5] = ps ->data[5 + 1],出现j+16,数组下标越界情况,这显然不是我们想要的,使用j最大值应该是到4即可。
5. 查询信息 查询数据与修改操作同理,区别仅在于不需要在查找成功后进一步进行输入修改相关信息等操作而已,此略。
6. 排序信息 这里笔者主要使用的是冒泡排序,在排序过程中需要定义一个结构体中间变量来进行整体交换,下面将对局部交换和整体交换两种情况进行详情介绍:
//1.只交换年龄,没有将其他数据对应交换
void SortCon(struct Contact* ps)
{
int i = 0;
int len = ps->size;
for (i = 0; i < len - 1;i++)
{
for (int j = 0; j < len - i - 1;j++ )
{
int max = ps -> data[j].age;
if (max > ps -> data[j+1].age )
{
ps -> data[j].age = ps -> data[j + 1].age;
ps -> data[j + 1].age = max ;
}
}
}
//2.整体交换
void SortCon(struct Contact* ps)
{
// struct PeoInfo s = ( ps -> data[ps->size] );
// int sz = sizeof(s) / sizeof(ps -> data[ps->size]);
//qsort(ps,sz,sizeof(ps[0]),cmp_cmt_by_name);
int i = 0;
int len = ps->size;
struct PeoInfo temp;//定义一个结构体中间变量
for (i = 0; i < len - 1;i++)
{
for (int j = 0; j < len - i - 1;j++ )
{
int max = ps -> data[j].age;
if (max > ps -> data[j+1].age )//按年龄从低到高的升序排列
{
temp = ps -> data[j];//注意:是将结构体整体交换,而非只交换年龄!
ps -> data[j] = ps -> data[j + 1];
ps -> data[j + 1] = temp;
}
}
}
项目总结
本次项目实现过程中,依然存在诸多问题,比如项目中冒泡排序的优化,在项目中添加其他实用性功能等。
对于C语言项目中结构体的使用问题,重点在于理解结构体的作用。尤其是使用嵌套结构体使极为重要,学会利用箭头运算符及点预算符是利用结构体的基本功。
虽然本次实验重点在于基于结构体实现通讯录的相关功能,但结构体的使用也贯穿始终。
写在后文
这是笔者作为新生小白第一次进行发稿,依然存在诸多不足,不论是在行文表述方面还是在知识点的介绍和运用方面,如有大神看到,请不吝赐教,当然如果也有同我一样的小白,希望此文能给彼此带来一点进步。