这题略微复杂,不过这是我第一次,先写完所有代码,再进行测试,也算从小白往外迈了一步。
下面这个是我预计需要使用的函数原型,一开始所有原型都有一个文件指针,我想每个函数都来操作文件,后来发现太麻烦了,于是增加了一个fread的函数,用来把文件读到内存中,然后从内存中操控。
并且一开始,所有原型还有一个字符指针,我预计是要分割用户输入内容的,后面也没有用到,因为strtok的NULL就完全足够了,增加这个字符指针完全是多此一举。
#ifndef safe
#define safe 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
typedef struct {
int id;
char description[20];//零件信息不超过20个字符,所以要限制输入
int quantity;//零件数量
double Total_price;//总价值
} cost;
/**
* @brief 增加新零件
*
* @param part
* @return true
* @return false
*/
bool news(cost * costs);
/**
* @brief 增加现有零件的数量
*
* @param part
* @return true
* @return false
*/
bool buy(cost * costs);
/**
* @brief 从现有零件中减去一定数量
*
*/
bool shell(cost * costs);
/**
* @brief 删除指定零件
*
* @param fp
* @param id 零件号
* @return true
* @return false
*/
bool deletes(cost * costs);
/**
* @brief 打印指定零件的信息
*
* @param id 如果id不是数字,而是all,则打印所有信息
*/
void print_num(cost * costs);
/**
* @brief 打印文件中所有零件的总价值
*
* @param fp
*/
void total(cost * costs);
/**
* @brief 从文件中读取内容,并存储在临时分配的内存中
*
* @param fp
* @return cost* 返回读取内容的数组指针
*/
cost * file_read(FILE *fp);
/**
* @brief 写入文件
*
* @param fp
* @param costs
*/
void file_write(FILE *fp,cost * costs);
#endif
然后是主代码,没什么好说的。
/**
* @file part_cost.c
* @author your name (you@domain.com)
* @brief C和指针第十五章第十一题 这题可以用数据结构,链表或者树的APT来做,储存数据,读取数据,操作数据
* 但是那样的话,我只是在复习,为了学到新东西,我决定用文本的方式进行数据存储和读取。
* @version 0.1
* @date 2023-04-20
*
* @copyright Copyright (c) 2023
*
*/
#include "part_cost.h"
void menu();
int main(int argc,char **argv)
{
if (argc > 1)//判断是否有文件
{//打印菜单,这个内容可以用用个函数包装,每次打印,但是我有点低烧就不写了
FILE *fp = fopen(argv[1],"r+");//查看是否有这个文件
if(fp == NULL)
{
fp = fopen(argv[1],"w+");//如果没有则创建文件
if(fp == NULL)
{
fprintf(stderr,"文件创建失败!");
exit(EXIT_FAILURE);
}
}
cost * costs = file_read(fp);
char buffer[256] = {0},*str; // 用以储存临时数据
while (1)
{
menu();
printf("> ");
fgets(buffer,256,stdin);
str = strtok(buffer," ");
if (strcmp(str,"new") == 0)//判断内容
{
if(!news(costs))
fprintf(stderr,"参数错误!");
}
else if (strcmp(str,"buy") == 0)
{
if(!buy(costs))
fprintf(stderr,"参数错误!");
}
else if (strcmp(str,"shell") == 0)
{
if(!shell(costs))
fprintf(stderr,"参数错误!");
}
else if (strcmp(str,"print") == 0)
{
print_num(costs);
}
else if (strcmp(str,"delete") == 0)
{
if(!deletes(costs))
fprintf(stderr,"参数错误!");
}
else if (strcmp(str,"total\n") == 0)
{
total(costs);
}
else if (strcmp(str,"end\n") == 0)
{
file_write(fp,costs);
break;
}
else
{
fprintf(stderr,"命令错误!(不存在的命令)");
}
}
}
else
{
fprintf(stderr,"参数错误");
exit(EXIT_SUCCESS);
}
exit(EXIT_SUCCESS);
}
void menu()
{
puts("\n参数之间用',' 分隔");
puts("可用命令 new 零件简述,零件数量,零件单价");
puts("可用命令 buy 零件号,零件数量,零件单价 ,买");
puts("可用命令 shell 零件号,零件数量,出售价格,卖");
puts("可用命令 print 零件号 or all 打印指定零件信息或者一表格形式打印全部信息");
puts("可用命令 delete 零件号 ,删除指定零件");
puts("可用命令 total 打印和计算文件中所有零件的总价值");
puts("end 退出");
}
至于原型的实现,在fread这里卡了我一段时间,主要是调试的问题,以及对malloc函数认识不到位,直接用sizeof算指针,导致内存较大,常常溢出。
并且在参数的效验上,我没有全部加上字符和数字的效验,所以如果参数输入错误,只会导致没有效果,而不会有提示,但是基础的内容我基本都做了。
#include "part_cost.h"
#include <ctype.h>
short totals = 0;//所有零件总数量
bool news(cost * costs)
{
char * par;//用来记录参数
cost part;
int num;
double value;//零件单价
//检查参数
par = strtok(NULL,",");
if (strlen(par) < 20)
{
strcpy(part.description,par);
}
else{
fprintf(stderr,"参数错误!!");
return 0;
}
par = strtok(NULL,",");//这个参数和下面那个参数,还可以加上判断每个参数输入是否是数字,也就是检测输入是否正确然后在转换,我也懒得写了
sscanf(par,"%d",&num);
if (num > 0)//零件数量非负数,且不等于0
{
part.quantity = num;
}
else{
fprintf(stderr,"参数错误!!!");
return 0;
}
par = strtok(NULL,"\n");
sscanf(par,"%lf",&value);
if (value > 0)//假设单价大于零
{
part.Total_price = part.quantity * value;
}
else{
fprintf(stderr,"参数错误!!!!");
return 0;
}
for (int i = 1; i <= totals; i++)//如果存在被删除的零件,则把这个零件的零件号分配给新零件使用
{
if(costs[i].description[0] == ' ')
{
part.id = i;
memcpy(&costs[i],&part,sizeof(cost));
return 1;
}
}
part.id = ++totals;//储存id大小
costs = (cost *)(realloc(costs,(totals + 1) * sizeof(cost)));
if(costs == NULL)
{
fprintf(stderr,"内存不足!");
free(costs);
exit(EXIT_FAILURE);
}
memcpy(&costs[totals],&part,sizeof(cost));
return 1;
}
bool buy(cost * costs)
{
if(costs == NULL)
{
fprintf(stderr,"库存中没有零件");
return 0;
}
char * par;//用来记录参数
int id,num;//零件号,数量,
double cost_each;//单价
//检查参数
par = strtok(NULL,",");
sscanf(par,"%d",&id);//零件号
if (id > totals || id < 0)
{
fprintf(stderr,"零件不存在");
return 0;
}
//如同我在上个函数里说的,我不会写检测输入是否合格,只做简单的测试,如果要测试是否是数字或者含有字符而错误,需要再写一个函数
//并且因为每个函数的参数不同,所以每个函数要有独特的参数检测,但基本都是字符数字区别测试的复制,并且还有换行符等。
//这样我用strtok实现不方便
par = strtok(NULL,",");
sscanf(par,"%d",&num);
if(num < 0)
{
fprintf(stderr,"参数错误!数量不能小于0");
return 0;
}
costs[id].quantity += num;//增加数量
par = strtok(NULL,"\n");
sscanf(par,"%lf",&cost_each);
if(cost_each < 0)//如果单价是负数,岂不是没有成本,虽然题目没有要求我还是加上了
{
fprintf(stderr,"参数错误!单价不能小于0");
return 0;
}
costs[id].Total_price += cost_each * num;
return 1;
}
bool shell(cost * costs)
{
if(costs == NULL)
{
fprintf(stderr,"库存中没有零件");
return 0;
}
char * par;//用来记录参数
int id,num;
double cost_each;//零件号,数量,单价
//检查参数
par = strtok(NULL,",");
sscanf(par,"%d",&id);//零件号
if (id > totals || id < 0)
{
fprintf(stderr,"零件不存在");
return 0;
}
par = strtok(NULL,",");
sscanf(par,"%d",&num);
if(num < 0)
{
fprintf(stderr,"参数错误!数量不能小于0\n");
return 0;
}
int diff = costs[id].quantity - num;
double price;//单价
if(diff > 0){//零件数量不能减少到负数
price = costs[id].Total_price / costs[id].quantity;//计算原来的单价
costs[id].quantity = diff;//减少数量
}
else
{
fprintf(stderr,"零件数量不足,请先补货\n");
return 0;
}
par = strtok(NULL,",");
sscanf(par,"%lf",&cost_each);
if(cost_each < 0)//如果单价是负数,岂不是没有成本,虽然题目没有要求我还是加上了
{
fprintf(stderr,"参数错误!单价不能小于0\n");
return 0;
}
double profit = cost_each - (price);//每单利润
costs[id].Total_price -= price * num;//总价值减去出售的价值
fprintf(stdout,"每单利润: %0.2lf 总利润 : %.2lf",profit, profit * num);//计算利润并打印
return 1;
}
bool deletes(cost * costs)
{
if(costs == NULL)
{
fprintf(stdin,"库存中没有零件");
return 0;
}
int id;
char * par = strtok(NULL,"\n");
sscanf(par,"%d",&id);
if (id >= totals || id < 0)
{
fprintf(stderr,"零件不存在");
return 0;
}
memset(costs[id].description,0,sizeof(costs[id].description));
//因为我的新零件的测试,是使用的空格,所以要设一个空格,这样读取文件和在数据流中都能进行删除
memset(costs[id].description,' ',sizeof(int));
return 1;
}
void print_num(cost * costs)
{
char *par = strtok(NULL,"\n");
bool flag = 0;//效验位
for (size_t i = 0; i < strlen(par); i++)//这个代码用于判断字符是否是数字,也可以用在上面的函数中
{
if(!isdigit(par[i]))
{
flag = 1;
break;
}
}
if(flag)
{
if(strcmp(par,"all") == 0)
{
fprintf(stdout,"|id ");
fprintf(stdout,"|零件描述信息(description)");
fprintf(stdout,"|零件数量(quantity)");
fprintf(stdout,"|总价值(Total_price)\n");
for (size_t i = 1; i <= totals; i++)
{
fprintf(stdout,"|%-5d",costs[i].id);
fprintf(stdout,"|%-25s",costs[i].description);
fprintf(stdout,"|%-18d",costs[i].quantity);
fprintf(stdout,"|%-5.2lf\n",costs[i].Total_price);
}
}else{
fprintf(stderr,"参数错误! 可以尝试print all or print id");
}
}
else
{
int id;//记录id
sscanf(par,"%d",&id);
if (id > totals || id < 0)
{
fprintf(stderr,"零件不存在");
return ;
}
fprintf(stdout,"零件号(id): %-5d\n",costs[id].id);
fprintf(stdout,"零件描述信息(description): %-20s\n",costs[id].description);
fprintf(stdout,"零件数量(quantity): %-8d\n",costs[id].quantity);
fprintf(stdout,"总价值(Total_price): %-23.2f\n",costs[id].Total_price);
}
}
void total(cost * costs)
{
double sum = 0; //计算储存所有零件的总价值
for (size_t i = 1; i <= totals; i++)
{
sum += costs[i].Total_price;
}
fprintf(stdout,"所有零件总价值为: %.2lf",sum);
}
cost * file_read(FILE *fp)
{
char buffer[251]; //缓冲区
if (fgets(buffer, 251, fp) == NULL) // 文件为空
{
cost *costs = (cost *)malloc(sizeof(cost)); // 分配一些内存来存储一条记录
if (costs == NULL)
{
fprintf(stderr, "无法分配内存");
exit(EXIT_FAILURE);
}
costs[0].id = 0;
memset(costs[0].description, 0, sizeof(costs[0].description)); // 初始化字符串为零
costs[0].quantity = 0;
costs[0].Total_price = 0;
return costs;
}
//用读模式打开时,不知道为什么文件会偏移,于是读不到第一个id,所以重绕指针
rewind(fp);
char *par;
cost * costs = (cost *)malloc(sizeof(cost));
if(costs == NULL)
{
fprintf(stderr,"内存不足!");
exit(EXIT_FAILURE);
}
size_t j = 0;
while (fgets(buffer,251,fp) != NULL)
{
//我在写代码时发现如果中间的间隔符使用宽字符,会导致读取后更改文件格式,然后数据出现错误
//所以在面对符号间隔等内容,最好不好中英文混用,全半角符号混用
par = strtok(buffer,":");
if(strcmp(par,"id") == 0)
{
par = strtok(NULL,"\n");
int id;
sscanf(par,"%d",&id);
costs[j].id = id;
}
else if (strcmp(par,"description") == 0)
{
par = strtok(NULL,"\n");
strcpy(costs[j].description,par);
}
else if (strcmp(par,"quantity") == 0)
{
par = strtok(NULL,"\n");
int quantity;
sscanf(par,"%d",&quantity);
costs[j].quantity = quantity;
}
else if (strcmp(par,"Total_price") == 0)
{
par = strtok(NULL,"\n");
double Total;//总价值
sscanf(par,"%lf",&Total);
costs[j].Total_price = Total;
j++;
costs = (cost *)realloc(costs,(j + 1) * sizeof(cost));
if(costs == NULL)
{
fprintf(stderr,"内存不足!");
free(costs);
exit(EXIT_FAILURE);
}
}
}
totals = j - 1;
//因为上面的逻辑是提前申请内存,然后变量j位移指向下一个,最后一次执行就会剩下一个多余的,所以下面去掉多余的。
costs = (cost *)realloc(costs,(totals + 1) * sizeof(cost));
return costs;
}
void file_write(FILE *fp,cost * costs)
{
fseek(fp,0,SEEK_SET);
fprintf(fp,"id:%d\n",costs[0].id);
fprintf(fp,"description: \n");
fprintf(fp,"quantity:0\n");
fprintf(fp,"Total_price:0\n\n");
for (int i = 1; i <= totals; i++)
{
fprintf(fp,"id:%d\n",costs[i].id);
if(costs[i].description[0] == 0)
{
fprintf(fp,"description: \n");
}
else{
fprintf(fp,"description:%s\n",costs[i].description);
}
fprintf(fp,"quantity:%d\n",costs[i].quantity);
fprintf(fp,"Total_price:%.2lf\n\n",costs[i].Total_price);
}
free(costs);
fclose(fp);
exit(EXIT_SUCCESS);
}