赞
踩
这篇博客记录的是菜鸟(就是本人@~@)在大一学年末的实训项目总结,公布之前已获得项目小组成员的同意,转载请保留申明,谢谢。
项目主要目的是加强数据结构代码能力(C语言版)以及提高团队合作能力,增加项目经验,如果哪里写的不恰当还请各位大神指点
在本次实训项目中用的是Windows环境下的Dev-C++
,实训老师推荐的是可跨平台的CLion
,但是由于种种原因暂时没有转变队伍内熟悉的IDE,这边提供Dev-C++的官网下载地址
在临时存储中,由于元素个数变化较大以及录入用户的信息压根不知道多大,因此会很明显看到在本系统中存在对链表
的大量操作demo,链表相对与顺序表而言的优势之一无外乎就是便于进行大量的数据增加和删除,而正好这个项目需要的就是这个
对于长期存储,由于我们暂时还未学习数据库,同时时间也不允许,因此运用了熟悉的C语言中的文件操作
函数,将用户数据保存在对应的文件中,而且老师也不允许我们用数据库来做,说对会数据库的同学来说这个完全没有难度了(◎ロ◎)
在日程生活中不难发现,我们平时用的APP或Web或小程序,一块有规律的数据一般情况下都会拥有查找功能,而查找的同时肯定也会将数据排序之后变成有序之后才显示给用户,增加用户体验感。因此便需要复习重温排序查找的相关算法。
查找常用算法:顺序查找、折半查找、分块查找、哈希查找等
排序常用算法:折半排序、希尔排序、快速排序、堆排序等
对线性表尤其是其中的链表的增删改查要有熟练的掌握,对于这些函数的编写要信手拈来。不会???正在加强技术的路上!!!
文件操作是根本,毕竟需要长期存储,要保证数据持久化,总不能这次注册登录之后就把你的账号自动注销掉了是吧,所以我们就得把文件操作给掌握,主要熟悉文件打开关闭、文件信息读写,而文件指针定位在这个项目中没用到所以我们对其只是一个简单的了解,感兴趣的可以自行去问问度娘
这是团队合作最怕的就是代码书写不规范,大家做的时候一定一定要统一代码的一些书写风格,要不然到后期你会发现你脑子里都是“这a是啥”、“这个func干哈的”、“这个b又是个什么东西”……(头疼)
有需求分析才有编写代码的方向,所以很荣幸当了一把队伍里面的客户,体验了上帝的感觉,确定下了以下功能模块
功能模块 | 功能分析 | 备注 |
---|---|---|
注册功能 | 用于新用户注册账号 | 不可裁剪 |
登陆功能 | 用于用户登录进入系统 | 不可裁剪 |
个人信息管理 | 用于查看修改个人账号信息 | 不可裁剪 |
通讯录管理 | 用于管理用户朋友圈的联系方式 | 不可裁剪 |
日程管理 | 用户记录用户的日程 | 不可裁剪 |
财务管理 | 用于记录用户的日常财务情况 | 不可裁剪 |
健身管理 | 用于记录用户的健身情况 | 不可裁剪 |
这里附上我小组项目的项目流程图
主要采用了二进制进行存储,一开始的时候由于不熟悉,采取了最蠢的办法,用fget和fput一行一行的进行读写文件,后面经过老师的一句话点拨——“为什么不直接将一个结点进行读写呢”,因此后来查找资料和个人demo测试之后发现,真香( ! w ! ),具体实现可查看- -> 财务管理模块
这个刚开始纠结了我和小组另外一位同学好一段时间,最后经过一次心血来潮想用strcmp来进行试验之后,发现排序成功了,然后去广泛查看他人的经验分享,原来中文排序的时候是根据它的拼音为标识进行排序的;
刚开始有想过放在一个文件里面,其他功能模块都只是它的子函数,但是合并起来之后发现居然有四千多行(!M !)然后就去找度娘搜索如何解决这个问题,最终决定了将功能模块封装成函数库,在主函数里面根据头文件进行调用,这样就控制了每个文件它的代码量,看起来就很舒服,真香( ~ w ~ )
由于是团队联合,代码的实现界面排版以及代码的风格会有一定的差异, 部分引用的是成员自写的代码思路,若哪里有问题欢迎各位大佬指点指点(+w+)
//结点结构
typedef struct userAccountInfo {
accountInfo user; //用户个人中心
struct userAccountInfo *next;
} userAccountInfo, *LinkuserAccountInfo;
在程序运行的开始优先将用户库文档内存放的数据加载进自定义的链表中,新用户注册时系统会判断用户名是否已存在,保证用户名的唯一性(充当userID的角色)。
登录时会根据用户输入的账号密码进行匹配,匹配成功则进入系统主菜单中
//登录遍历链表匹配账号密码
while(p) {
if(!strcmp(UserName, p->user.userName)) {
printf("请输入密码:");
scanf("%s",PassWord);
getchar();
if(!strcmp(PassWord, p->user.userPassword)) {
printf("正在登陆中请稍候……\n");
Sleep(900); //进行等待0.9秒,可删除
system("cls"); //清屏
mainMenu(p->user);
return ;
} else {
while(errortime) {
printf("请重新输入密码(还剩%2d次机会):", errortime);//密码错误重新输入
scanf("%s", PassWord);
getchar();
errortime--;
if(!strcmp(PassWord, p->user.userPassword)) { //最多只能错三次
printf("正在登陆中请稍候……\n");
Sleep(900);
system("cls");
mainMenu(p->user);
return ;
}
}
if(!errortime) { //密码错误达到最大值
printf("密码错误多次!!自动退出登录界面!!!\n");
return ;
}
}
}
p = p->next;
}
注册时会检测申请注册的账号ID是否已存在,已存在的会提示重新输入
p = userInfo->next;
char userPassword_1[SCOPE];
printf("—————————————————————\n");
printf("| 请输入你要注册的用户名 |\n");
printf("—————————————————————\n\n");
scanf("%s", s->user.userName); //输入注册用户名
getchar();
while(p||(strcmp(s->user.userName, "admin") == 0)) { //判断是否该用户名是否和管理员冲突
if(!strcmp(s->user.userName, "admin") || !strcmp(s->user.userName, p->user.userName)) {
printf("———————————————————————\n");
printf("| 该用户已经存在!!! |\n");
printf("| 请重新输入你要注册的用户名 |\n");
printf("———————————————————————\n\n");
scanf("%s", s->user.userName); //重新输入用户名
getchar();
p = userInfo;
}
p = p->next; //移动遍历
}
该系统中设定的管理员拥有查看用户信息、注销用户账号和重置用户密码的功能,主要实现也是运用了链表的遍历进行完成,下面附上注销用户账号的核心代码(判断选项是否合法可以换用do-while循环来进行控制)
/*注销用户*/
void cancelUserAccount(LinkuserAccountInfo &user, char *userTarget) {
userAccountInfo *p;
p = searchUsesrInfo(user, userTarget); //查找目标结点
if(!p) {
return ;
}
int chooseSure;
printf("\n—————————————————————————————————\n");
printf(" 是否确认注销用户\"%s\": \n",userTarget);
printf(" 1. 确认 2. 取消 \n");
printf("—————————————————————————————————\n\n");
chooseSure = getch();
while(chooseSure > 2+48 || chooseSure < 1+48) {
printf("请输入正确的选项\n");
printf("\n—————————————————————————————————\n");
printf(" 是否确认注销用户\"%s\": \n",userTarget);
printf(" 1. 确认 2. 取消 \n");
printf("—————————————————————————————————\n\n");
chooseSure = getch();
}
switch(chooseSure) {
case '1': {
system("cls");
userAccountInfo *q;
q = p->next;
p->next = q->next;
usernum--;
free(q);
printf("\n—————————————————————————————————\n");
printf(" 用户\"%s\"已被注销 \n", userTarget);
printf("—————————————————————————————————\n\n");
system("pause");
system("cls");
return ;
}
case '2': {
system("cls");
break;
}
}
}
该模块主要功能是查看和修改个人的基本信息,以及修改个人账号密码
/*修改用户密码*/
Status changePassword(char *password) {
int countError = 0; //输入密码错误次数
char inputWord[15]; //输入的密码
printf("\n—————————————————————————————————\n");
printf(" 请输入你的旧密码 \n");
printf("—————————————————————————————————\n");
cin >> inputWord;
for(countError = 1; countError <= maxErrorTime; countError++) { //若用户输入密码错误次数超过设定次数则无法再修改密码
if(countError == maxErrorTime) { //错误次数过多,退出修改密码程序
printf("\n—————————————————————————————————\n");
printf(" 你输入的密码错误次数过多 \n");
printf(" 将自动退出该界面 \n");
printf("—————————————————————————————————\n");
return ERROR;
}
//输入与用户旧密码相同时触发
if(!strcmp(password, inputWord)) {
int flag = 1;
char newPassword[15], newPasswordAgain[15];
//判断用户两次输入新密码是否一致
while(flag) {
printf("\n—————————————————————————————————\n");
printf(" 请输入你的新密码 \n");
printf("—————————————————————————————————\n");
cin >> newPassword; //输入新密码
printf("\n—————————————————————————————————\n");
printf(" 请再次输入新密码 \n");
printf("—————————————————————————————————\n");
cin >> newPasswordAgain;//再次输入新密码
//判断两次输入是否相等
if(!strcmp(newPassword, newPasswordAgain)) { //两次输入新密码相同时更新密码
strcpy(password, newPassword);
printf("\n—————————————————————————————————\n");
printf(" 密码修改成功 \n");
printf("—————————————————————————————————\n");
return OK;
} else { //两次输入新密码不同时重新输入新密码
printf("\n—————————————————————————————————\n");
printf(" 两次密码输入不同 \n");
printf(" 请重新输入新密码 \n");
printf("—————————————————————————————————\n");
}
}
} else {
printf("\n—————————————————————————————————\n");
printf(" 密码输入错误 \n");
printf(" 请重新输入密码 \n");
printf("—————————————————————————————————\n");
cin >> inputWord;
}
}
}
主要记录用户的日常出行数据,同样具备增删改查的功能
/*新建日程*/
Status userscheduleManaInsert(LinkuserscheduleMana &L) {
userscheduleMana *s;
s = new userscheduleMana;
printf("\n请输入时间:");
scanf("%s",s->elem.eventTime);
getchar();
printf("\n请输入任务:");
scanf("%s",s->elem.EventAddress);
getchar();
s->next = L->next;
L->next = s;
printf("增加成功!!!!\n");
system("pause");
system("cls");
}
采用对链表的操作和对文件的读写操作,从而实现增删改查的功能
/*排序联系人信息*/
void SortInformation(InformationLinkList &L,int n)
{
int i,j;
Contacts temp[1];
FILE *fp = fopen(DataAddress, "wb+");
if (fp == NULL)
{
printf("文件无内容,请先新建联系人信息\n");
}
Contacts read_demo[Max];
fread(read_demo, sizeof(Contacts),n, fp);
for( i=1;i<n;i++)//利用插入排序法
{
strcpy(temp[0].ContactsNUm,read_demo[i].ContactsNUm);
strcpy(temp[0].ContactsName,read_demo[i].ContactsName);
strcpy(temp[0].ContactsAge,read_demo[i].ContactsAge);
strcpy(temp[0].ContactsPhone,read_demo[i].ContactsPhone);
strcpy(temp[0].ContactsAddress,read_demo[i].ContactsAddress);
strcpy(temp[0].ContactsMail,read_demo[i].ContactsMail);
for(j=i-1;j>=0&&strcmp(temp[0].ContactsNUm,read_demo[j].ContactsNUm)<0;j--){
strcpy(read_demo[j+1].ContactsNUm,read_demo[j].ContactsNUm);
strcpy(read_demo[j+1].ContactsName,read_demo[j].ContactsName);
strcpy(read_demo[j+1].ContactsAge,read_demo[j].ContactsAge);
strcpy(read_demo[j+1].ContactsPhone,read_demo[j].ContactsPhone);
strcpy(read_demo[j+1].ContactsAddress,read_demo[j].ContactsAddress);
strcpy(read_demo[j+1].ContactsMail,read_demo[j].ContactsMail);
}
strcpy(read_demo[j+1].ContactsNUm,temp[0].ContactsNUm);
strcpy(read_demo[j+1].ContactsName,temp[0].ContactsName);
strcpy(read_demo[j+1].ContactsAge,temp[0].ContactsAge);
strcpy(read_demo[j+1].ContactsPhone,temp[0].ContactsPhone);
strcpy(read_demo[j+1].ContactsAddress,temp[0].ContactsAddress);
strcpy(read_demo[j+1].ContactsMail,temp[0].ContactsMail);
}
printf("排序后的联系人信息为(编号从小到大):\n");
printf("编号 姓名 年龄 电话号码 通讯地址 邮箱\n");
printf("——————————————————————————————————————\n");
for( i=0;i<n;i++)
printf("|%s\t%s\t%s\t%s\t%s\t%s|\n",read_demo[i].ContactsNUm,read_demo[i].ContactsName, read_demo[i].ContactsAge,
read_demo[i].ContactsPhone,read_demo[i].ContactsAddress,read_demo[i].ContactsMail);
printf("——————————————————————————————————————\n");
fwrite(read_demo,sizeof(Contacts),n,fp);
fflush(stdin);
fclose(fp);
}
创建结构体链表存放方案各个数据域,录入链表中的结点,增删改查为对链表进行基础操作,然后再将结点录入文档和读取文档
/*按照名称查找方案信息*/
fitnessNode *searchFitnessInfo(Fitness &userFitness, char *target) {
fitnessNode *p;
p = userFitness;
//对链表进行顺序查找
while(p->next) {
if(!strcmp(p->next->data.schemeName, target)) {
printf("—————————————————————————————————\n\n");
printf(" 方案名称:%s \n",p->next->data.schemeName);
printf(" 方案类型:%s \n",p->next->data.schemeCategory);
printf(" 方案强度:%s \n",p->next->data.schemeIntensity);
printf(" 方案描述:%s \n",p->next->data.schemeDescribe);
printf("—————————————————————————————————\n\n");
return p; //返回前驱节点
}
p = p->next;
}
return NULL; //若不存在,则返回空
}
可能刷到这里增删改查都已经看累了,下面是结合了自己个人想法和老师的一句点睛提醒自编的读写文件代码(二进制),基本思想就是每次读写的
/*加载初始化财务信息*/
void initFinance() {
userFinance = (FinNode *)malloc(sizeof(FinNode));//初始化链表
userFinance->next = NULL;
FILE *fp;
fp = fopen("financeInfo.dat", "rb"); //打开目标文件
if(!fp) {
printf("\n—————————————————————————————————\n");
printf(" 数据读取错误! \n");
printf("——————————————————————————————————\n");
fclose(fp);
return ;
}
// fwrite(&numFinanceInfo, sizeof(numFinanceInfo), 1, fp); //用于开发者在最初的时候将numFinanceInfo置零
fread((void*)&numFinanceInfo, sizeof(numFinanceInfo), 1, fp); //财从文件读取务数据结点数
if(numFinanceInfo == 0) {
fclose(fp);
return ;
}
int tempNumFin = numFinanceInfo;
FinNode *p;
p = userFinance->next; //对链表进行录入数据
while(tempNumFin--) { //根据信息个数一个个读取出来
p = (FinNode *)malloc(sizeof(FinNode));
fread((void*)&p->data, sizeof(financeInfo), 1, fp); //以每次读取结点大小进链表,读取financeInfo次
p->next = userFinance->next;
userFinance->next = p;
}
fclose(fp);
}
/*更新财务信息*/
void upDataFinance() {
FILE *fp;
fp = fopen("financeInfo.dat", "wb+"); //打开文件为二进制文件覆盖写
if(!fp) {
printf("\n—————————————————————————————————\n");
printf(" 数据出错,可能数据包已丢失 \n");
printf("——————————————————————————————————\n");
fclose(fp);
return ;
}
fwrite(&numFinanceInfo, sizeof(numFinanceInfo), 1, fp);
FinNode *p;
p = userFinance->next;
while(numFinanceInfo--) { //根据信息个数决定写入的次数
if(fwrite(p, sizeof(financeInfo), 1, fp) == 1) { //给到每个结点的地址,写入一个结点的大小,写一次循环
p = p->next;
}
}
fclose(fp);
}
收获和成长肯定是有的,我主要总结成以下精简的几个小点
得想办法搞到第一手源码 --> 快点这里
gitee仓库已上传 --> https://gitee.com/xbaozi-ch/InfoMS.git
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。