🌳🌲🌱本文已收录至:那些年我们写过的课设
更多知识尽在此专栏中!
🎉🎉🎉欢迎点赞、收藏、关注 🎉🎉🎉
目录
🌳前言
🌳正文
🌲核心功能讲解
🌲静态版
🌱增加信息
🌱删除信息
🌱姓名排序
🌱注意事项
🌲动态版
🌱动态开辟
🌱枚举常量
🌱内存归还
🌱注意事项
🌲文件版
🌱文件加载
🌱全面排序
🌱信息保存
🌱注意事项
——————源码区——————
🌲静态版
🌱Contacts.h 功能声明头文件
🌱Contacts.c 功能实现源文件
🌱test.c 主函数源文件
🌲动态版
🌱Contacts.h 功能声明头文件
🌱Contacts.c 功能实现源文件
🌱test.c 主函数源文件
🌲文件操作版
🌱Contacts.h 功能声明头文件
🌱Contacts.c 功能实现源文件
🌱test.c 主函数源文件
🌳总结
🌳前言
相信每个科班的同学都有过C语言课设的经历,比如教职工工资管理系统、图书信息管理系统、学生信息管理系统、通讯录系统等,其实这些课设任务的底层逻辑都是一致的,无非就是对结构体变量进行增删查改操作,同时配合文件操作将数据保存在文件夹中,本文将以通讯录举例,从静态版到文件版,让大家明白通讯录系统是如何逐步完善的。
注意:文末有三个版本的所有源码,系统分为三个文件夹,即声明功能实现函数的头文件 Contacts.h 、实现各种功能函数的源文件 Contacts.c 、包含主函数及各种功能调用的源文件 test.c 。三个文件相辅相成,灵活强大。其中文件版由动态版迭代而来,而动态版是静态版的改进版本,因此三个版本中大部分代码都是一致的,为了突出差异,特地分成了三个板块。
🌳正文
首先先介绍一下不同版本中的核心功能(其他部分也挺重要的,但因文章篇幅限制,挑出了相对重要的部分讲解,当然后面源码区中有注释)
🌲核心功能讲解
🌲静态版
静态版的通讯录是最原始的版本,通讯录大小在程序设计时就已经被预设好了,如果存储信息数大于预设值,会提示内存已满,当然可以将预设值设计得非常大,但这极有可能用不完,会造成浪费,抛开容量这个问题来说,静态版通讯录功能还是挺全的,主要用到了自定义类型的知识。
#define MAX 100 //这是静态版中,通讯录的预设大小
🌱增加信息
通讯录包含了姓名、性别、年龄等信息,是一个结构体类型的数据。
//联系人信息
struct PeoInfo
{
char name[MAX_NAME];
char sex[MAX_SEX];
int age;
char number[MAX_NUMBER];
char address[MAX_ADDRESS];
};
//包含下标的信息
typedef struct Contact
{
struct PeoInfo data[MAX];
int sz;
}Con;
那么对通讯录增加信息,本质上就是在对结构体进行赋值,这里直接分语句对结构体中不同成员进行赋值。当然赋值前要先进行容量判断,看当前结构体下标是否等于预设值,如果等于,就无法添加信息,当前功能执行失败;小于的话能正常赋值,不过要记得赋值一组数据后,结构体下标要+1,避免下次对同一组数据进行重复赋值。
void ConAdd(Con* pc)//增加信息
{
assert(pc);
if (pc->sz == MAX)
{
printf("内存已满,请尝试删除部分联系人!\n");
return;
}
//逐个输入
printf("请输入姓名:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入号码:>");
scanf("%s", pc->data[pc->sz].number);
printf("请输入住址:>");
scanf("%s", pc->data[pc->sz].address);
pc->sz++;//每添加一条信息,长度就+1
printf("增加成功!\n");
}
注意:对结构体的大多数操作,都需要传地址,因为要对结构体本身进行修改。赋值时,要理清两个结构体间的关系,确保数据不会赋错地方。
🌱删除信息
删除信息本质上就是覆盖,找到想要删除的联系人所对应的下标,根据此下标,逐级将后面的结构体数据赋给前面的结构体,这样就完成了删除操作。
将上面的图解转换为代码展示如下(寻找下标需要额外封装一个查找函数):
static int Find(const Con* pc, const char* name)//辅助查找函数
{
assert(pc);
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(name, pc->data[i].name) == 0)
return i;//返回下标
}
return -1;//没有找到
}
void ConErase(Con* pc)//删除信息
{
assert(pc);
if (!(pc->sz))
{
printf("当前通讯录为空,请尝试添加联系人!\n");
return;
}
char name[MAX_NAME] = "0";//临时存储待查找姓名
printf("请输入你想删除联系人的姓名:>");
scanf("%s", name);
if (Find(pc, name) == -1)
{
printf("没有找到此联系人!\n");
}
else
{
int i = Find(pc, name);
for (; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;//删除完后,长度-1
printf("删除成功!\n");
}
}
注意:通讯录删除的底层逻辑是覆盖,对于当前程序来说(顺序表),删除是比较麻烦的,如果通讯录内数据很多,每次删除都会浪费 O(n) 的时间去完成覆盖,改变这一缺点的方法是采用链式存储。
🌱姓名排序
通讯录中的信息存储在一个结构体变量中,普通的排序无法完成任务,因此这里用到了C语言中的库函数 qsort ,它可以适用于所有数据类型的排序,忘记怎么使用的可以点这里。
有了 qsort 的加持,排序就变得很简单了,这里按姓名进行排序,比较函数在设计时需要将 e1、e2 转为对应的结构体指针类型,才能成功访问到姓名这个数据域。
static int cmp(const void* e1, const void* e2)
{
return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name);
}
void ConSortByName(Con* pc)//按名字排序
{
assert(pc);
if (pc->sz == 0)
{
printf("当前通讯录为空,无法排序!\n");
return;
}
else
{
qsort(pc, pc->sz, sizeof(struct PeoInfo), cmp);//快速排序
printf("排序已完成,信息如下:>\n\n");
ConPrint(pc);
}
}
注意: qsort 在传递第三个参数(待排序数据大小)时,要特别注意,需要排序的数据大小为基本信息结构体的大小,不能错写成带下标结构体大小,这样在排序时信息是对不上的。排序完成后,直接调用打印函数,展示排序后的信息。
🌱注意事项
- 1.结构体类型在设计时,为基本信息+带下标结构体的模式,其中后者包含前者
- 2.打印通讯录时,格式化数据要和提示行数据对应上,比如 姓名-name
- 3.增加一组信息,下标+1;删除一组信息,下标-1
- 4.全部删除信息,就是将当前通讯录进行初始化,下标会归0
- 5.在进行排序时,需要注意逻辑设计,如果是按姓名排,比较函数就要使用字符比较的方式;如果是按年龄排,用整型数据比较的方式
🌲动态版
动态版解决了静态版最大的痛点——最大容量不好设置,动态版通讯录用到了动态内存管理的知识,遵循用多少、申请多少的原则,动态版通讯录能够无限空间且不会造成浪费,需要注意的是动态开辟的空间,在通讯录结束时要归还给操作系统。
🌱动态开辟
为了满足动态内存开辟的需求,将静态版通讯录中的结构体类型进行了重新设计,将原来的数组模式改为指针类型(方便节点申请),新增容量这个成员,当下标等于容量时,进行扩容,在原来基础上申请更多的空间来存储数据,关于动态内存开辟可以点这里。
//联系人信息
struct PeoInfo
{
char name[MAX_NAME];
char sex[MAX_SEX];
int age;
char number[MAX_NUMBER];
char address[MAX_ADDRESS];
};
typedef struct Contact
{
struct PeoInfo* data;
int sz;//下标
int capacity;//容量
}Con;
空间开辟函数是独立封装的,当我们增加联系人信息时,会判断空间是否已达到容量值,如果达到了,进入扩容函数,申请足够的空间,成功后将容量和指针信息更新即可。
void checkCapacity(Con* pc)
{
int newCapacity = (pc->capacity) * INC_SZ;
struct PeoInfo* ret = (struct PeoInfo*)realloc(pc->data, sizeof(struct PeoInfo) * newCapacity);
assert(ret);
pc->capacity = newCapacity;
pc->data = ret;
printf("增容成功!\n");
}
注意:动态内存开辟后要遵循开辟规则,对返回的指针进行判断,看是否扩容失败,如果失败了,要及时终止程序。当然在程序运行结束后,要记得释放内存。
🌱枚举常量
枚举常量的存在可以使case语句更清晰,而不是依赖于数字1、2、3,见名知意,是一种提高程序可读性的好方法,关于枚举常量的介绍可以点这里。
enum Menu
{
Exit,
Name,
Sex,
Age,
Number,
Address
}; //在case语句中,Exit就可以直接表示为整型 0
//实际运用
case Exit:
printf("中止修改!\n");
break;
注意:在使用枚举常量时,要注意默认从0开始往后枚举,如果需要指定枚举值,需要提前设定,确保枚举常量的正确性。
🌱内存归还
内存归还就是释放之前开辟的空间,可以将这个函数放在退出通讯录的地方。因为之前开辟的空间是连续的,所以直接释放指针指向空间的数据(结构体起始位置),释放后指针置空,下标和容量归零就可以了。
void ConDestroy(Con* pc)//销毁通讯录
{
assert(pc);
free(pc->data);
pc->data = NULL;
pc->sz = pc->capacity = 0;
}
注意:释放的空间必须是已经申请好的,释放后指针要置空,避免野指针,下标和容量置空,确保销毁通讯录的全面性。
🌱注意事项
- 1.动态版通讯录在初始化时,需要先申请默认大小的空间,容量也要设为默认值
- 2.动态开辟时,要注意大小申请的匹配性,为基本信息结构体大小
- 3.其他操作与静态版基本一致,也可以通过下标访问操作符配合指针,访问成员变量
- 4.在进行排序时,操作对象为 pc->data,即基本信息结构体
- 5.内存归还时,要合情合理,不能随意操作未开辟/已归还的空间
🌲文件版
文件版在动态版的基础上进行了改进,可以从文件中读取到已有的联系人信息,或把新获取的联系人信息存入文件夹中,做到数据的持久化存储,这会用到文件操作相关知识,可以点这里回顾知识。
🌱文件加载
文件加载函数可以放在初始化函数中,当然文件加载前还需要判断当前通讯录容量是否足够,因此需要对扩容函数进行声明确保其能在初始化函数中使用。
void ConLoad(Con* pc)//加载通讯录信息
{
assert(pc);
FILE* fp = fopen("Contact.txt", "r");//打开文件
if (NULL == fp)
{
perror("fopen::Contact.txt");
return;
}
//读数据
struct PeoInfo tmp = { 0 };//临时存储
int ret = 0;
char ch[10] = "0";
fscanf(fp, "%s %s %s %s %s", ch, ch, ch, ch, ch); //读取标头信息
while (ret = (fscanf(fp, "%s %s %d %s %s", tmp.name, tmp.sex,
&(tmp.age), tmp.number, tmp.address)) >= 1)
{
if (pc->sz == pc->capacity)
buyNewCapacity(pc);//扩容
pc->data[pc->sz] = tmp;
pc->sz++;
}
if (feof(fp))
printf("\nEnd by EOF\n");
else if (ferror(fp))
printf("\nEnd by IO\n");
//关闭文件
fclose(fp);
fp = NULL;
}
注意:文件加载遵循文件打开三步走,即打开文件、使用文件、关闭文件,在打开文件后,要对文件指针进行空指针判断。加载文件时,会读取文件中的标头信息,在循环读取通讯录数据,这里采用了格式化读取,每读取成功一个数据,下标+1。
🌱全面排序
全面排序在按姓名排序的基础上做了升级,现在能通过菜单,选择不同的排序逻辑:按姓名、按年龄、按地址……为了适用于所有的排序,设计出了不同排序比较函数。
//各种排序的比较函数
int cmpByName(const void* e1, const void* e2)
{
return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name); //按姓名
}
int cmpBySex(const void* e1, const void* e2)
{
return strcmp(((struct PeoInfo*)e1)->sex, ((struct PeoInfo*)e2)->sex); //按性别
}
int cmpByAge(const void* e1, const void* e2)
{
return (((struct PeoInfo*)e1)->age) - (((struct PeoInfo*)e2)->age); //按年龄
}
int cmpByNumber(const void* e1, const void* e2)
{
return strcmp(((struct PeoInfo*)e1)->number, ((struct PeoInfo*)e2)->number); //按电话号码
}
int cmpByAddress(const void* e1, const void* e2)
{
return strcmp(((struct PeoInfo*)e1)->address, ((struct PeoInfo*)e2)->address); //按地址
}
注意:不同的成员变量所需要的比较函数不同,需要根据需求设计。
🌱信息保存
信息保存即文件写入操作,将当前程序中结构体的数据写入到文件中,正式写入数据前需要先写入标头信息,通过 for 循环将通讯录中的数据全部写入文件中。
void ConSave(Con* pc)//通讯录信息保存
{
assert(pc);
FILE* fp = fopen("Contact.txt", "w");//打开文件
if (NULL == fp)
{
perror("fopen::Contact.txt");
return;
}
//写文件
fprintf(fp, "%-10s\t%-5s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址"); //写入标头信息
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fprintf(fp, "%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[i].name, pc->data[i].sex,
pc->data[i].age, pc->data[i].number, pc->data[i].address);
}
//关闭文件
fclose(fp);
fp = NULL;
}
注意:文件写入的操作指令是 "w" ,指令给错后将无法写入数据。格式化写入同格式化读取一样,格式一定要匹配上。记得关闭文件,并把文件指针置空。
🌱注意事项
- 1.文件版通讯录核心在于文件读取和写入操作,需要对文件操作有一定的了解
- 2.在读取文件前,务必确保目标文件存在,否则会读取失败
- 3.如果想在原来数据基础上追加数据,需要配合指令 "a"
——————源码区——————
下面是不同版本的源码,文件版为重新编写的版本,在部分变量和函数命名上可能与前两个版本有差异,但底层逻辑是一致的。
🌲静态版
🌱Contacts.h 功能声明头文件
#pragma once #include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h> #include<windows.h> #define MAX 100 #define MAX_NAME 10 #define MAX_SEX 5 #define MAX_NUMBER 15 #define MAX_ADDRESS 30 //联系人信息 struct PeoInfo { char name[MAX_NAME]; char sex[MAX_SEX]; int age; char number[MAX_NUMBER]; char address[MAX_ADDRESS]; }; //包含下标的信息 typedef struct Contact { struct PeoInfo data[MAX]; int sz; }Con; void ConInit(Con* pc);//初始化通讯录 void ConPrint(const Con* pc);//打印通讯录 void ConAdd(Con* pc);//增加信息 void ConErase(Con* pc);//删除信息 void ConFind(const Con* pc);//查找信息 void ConRevise(Con* pc);//修改信息 void ConEraseAll(Con* pc);//全部删除 void ConSortByName(Con* pc);//按名字排序
🌱Contacts.c 功能实现源文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"Contact.h" static void menu() { printf("*******************************\n"); printf("******1.Name 2.Age ******\n"); printf("******3.Sex 4.Number******\n"); printf("******5.Address 6.Exit ******\n"); printf("*******************************\n"); } void ConInit(Con* pc)//初始化通讯录 { assert(pc); pc->sz = 0;//下标归零 memset(pc->data, 0, sizeof(struct PeoInfo) * MAX);//内存设置,初始化 } void ConPrint(const Con* pc)//打印通讯录 { assert(pc); if (pc->sz == 0) { printf("当前通讯录为空,请尝试添加联系人!\n"); return; } int i = 0; //经测试,发现使用宏定义的常量,如 MAX_NAME 代替 10,打印会出错 printf("%-10s\t%-5s\t%-s\t%-15s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址"); for (i = 0; i < pc->sz; i++) { printf("%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].number, pc->data[i].address); } } void ConAdd(Con* pc)//增加信息 { assert(pc); if (pc->sz == MAX) { printf("内存已满,请尝试删除部分联系人!\n"); return; } //逐个输入 printf("请输入姓名:>"); scanf("%s", pc->data[pc->sz].name); printf("请输入性别:>"); scanf("%s", pc->data[pc->sz].sex); printf("请输入年龄:>"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入号码:>"); scanf("%s", pc->data[pc->sz].number); printf("请输入住址:>"); scanf("%s", pc->data[pc->sz].address); pc->sz++;//每添加一条信息,长度就+1 printf("增加成功!\n"); } static int Find(const Con* pc, const char* name)//辅助查找函数 { assert(pc); int i = 0; for (i = 0; i < pc->sz; i++) { if (strcmp(name, pc->data[i].name) == 0) return i;//返回下标 } return -1;//没有找到 } void ConErase(Con* pc)//删除信息 { assert(pc); if (!(pc->sz)) { printf("当前通讯录为空,请尝试添加联系人!\n"); return; } char name[MAX_NAME] = "0";//临时存储待查找姓名 printf("请输入你想删除联系人的姓名:>"); scanf("%s", name); if (Find(pc, name) == -1) { printf("没有找到此联系人!\n"); } else { int i = Find(pc, name); for (; i < pc->sz - 1; i++) { pc->data[i] = pc->data[i + 1]; } pc->sz--;//删除完后,长度-1 printf("删除成功!\n"); } } void ConFind(const Con* pc)//查找信息 { assert(pc); printf("请输入你想查找联系人的姓名:>"); char name[MAX_NAME] = "0";//临时存储待查找姓名 scanf("%s", name); int ret = Find(pc, name);//判断变量 if (ret == -1) { printf("没有找到这个联系人!\n"); } else { printf("此联系人信息如下:\n\n"); printf("%-10s\t%-5s\t%-s\t%-15s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址"); printf("%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[ret].name, pc->data[ret].sex, pc->data[ret].age, pc->data[ret].number, pc->data[ret].address); } } void ConRevise(Con* pc)//修改信息 { assert(pc); char name[MAX_NAME] = "0";//存储查找人的姓名 printf("请输入你想修改联系人的姓名:>"); scanf("%s", name); int ret = Find(pc, name);//判断变量 if (ret == -1) { printf("没有找到这个联系人!\n"); } else { int input = 0; menu();//菜单,不同于主函数中的menu printf("请选择你想修改的信息:>"); scanf("%d", &input); //分类讨论,本来想再封装一个函数,考虑到每次修改内容都不同 //就没有设计了 switch (input) { case 1: printf("请输入你想修改的值:>"); scanf("%s", pc->data[ret].name); printf("修改成功!\n"); break; case 2: printf("请输入你想修改的值:>"); scanf("%s", pc->data[ret].sex); printf("修改成功!\n"); break; case 3: printf("请输入你想修改的值:>"); scanf("%d", &(pc->data[ret].age)); printf("修改成功!\n"); break; case 4: printf("请输入你想修改的值:>"); scanf("%s", pc->data[ret].number); printf("修改成功!\n"); break; case 5: printf("请输入你想修改的值:>"); scanf("%s", pc->data[ret].address); printf("修改成功!\n"); break; case 0: printf("中止修改!\n"); break; default: printf("选择有误!\n"); break; } } } void ConEraseAll(Con* pc)//全部删除 { assert(pc); ConInit(pc);//直接初始化,就是全部删除 printf("通讯录中的所有信息都已重置!\n"); } static int cmp(const void* e1, const void* e2) { return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name); } void ConSortByName(Con* pc)//按名字排序 { assert(pc); if (pc->sz == 0) { printf("当前通讯录为空,无法排序!\n"); return; } else { qsort(pc, pc->sz, sizeof(struct PeoInfo), cmp);//快速排序 //出现bug,原因快排设计大小为char,与类型不匹配 printf("排序已完成,信息如下:>\n\n"); ConPrint(pc); } }
🌱test.c 主函数源文件
#define _CRT_SECURE_NO_WARNINGS 1 //实现通讯录 #include"Contact.h" //第一代通讯录,无bug //刷新git提交 void menu() { printf("****** 通讯录 1.0 ********\n"); printf("******************************\n"); printf("******1.Add 2.Del ******\n"); printf("******3.Find 4.Revise******\n"); printf("******5.Show 6.Empty ******\n"); printf("******7.Sort 0.Exit ******\n"); printf("******************************\n"); } int main() { Con C; ConInit(&C); int input = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); system("cls"); switch (input) { case 1: ConAdd(&C); break; case 2: ConErase(&C); break; case 3: ConFind(&C); break; case 4: ConRevise(&C); break; case 5: ConPrint(&C); break; case 6: ConEraseAll(&C); break; case 7: ConSortByName(&C); break; case 0: printf("退出通讯录!\n"); break; default: printf("选择错误,请重新选择!\n"); break; } } while (input); return 0; }
🌲动态版
🌱Contacts.h 功能声明头文件
#pragma once #include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h> #include<windows.h> #define MAX 100 #define MAX_NAME 10 #define MAX_SEX 5 #define MAX_NUMBER 15 #define MAX_ADDRESS 30 #define DEFAULT_SIZE 2 #define INC_SZ 2 //联系人信息 struct PeoInfo { char name[MAX_NAME]; char sex[MAX_SEX]; int age; char number[MAX_NUMBER]; char address[MAX_ADDRESS]; }; typedef struct Contact { struct PeoInfo* data; int sz;//下标 int capacity;//容量 }Con; void ConInit(Con* pc);//初始化通讯录 void ConPrint(const Con* pc);//打印通讯录 void ConAdd(Con* pc);//增加信息 void ConErase(Con* pc);//删除信息 void ConFind(const Con* pc);//查找信息 void ConRevise(Con* pc);//修改信息 void ConEraseAll(Con* pc);//全部删除 void ConSortByName(Con* pc);//按名字排序 void ConDestroy(Con* pc);//销毁通讯录
🌱Contacts.c 功能实现源文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"Contacts.h" static void menu() { printf("*******************************\n"); printf("******1.Name 2.Sex ******\n"); printf("******3.Age 4.Number******\n"); printf("******5.Address 0.Exit ******\n"); printf("*******************************\n"); } enum Menu { Exit, Name, Sex, Age, Number, Address }; void ConInit(Con* pc)//初始化通讯录 { assert(pc); pc->data = (struct PeoInfo*)malloc(sizeof(struct PeoInfo) * DEFAULT_SIZE); if (pc->data == NULL) { perror("ConInit"); return; } pc->sz = 0;//下标归零 pc->capacity = DEFAULT_SIZE;//容量归0 } void ConPrint(const Con* pc)//打印通讯录 { assert(pc); if (pc->sz == 0) { printf("当前通讯录为空,请尝试添加联系人!\n"); return; } int i = 0; //经测试,发现使用宏定义的常量,如 MAX_NAME 代替 10,打印会出错 printf("%-10s\t%-5s\t%-s\t%-15s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址"); for (i = 0; i < pc->sz; i++) { printf("%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].number, pc->data[i].address); } } void checkCapacity(Con* pc) { int newCapacity = (pc->capacity) * INC_SZ; struct PeoInfo* ret = (struct PeoInfo*)realloc(pc->data, sizeof(struct PeoInfo) * newCapacity); assert(ret); pc->capacity = newCapacity; pc->data = ret; printf("增容成功!\n"); } void ConAdd(Con* pc)//增加信息 { assert(pc); if (pc->sz == pc->capacity) checkCapacity(pc); //逐个输入 printf("请输入姓名:>"); scanf("%s", pc->data[pc->sz].name); printf("请输入性别:>"); scanf("%s", pc->data[pc->sz].sex); printf("请输入年龄:>"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入号码:>"); scanf("%s", pc->data[pc->sz].number); printf("请输入住址:>"); scanf("%s", pc->data[pc->sz].address); pc->sz++;//每添加一条信息,长度就+1 printf("增加成功!\n"); } static int Find(const Con* pc, const char* name)//辅助查找函数 { assert(pc); int i = 0; for (i = 0; i < pc->sz; i++) { if (strcmp(name, pc->data[i].name) == 0) return i;//返回下标 } return -1;//没有找到 } void ConErase(Con* pc)//删除信息 { assert(pc); if (!(pc->sz)) { printf("当前通讯录为空,请尝试添加联系人!\n"); return; } char name[MAX_NAME] = "0";//临时存储待查找姓名 printf("请输入你想删除联系人的姓名:>"); scanf("%s", name); if (Find(pc, name) == -1) { printf("没有找到此联系人!\n"); } else { int i = Find(pc, name); for (; i < pc->sz - 1; i++) { pc->data[i] = pc->data[i + 1]; } pc->sz--;//删除完后,长度-1 printf("删除成功!\n"); } } void ConFind(const Con* pc)//查找信息 { assert(pc); printf("请输入你想查找联系人的姓名:>"); char name[MAX_NAME] = "0";//临时存储待查找姓名 scanf("%s", name); int ret = Find(pc, name);//判断变量 if (ret == -1) { printf("没有找到这个联系人!\n"); } else { printf("此联系人信息如下:\n\n"); printf("%-10s\t%-5s\t%-s\t%-15s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址"); printf("%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[ret].name, pc->data[ret].sex, pc->data[ret].age, pc->data[ret].number, pc->data[ret].address); } } void ConRevise(Con* pc)//修改信息 { assert(pc); char name[MAX_NAME] = "0";//存储查找人的姓名 printf("请输入你想修改联系人的姓名:>"); scanf("%s", name); int ret = Find(pc, name);//判断变量 if (ret == -1) { printf("没有找到这个联系人!\n"); } else { int input = 0; menu();//菜单,不同于主函数中的menu printf("请选择你想修改的信息:>"); scanf("%d", &input); //分类讨论,本来想再封装一个函数,考虑到每次修改内容都不同 //就没有设计了 switch (input) { case Name: printf("请输入你想修改的值:>"); scanf("%s", pc->data[ret].name); printf("修改成功!\n"); break; case Sex: printf("请输入你想修改的值:>"); scanf("%s", pc->data[ret].sex); printf("修改成功!\n"); break; case Age: printf("请输入你想修改的值:>"); scanf("%d", &(pc->data[ret].age)); printf("修改成功!\n"); break; case Number: printf("请输入你想修改的值:>"); scanf("%s", pc->data[ret].number); printf("修改成功!\n"); break; case Address: printf("请输入你想修改的值:>"); scanf("%s", pc->data[ret].address); printf("修改成功!\n"); break; case Exit: printf("中止修改!\n"); break; default: printf("选择有误!\n"); break; } } } void ConEraseAll(Con* pc)//全部删除 { assert(pc); ConInit(pc);//直接初始化,就是全部删除 printf("通讯录中的所有信息都已重置!\n"); } static int cmp(const void* e1, const void* e2) { return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name); } void ConSortByName(Con* pc)//按名字排序 { assert(pc); if (pc->sz == 0) { printf("当前通讯录为空,无法排序!\n"); return; } else { qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmp);//快速排序 //出现bug,原因快排设计大小为char,与类型不匹配 //*bug已解决,排序已全面推广 printf("排序已完成,信息如下:>\n\n"); ConPrint(pc); } } void ConDestroy(Con* pc)//销毁通讯录 { assert(pc); free(pc->data); pc->data = NULL; pc->sz = pc->capacity = 0; }
🌱test.c 主函数源文件
#define _CRT_SECURE_NO_WARNINGS 1 //实现通讯录 #include"Contacts.h" //9_25 进行改造,改为动态版本 void menu() { printf("******************************\n"); printf("******1.Add 2.Del ******\n"); printf("******3.Find 4.Revise******\n"); printf("******5.Show 6.Empty ******\n"); printf("******7.Sort 0.Exit ******\n"); printf("******************************\n"); } enum Menu { Exit, Add, Del, Find, Revise, Show, Empty, Sort }; int main() { Con C; ConInit(&C); int input = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); system("cls"); switch (input) { case Add: ConAdd(&C); break; case Del: ConErase(&C); break; case Find: ConFind(&C); break; case Revise: ConRevise(&C); break; case Show: ConPrint(&C); break; case Empty: ConEraseAll(&C); break; case Sort: ConSortByName(&C); break; case Exit: printf("退出通讯录!\n"); ConDestroy(&C); break; default: printf("选择错误,请重新选择!\n"); break; } } while (input); return 0; }
🌲文件操作版
🌱Contacts.h 功能声明头文件
#pragma once #include<stdio.h> #include<stdlib.h> #include<windows.h> #include<assert.h> #define DEFAULT_SIZE 3 //默认通讯录大小为3,如果不够用,会扩容 #define INC_SZ 2 * DEFAULT_SIZE //每次扩容两倍 #define MAX_NAME 10 #define MAX_SEX 5 #define MAX_NUMBER 20 #define MAX_ADDRESS 50 struct PeoInfo { char name[MAX_NAME];//姓名 char sex[MAX_SEX];//性别 int age;//年龄 char number[MAX_NUMBER];//电话号码 char address[MAX_ADDRESS];//住址 }; typedef struct ContactByPeo { struct PeoInfo* data;//数据域 int sz;//下标 int capacity;//容量 }Con; void ConInit(Con* pc);//初始化通讯录 void ConDisplay(const Con* pc);//打印通讯录 void ConDestroy(Con* pc);//销毁通讯录 //增删查改排 void ConAdd(Con* pc);//增加联系人信息 void ConDelte(Con* pc);//删除联系人信息 void ConFind(const Con* pc);//查找联系人信息 void ConRevise(Con* pc);//修改联系人信息 void ConSort(Con* pc);//对信息进行排序 int ConInfoNum(const Con* pc);//统计通讯录中的信息数 void ConLoad(Con* pc);//加载通讯录信息 void ConSave(Con* pc);//通讯录信息保存
🌱Contacts.c 功能实现源文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"Contact.h" void buyNewCapacity(Con* pc);//声明扩容函数 void ConLoad(Con* pc);//声明 加载通讯录信息 void ConInit(Con* pc)//初始化通讯录 { assert(pc); pc->data = (struct PeoInfo*)malloc(sizeof(struct PeoInfo) * DEFAULT_SIZE); if (NULL == pc->data) { //申请失败,终止初始化 perror("malloc"); return; } pc->sz = 0;//下标为0 pc->capacity = DEFAULT_SIZE;//确定容量 ConLoad(pc);//加载通讯录 } void ConDisplay(const Con* pc)//打印通讯录 { assert(pc); if (pc->sz == 0) { printf("当前通讯录为空,请尝试添加联系人!\n"); return; } printf("%-10s\t%-5s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址"); int i = 0; for (i = 0; i < pc->sz; i++) { printf("%-10s\t%-5s\t%-d\t%-20s\t%-30s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].number, pc->data[i].address); } } void ConDestroy(Con* pc)//销毁通讯录 { assert(pc); free(pc->data); pc->data = NULL; pc->sz = pc->capacity = 0; } //扩容函数,如果下标碰到容量了,就需要扩容 void buyNewCapacity(Con* pc) { assert(pc); struct PeoInfo* ret = (struct PeoInfo*)realloc(pc->data, sizeof(struct PeoInfo) * INC_SZ); if (NULL == ret) { //申请失败 perror("relloc"); return; } pc->data = ret; pc->capacity += INC_SZ;//容量增加 } //增删查改排 void ConAdd(Con* pc)//增加联系人信息 { assert(pc); if (pc->capacity == pc->sz) buyNewCapacity(pc);//容量不足就扩容 printf("请输入姓名:>"); scanf("%s", pc->data[pc->sz].name); printf("请输入性别:>"); scanf("%s", pc->data[pc->sz].sex); printf("请输入年龄:>"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入电话:>"); scanf("%s", pc->data[pc->sz].number); printf("请输入住址:>"); scanf("%s", pc->data[pc->sz].address); printf("信息增加完毕!\n"); pc->sz++; } const int findByName(const Con* pc)//只允许在这个文件内使用 { assert(pc); char findName[MAX_NAME] = "0"; printf("请输入想查找的联系人姓名:>"); scanf("%s", findName); int i = 0; for (i = 0; i < pc->sz; i++) { if (strcmp(pc->data[i].name, findName) == 0) return i; } return -1; } void ConDelte(Con* pc)//删除联系人信息 { assert(pc); int ret = findByName(pc);//顺序表,可以返回下标 if (ret >= 0) { int input = 0; printf("确认删除此人信息吗?取消输入0,否则输入任意数:>"); scanf("%d", &input); if (input == 0) { printf("取消成功!\n"); return; } printf("正在删除中…………\n"); Sleep(1500); int i = ret; for (; i < pc->sz; i++) pc->data[i] = pc->data[i + 1]; pc->sz--; printf("删除完成!\n"); } else printf("查无此人!\n"); } void DisplayByRet(const Con* pc, int ret) { assert(pc); printf("已经找到这个人了,信息如下所示:\n"); printf("%-10s\t%-5s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址"); printf("%-10s\t%-5s\t%-d\t%-20s\t%-30s\n", pc->data[ret].name, pc->data[ret].sex, pc->data[ret].age, pc->data[ret].number, pc->data[ret].address); } void ConFind(const Con* pc)//查找联系人信息 { assert(pc); int ret = findByName(pc); if (ret >= 0) { DisplayByRet(pc, ret); return; } else printf("查无此人!\n"); } //服务于修改的菜单 void menuByReviseAndSort() { printf("***************************************\n"); printf("********** 0.终止 1.姓名 **********\n"); printf("********** 2.性别 3.年龄 **********\n"); printf("********** 4.电话 5.住址 **********\n"); printf("***************************************\n"); } enum MenuByReviseAndSort { 终止,姓名, 性别, 年龄, 电话, 住址 }; void ConRevise(Con* pc)//修改联系人信息 { assert(pc); //因为待修改的信息,有共同特征,因此可以用宏定义来实现 int ret = findByName(pc); if (ret >= 0) { DisplayByRet(pc, ret); int input = 1; while (input) { menuByReviseAndSort(); printf("请选择你想修改的信息:>"); scanf("%d", &input); switch (input) { case 终止: printf("终止修改成功!\n"); break; case 姓名: printf("请输入修改后的信息:>"); scanf("%s", pc->data[ret].name); printf("修改成功!\n"); break; case 性别: printf("请输入修改后的信息:>"); scanf("%s", pc->data[ret].sex); printf("修改成功!\n"); break; case 年龄: printf("请输入修改后的信息:>"); scanf("%d", &(pc->data[ret].age)); printf("修改成功!\n"); break; case 电话: printf("请输入修改后的信息:>"); scanf("%s", pc->data[ret].number); printf("修改成功!\n"); break; case 住址: printf("请输入修改后的信息:>"); scanf("%s", pc->data[ret].address); printf("修改成功!\n"); break; default: printf("选择错误,请重新选择!\n"); break; } } } else printf("查无此人!\n"); } //各种排序的比较函数 int cmpByName(const void* e1, const void* e2) { return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name); //按姓名 } int cmpBySex(const void* e1, const void* e2) { return strcmp(((struct PeoInfo*)e1)->sex, ((struct PeoInfo*)e2)->sex); //按性别 } int cmpByAge(const void* e1, const void* e2) { return (((struct PeoInfo*)e1)->age) - (((struct PeoInfo*)e2)->age); //按年龄 } int cmpByNumber(const void* e1, const void* e2) { return strcmp(((struct PeoInfo*)e1)->number, ((struct PeoInfo*)e2)->number); //按电话号码 } int cmpByAddress(const void* e1, const void* e2) { return strcmp(((struct PeoInfo*)e1)->address, ((struct PeoInfo*)e2)->address); //按地址 } void ConSort(Con* pc)//对信息进行排序 { assert(pc); int input = 1; while (input) { menuByReviseAndSort(); printf("选择排序方式:>"); scanf("%d", &input); switch (input) { case 终止: printf("终止排序成功!\n"); break; case 姓名: qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpByName); printf("按性别排序已经完成了,信息如下:\n"); ConDisplay(pc); break; case 性别: qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpBySex); printf("按性别排序已经完成了,信息如下:\n"); ConDisplay(pc); break; case 年龄: qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpByAge); printf("按年龄排序已经完成了,信息如下:\n"); ConDisplay(pc); break; case 电话: qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpByNumber); printf("按电话排序已经完成了,信息如下:\n"); ConDisplay(pc); break; case 住址: qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpByAddress); printf("按住址排序已经完成了,信息如下:\n"); ConDisplay(pc); break; default: printf("选择错误,请重新选择!\n"); break; } } } int ConInfoNum(const Con* pc)//统计通讯录中的信息数 { assert(pc); return pc->sz; } void ConLoad(Con* pc)//加载通讯录信息 { assert(pc); FILE* fp = fopen("Contact.txt", "r");//打开文件 if (NULL == fp) { perror("fopen::Contact.txt"); return; } //读数据 struct PeoInfo tmp = { 0 };//临时存储 int ret = 0; char ch[10] = "0"; fscanf(fp, "%s %s %s %s %s", ch, ch, ch, ch, ch); while (ret = (fscanf(fp, "%s %s %d %s %s", tmp.name, tmp.sex, &(tmp.age), tmp.number, tmp.address)) >= 1) { if (pc->sz == pc->capacity) buyNewCapacity(pc);//扩容 pc->data[pc->sz] = tmp; pc->sz++; } if (feof(fp)) printf("\nEnd by EOF\n"); else if (ferror(fp)) printf("\nEnd by IO\n"); //关闭文件 fclose(fp); fp = NULL; } void ConSave(Con* pc)//通讯录信息保存 { assert(pc); FILE* fp = fopen("Contact.txt", "w");//打开文件 if (NULL == fp) { perror("fopen::Contact.txt"); return; } //写文件 fprintf(fp, "%-10s\t%-5s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址"); //写入标头信息 int i = 0; for (i = 0; i < pc->sz; i++) { fprintf(fp, "%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].number, pc->data[i].address); } //关闭文件 fclose(fp); fp = NULL; }
🌱test.c 主函数源文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"Contact.h" //第三代通讯录(文件版),刷新git提交 void menuByCon() { printf("***************************************\n"); printf("********** 0.退出 1.显示 **********\n"); printf("********** 2.增加 3.删除 **********\n"); printf("********** 4.查找 5.修改 **********\n"); printf("********** 6.排序 7.数量 **********\n"); printf("***************************************\n"); } enum MenuByCon { 退出, 显示, 增加, 删除, 查询, 修改, 排序, 数量 }; int main() { Con c1;//创建了一个通讯录 ConInit(&c1); int input = 1; while (input) { menuByCon(); printf("请选择你的操作(只能输入数字):>"); scanf("%d", &input); system("cls"); switch (input) { case 退出: printf("退出通讯录系统!\n"); ConSave(&c1);//保存通讯录 ConDestroy(&c1);//销毁通讯录 break; case 显示: ConDisplay(&c1); break; case 增加: ConAdd(&c1); break; case 删除: ConDelte(&c1); break; case 查询: ConFind(&c1); break; case 修改: ConRevise(&c1); break; case 排序: ConSort(&c1); break; case 数量: printf("当前通讯录中信息数为:%d\n", ConInfoNum(&c1)); break; default: printf("选择错误,请重新选择!\n"); break; } } return 0; }
🌳总结
以上就是三个不同版本通讯录的分享,如果是学习的话,可以从静态版开始,逐步升级为文件版,后期可以尝试升级为数据库版;如果是为了课设做准备的话,可以直接看文件版,功能全面,运行稳定。总之,以上就是本期C语言课设分享的全部内容了,作为代码分享类文章,并没有进行太过详细的讲解,但代码量是可以得到保证的。
如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正
相关文章推荐
C语言进阶——自定义类型
C语言进阶——动态内存管理
C语言进阶——文件操作