本文中的项目利用链表来储存用户信息,利用了结构体数组存储红包组,设置了主菜单、登录界面、注册界面、红包管理界面,实现了发红包、抢红包、从文件中读取明细、退出登录的功能。本人能力水平有限,代码内容可能不是特别完美,只是想给学弟学妹们撑把伞(把自己感动到了~~),欢迎大家批评指导。
在开始讲述项目前,作者先给同学们些建议:
- 先设想一下自己程序的功能,根据功能思考并设计需要哪些数据结构,将功能思考和数据结构填写在报告中的需求分析部分。(有未知的部分也可以标注一下,例如自己认为编写该程序时的难点)编写程序时,按照需求分析清晰的按照思路走。
- 新建工程后不要急于求成,逐个模块逐个模块编写,要先能编译运行,咱也不用那么急哈(完成最后再编译运行会发现 报错*233 + 警告* 555 ,真的是一段痛苦的经历,在机房坐牢改报错......)
目录
一、任务分析
(一)考查重点
在老师发的c语言课程设计压缩包中的“C语言课程设计指导书.pdf”中的要求(下图)——文件相关函数的使用,结构体数组、链表的增删改查和排序。所以!!!在回答老师问题的时候一定要说清楚用了数组还是链表,链表会加印象分!!!
一定要注意效率!!!每两天完成一组任务,后一天早上检查(可以提前问问老师,求求情,应该能周五两组任务一起交。。。本文就只是任务一的第二小题)
(二)审审题目
- 要设立一个关于用户的结构体,应包含用户名、昵称、学号、零钱余额。
- 红包的结构体,包括该组红包的总个数、总金额、当前剩余红包数、随机的红包金额、抢到某个红包的人的昵称。
- 界面:登录界面、注册界面、红包界面。
- 文件相关:数据变更存入文件。
- 各种功能的创建。(上图给了温馨提示:红包随机数的函数)
- 使用链表。
开源讲解部分
(一)需求分析
根据需求,发现需要的数据有用户信息,红包信息。
(二)头文件的建立
项目结构:
全局变量在多文件项目中的使用:
- 建议在main函数中定义全局变量,定义方法同之前单文件。
- 在其他文件中想要使用时,在全局变量前加上extern(只能有一次不加的,否则会报错重复定义)。
#pragma once
struct human
{
char yonghuming[20];//用户名
char nicheng[20]; //昵称
long long xuehao; //学号
float balance;//余额
char password[25]; //登录口令
struct human* next;
};
typedef human HM;
extern HM* p; /*移动指针*/
//人!!!!!!!!!!!
typedef struct acceptNAME {
char name[20];
}ACNAME;
typedef struct red_bag
{
//一个红包组的定义
int all; //该组红包总数,用于打印抢到的钱
int num; //该组红包剩余数量
char name[20]; //发红包的人的昵称
char wishes[50]; //红包祝福语
float money[100]; //不同个红包的金额
ACNAME ren[100]; //收红包的人的名
}RB;
extern RB rb[]; //红包总组数
extern int zushu; //当前红包发了几组
//红包!!!!!!!!!!
#ifndef _ANNOUNCE_H
#define _ANNOUNCE_H
#pragma once
#pragma warning(disable:4996)
#include"stdio.h"//printf()、scanf()等函数
#include"string.h"//strlen()、strcpy()等函数
#include"stdlib.h"//atoi()函数、exit()、system()等函数
#include"human.h"//引用结构体定义
#include "malloc.h"//malloc()内存分配函数
int load_menu_select();//登录界面
int loginPassword(HM* head);//登录账号检测
int find(long long xuehao, char arr[]);//遍历查找
void crea();//节点
int red_menu_select(HM* it);//红包界面
void Send(HM* he);//发红包
void accept(HM* p);//收红包
float rands(float everag);//随机红包金额
void save(int flag, HM* h, float qian, int geshu, int getwhich);//保存记录
void readd(HM * h);//读取记录
#endif
(三)用户的信息——链表的创建
- 先定义用户的结构体信息(放在头文件human.h里)。
2. 在这里给没来得及学习链表相关知识的同学推荐一下仔仔木的博客文章,动画真的清晰易懂,但是关键在于头指针的用法:头节点的数据域不放内容(或随机)指针域指向NULL。
且每一个节点都是结构体指针!!
3. 这里用的是带头节点的头插法,要是想创新的话可以搞其他链表试试。
http://t.csdnimg.cn/7TRfzhttp://t.csdnimg.cn/7TRfz
#pragma once
#include"announce.h"
#include"human.h"
#include"time.h"
#pragma warning(disable:4996)
HM* p = NULL; /*移动指针*/
RB rb[25]; //红包总组数
int zushu = 0 ; //当前红包发了几组
HM* head = NULL;//头节点
int main()
{
system("color 1f");
head = (HM*)malloc(sizeof(human)); //给指针申请空间,不然没有调取内存的权限
head->next = NULL; //指针域指空
3. 创建链表函数 void crea() 的定义(crea.cpp)
#include"announce.h"
#include"human.h"
#include"stdio.h"
#pragma warning(disable:4996)
#pragma once
extern HM* head;
/*创建链表,完成数据录入功能,新结点在表头插入*/
void crea()
{
HM* info; /*h:头结点指针;info:新结点指针*/
info = (HM*)malloc(sizeof(HM)); /*申请空间,创立新节点*/
if (!info){ /*如果指针info为空*/
printf("\n内存分配失败");
return ; /*返回空指针*/
}
printf("\n\n *****************登录界面*************************\n\n");
printf("\n 请按如下提示输入相关信息(输入回车结束输入)\n");
printf(" 输入学号:");
scanf("%lld", &info->xuehao); /*输入学号*/
printf(" 输入昵称:");
scanf("%s",& info->nicheng); /*输入昵称*/
printf(" 输入用户名:");
scanf("%s", &info->yonghuming); /*输入用户名*/
printf(" 输入登录口令:");
scanf("%s",& info->password); /*输入登录口令*/
info->balance = 300; //创建用户余额初始化300
//带头节点的头插法
if (head->next != NULL)
{
info->next = head->next; /*将新节点的指针指向头指针指向的节点*/
}
else
{
info->next = NULL; //链表只有这个没有值的头节点时,新节点的指针指空
}
head->next = info; //头指针指向新节点,头指针内始终没内容
}
(四)链表的查询
- find()函数从头到尾遍历除了头节点的其他链表的节点
#pragma once
#include"announce.h"
#include"human.h"
extern HM* p;
extern HM* head;
int find(long long xuehao,char arr[])
{
//HM* p=head;
p = head->next;
//移动的指针换到头的位置
while (p!=NULL&&xuehao!=p->xuehao){
p = p->next;
}
if (p == NULL) {
return 0;
}
//根据返回值判断
//返回0 为不存在该人,直接创建新用户
//返回1 为存在该人,且登录口令正确
//返回2 为存在该人,但登录口令错误
if (strcmp(p->password,arr)) {
return 2;
}
else {
system("cls");
printf(" 口令正确!");
return 1;
}
}
(五)主函数
在主函数中设置了获取三个界面的返回参数的功能。
srand()函数为随机种子函数,是初始化rand()函数用的,不能放入rands.cpp文件中随rands()的调用而调用。srand函数是根据系统时间随机生成的种子,如果每次调用rands() 都生成一遍种子,就会因为计算机运算速度过快,而生成相同的种子,从而生成多个相同的随机数(并非偶然!!!)
#pragma once
#include"announce.h"
#include"human.h"
#include"time.h"
#pragma warning(disable:4996)
HM* p = NULL; /*移动指针*/
RB rb[25]; //红包总组数
int zushu = 0 ; //当前红包发了几组
HM* head = NULL;//指头
int main()
{
system("color 1f");
head = (HM*)malloc(sizeof(human));
head->next = NULL;
srand((unsigned int)time(NULL));
//flag1为界面循环退出标识(退出程序),flag2为登录界面退出标识,flag3为登录界面退出标识
for (int flag1 = 1; flag1;) {
switch (load_menu_select())
{
case 1: //选1进入登录界面
for (int flag2 = 1; flag2;) {
int o = loginPassword(head);//根据返回值判断
//返回0 为不存在该人,直接创建新用户
//返回1 为存在该人,且登录口令正确
//返回2 为返回上层
if (o == 1) {
//system("cls");//清屏
printf("\n\n *******登录成功*******\n");
for (int u = 1; u;) {
switch (red_menu_select(p)) {
case 1: Send(p); printf("\n 发红包成功!!!"); break; // 1. 发红包
case 2: accept(p); break;//2. 抢红包
case 3: readd(p); printf("\n 读取文件成功!!!"); break; // 3. 从文件中读入所有记录
case 4: u = 0; p = head; break;// 4. 退出登录
case 5: system("cls"); break;//5.清屏
}
}
}
else if (o == 0) {
system("cls");//清屏
printf("\n\n 该账号未创建,即将跳转至注册界面");
crea();
p = head->next;
for (int flag3=1; flag3;) {
switch (red_menu_select(p)) {
case 1: Send(p); printf("\n 发红包成功!!!"); break;// 1. 发红包
case 2: accept(p); break;//2. 抢红包
case 3: readd(p); printf("\n 读取文件成功!!!"); break;// 3. 从文件中读入所有记录
case 4: flag3 = 0; p = head; break;// 4. 退出登录
case 5: system("cls"); break;//5.清屏
}
}
}
else if (o == 2) flag2 = 0;
}
break;
case 2: exit(0); //选2退出程序
}
}
}
(六)主菜单界面
哈哈哈哈感觉有点单调。
#include"announce.h"
#include"stdio.h"
#pragma warning(disable:4996)
#pragma once
int load_menu_select()
{
printf("\n\n ***************主菜单**************\n\n");
printf(" 1. 进入红包管理系统\n");
printf(" 2. 退出\n\n");
printf(" ***********************************\n\n");
int s=0;
do
{
printf(" 请选择操作(1-2):");
scanf("%d",&s);
if (s<1||s>2) {
printf("输入非法!!!请重新输入!!");
fflush(stdin); //清除缓冲区错误的数据
}
} while (s < 1 || s>2); /*选择项不在1~2之间重输*/
system("cls");
return s; /*返回选择项,主程序根据该数调用相应的函数*/
}
(七)注册界面
这里也可以设置一个退出程序的功能( if (条件) exit(0); )。
#pragma once
#include"announce.h"
#include"human.h"
#include"stdio.h"
#include"string.h"
int loginPassword(HM* head)
{
for (;;) {
printf("\n\n*****************登录界面*************************\n\n");
printf(" (未注册的用户将直接进入注册)");
printf("\n 请输入学号(十位):");
printf("\n 登录口令:\n");
printf(" (按回车或空格结束一项输入)\n\n");
printf("**************************************************\n");
long long xuehao=0;
char arr[25]="\0";
scanf("%lld", &xuehao);
scanf("%s", arr);
switch ( find(xuehao,arr) )
//根据返回值判断
//返回0 为不存在该人,直接创建新用户
//返回1 为存在该人,且登录口令正确
//返回2 为存在该人,但登录口令错误
{
case 0: return 0;
case 1: return 1;
case 2:
printf("\n 口令输入错误!!输入1重新输入!!\n");
printf(" 或输入0返回上层\n");
int te=0;
scanf("%d", &te);
if (te == 1)
continue;
else
return 2;
}
}
}
(八)红包选择界面
可以再在这里加功能,什么私发红包、口令红包之类的。
#pragma once
#include"announce.h"
int red_menu_select(HM *it)
{
printf("\n\n ***************红包主菜单**************\n\n");
printf(" %s用户,您的零钱余额是:%.2f\n",it->nicheng,it->balance);
printf(" 1. 发红包\n");
printf(" 2. 抢红包\n");
printf(" 3. 从文件中读入所有记录\n");
printf(" 4. 退出登录\n\n");
printf(" ***************************************\n\n");
char s[3]="\0";
do
{
printf(" 请选择操作(1-4):");
scanf("%s", &s);
if (s[0] < '0' || s[0]> '4') {
printf("\n 输入非法!!!请重新输入!\n");
fflush(stdin); //清除缓冲区错误的数据
}
} while (s[0] < '0' || s[0]> '4'); /*选择项不在0~4之间重输*/
return s[0] - 48 ;
}
(九)随机数生成函数
随机数函数这里传入的参数因为本人能力雀氏有限(真),只能保证总钱数不超过总金额,红包金额还不够随机,各位可以在这里做些修改。
#pragma once
#include<stdio.h>
#include"stdlib.h"
#include"time.h"
#include"announce.h"
float rands(float everag)
{
float a = 0;
do {
a=rand()%(( int )everag*100)/101.0 ;
} while (a>(float)everag*1.1||a<0.01);
return a;
}
(十)发红包
#pragma once
#include"announce.h"
extern RB rb[]; //红包总组数
extern int zushu; //当前红包发了几组
void Send(HM *he) //X个,共Y元,余额balance
{
if (he->balance == 0) {
printf(" 你没钱啦哈哈哈哈~~~~快去抢点红包吧~~~~~~");
return;
}
int s = 0; //当前组红包数量
char X[3] = {"\0"};//筛选非法变量
printf(" 红包数量:");
do{
scanf("%s",X );
s = atoi(X);
if (s < 1 || s>99) {
printf(" !!输入非法!!重新输入\n");
fflush(stdin);//清除非法输入
}
} while (s<1||s>99); //筛选非法输入
float Y = 0; //红包总金额
int t = 0;
printf(" 红包总金额:");
scanf("%f", &Y );
do {
if (Y < 1 || Y>99) {
printf(" !!输入非法!!重新输入\n");
fflush(stdin);//清除非法输入
}
} while (Y < 1 || Y>99);
for ( ; Y > he->balance ; scanf( "%f",&Y ) ) {
//如果金额大于余额,进入循环
printf(" 余额不足!!重新输入!");
fflush(stdin);
}
//红包能发出来了
zushu++;//红包总组数++从1开始
float y = Y;//赋值红包剩余金额
he->balance -= Y;//该人的余额减去发红包的钱
strcpy(rb[zushu].name,he->nicheng);//记录是谁发的红包组
printf(" 请输入红包祝福语:");
scanf("%s", rb[zushu].wishes);
rb[zushu].num = s; //定义该组红包数量
rb[zushu].all = s; //记录总数
float ever = y*1.2/s ; //随机金额封顶
for ( ; s>1 ; ) { //s是第几个红包从大到1,1不随机
ever = 0.01 + y /s ; //随机数限制参数
rb[zushu].money[s] = rands(ever); //给该红包组的第s个红包随机钱数
y -= rb[zushu].money[s]; //余额减去随机的钱数
s--;
}
rb[zushu].money[1] = y; //把剩下的钱放到第一个红包中
y = 0; //剩余金额清零
save(1, he , Y, s,0); //保存记录到文件当中
system("cls");
}
(十一)收红包
#pragma once
#include"announce.h"
extern RB rb[]; //红包总组数
extern int zushu; //当前红包发了几组
extern HM* p; /*移动指针*/
//收红包
void accept(HM* p)
{
if (zushu == 0) { //红包剩余数量为零,退出
system("cls");
printf("\n\n********************************************************************\n\n");
printf(" 还没有人发红包呢~~~要不你去发一个吧~~~\n");
return;
}
printf("~~~~~~哦吼快来抢红包吧!!\n");
int i = 0; //选择的红包组
for (int flag = 1; flag;) {
printf(" 当前还有 %d 个红包等待你来抢!!\n", zushu);
for (int k = 1; k <= zushu; k++) {
if (rb[k].num == 0) {
system("cls");
printf(" %d、%s发的红包被抢完了 ( 进入可查看其他用户的手气 )\n", k, rb[k].name);
}
else
printf(" %d、%s发的红包剩%d个\n", k, rb[k].name, rb[k].num);
}
printf("\n\n********(输入数字1-%d抢对应的红包)*******\n\n", zushu);
scanf("%d", &i); //选择第i组
if (rb[i].num == 0) {
printf("该组红包被抢完了,请选择其他组吧!\n");
for (int j = rb[i].all; rb[i].num < j; j--) {
printf("%s 用户抢了 %.2f 元!\n", rb[i].ren[j].name, rb[i].money[j]);
}
flag = 1;
fflush(stdin);
}
else {
flag = 0;
}
}
int hbRest = rb[i].num; //第i组的第hbRest个红包
strcpy(rb[i].ren[hbRest].name, p->nicheng); //领取人的昵称进红包结构体组
printf("%s用户给你的红包赠语:%s\n\n", rb[i].name, rb[i].wishes); //打印该红包组的祝福语
float t = rb[i].money[hbRest];
printf("\n 你抢到了%.2f元!!!\n\n", t);
if (rb[i].all != rb[i].num)
for (int j = rb[i].all; j > rb[i].num; j--) {
printf("%s 用户抢了 %.2f 元!\n", &rb[i].ren[j], rb[i].money[hbRest]);
}
p->balance += rb[i].money[hbRest]; //这个人的余额加上该红包组的最后一个红包的金额
save(0, p, t, 0, i); //保存记录到文件当中
rb[i].num--; //当前组红包数--
return;
//rb[i].money[hbRest] = 0; //把抢了的红包金额清零
}
(十二)保存到文件中
outfile存的文件名可以根据需求自行更改。
#pragma once
#include"announce.h"
extern RB rb[55]; //红包总组数
extern int zushu; //当前红包发了几组
extern HM* p; /*移动指针*/
void save(int flag, HM* h, float qian, int geshu, int getwhich)
//flag=1为支出,flag=0为收入,学号判定人加记录
{
FILE* fp; /*定义指向文件的指针*/
char outfile[30] = "e:\\RMBuser.txt"; //头位置
if ((fp = fopen(outfile, "a+")) == NULL) /*为输出打开一个二进制文件,如没有则建立*/
{
printf("不能打开文件,文件保存失败!\n");
}
else{
if (flag) {
fprintf(fp,"\n\n **************支出***************\n\n %s用户发了%d个红包,余额-%.2f元,余额为%.2f元\n ***********************************************************", p->nicheng,geshu ,qian,p->balance);
}
else {
fprintf(fp, "\n\n **************收入***************\n\n %s用户抢了一个%s发的红包,余额+%.2f元,余额为%.2f元\n ***********************************************************",p->nicheng,rb[getwhich].name, qian, p->balance);
}
}
fclose(fp); /*关闭文件*/
printf("\n\n-----所有记录已经成功保存至文件%s中!-----\n\n", outfile); /*显示保存成功*/
return;
}
(十三)从文件中读取
#pragma once
#include"announce.h"
extern HM* p; /*移动指针*/
void readd(HM * h)
{
FILE* fp; /*定义指向文件的指针*/
if ((fp = fopen(outfile, "rb")) == NULL) /*为输出打开一个二进制文件,如没则建立*/
{
printf("目前没有记录哦!\n");
return;
}
char str[ 200 + 1];
while (fgets(str, 200 , fp) != NULL) {
printf("%s", str);
}
fclose(fp);
return;
}