C和指针 第十五章第十一题

这题略微复杂,不过这是我第一次,先写完所有代码,再进行测试,也算从小白往外迈了一步。

下面这个是我预计需要使用的函数原型,一开始所有原型都有一个文件指针,我想每个函数都来操作文件,后来发现太麻烦了,于是增加了一个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);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值