🌳🌲🌱本文已收录至:那些年我们写过的课设
更多知识尽在此专栏中!
?
🎉🎉🎉欢迎点赞、收藏、关注?🎉🎉🎉
目录
🌳前言
🌳正文
🌲核心功能讲解
🌲静态版
🌱增加信息
🌱删除信息
🌱姓名排序
🌱注意事项
🌲动态版
🌱动态开辟
🌱枚举常量
🌱内存归还
🌱注意事项
🌲文件版
🌱文件加载
🌱全面排序
🌱信息保存
🌱注意事项
? ? ? ? ?——————源码区——————
🌲静态版
🌱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"
? ? ? ? ?——————源码区——————
? 下面是不同版本的源码,文件版为重新编写的版本,在部分变量和函数命名上可能与前两个版本有差异,但底层逻辑是一致的。
🌲静态版
#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);//按名字排序
#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语言进阶——文件操作
|