程序设计基础

程序设计基础RLE 压缩与解压缩算法 BMP 位图的缩放等 西电程序设计基础课程 问题求解程序设计基础

程序设计基础课程设计报告

题目一:高精度计算

高精度计算

​ 涉及知识点:数组、流程控制、函数等

​ 要求:用整型数组表示10进制大整数(超过2^32的整数),数组的每个素存储大整数的一位数字,实现大整数的加减法。


1.1设计思路

​ 首先,使用字符数组a,b接收两个大整数,然后,从个位开始,对每一位进行加减法计算,加法时满足满十进一,减法时满足小零借一。具体的设计思路如下:

1.1.1加法运算

请添加图片描述

特点:满十进一

​ 每次满十后数据会储存在变量carry中,其中carry = sum/10,而结果res[i] = sum%10


1.1.2减法运算

首先,需要比较输入数据中a与b的大小关系,若a<b,调换a与b的位置,便于计算,这样,这种情况可以转化为a>b的情况,只需要最后输出时在最前面添加上一个“-”负号即可。

如图所示:
请添加图片描述
请添加图片描述

特点:小于零则借一

​ 从个位开始,若a[i] - b[i] < 0,则向下一位借一,此位可以加上10,下一位则需要在此基础上再减去1即可。

注意:

​ 做减法运算时,若最后几位(即最高的几位)中,出现了0,则需要去掉,这些都是减完后高位变成的零,输出时不 需要将其输出,所以应该删去。

例如:

 -  首先,需要将两个数逆序,然后从个位开始相减计算。 5 2 9 5 8 2 4 3 2 3 5 4 2 4 - ------------------- 2 0 6 0 4 0 0 ----> reverse后,有40602,最后面的两个零应该删去 

1.2相关代码

#include<stdio.h> #include<string.h> #define MAX_LEN 1000 void reverse(char* str) //将一个字符串从前到后颠倒顺序 { 
    int len = strlen(str); int i = 0; for (i=0; i<len/2; i++) { 
    char temp = str[i]; str[i] = str[len-1-i]; str[len-1-i] = temp; } } void add(char* a, char* b, char* res) //实现加法运算 { 
    int carry = 0; //记录下需要进的位数,遵循满十进一,满二十进二...... int i = 0; int j = 0; int k = 0; int len_a = strlen(a); int len_b = strlen(b); while (i < len_a || j < len_b) { 
    int x = (i<len_a) ? (a[i] - '0') : 0; int y = (j<len_b) ? (b[i] - '0') : 0; int sum = x + y + carry; //在上一位进的位数要加到更高的这一位上 res[k++] = (sum % 10) + '0'; //没有满十的部分即为这一位应该填入的数值 carry = sum / 10; i++; j++; } if (carry) //判断最后一位是否也进位了,如果是,则位数需要加一,即为carry的值 { 
    res[k++] = carry + '0'; } res[k] = '\0'; reverse(res); } void subtract(char* a, char* b, char* res) //实现减法运算 { 
    int borrow = 0; //若减完后数值小于零,则记录borrow = 1,即向前借一位 int i = 0; int j = 0; int k = 0; int len_a = strlen(a); int len_b = strlen(b); char c[MAX_LEN] = { 
   '\0'}; //用于交换a,b两个数 // 判断a,b的大小关系 reverse(a); reverse(b); int flag = 0; if (len_a < len_b) { 
    flag = -1; } else if (len_a > len_b) { 
    flag = 1; } else { 
    flag = strcmp(a,b); } if (flag < 0) //满足a < b时,交换a,b的顺序,便于后面计算 { 
    strcpy(c,a); strcpy(a,b); strcpy(b,c); int tmp = len_a; len_a = len_b; len_b = tmp; } reverse(a); reverse(b); while (i < len_a || j < len_b) { 
    int x = (i<len_a) ? (a[i] - '0') : 0; int y = (j<len_b) ? (b[i] - '0') : 0; int diff = x - y - borrow; //需要减掉借的位数 //判断每一位减完后是否小于零,若小于零,则需要向更高一位借一,此为可以加上10 if (diff < 0) { 
    diff += 10; borrow = 1; } else //若不是小于零,则无需借位,改变borrow值为0 { 
    borrow = 0; } res[k++] = diff + '0'; i++; j++; } while (k > 1 && res[k - 1] == '0') { 
    k--; //减去高位上多余的零 } if (flag < 0) //当a < b时,需要添加上负号'-' { 
    res[k++] = '-'; } res[k] = '\0'; reverse(res); } int main() { 
    char sa[MAX_LEN] = { 
   '\0'}; char sb[MAX_LEN] = { 
   '\0'}; char res[MAX_LEN] = { 
   '\0'}; printf("Enter the first number: a = "); scanf("%s",sa); printf("Enter the second number: b = "); scanf("%s",sb); reverse(sa); reverse(sb); add(sa,sb,res); printf("a + b = %s\n",res); //实现加法功能 subtract(sa,sb,res); printf("a - b = %s\n",res); //实现减法功能更 return 0; } 

1.3运行截图

请添加图片描述

题目二:简单数据结构-堆栈模拟

简单数据结构-堆栈模拟

​ 涉及知识点:内存管理、结构体定义、基本数据结构

要求:

​ 编写一个程序模拟堆栈,要求能够模拟入栈、出栈、返回栈顶素等基本操作。栈中素可用整数代替。不能使用C++模板库预定义的类型。程序运行中可输入多组入栈、出栈操作,每次操作后展示栈中素。


2.1设计思路

2.1.1栈的概念及结构

1.栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除素操作。进行数据插入和删除
操作的一端称为栈顶,另一端称为栈底。栈中的数据素遵守后进先出LIFO(Last In First Out)
的原则。

2.压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

3.出栈:栈的删除操作叫做出栈。出数据也在栈顶。
请添加图片描述
请添加图片描述

2.1.2栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
请添加图片描述

2.2相关代码

#include <stdio.h> #include <stdbool.h> #include <assert.h> #include <stdlib.h> typedef int STDataType; typedef struct Stack { 
    STDataType* a; // int top; //栈顶 int capacity; //容量 }ST; void StackInit(ST* ps); void StackDestory(ST* ps); // 入栈 void StackPush(ST* ps, STDataType x); // 出栈 void StackPop(ST* ps); STDataType StackTop(ST* ps); int StackSize(ST* ps); bool StackEmpty(ST* ps); void StackInit(ST* ps) { 
    assert(ps); ps->a = (STDataType*)malloc(sizeof(STDataType) * 4); if (ps->a == NULL) { 
    printf("malloc fail\n"); exit(-1); } ps->capacity = 4; ps->top = 0; } void StackDestory(ST* ps) { 
    assert(ps); free(ps->a); //释放栈中所有素 ps->a = NULL; ps->top = ps->capacity = 0; } // 入栈 void StackPush(ST* ps, STDataType x) { 
    assert(ps); // 满了-->增容 if (ps->top == ps->capacity) { 
    STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType)); //重新分配内存空间,同时实现栈的扩容 if (tmp == NULL) { 
    printf("realloc fail\n"); exit(-1); } else { 
    ps->a = tmp; ps->capacity *= 2; } } ps->a[ps->top] = x; ps->top++; } // 出栈 void StackPop(ST* ps) { 
    assert(ps); // 栈空了,调用Pop,直接中止程序报错 assert(ps->top > 0); //ps->a[ps->top - 1] = 0; ps->top--; } //取栈顶素 STDataType StackTop(ST* ps) { 
    assert(ps); // 栈空了,调用Top,直接中止程序报错 assert(ps->top > 0); return ps->a[ps->top - 1]; } //输出栈的大小 int StackSize(ST* ps) { 
    assert(ps); return ps->top; } //判断栈是否为空 bool StackEmpty(ST* ps) { 
    assert(ps); return ps->top == 0; } int main() { 
    ST st; StackInit(&st); int flag = 1; STDataType num; STDataType top; int size; bool judge; int i = 0; char c = '\0'; printf("------欢迎使用堆栈模型------\n"); printf("0.Exit\n1.StackPush\n2.StackPop\n3.StackTop\n4.StackSize\n5.StackEmpty\n"); while (flag) { 
    printf("请输入您要进行的操作: "); scanf("%d",&flag); switch (flag) { 
    case 0: StackDestory(&st); break; case 1: printf("请输入您要进栈的素:"); for (i=0; c!='\n'; i++) { 
    scanf("%d",&num); StackPush(&st,num); c = getchar(); } c = '\0'; break; case 2: StackPop(&st); break; case 3: top = StackTop(&st); printf("栈顶素为:"); printf("%d\n",top); break; case 4: size = StackSize(&st); printf("栈的大小为:"); printf("%d\n",size); break; case 5: judge = StackEmpty(&st); if (judge) { 
    printf("栈为空\n"); } else { 
    printf("栈非空\n"); } break; default: break; } //每次操作完成后会自动返回此时栈中的所有素 if (flag != 0) { 
    if (st.top == 0) { 
    printf("栈中无素"); } else { 
    printf("现在栈中的素有:"); for (int i = 0; i<st.top; i++) { 
    printf("%d ",(st.a)[i]); } } printf("\n"); } } return 0; } 

2.3运行截图

请添加图片描述

题目三:位图图像文件压缩

位图图像文件压缩

涉及知识点:

​ 文件读写、结构体定义、内存管理、基本图像处理算法、命令行参数

要求:

​ 编写一个程序,可以在命令行输入参数,完成指定文件的缩放,并存储到新文件,命令行参数如下:

​ zoom file1.bmp 200 file2.bmp

​ 第一个参数为可执行程序名称,第二个参数为原始图像文件名,第三个参数为缩放比例(百分比),第四个参数为新文件名


3.1位图图像文件介绍

​ 位图(Bitmap),它是Windows操作系统中的标准图像文件格式,按照像素深度可以分类为:单色位图、16色位图、256色位图、16位位图、24位位图、32位位图等。

  • 单色位图:即非黑即白,1bit就可以表示一个像素对应的颜色值。
  • 16色位图:整张图像中只包含16种颜色,而2^4=16,所以一个像素表示需要4bit,即1/2个字节(byte)。
  • 256色位图:整张图像中包含256种颜色,而2^8=256,所以一个像素表示需要8bit,即1个字节(byte)。、
  • 24位位图:顾名思义,每个像素点储存颜色信息占用24bit,即3字节(byte),对应着G,B,R值(在位图中不为R,G,B)需要注意。

​ 对于以上前三种位图格式,在文件中都是包含位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、调色板(color table)、位图图像数据(data bits)阵列。

​ 值得注意的是,图像中包含的所有颜色种类对应的信息都储存在调色板中,里面有图像中所有的颜色对应的信息(R,G,B,Reserve),而位图图像数据中只是储存着每个像素点对应着的颜色在调色板中颜色列表的索引(序号)。

​ 对于24位位图,不包含调色板信息,因为它所有的颜色信息都可以直接在位图图像数据中体现。

​ 综合来说,位图的文件结构大同小异,只是一个使用调色板来存储颜色信息,其后为每个像素依次的颜色索引值,而24位图则采用直接储存每个像素的颜色信息的方法,因此无需调色板,这样的图像可以有更多的颜色选择。

​ 在后面的程序设计中,我主要实现256色位图和24位位图两种格式图像的缩放

3.2位图文件结构体介绍

3.2.1整体结构

BMP格式的文件从头到尾依次是如下信息:

​ · bmp文件头(bmp file header):共14字节;

​ · 位图信息头(bitmap information):共40字节;

​ · 调色板(color palette):可选;

​ · 位图点阵数据(bits data);

24与32位真彩色位图没有颜色表,所以只有1、2、4这三部分。

3.2.2位图文件头

​ 位图文件头共占用14个字节,定义如下:

typedef struct tagBITMAPFILEHEADER { 
    WORD bfType; //文件标识,规定为0x4D42,字符显示就是'BM' DWORD bfSize; //文件大小 WORD bfReserved1; //保留,必须设置为0 WORD bfReserved2; //保留,必须设置为0 DWORD bfOffBits; //从头到点阵数据的偏移 } BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER; 
3.2.3位图信息头

​ 位图信息头共占用40个字节,定义如下:

typedef struct tagBITMAPINFOHEADER{ 
    DWORD biSize; //位图信息头的大小,一般为40 LONG biWidth; //位图的宽度,单位:像素 LONG biHeight; //位图的高度,单位:像素 WORD biPlanes; //颜色平面数,一般为1 WORD biBitCount; //每个像素占用比特数(bit) DWORD biCompression; //压缩类型,BI_RGB(0)为不压缩 DWORD biSizeImage; //位图数据的大小,当用BI_RGB格式时,可以设置为0 LONG biXPelsPerMeter; //水平分辨率,单位:像素/米 LONG biYPelsPerMeter; //垂直分辨率,单位:像素/米 DWORD biClrUsed; //调色板的颜色数,为0则颜色数为2的biBitCount次方 DWORD biClrImportant; //重要的颜色数,0代表所有颜色都重要 } BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER; 
3.2.4调色板

​ 16、24与32位位图没有调色板。调色板就是一个颜色的索引,为结构体RGBQUAD的数组,数组长度为biClrUsed,如果biClrUsed=0则数组长度为2的biBitCount次方。RGBQUAD结构体定义如下:

typedef struct tagRGBQUAD { 
    BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD; 
3.2.5位图数据

​ ·如果biHeight>0,代表位图倒立,图像数据是从左下角到右上角排列的;相反,如果biHeight<0,代表位图正向,图像数据是从左上角到右下角排列的。

​ ·如果是24位色图,按照BGR的顺序排列,32位色图按照BGRAlpha排列。

​ ·位图数据排列还有一个规则,就是对齐。

​ ·Windows默认的扫描的最小单位是4字节,如果数据对齐满足这个值的话对于数据的获取速度等都是有很大的增益的。因此,BMP图像顺应了这个要求,要求每行的数据的长度必须是4的倍数,如果不够需要进行比特填充(以0填充),这样可以达到按行的快速存取。这样的话,位图数据的大小就不一定是宽x高x每像素字节数了,因为每行还可能有0填充。

​ ·填充后的每行数据如下:
请添加图片描述

即:

​ 每行的字节数等于:每像素的比特数(bit),即为biBitCount,乘以图片的宽度(biWidth),再加上31的和除以32,并向下取整,最后乘以4。

​ 得到了每行的字节数,进而就能够得到原始位图数据,或者说存储图片的所有像素的色彩信息的数据的大小了:
请添加图片描述

即:

​ 原始位图数据大小等于:每行的字节数乘以图像高度(即biHeight)

3.2.6文件的组成结构
  • 256色位图:
    File Size = Bitmap File Header Size + DIB Header SiZe + Color Palette Size + Pixel Array Size 
  • 24位位图:
    File Size = Bitmap File Header Size + DIB Header SiZe + Pixel Array Size 

3.3图像处理算法——双线性插值

​ 想要实现图像的缩放,需要使用图像处理算法,这里我主要介绍双线性插值,在文中后面的相应代码中也使用了双线性插值算法来处理图像数据。

首先,利用相似三角形的性质,可以得到以下结论:
请添加图片描述

于是,我们可以得到单线性插值的基本公式:
在这里插入图片描述

值得注意的是,这里的y值不应该只是被看作是因变量,而是在各自的条件x下的对应目标值,y可以有实际意义。

最后,我们便可以得到双线性插值的推理过程和最终的公式:
在这里插入图片描述
在这里插入图片描述

至于在图像中双线性插值的实际应用方法如下:
在这里插入图片描述

至此,我们已经完成了程序设计的前置知识的概览。

3.4相关代码

3.3.1BmpFormat.h
# ifndef BMP_H # define BMP_H #pragma pack(1)  ''' 至关重要,使得内存对齐,否则打印sizeof(BITMAPFILEHEADER),会显示为16字节,这样导致后面所有的内容都会错位,图像无法正常显示出来 ''' typedef struct tagBITMAPFILEHEADER { 
    unsigned short bfType; // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件 unsigned int bfSize; // 文件大小 以字节为单位(2-5字节) unsigned short bfReserved1; // 保留,必须设置为0 (6-7字节) unsigned short bfReserved2; // 保留,必须设置为0 (8-9字节) unsigned int bfOffBits; // 从文件头到像素数据的偏移 (10-13字节) } BITMAPFILEHEADER; //图像信息头结构体 typedef struct tagBITMAPINFOHEADER { 
    unsigned int biSize; // 此结构体的大小 (14-17字节) long biWidth; // 图像的宽 (18-21字节) long biHeight; // 图像的高 (22-25字节) unsigned short biPlanes; // 表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1 (26-27字节) unsigned short biBitCount; // 一像素所占的位数,一般为24 (28-29字节) unsigned int biCompression; // 说明图象数据压缩的类型,0为不压缩。 (30-33字节) unsigned int biSizeImage; // 像素数据所占大小, 这个值应该等于上面文件头结构中bfSize-bfOffBits (34-37字节) long biXPelsPerMeter; // 说明水平分辨率,用象素/米表示。一般为0 (38-41字节) long biYPelsPerMeter; // 说明垂直分辨率,用象素/米表示。一般为0 (42-45字节) unsigned int biClrUsed; // 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。 (46-49字节) unsigned int biClrImportant; // 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。(50-53字节) } BITMAPINFOHEADER; //256色位图需要使用调色板,里面会储存所有的颜色信息。 typedef struct tagRGBQUAND { 
    unsigned char rgbBlue; //该颜色的蓝色分量 (值范围为0-255) unsigned char rgbGreen; //该颜色的绿色分量 (值范围为0-255) unsigned char rgbRed; //该颜色的红色分量 (值范围为0-255) unsigned char rgbReserved;// 保留,必须为0 } RGBQUAD; #endif 
3.3.2Zoom.c
#include <stdio.h> #include <malloc.h> #include <errno.h> #include <string.h> #include <math.h> #include <stdlib.h> #include "BmpFormat.h" BITMAPFILEHEADER fileHeader; // 原图文件头 BITMAPINFOHEADER infoHeader; // 原图消息头 RGBQUAD *palette; //调色板 //24位位图,每个像素占3个字节,直接储存,无需使用调色板 int Zoom( FILE *fp, FILE *res, double pzoom) { 
    int byte = infoHeader.biBitCount / 8; //每个像素占用的字节数 unsigned int oldWidth = infoHeader.biWidth; unsigned int oldHeight = infoHeader.biHeight; // long ILineByte = (oldWidth*byte + 3) / 4 * 4; long ILineByte = (oldWidth*infoHeader.biBitCount + 31) / 32 * 4; unsigned char *pIBuf = (unsigned char*)malloc(sizeof(unsigned char)*ILineByte*oldHeight); fseek(fp, 54, SEEK_SET); fread(pIBuf, ILineByte*oldHeight, 1, fp); unsigned int newWidth = (int)(oldWidth * pzoom + 0.5); unsigned int newHeight = (int)(oldHeight * pzoom + 0.5); // long INewLineByte = (newWidth*byte + 3) / 4 * 4; long INewLineByte = (newWidth*infoHeader.biBitCount + 31) / 32 * 4; unsigned char *pINewBuf = (unsigned char*)malloc(sizeof(unsigned char)*INewLineByte*newHeight); unsigned int i,j,k; unsigned int x1, y1, x2, y2; // 原图所在像素点的宽高 unsigned int X, Y; for (i=0; i<newHeight; i++) { 
    for (j=0; j<newWidth; j++) { 
    X = i / pzoom; x1 = (int)X; x2 = x1 + 1; Y = j / pzoom; y1 = (int)Y; y2 = y1 + 1; for (k=0; k<3; k++) { 
    if ((X<oldHeight-1)&&(Y<oldWidth-1)) { 
    *(pINewBuf + i*INewLineByte + j*3 + k) = *(pIBuf + x1*ILineByte + y1*3 + k)*(x2-X)*(y2-Y)+ *(pIBuf + x2*ILineByte + y1*3 + k)*(X-x1)*(y2-Y)+ *(pIBuf + x1*ILineByte + y2*3 + k)*(x2-X)*(Y-y1)+ *(pIBuf + x2*ILineByte + y2*3 + k)*(X-x1)*(Y-y1); } else { 
    *(pINewBuf + i*INewLineByte + j*3 + k) = 255; } } } } infoHeader.biWidth = newWidth; infoHeader.biHeight = newHeight; infoHeader.biSizeImage = INewLineByte*newHeight; fileHeader.bfSize = 54 + infoHeader.biSizeImage; fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, res); fwrite(&infoHeader, sizeof(BITMAPINFOHEADER), 1, res); fwrite(pINewBuf, infoHeader.biSizeImage, 1, res); free(pIBuf); free(pINewBuf); return 0; } //256色位图,需要使用调色板,调色板中存有最多2^8=256个颜色种类,而最后使用双线性插值算法其实是对颜色索引进行插值 int Zoom2( FILE *fp, FILE *res, double pzoom) { 
    // fread(&fileHeader, sizeof(BITMAPFILEHEADER), 1, fp); // fread(&infoHeader, sizeof(BITMAPINFOHEADER), 1, fp); unsigned int oldWidth = infoHeader.biWidth; unsigned int oldHeight = infoHeader.biHeight; long ILineByte = (oldWidth + 3) / 4 * 4; unsigned char *pIBuf = (unsigned char*)malloc(sizeof(unsigned char)*ILineByte*oldHeight); //加入另一种情况,256色位图,需要使用到调色板 int colorNum = (int)pow(2, infoHeader.biBitCount); RGBQUAD *palette = (RGBQUAD*)malloc(colorNum * sizeof(RGBQUAD)); //分配调色板空间 // fseek(fp, 54, SEEK_SET); fread(palette, sizeof(RGBQUAD), colorNum, fp); fseek(fp, 54 + colorNum*4, SEEK_SET); fread(pIBuf, ILineByte*oldHeight, 1, fp); unsigned int newWidth = (int)(oldWidth * pzoom + 0.5); unsigned int newHeight = (int)(oldHeight * pzoom + 0.5); long INewLineByte = (newWidth + 3) / 4 * 4; unsigned char *pINewBuf = (unsigned char*)malloc(sizeof(unsigned char)*INewLineByte*newHeight); unsigned int i,j; unsigned int x1, y1, x2, y2; // 原图所在像素点的宽高 unsigned int X, Y; for (i=0; i<newHeight; i++) { 
    for (j=0; j<newWidth; j++) { 
    X = i / pzoom; x1 = (int)X; x2 = x1 + 1; Y = j / pzoom; y1 = (int)Y; y2 = y1 + 1; //这里双线性插值数据其实是对应调色板的索引 if ((X<oldHeight-1)&&(Y<oldWidth-1)) { 
    *(pINewBuf + i*INewLineByte + j) = *(pIBuf + x1*ILineByte + y1)*(x2-X)*(y2-Y)+ *(pIBuf + x2*ILineByte + y1)*(X-x1)*(y2-Y)+ *(pIBuf + x1*ILineByte + y2)*(x2-X)*(Y-y1)+ *(pIBuf + x2*ILineByte + y2)*(X-x1)*(Y-y1); } else { 
    *(pINewBuf + i*INewLineByte + j) = 0; } } } infoHeader.biWidth = newWidth; infoHeader.biHeight = newHeight; infoHeader.biSizeImage = INewLineByte*newHeight; fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorNum*sizeof(RGBQUAD) + infoHeader.biSizeImage; fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, res); fwrite(&infoHeader, sizeof(BITMAPINFOHEADER), 1, res); fwrite(palette, sizeof(RGBQUAD), colorNum, res); fwrite(pINewBuf, infoHeader.biSizeImage, 1, res); free(pIBuf); free(pINewBuf); return 0; } int judge( FILE *fp, FILE *res, double pzoom) { 
    fread(&fileHeader, sizeof(BITMAPFILEHEADER), 1, fp); fread(&infoHeader, sizeof(BITMAPINFOHEADER), 1, fp); if (infoHeader.biBitCount == 24) { 
    Zoom(fp, res, pzoom); } else if (infoHeader.biBitCount == 8) { 
    Zoom2(fp, res, pzoom); } return 0; } int main(int argc, char* argv[]) { 
    double pzoom = (double)atoi(argv[2]) / 100.0; if (argc != 4) { 
    printf("input error!"); return 0; } FILE *fp = fopen(argv[1], "rb"); FILE *res = fopen(argv[3], "wb"); if (fp == NULL) { 
    printf("%s",strerror(errno)); fclose(fp); return 0; } judge(fp, res, pzoom); printf("success!"); fclose(fp); fclose(res); return 0; } 

3.5运行截图

24位位图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

256色位图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题目四:RLE压缩解压算法

RLE压缩解压算法

​ 涉及知识点:文件读写、位操作、内存管理、结构体定义、RLE算法、命令行参数

要求:

​ 编写一个程序,可以在命令行输入参数,完成指定文件的压缩解压

命令行参数如下

​ rle file1 –c(-d) file2

​ 第一个参数为可执行程序名称,第二个参数为原始文件名,第三个参数为压缩或解压缩选项,第四个参数为新文件名


4.1题目分析

​ 首先,分析题目可知用 RLE 算法结合位运算来实现解、压缩算法,文件的本质都是二进制文件,所以这道题需要通过位操作和 RLE 算法直接压缩二进制代码。

4.2RLE算法原理分析

​ RLE 算法的本质在于区分连续字节块和非连续字节块,用单独的字节来存储连续字节块和非连续字节块的长度。

​ RLE 压缩算法(简称 RLE 算法)的基本思路是把数据按照线性序列分成两种情况:一种是连续的重复数据块,另一种是连续的不重复数据块。

​ RLE 算法的原理就是用一个表示块数的属性加上一个数据块代表原来连续的若干块数据,从而达到节省存储空间的目的。一般 RLE 算法都选择数据块的长度为 1 字节,表示块数的属性也用 1 字节表示,对于颜色数小于 256 色的图像文件或文本文件,块长度选择 1 字节是比较合适的。

4.2.1连续重复数据的处理:

​ RLE 算法有很多优化和改进的变种算法,这些算法对连续重复数据的处理方式基本上都是一样的。对于连续重复出现的数据,RLE 算法一般用两字节表示原来连续的多字节重复数据。我们用一个例子更直观地说明 RLE 算法对这种情况的处理,假如原始数据有 5 字节的连续数据:

​ [data] [data] [data] [data] [data]

​ 则压缩后的数据就包含块数和 [data] 两字节,其中 [data] 只存储了一次,节省了存储空间:

​ [5] [data]

​ 需要注意的是,一般 RLE 算法都采用插入一个长度属性字节存储连续数据的重复次数,因此能够表达的极大值就是 255 字节,如果连续的相同数据超过 255 字节时,就从第 255 字节处断开,将第 256 字节以及 256 字节后面的数据当成新的数据处理。

​ 我采用的算法,长度属性字节的最高位被用来做一个标志位,只有 7 位用来表示长度,所以最大计数为127。无论后面是否是相同的数据,都需要重新来进行计数。

4.2.2连续非重复数据的处理

​ 对于连续的非重复数据,RLE 算法的处理方法一般是不对数据进行任何处理,直接将原始数据作为压缩后的数据存储。

​ 假如有以下 5 字节的连续非重复数据:

​ [datal] [data2] [data3] [data4] [data5]

​ 按照这种处理方法,最后的数据和原始数据一样:

​ [data1] [data2] [data3] [data4] [data5]

​ 现在有一个问题。在 RLE 算法解码的时候,如何区分连续重复和非重复数据?解决方法是把连续非重复数据也当成一组数据整体考虑。首先给连续重复数据和连续非重复数据都附加一个表示长度的属性字节,并利用这个长度属性字节的最高位来区分两种情况。

​ 长度属性字节的最高位如果是 1,则表示后面紧跟的是个重复数据,需要重复的次数由长度属性字节的低 7 位(最大值是 127)表示。长度属性字节的最高位如果是 0,则表示后面紧跟的是非重复数据,长度也由长度属性字节的低 7 位表示。

采用这种算法,压缩后的数据非常有规律,两种类型的数据都从长度属性字节开始,除了标志位的不同,后跟的数据也不同。第一种情况后跟一个字节的重复数据,第二种情况后跟的是若干个字节的连续非重复数据。

4.3算法实现

​ 釆用前面给出的优化方式,编码算法不仅要能够识别连续重复数据和连续非重复数据两种情况,还要能够统计出两种情况下数据块的长度。

​ 编码算法从数据的起始位置开始向后搜索,如果发现后面是重复数据且重复次数超过 2,则设置连续重复数据的标志并继续向后查找,直到找到第一个与之不相同的数据为止,将这个位置记为下次搜索的起始位置,根据位置差计算重复次数,最后长度属性字节以及一个字节的原始重复数据一起写入压缩数据;如果后面数据不是连续重复数据,则继续向后搜索查找连续重复数据,直到发现连续重复的数据且重复次数大于 2 为止,然后设置不重复数据标志,将新位置记为下次搜索的起始位置,最后将长度属性字节写入压缩数据并将原始数据逐字节复制到压缩数据。然后从上一步标记的新的搜索起始位开始,一直重复上面的过程,直到原始数据结束。

​ Rle_Encode() 函数是 RLE 算法的实现。
​ IsRepetitionStart() 函数判断从 src 开始的数据是否是连续重复数据。根据算法要求,只有数据重复出现两次以上才算作连续重复数据,因此IsRepetitionStart() 函数检査连续的 3 字节是否是相同的数据,如果是则判定为出现连续重复数据。之所以要求至少要 3 字节的重复数据才判定为连续重复数据,是为了尽量优化对短重复数据间隔出现时的压缩效率。
​ 如果是连续重复数据,则调用 GetRepetitionCount() 函数计算出连续重复数据的长度,将长度属性字节的最高位置 1 并向输出缓冲区写入一个字节的重复数据,具体做法是将 GetRepetitionCount() 函数的返回值 count|0x80 得到最高位置 1 的长度属性字节。
​ 如果不是连续重复数据,则调用 GetNonRepetitionCount() 函数计算连续非重复数据的长度,将长度属性字节的最高位置 0 并向输出缓冲区复制连续的多个非重复数据。

​ Rle_Decode() 函数是解压缩算法的实现代码,每组数据的第一字节是长度标识字节,其最高位是标识位,低 7 位是数据长度属性,根据标识位分别进行处理即可。

​ 因为两种情况下的压缩数据首部都是 1 字节的长度属性标识,只要根据这个标识判断如何处理就可以了。首先从压缩数据中取出 1 字节的长度属性标识,然后判断是连续重复数据的标识还是连续非重复数据的标识,具体做法是通过位操作,也就是标识字节&0x80 是否等于 0x80 来判断标识字节最高位是否是 1):

​ 如果是连续重复数据,则将标识字节后面的数据重复复制 n 份写入输出缓冲区;如果是连续非重复数据,则将标识字节后面的 n 个数据复制到输出缓冲区。n 的值是标识字节与 0x7F 做与操作后得到,因为标识字节低 7 位就是数据长度属性。

4.4相关代码

#include<stdio.h> #include<stdlib.h> #include<string.h> //判断是否为有重复数超过3的重复数据 int IsrepetitionStart(unsigned char *src,int srcLeft) { 
    if(srcLeft<3){ 
    //剩余数据数不足3返回0  return 0; } if((src[0]==src[1])&&(src[1]==src[2])){ 
    return 1; } return 0; } //获得重复数据个数  int GetRepetitionCount(unsigned char *src,int srcLeft) { 
    int repeatedbuf=src[0]; //repeatedbuf表示重复的值  int length=1; // 长度  while(length<srcLeft&&length<0x7f&&src[length]==repeatedbuf) { 
    //长度标识占一字节,高位表示 重复与否,因此length最大为127  length++; } return length; //返回的length<=127,important  } //获得不重复数据个数  int GetNonRepetitionCount(unsigned char *src,int srcLeft) { 
    if(srcLeft<3){ 
    return srcLeft; } int length=2; int a=src[0],b=src[1]; while(length<srcLeft&&length<0x7f&&((a!=b)||(b!=src[length]))) { 
    //三个连续数不全相等  a=b; b=src[length]; length++; } return length; } //压缩算法,返回压缩后数据大小 //传入:输入数据缓冲区首地址,输入数据大小, 输出缓冲区首地址, 输出数据大小 int Rle_Encode(unsigned char *inbuf,int inSize,unsigned char *outbuf,int onuBufSize) { 
    unsigned char *src=inbuf; //定义指针遍历输入数据  int i; int encSize=0; //输出缓冲区大小  int srcLeft=inSize; while(srcLeft>0) { 
    int count=0; if(IsrepetitionStart(src,srcLeft)) //有重复  { 
    if((encSize+2)>onuBufSize) { 
    //输出缓冲区空间不够了 return -1; } count=GetRepetitionCount(src,srcLeft); outbuf[encSize++]=count|0x80; // //按位或运算,保证高位为1,传入输出缓冲区,(即为长度标识) outbuf[encSize++]=*src; //即为数据标识  src+=count; //设置新的搜索位置  srcLeft-=count; //更新剩余数据数 } else //无重复  { 
    count=GetNonRepetitionCount(src,srcLeft); if((encSize+count+1)>onuBufSize) { 
    return -1; } outbuf[encSize++]=count; for(i=0;i<count;i++) { 
    //逐个复制这些数据  outbuf[encSize++]=*src++; } srcLeft-=count; } } return encSize; } //解压算法  int Rle_Decode(unsigned char *inbuf,int inSize,unsigned char *outbuf,int onuBufSize) { 
    unsigned char *src=inbuf; int i; int decSize=0; //输出缓冲区大小  int count=0; while(src<(inbuf+inSize)) { 
    unsigned char sign=*src++; //定义指针遍历输入数据  count=sign & 0x7F; //获取长度标识,按位与运算,保留常量(转换为二进制形式)的后7位数 if((decSize+count)>onuBufSize) // //输出缓冲区空间不够了 { 
    return -1; } if((sign&0x80)==0x80) // { 
    //连续重复数据标志  for(i=0;i<count;i++) { 
    outbuf[decSize++]=*src; } src++; } else { 
    for(i=0;i<count;i++) { 
    outbuf[decSize++]=*src++; } } } return decSize; } //文件压缩  int Compression(char*Inputfilename,char*Outputfilename) { 
    FILE *Input=fopen(Inputfilename, "rb"); //源文件  FILE *Output=fopen(Outputfilename, "wb"); //目标文件  if (Input==NULL||Output==NULL) { 
    printf("We can't open the file successfully!"); } unsigned char*inbuf; //输入缓存区  unsigned char*outbuf; //输出缓存区  inbuf =(unsigned char*)malloc((sizeof(unsigned char))*1024*1024*1024); outbuf=(unsigned char*)malloc((sizeof(unsigned char))*1024*1024*1024); int length; while ((length=fread(inbuf, sizeof(unsigned char),1024,Input))!= 0) { 
    //length表示读入的数据块数目 int tmp=Rle_Encode(inbuf,length,outbuf,1024*1024*1024); if(tmp==-1) { 
    return -2; } fwrite(outbuf, sizeof(unsigned char),tmp,Output); //输出缓冲区数据写入目标文件  } fclose(Input); fclose(Output); return 0; } int Decompression(char*Inputfilename,char*Outputfilename) { 
    //文件解压  FILE *Input=fopen(Inputfilename, "rb"); FILE *Output=fopen(Outputfilename, "wb"); if (Input==NULL||Output==NULL) { 
    printf("We can't open the file successfully!"); } unsigned char*inbuf; //输入缓存区  unsigned char*outbuf; //输出缓存区  inbuf=(unsigned char*)malloc((sizeof(unsigned char))*1024*1024*1024); outbuf=(unsigned char*)malloc((sizeof(unsigned char))*1024*1024*1024); int length; while((length=fread(inbuf, sizeof(unsigned char),1024*1024*1024,Input))!=0) { 
    int tmp=Rle_Decode(inbuf,length,outbuf,1024*1024*1024); if(tmp==-1) { 
    return -2; } fwrite(outbuf, sizeof(unsigned char),tmp,Output); } fclose(Input); fclose(Output); return 0; } int main(int argc,char**argv) { 
    if(strcmp(argv[2],"-c")==0) { 
    Compression(argv[1],argv[3]); //压缩 } else if(strcmp(argv[2],"-d")==0) { 
    Decompression(argv[1],argv[3]); //解压缩 } return 0; } 

4.5运行截图

说明:rle.txt为原始文件,res.txt为压缩后的文件,ret.txt为再次解压后的文件。

通过对比,我们可以验证出压缩与解压缩算法的正确性,最后可以看到rle.txt文件与ret.txt文件中的数据完全一致,且res.txt文件的所占内存的确也小于另外这两个文件,可见,算法实现过程是正确的。
在这里插入图片描述

题目五:简单文件数据库-模拟餐馆点餐系统

简单文件数据库-模拟餐馆点餐系统

​ 涉及知识点:文件读写、内存管理、结构体定义、基本数据结构、高级格式化输入输出

要求:

编写一个程序模拟餐馆点餐系统。

  1. 用户分为管理员和用餐者两类,分别显示不同文本格式菜单,通过菜单项对应数字进行选择。
  2. 用餐者菜单包括菜品分类(大类)、菜品列表(含每道菜品的构成信息)、点餐、付费、查询等功能。
  3. 管理员菜单包括菜品信息和用餐者信息录入、修改和删除。菜品信息至少应包括:编号、菜名、类别、价格等;用餐者信息至少应包括:编号、姓名、点餐信息、付费金额、用餐状态(就餐或打包)等。可根据菜品名称或编号进行菜品构成查询;可查询用餐者点餐情况、费用情况;可统计菜品的日点餐量、日营业额等(选做)。

命令行参数如下:

Libsim –a (-u) xxxx

第一个参数为可执行程序名称;第二个参数为用户身份,-a表示管理员,-u表示用餐者;第三个参数为用餐者名称


5.1设计思路

​ 由于点餐系统包括用餐者和管理员两个类别,对于他们各自而言,都有不同的菜单显示和操作方法,需要单独定义各自的操作函数和界面设计。因此,这里使用了多文件编程的思想,使得逻辑结构更加的简单清晰。分别定义用餐者的操作和管理员的操作,每次每个对象完成操作后,必须将操作中对于信息的改动写入到txt文本文件中去,这样,数据才能保存下来。否则,程序运行结束后相关的内存被释放,用户和菜单的数据都会丢失,这是本程序实现过程中的重点内容。

​ 下面介绍本程序可以实现的功能。

对于用餐者而言,可以实现:

  • 查看菜单,菜单内容包括食物种类以及食物的名称和价格
  • 实现点餐功能,可以一次性点多个餐饮食品
  • 查看点餐信息,包括所点的所有的食物的名称和价格,以及最后的总价
  • 结账功能,即可以显示出账单信息
  • 保存用户信息,这一步是自动的,在用户选择结账时,用户的姓名、点餐详情、点餐金额等信息都会被存入txt文本文件中

对于管理员而言,可以实现:

  • 查看所有的餐品信息,并对餐品信息进行修改,包括增删改查
  • 查看目前的收入总金额
  • 查看已经点餐的所有的顾客信息,包括姓名、餐品名称、餐品价格、点餐的总金额等
  • 修改菜单,可以改动餐品价格
  • 对管理员的所有的改动的信息进行存储,即同时变动txt文件中的信息,将新的信息存入到txt文件中对应的位置处

5.2相关代码

system.h
#ifndef __SYSTEM_H__ #define __SYSTEM_H__ #include<stdio.h> #include<stdlib.h> #include<windows.h> #include<string.h> #include<conio.h> #include<stdbool.h> /*宏定义区*/ #define max_dish_str 100 //最大字符串长度 #define max_dish_num 30 //最大的菜品数量 #define max_cus_name_str 10 //用餐者姓名长度 #define max_cus_num 500 //最大的用餐者数量 /*定义菜品结构体*/ typedef struct dishes { 
    char name[max_dish_str]; int price; }dish; /*定义全局变量*/ dish list1[max_dish_num]; int num1 = 0; int *p1 = &num1; dish list2[max_dish_num]; int num2 = 0; int *p2 = &num2; dish list3[max_dish_num]; int num3 = 0; int *p3 = &num3; dish list4[max_dish_num]; int num4 = 0; int *p4 = &num4; dish list5[max_dish_num]; int num5 = 0; int *p5 = &num5; dish list6[max_dish_num]; int num6 = 0; int *p6 = &num6; dish info_cus_order[max_dish_num]; //存放顾客点餐菜品信息 int cus_order_num = 0; //存放顾客点餐菜品数量 int *cus = &cus_order_num; #endif 
general_function.h
#ifndef __GENERAL_FUNCTION_H__ #define __GENERAL_FUNCTION_H__ #include "system.h" int function_read(dish list[], FILE *fp); void load_dishes(); //加载菜品信息到结构体数组,嵌套功能模块1:读取txt写入到每个结构体数组中 void load_dishes() { 
    FILE *fp; fp = fopen("炒菜类.txt","r"); //读取txt文件中的信息到list1这个结构体数组中 *p1 = function_read(list1, fp); fp = fopen("凉菜类.txt","r"); //读取txt文件中的信息到list2这个结构体数组中 *p2 = function_read(list2, fp); fp = fopen("砂锅类.txt","r"); //读取txt文件中的信息到list3这个结构体数组中 *p3 = function_read(list3, fp); fp = fopen("甜品类.txt","r"); //读取txt文件中的信息到list4这个结构体数组中 *p4 = function_read(list4, fp); fp = fopen("饮品类.txt","r"); //读取txt文件中的信息到list5这个结构体数组中 *p5 = function_read(list5, fp); fp = fopen("主食类.txt","r"); //读取txt文件中的信息到list6这个结构体数组中 *p6 = function_read(list6, fp); fclose(fp); } int function_read(dish list[], FILE *fp) { 
    int num = 0; //索引从0开始 定义为1统计个数 char t[max_dish_str] = { 
   '\0'}; char c[max_dish_str] = { 
   '\0'}; int i = 0; if(fp == NULL) { 
    printf("Can't open this file\n"); exit(1); } while(!feof(fp)) //检测流上的文件结束符,如果文件s结束,则返回非0值,否则返回0 { 
    if(fgets(t, max_dish_str, fp) != NULL && t[0] != '\n' && t[0] != EOF) { 
    num++; } } rewind(fp); //将指针重置到第一行 for(i=0; i<num; i++) { 
    fgets(c,max_dish_str,fp); sscanf(c, "%s %d", list[i].name, &list[i].price); //从字符串c中读取到name和price信息,空格为分界线,输入流为字符串c,输出赋值到对应的结构体中 } fclose(fp); return num; } #endif 
admin_system.h
#ifndef __ADMIN_SYSTEM_H__ #define __ADMIN_SYSTEM_H__ #include"system.h" /*函数声明*/ char show_admin_menu(); char show_menu(); char show_admin_submenu(); int function_aread(FILE *fp); void function_sort(dish list[], int num); void output_admin_submenu(dish list[], int num); void change_dish_price(dish list[], int *p, char ch); int function_change(dish list[], int num, FILE *fp); int function_del(dish list[], int *p, FILE *fp); int function_add(dish list[], int *p, FILE *fp); void del_dish(dish list[], int *p, char ch); void add_dish(dish list[], int *p, char ch); void show_customer_info(); void show_income(); /*菜单函数*/ //管理员主菜单 char show_admin_menu() { 
    char ch = '\0'; system("cls"); printf("\n-------------------------------\n"); printf("\t1\t查看所有的菜品信息\t\n"); printf("\t2\t查看用餐者信息\t\n"); printf("\t3\t查看日营业额\t\n"); printf("\t4\t修改菜品价格\t\n"); printf("\t5\t删除指定菜品\t\n"); printf("\t6\t添加指定菜品\t\n"); printf("\t0\t结束\t\n"); printf("-------------------------------\n"); printf("输入序号:"); ch = getch(); return ch; } // 管理员二级菜单:添加或删除菜品的提示 char show_menu() { 
    system("cls"); char ch = '\0'; printf(" ---------------------------\n"); printf(" | 1 炒菜类 |\n"); printf(" | 2 凉菜类 |\n"); printf(" | 3 砂锅类 |\n"); printf(" | 4 甜品类 |\n"); printf(" | 5 饮品类 |\n"); printf(" | 6 主食类 |\n"); printf(" | 0 退出菜单界面 |\n"); printf(" ------------------------------\n"); printf(" 输入序号:"); ch = getch(); return ch; } void output_admin_submenu(dish list[], int num) { 
    int i = 0; printf("\n-------------------------------------------------\n"); printf("|\t序号\t菜品\t\t价格\t\t|\n"); printf("-------------------------------------------------\n"); for(i=0; i<num; i++) { 
    if(strlen(list[i].name) <= 6) printf("|\t%d\t%-15s%d\t\t|\n", i+1, list[i].name, list[i].price); else printf("|\t%d\t%-15s%d\t\t|\n", i+1, list[i].name, list[i].price); } printf("|\t0\t返回\t\t\t\t|\n"); printf("-------------------------------------------------\n"); } /*程序运行主要函数*/ char show_admin_submenu() //展示所有菜品信息 { 
    system("cls"); char ch = '\0'; FILE *fp = NULL; printf("\t\t------------------------------------------------\n"); printf("\t\t|\t\t1\t炒菜类\t\t\t|\n"); printf("\t\t------------------------------------------------\n"); fp = fopen("炒菜类.txt","r"); function_aread(fp); printf("\t\t------------------------------------------------\n"); printf("\t\t|\t\t2\t凉菜类\t\t\t|\n"); printf("\t\t------------------------------------------------\n"); fp = fopen("凉菜类.txt","r"); function_aread(fp); printf("\t\t------------------------------------------------\n"); printf("\t\t|\t\t3\t砂锅类\t\t\t|\n"); printf("\t\t------------------------------------------------\n"); fp = fopen("砂锅类.txt","r"); function_aread(fp); printf("\t\t------------------------------------------------\n"); printf("\t\t|\t\t4\t甜品类\t\t\t|\n"); printf("\t\t------------------------------------------------\n"); fp = fopen("甜品类.txt","r"); function_aread(fp); printf("\t\t------------------------------------------------\n"); printf("\t\t|\t\t5\t饮品类\t\t\t|\n"); printf("\t\t------------------------------------------------\n"); fp = fopen("饮品类.txt","r"); function_aread(fp); printf("\t\t------------------------------------------------\n"); printf("\t\t|\t\t6\t主食类\t\t\t|\n"); printf("\t\t------------------------------------------------\n"); fp = fopen("主食类.txt","r"); function_aread(fp); printf("\t\t------------------------------------------------\n"); printf("退出界面请按任意键"); system("pause"); system("cls"); return ch; } // 展示用餐者信息 void show_customer_info() { 
    FILE *fp = fopen("diner_info.txt","r"); char des[max_dish_str] = { 
   '\0'}; system("cls"); while (fgets(des, max_dish_str, fp) != NULL) { 
    printf("%s", des); } printf("\n"); fclose(fp); } void show_income() { 
    int sum = 0; int count = 0; char s[max_dish_str] = { 
   '\0'}; FILE *fp = fopen("diner_info.txt","r"); char temp[max_dish_str] = { 
   '\0'}; while (fgets(temp, max_dish_str, fp) != NULL) { 
    if (temp[0] == '-' && temp[1] != '-') { 
    sscanf(temp, "%s %d", s, &count); sum += count; // printf("%d\n",sum); } } printf("\n今日营业额为:%d\n", sum); } // 实现菜品的价格修改,内嵌功能模块3:实现结构体菜品的价格修改与文件的重构 void change_dish_price(dish list[], int *p, char ch) { 
    int i = 1; FILE *fp; while(i != 0) { 
    system("cls"); switch(ch) { 
    case '1': output_admin_submenu(list, *p); fp = fopen("炒菜类.txt", "w"); i = function_change(list1, num1, fp); fclose(fp); break; case '2': output_admin_submenu(list, *p); fp = fopen("凉菜类.txt", "w"); i = function_change(list2, num2, fp); fclose(fp); break; case '3': output_admin_submenu(list, *p); fp = fopen("砂锅类.txt", "w"); i = function_change(list3, num3, fp); fclose(fp); break; case '4': output_admin_submenu(list, *p); fp = fopen("甜品类.txt", "w"); i = function_change(list4, num4, fp); fclose(fp); break; case '5': output_admin_submenu(list,*p); fp = fopen("饮品类.txt", "w"); i = function_change(list5, num5, fp); fclose(fp); break; case '6': output_admin_submenu(list,*p); fp = fopen("主食类.txt", "w"); i = function_change(list6, num6, fp); fclose(fp); break; default: break; } } } // 实现菜品的删除,内嵌功能模块4:实现结构体菜品的删除与文件的重构 void del_dish(dish list[], int *p, char ch) { 
    int i = 1; FILE *fp; while(i != 0) { 
    system("cls"); switch(ch) { 
    case '1': output_admin_submenu(list, *p); fp = fopen("炒菜类.txt", "w"); i = function_del(list1, p1, fp); fclose(fp); break; case '2': output_admin_submenu(list, *p); fp = fopen("凉菜类.txt", "w"); i = function_del(list2, p2, fp); fclose(fp); break; case '3': output_admin_submenu(list, *p); fp = fopen("砂锅类.txt", "w"); i = function_del(list3, p3, fp); fclose(fp); break; case '4': output_admin_submenu(list, *p); fp = fopen("甜品类.txt", "w"); i = function_del(list4, p4, fp); fclose(fp); break; case '5': output_admin_submenu(list,*p); fp = fopen("饮品类.txt", "w"); i = function_del(list5, p5, fp); fclose(fp); break; case '6': output_admin_submenu(list,*p); fp = fopen("主食类.txt", "w"); i = function_del(list6, p6, fp); fclose(fp); break; default: break; } } } void add_dish(dish list[], int *p, char ch) { 
    int i = 1; FILE *fp; while(i != 0) { 
    system("cls"); switch(ch) { 
    case '1': output_admin_submenu(list, *p); fp = fopen("炒菜类.txt", "w"); i = function_add(list1, p1, fp); fclose(fp); break; case '2': output_admin_submenu(list, *p); fp = fopen("凉菜类.txt", "w"); i = function_add(list2, p2, fp); fclose(fp); break; case '3': output_admin_submenu(list, *p); fp = fopen("砂锅类.txt", "w"); i = function_add(list3, p3, fp); fclose(fp); break; case '4': output_admin_submenu(list, *p); fp = fopen("甜品类.txt", "w"); i = function_add(list4, p4, fp); fclose(fp); break; case '5': output_admin_submenu(list,*p); fp = fopen("饮品类.txt", "w"); i = function_add(list5, p5, fp); fclose(fp); break; case '6': output_admin_submenu(list,*p); fp = fopen("主食类.txt", "w"); i = function_add(list6, p6, fp); fclose(fp); break; default: break; } } } /*功能模块*/ int function_aread(FILE *fp) { 
    int num = 0; //索引从0开始定义,初始值为1,用来统计个数 char c[max_dish_str] = { 
   '\0'}; char des[max_dish_str] = { 
   '\0'}; char dish_name[max_dish_str] = { 
   '\0'}; int dish_price = 0; int i = 0; if(fp == NULL) { 
    printf("Can't open this file\n"); exit(1); } while(!feof(fp)) //检测流上的文件结束符,如果文件s结束,则返回非0值,否则返回0 { 
    if(fgets(des, max_dish_str, fp) != NULL && des[0] != '\n' && des[0] != EOF) { 
    num++; } } rewind(fp); //将指针重置到第一行 for(i=0; i<num; i++) { 
    fgets(c, max_dish_str, fp); sscanf(c, "%s %d", dish_name, &dish_price); //从字符串c中读取到name和price信息,空格为分界线,输入流为字符串c,输出赋值到对应的结构体中 printf("\t|\t%d\t%-20s%10d |\n", i+1, dish_name, dish_price); } fclose(fp); return num; } // 功能模块:实现结构体菜品的价格修改与文件的重构 int function_change(dish list[], int num, FILE *fp) { 
    if(fp == NULL) { 
    printf("Can't open this file\n"); exit(1); } int i,n; char temp[max_dish_str],t[max_dish_str]; printf("\n输入ID:"); scanf("%d", &n); if(n == 0) { 
    for(i=0; i<num; i++) { 
    strcpy(temp, list[i].name); strcat(temp," "); itoa(list[i].price, t, 10); //itoa():将整型值转换为字符串,参数依次为:要复制的内容,复制给谁,以什么进制 strcat(temp, t); strcat(temp, "\n"); fputs(temp, fp); } return n; } printf("输入修改后的价格:"); scanf("%d", &list[n-1].price); function_sort(list,num); for(i=0; i<num; i++) { 
    strcpy(temp, list[i].name); strcat(temp," "); itoa(list[i].price, t, 10); //itoa():将整型值转换为字符串,参数依次为:要复制的内容,复制给谁,以什么进制 strcat(temp, t); strcat(temp, "\n"); fputs(temp, fp); } printf("修改完成\n"); system("pause"); system("cls"); return 1; } int function_del(dish list[], int *p, FILE *fp) { 
    if(fp == NULL) { 
    printf("Can't open this file\n"); exit(1); } int i,n; char temp[max_dish_str],t[max_dish_str]; printf("\n输入想要修改的菜品ID:"); scanf("%d",&n); if(n == 0) { 
    for(i=0; i<*p; i++) { 
    strcpy(temp,list[i].name); strcat(temp," "); itoa(list[i].price,t,10); //itoa():将整型值转换为字符串,参数依次为:要复制的内容,复制给谁,以什么进制 strcat(temp,t); strcat(temp,"\n"); fputs(temp,fp); } return 0; } for(i=n-1; i<*p; i++) { 
    strcpy(list[i].name,list[i+1].name); list[i].price=list[i+1].price; } *p = *p-1; for(i=0; i<*p; i++) { 
    strcpy(temp,list[i].name); strcat(temp," "); itoa(list[i].price,t,10); //itoa():将整型值转换为字符串,参数依次为:要复制的内容,复制给谁,以什么进制 strcat(temp,t); strcat(temp,"\n"); fputs(temp,fp); } printf("删除完成\n"); system("pause"); system("cls"); return 1; } int function_add(dish list[], int *p, FILE *fp) { 
    if(fp == NULL) { 
    printf("Can't open this file\n"); exit(1); } int i; char temp[max_dish_str],t[max_dish_str]; printf("\n输入0 0返回上一级\n"); printf("输入菜品名和价格,中间使用空格隔开:\n"); scanf("%s%d",list[*p].name,&list[*p].price); if(list[*p].price == 0) { 
    for(i=0; i<*p; i++) { 
    strcpy(temp,list[i].name); strcat(temp," "); itoa(list[i].price,t,10); //itoa():将整型值转换为字符串,参数依次为:要复制的内容,复制给谁,以什么进制 strcat(temp,t); strcat(temp,"\n"); fputs(temp,fp); } return 0; } *p = *p + 1; function_sort(list, *p); for(i=0; i<*p; i++) { 
    strcpy(temp,list[i].name); strcat(temp," "); itoa(list[i].price,t,10); //itoa():将整型值转换为字符串,参数依次为:要复制的内容,复制给谁,以什么进制 strcat(temp,t); strcat(temp,"\n"); fputs(temp,fp); } printf("添加完成\n"); system("pause"); system("cls"); return 1; } // 功能模块6:对经过修改的菜品结构体按价格从低到高排序 void function_sort(dish list[], int num) { 
    int i,j; dish t; for(j=num-1; j>0; j--) { 
    for(i=0; i<j; i++) if(list[i].price>list[i+1].price) { 
    t = list[i]; list[i] = list[i+1]; list[i+1] = t; } } } #endif 
customer_system.h
#ifndef __CUSTOMER_SYSTEM_H__ #define __CUSTOMER_SYSTEM_H__ #include "system.h" #include "general_function.h" /*函数声明*/ char welcome_screen(); char show_diners_menu(); void output_submenu(dish list[], int num); void creat_file(); void load_dishes(); void order_dish(dish list[], int num); void settlement(dish list[], int num); void write_info_to_doc(char name[], dish list[], int num); int function_read(dish list[], FILE *fp); void function_ordering(dish list[], int choice); /*菜单区*/ //欢迎菜单 char welcome_screen() { 
    system("cls"); char ch = '\0'; printf(" ---------------------------\n"); printf(" | 欢迎光临本餐厅 |\n"); printf(" | 请按序号选择菜品 |\n"); printf(" ---------------------------\n"); printf(" | 1 菜单 |\n"); printf(" | 2 结算 |\n"); printf(" | 3 查看 |\n"); printf(" ---------------------------\n"); printf(" 请输入序号:"); ch = getch(); printf("\n"); return ch; } //用餐者主菜单 char show_diners_menu() { 
    system("cls"); char ch = '\0'; printf(" ------------------------------\n"); printf(" | 欢迎光临本餐厅 |\n"); printf(" | 请按序号选择菜品 |\n"); printf(" ------------------------------\n"); printf(" | 1 炒菜类 |\n"); printf(" | 2 凉菜类 |\n"); printf(" | 3 砂锅类 |\n"); printf(" | 4 甜品类 |\n"); printf(" | 5 饮品类 |\n"); printf(" | 6 主食类 |\n"); printf(" | 0 退出菜单 |\n"); printf(" ------------------------------\n"); printf(" 输入序号:"); ch = getch(); return ch; } //输出子菜单(用户端) void output_submenu(dish list[], int num) //list为副菜单,num为副菜单中菜品数量 { 
    system("cls"); int i; printf("\n-------------------------------------------\n"); printf("|\t序号\t菜品\t\t价格\t\t|\n"); printf("---------------------------------------------\n"); for (i=0; i<num; i++) { 
    if (strlen(list[i].name) <= 6) { 
    printf("|\t%d\t%s\t\t%d\t\t|\n", i+1, list[i].name, list[i].price); } else { 
    printf("|\t%d\t%s\t%d\t\t|\n", i+1, list[i].name, list[i].price); } } printf("|\t0\t返回\t\t\t\t|\n"); printf("------------------------------------------\n"); printf("请输入序号:\n"); } /*主程序运行所需函数*/ //创建程序运行所需的txt文件 void creat_file() { 
    FILE *fp = fopen("炒菜类.txt","a"); if(fp == NULL) return; fclose(fp); fp = fopen("凉菜类.txt","a"); if(fp == NULL) return; fclose(fp); fp = fopen("砂锅类.txt","a"); if(fp == NULL) return; fclose(fp); fp = fopen("主食类.txt","a"); if(fp == NULL) return; fclose(fp); fp = fopen("饮品类.txt","a"); if(fp == NULL) return; fp = fopen("甜品类.txt","a"); if(fp == NULL) return; fclose(fp); } //实现点餐的函数,嵌套功能模块2:将点餐者的点餐信息存入结构体中 void order_dish(dish list[], int num) //list为小类的菜单,num为菜单中的菜品总数 { 
    int choice = 0; system("cls"); //清空屏幕 output_submenu(list, num); //显示小类菜单 while(true) { 
    scanf("%d", &choice); if (choice>=0 && choice<=num) { 
    if (choice == 0) { 
    break; } else { 
    function_ordering(list, choice); //每点一道菜,就将菜品信息存入用餐者点餐菜单中去 } } else { 
    printf("输入错误,请重新输入:\n"); } } } //查看已点菜单,通过顾客点餐的结构体来输出 void settlement(dish list[], int num) { 
    int i = 0; int sum = 0; for (i=0,sum=0; i<num; i++) { 
    sum += list[i].price; } printf("\n------------------------------\n"); printf("|\t序号\t菜单\t\t价格\t\t|\n"); printf("--------------------------------\n"); for (i=0; i<num; i++) { 
    if(strlen(list[i].name) <= 6) { 
    printf("|\t%d\t%s\t\t%d\t\t|\n",i+1,list[i].name,list[i].price); } else { 
    printf("|\t%d\t%s\t%d\t\t|\n",i+1,list[i].name,list[i].price); } } printf("--------------------------------\n"); printf("总价格为:%d\n\n",sum); } void write_info_to_doc(char name[], dish list[], int num) //name为用餐者姓名,list为用餐者点餐菜单,num为用餐者点的总菜数 { 
    int i = 0; int sum = 0; for (i=0,sum=0; i<num; i++) { 
    sum += list[i].price; } FILE *fp = fopen("diner_info.txt","a"); fprintf(fp, "用餐者姓名:%s\n",name); fprintf(fp, "菜单如下:\n"); for (i=0; i<num; i++) { 
    fprintf(fp, "\t%d\t%-20s\t\t%d\n", i+1, list[i].name, list[i].price); } fprintf(fp, "-总计: %d\n", sum); fprintf(fp, "-----------------------------------------\n"); } /*功能模块区*/ void function_ordering(dish list[], int choice) //list为小类菜单,choice为菜单中的第choice道菜 { 
    //将信息存入用餐者点餐菜单中去 strcpy(info_cus_order[*cus].name, list[choice-1].name); info_cus_order[*cus].price = list[choice-1].price; *cus = *cus + 1; } #endif 
main.c
#include "customer_system.h" #include "admin_system.h" int main(int argc, char *argv[]) { 
    creat_file(); load_dishes(); char ch = '\0'; char res = '\0'; //判断是哪种角色:用户&管理员 if (strcmp(argv[1], "-u") == 0) //用餐者 { 
    int flag = 1; bool judge = true; while (judge) { 
    ch = welcome_screen(); flag = 1; switch (ch) { 
    case '1': //点餐界面 while (flag) { 
    res = show_diners_menu(); //展示主菜单,同时读取到需要点餐的大类 //实现具体的点餐操作 switch (res) { 
    case '1': order_dish(list1, num1); break; case '2': order_dish(list2, num2); break; case '3': order_dish(list3, num3); break; case '4': order_dish(list4, num4); break; case '5': order_dish(list5, num5); break; case '6': order_dish(list6, num6); break; case '0': flag = 0; break; default: break; } } break; case '2': //结算界面 system("cls"); settlement(info_cus_order, cus_order_num); write_info_to_doc(argv[2], info_cus_order, cus_order_num); system("pause"); system("cls"); judge = false; break; case '3': //查看界面 system("cls"); settlement(info_cus_order, cus_order_num); system("pause"); system("cls"); break; default: break; } } } else //管理员 { 
    system("cls"); char ch = '\0'; bool jdg = true; bool flg = true; load_dishes(); while (jdg) { 
    system("cls"); res = show_admin_menu(); flg = true; switch(res) { 
    case '1': //查看菜品信息 show_admin_submenu(); break; case '2': //查看用餐者信息 show_customer_info(); system("pause"); break; case '3': //查看日营业额 show_income(); system("pause"); break; case '4': //修改菜品价格 while (flg) { 
    system("cls"); ch = show_menu(); switch(ch) { 
    case '1': change_dish_price(list1, p1, ch); break; case '2': change_dish_price(list2, p2, ch); break; case '3': change_dish_price(list3, p3, ch); break; case '4': change_dish_price(list4, p4, ch); break; case '5': change_dish_price(list5, p5, ch); break; case '6': change_dish_price(list6, p6, ch); break; case '0': flg = false; break; default: break; } } break; case '5': //删除菜品 while (flg) { 
    system("cls"); ch = show_menu(); switch(ch) { 
    case '1': del_dish(list1, p1, ch); break; case '2': del_dish(list2, p2, ch); break; case '3': del_dish(list3, p3, ch); break; case '4': del_dish(list4, p4, ch); break; case '5': del_dish(list5, p5, ch); break; case '6': del_dish(list6, p6, ch); break; case '0': flg = false; break; default: break; } } break; case '6': //添加菜品 while (flg) { 
    system("cls"); ch = show_menu(); switch(ch) { 
    case '1': add_dish(list1, p1, ch); break; case '2': add_dish(list2, p2, ch); break; case '3': add_dish(list3, p3, ch); break; case '4': add_dish(list4, p4, ch); break; case '5': add_dish(list5, p5, ch); break; case '6': add_dish(list6, p6, ch); break; case '0': flg = false; break; default: break; } } break; case '0': //结束 jdg = false; break; default: break; } } } return 0; } 

5.3运行截图

管理员:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
用户:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

今天的文章 程序设计基础分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2024-12-18 23:40
下一篇 2024-12-18 23:33

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/89485.html