本文已收录至:那些年我们写过的课设
更多知识尽在此专栏中!
欢迎点赞、收藏、关注
目录
前言
正文
核心功能讲解
静态版
增加信息
删除信息
姓名排序
注意事项
动态版
动态开辟
枚举常量
内存归还
注意事项
文件版
文件加载
全面排序
信息保存
注意事项
——————源码区——————
静态版
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 在传递第三个参数(待排序数据大小)时,要特别注意,需要排序的数据大小为基本信息结构体的大小,不能错写成带下标结构体大小,这样在排序时信息是对不上的。排序完成后,直接调用打印函数,展示排序后的信息。
动态版解决了静态版最大的痛点——最大容量不好设置,动态版通讯录用到了动态内存管理的知识,遵循用多少、申请多少的原则,动态版通讯录能够无限空间且不会造成浪费,需要注意的是动态开辟的空间,在通讯录结束时要归还给操作系统。
为了满足动态内存开辟的需求,将静态版通讯录中的结构体类型进行了重新设计,将原来的数组模式改为指针类型(方便节点申请),新增容量这个成员,当下标等于容量时,进行扩容,在原来基础上申请更多的空间来存储数据,关于动态内存开辟可以点这里。
//联系人信息
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;
}
注意:释放的空间必须是已经申请好的,释放后指针要置空,避免野指针,下标和容量置空,确保销毁通讯录的全面性。
文件版在动态版的基础上进行了改进,可以从文件中读取到已有的联系人信息,或把新获取的联系人信息存入文件夹中,做到数据的持久化存储,这会用到文件操作相关知识,可以点这里回顾知识。
文件加载函数可以放在初始化函数中,当然文件加载前还需要判断当前通讯录容量是否足够,因此需要对扩容函数进行声明确保其能在初始化函数中使用。
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" ,指令给错后将无法写入数据。格式化写入同格式化读取一样,格式一定要匹配上。记得关闭文件,并把文件指针置空。
下面是不同版本的源码,文件版为重新编写的版本,在部分变量和函数命名上可能与前两个版本有差异,但底层逻辑是一致的。
#pragma once #include
#include #include #include #include #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);//按名字排序
#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); } }
#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; }
#pragma once #include
#include #include #include #include #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);//销毁通讯录
#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; }
#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; }
#pragma once #include
#include #include #include #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);//通讯录信息保存
#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; }
#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语言进阶——文件操作