《程序设计基础课程设计》实验报告

《程序设计基础课程设计》实验报告

班 级: 学 号: 姓 名:

完成题目:1、2、3、4、5、6

概述

此次六道题目里面第四题是参考某博主的文章后实现的,有一些地方仍然不是特别理解,但是原文章里面存在一些小错误,在这里修正了。第六题的图书管理系统我最初是没有使用文件操作的,后来演示的时候老师要求必须使用文件操作,于是就进行了修改,目前存在一个缺陷:读者归还图书的时候,只能优先归还最后借阅的一本书(也就是txt文件里面该读者名下的最下面的一本书),这个bug我当时没有解决(想法是在嵌套结构体里面从要归还的书开始,后面的书依次前移,然后多余的最后一个删掉,这样编写后还会出现上面的问题,但是我在代码里面还保留了这部分)
由于基本上还没有接触算法之类的内容,所以报告里面的算法描述基本都是根据自己的代码里面主体使用了什么来写的,还望见谅。
第1题
1.原题:假设平面上有1~N(x,y)个坐标点,编程判断这N(x,y)个点能组成多少个有效三角形。
问题分析:本题为一道基本编程题,要点就是判断三个点能构成三角形的条件
解决方案(思路): 三个点可以构成一个有效三角形等价于这三点不共线等价于这三个点围成的图形的面积不为0,而三角形的面积计算可以用行列式来表示,故可以转化为判断行列式的值是否为0
算法描述:通过定义两个浮点型数组x,y作为每一个点的横纵坐标,然后再代入行列式展开的结果中,判定其值是否为0,若不为0,则计数变量递增
源程序:

#include<stdio.h>
int main()
{
	int n,i,j,k,t=0;
	float x[1000],y[1000],s;
	scanf("%d",&n);
	for(i=0;i<n;i++)
	{
		scanf("%f%f",&x[i],&y[i]);//读入每一个点的横纵坐标
	}
	for(i=0;i<n-2;i++)
	{
		for(j=i+1;j<n-1;j++)
		{
			for(k=j+1;k<n;k++)
			{
				s=0.5*(x[i]*y[j]+x[j]*y[k]+x[k]*y[i]-x[i]*y[k]-x[j]*y[i]-x[k]*y[j]);/*三角形面积行列式公式的展开
				由于其正负与所选三个点的顺逆时针排列有关,故一般取绝对值,但是在这道题中并无影响*/
				if(s!=0.0) t++;
			}
		}
	}
	printf("%d",t);
	return 0;
}

测试数据:在这里插入图片描述
第2题
2.原题:用整型数组表示10进制大整数(超过2^32的整数),数组的每个元素存储大整数的一位数字,实现大整数的加减法。
问题分析:高精度加法相对于减法要简单一点,加法只需要判断进位情况,而减法则需要判断借位情况和差的正负号。
解决方案(思路):在进行编程之前先寻找一些规律:对于高精度加法,两个大整数之和的位数最多是其中较大数的数位加一,再想办法解决进位问题即可;对于高精度减法,两个数的差的位数最大为较大数的数位(这是建立在被减数大于减数的基础下的,如果被减数小于减数,可以用减数减去被减数,然后再添加负号)。
下面开始编程,先考虑加法:以字符串的形式分两行输入两个数字串,分别将每一个数字字符转换成整数存储在两个不同的数组里面,开第三个数组,存储前面两个数组各位数字之和。然后开始对第三个数组进行修改:从个位开始,c[i]=c[i]%10,c[i+1]+=c[i]/10;掌握了加法的进位表示方法之后,减法的借位应该也就没有问题了,考虑到差的正负情况,我引入了一个标记flag,根据标记的取值情况判断两个数的大小,然后决定是否输入负号,此外,比较两个大整数大小的时候我另外定义了一个函数。
算法描述:定义比较高精度大小的函数,开两个字符数组进行大整数的输入,并记录其长度,再开四个整型数组,其中两个用来存储大整数的每一位,另外两个分别记录和与差。
源程序:

#include<stdio.h>
#include<string.h>
int max(int a,int b)//简单的max函数
{
	if(a>b) return a;
	else return b;
}
int compare(int a[],int b[])//判断大整数大小的函数
{
	int i;
	if(a[0]>b[0]) return 1;//此处a[0],b[0]分别表示两个大整数的位数,根据位数可简单比较
	if(a[0]<b[0]) return -1;
	for(i=a[0];i>0;i--)//位数相同时,从高位到低位依次进行判断
	{
		if(a[i]>b[i]) return 1;
		if(a[i]<b[i]) return -1;
	}
	return 0;}
int main()
{
	char m[100],n[100];
	int a[101]={0},b[101]={0},c[102]={0},d[101]={0},i,t,flag;
	scanf("%s%s",m,n);
	a[0]=strlen(m),b[0]=strlen(n);
	for(i=1;i<=a[0];i++)
	{
		a[i]=m[a[0]-i]-'0';
	}//将字符数字转换为整型,存入数组,下面原理相同
	for(i=1;i<=b[0];i++)
	{
		b[i]=n[b[0]-i]-'0';
	}
	t=max(a[0],b[0]);
	for(i=1;i<=t;i++)
	{
		c[i]=a[i]+b[i];
	}//先将两个大整数每一位的和存入一个新的数组,然后再进行具体操作
	for(i=1;i<=t;i++)
	{
		c[i+1]+=c[i]/10;//进位处理
		c[i]%=10;//确保新数组中的每一位都是一个数字(即不大于10)
	}
	if(c[t+1]>0) t++;//当产生的结果比原数中较大数的数位还要多一时,数位进行相应的更新
	printf("高精度加法计算结果为:\n");
	for(i=t;i>0;i--)
	{
		printf("%d",c[i]);
	}
	printf("\n");
	flag=compare(a,b);//标记量
	printf("高精度减法计算结果为:\n");
	if(flag==0) printf("0");//两数相等时直接输出0即可
	else if(flag==1)
	{
		for(i=1;i<=a[0];i++)
		{
			if(a[i]<b[i])
			{
				a[i+1]--;a[i]+=10;//借位操作
			}
			d[i]=a[i]-b[i];
		}
		while(d[a[0]]==0) a[0]--;//和加法类似,进行数位更新
		for(i=a[0];i>0;i--)
		{
			printf("%d",d[i]);
		}
	}
	else if(flag==-1)//a-b=-(b-a),基于上一个分支里面的操作,在输出前加一个负号即可
	{
		for(i=1;i<=b[0];i++)
		{
			if(a[i]>b[i])
			{
				b[i+1]--;b[i]+=10;
			}
			d[i]=b[i]-a[i];
		}
		while(d[b[0]]==0) b[0]--;
		printf("-"); 
		for(i=b[0];i>0;i--)
		{
			printf("%d",d[i]);
		}
	}
}

测试数据:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到程序的计算结果与手机上的用计算器算的结果一样。
第3题
3.原题:编写一个程序模拟堆栈,要求能够模拟入栈、出栈、返回栈顶元素等基本操作。栈中元素可用整数代替。不能使用C++模板库预定义的类型。程序运行中可输入多组入栈、出栈操作,每次操作后展示栈中元素。
问题分析:由于每次操作后都要展示栈中的元素,故需要将其存储下来。除此之外要实现对栈的基本操作,且每一种操作的次数都未知,所以需要定义成函数,其中返回栈顶元素可以与展示栈内所有元素合并在一起,即只需要将栈内元素从高到低依次输出即可
解决方案(思路):分别定义入栈函数,出栈函数以及遍历函数。因为可以使用整数代替栈内元素,故可以用整型数组作为栈,将其初始化为空栈,每次压栈、弹栈时,栈顶元素更新,栈的长度相应的加一或减一,而对于遍历函数,只需要从栈顶开始依次输出栈内元素即可。
算法描述:通过适当运用指针实现入栈函数、出栈函数及遍历函数,然后在主函数中实现提示操作应输入的数字,并通过分支语句进行相应的函数运行,由于每次操作后都要展示栈内元素,故遍历函数在每个分支中都要运行。通过while循环进行不确定次数的操作,并提供终止循环的操作。还要注意当栈满的时候再进行压栈操作要进行提示,栈空时再进行弹栈操作时也要进行提示。
源程序

#include<stdio.h>
#define N 10
int push(int *stack,int len,int *top,int x) 
{//函数参数分别表示指向表示栈的数组的指针,栈的长度,指向栈顶的指针,入栈的元素 
	if(*top>=len)
	{
		return 1;
	} //如果超过了栈的长度,则栈满 
	stack[*top]=x; //把x作为栈顶元素
	(*top)++;
	return 0;
}//入栈函数
int pop(int *stack,int *top,int *x)
{
	if(*top==0)
	{
		return 1;
	}//如果栈顶指针指向0,则说明栈中没有元素,栈空 
	(*top)--;
	*x=stack[*top];//将出栈元素保存 
	return 0;
}//出栈函数
void ShowStack(int *stack,int top)
{
	int i;
	for(i=top-1;i>=0;i--)
	{
		printf("%d\n",stack[i]);//从栈顶元素依次输出
	}
}//遍历函数
//主要操作是上面的三个函数,主函数里面只是加入了一些提示信息,然后根据提示调用函数即可
int main()
{
	int stack[N],num,top=0,operation;
	while(1)
	{
		printf("请选择:1.入栈,2.出栈,3.退出程序\n");
		scanf("%d",&operation);
		if(operation==3)
		{
			return 0;
		}
		else if(operation==1)
		{
			printf("请输入进栈元素: ");
			scanf("%d",&num);
			if(push(stack,N,&top,num)==0)
			{
				printf("此时栈内元素为:\n");
				ShowStack(stack,top);
				printf("\n");
			}
			else
			{
				printf("栈满\n\n");
			}
		}
		else if(operation==2)
		{
			if(pop(stack,&top,&num)==0)
			{
				printf("出栈元素为: %d\n",num);
				printf("此时栈内元素为:\n");
				ShowStack(stack,top);
				printf("\n");
			}
			else
			{
				printf("栈空\n\n");
			}
		}
		else printf("输入错误,请重新输入\n");
	}
}

测试数据:(为了显示栈满的提示,定义了一个长为10的栈,第一次运行时忘了展示栈空的提示了,下面单独附上)
在这里插入图片描述在这里插入图片描述
第4题
4.原题编写一个程序,可以在命令行输入参数,完成指定文件的缩放,并存储到新文件,命令行参数如下
zoom file1.bmp 200 file2.bmp;第一个参数为可执行程序名称,第二个参数为原始图像文件名,第三个参数为缩放比例(百分比),第四个参数为新文件名
问题分析:该题涉及到命令行参数,所以需要先了解命令行的参数由谁来接收。一个程序开始于对函数main()的调用。在这样做的时候,有两个参数被送给main(),其中的一个描述了命令行参数的个数,通常称为argc;另一个是命令行参数的数组,通常称为argv。命令行参数都是字符串,所以argv的类型是char* [argc+1]。该程序的名字也作为argv[0]传进来,所以argc的值至少是1。这个参数的表总以0结束,也就是说,argv[argc]==0。先把不熟悉的内容了解了之后就可以着手做题了。
解决方案:先定义一个存储头文件数据结构体,用来保存图片类型,位图文件的大小等内容;然后定义一个存储位图信息的结构体,包括所占空间,位图的宽度、高度等内容;(这些内容都是在一个同学分享的bmp格式详解文件里面了解到的。)最后,分析其原理:图片缩小的原则就是按照一定的比例从范围内的像素点中抽去像素点。而放大的原则正好相反,将一个或多个像素点按照比例复制在其周围。
另外,在操作时我还遇到了两个问题:一个是当命令行起始路径是C盘的时候,会显示拒绝访问,原因应该是放在C盘会导致其权限不够;另一个就是会遇到图片打开失败的情况,这个我猜测是由于图片使用的快捷方式的原因(因为当我把快捷方式替换为原图片后就没问题了)。
在第一次验收的时候该程序只能放大,不能缩小,通过和同学的讨论发现是由于将其他类型的字符转化为整型是没有加括号的原因,,即第2、4、5、6行(int)后面的数据没有加括号

	printf("新图片的宽度为:");
	printf("%d \n",(int)(s*old_width));
	printf("新图片的高度为:"); 
	printf("%d \n",(int)(s*old_height));
	new_width=(int)(s*old_width);
	new_height=(int)(s*old_height);

算法描述:本题涉及到文件读写、结构体定义、内存管理、基本图像处理算法、命令行参数,先定义两个结构体实现相应内容的存储,然后定义Bmp_Bigger_And_Smaller函数,通过调用该函数实现位图的放大或缩小。
源程序

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#pragma pack(1) //必须在结构体定义之前使用,这是为了让结构体中各成员按1字节对齐
typedef struct tagBITMAPFILEHEADER
{
	unsigned short bfType;		 //保存图片类型
	unsigned long bfSize; 	 //位图文件的大小
	unsigned short bfReserved1;//位图文件保留字
	unsigned short bfReserved2;//位图文件保留字
	unsigned long bfOffBits;  //RGB数据偏移地址
}BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER
{
	unsigned long 	biSize; 		 //本结构所占用字节数
	unsigned long  biWidth; 	 //位图的宽度,以像素为单位
	unsigned long  biHeight; 	 //位图的高度,以像素为单位
	unsigned short biPlanes; 	 //目标设备的级别
	unsigned short biBitCount;  //每个像素所需的位数
	unsigned long  biCompression;//位图压缩类型
	unsigned long  biSizeImage;  //位图的大小
	unsigned long  biXPelsPerMeter;//位图水平分辨率
	unsigned long  biYPelsPerMeter;//位图垂直分辨率
	unsigned long  biClrUsed; 		 //位图实际使用的颜色表中的颜色数
	unsigned long  biClrImportant; //位图显示过程中重要的颜色数
}BITMAPINFOHEADER;
void Bmp_Bigger_And_Smaller(BITMAPFILEHEADER head,BITMAPINFOHEADER info,double s,char *a,char *b)
{
	FILE *fpr1=fopen(a,"rb");
	FILE *fpw2=fopen(b,"wb");
	if(fpr1==NULL||fpw2==NULL)
	{
	printf("图片打开失败!\n");
	return ;
	}
	//读取原照片的头信息
	fread(&head,sizeof(BITMAPFILEHEADER),1,fpr1);
	fread(&info,sizeof(BITMAPINFOHEADER),1,fpr1);
	unsigned int old_width=info.biWidth;//获取原图片的宽
	unsigned int old_height=info.biHeight;//获取原图片的高
	//获取原图片的位图数据
	unsigned char *src_data=(unsigned char *)malloc(old_width*old_height*3);
	fseek(fpr1,54,SEEK_SET);			//随机定位,以字节数为1开始定位,后54位 
	fread(src_data,old_width*old_height*3,1,fpr1);
	printf("原图片的宽度为:"); 
	printf("%d\n",old_width);
	printf("原图片的高度为:"); 
	printf("%d\n",old_height);
	//修改原照片的宽高
	unsigned int new_width,new_height;
	printf("新图片的宽度为:");
	printf("%d \n",(int)(s*old_width));
	printf("新图片的高度为:"); 
	printf("%d \n",(int)(s*old_height));
	new_width=(int)(s*old_width);
	new_height=(int)(s*old_height);
	head.bfSize=new_width*new_height*3+54;
	info.biWidth=new_width;
	info.biHeight=new_height;
	//将修改过的头信息前54个字节,写进新照片
	fwrite(&head,sizeof(BITMAPFILEHEADER),1,fpw2);
	fwrite(&info,sizeof(BITMAPINFOHEADER),1,fpw2);
	//现在是把内容字节缩放,并且拷贝到put_data 
	int i=0,j=0;
	unsigned long dwsrcX,dwsrcY;
	unsigned char *pucDest;
	unsigned char *pucSrc;
	unsigned char *dest_data=(unsigned char *)malloc(new_width*new_height*3);
	//这个拷贝字节数的作用,先按照宽度,在按照高度。双重循环 
	for(i=0;i<new_height;i++)
	{
		dwsrcY=i/s;
		pucDest=dest_data+i*new_width*3;		//下面完整的for循环,所存入的字节量 
		pucSrc=src_data+dwsrcY*old_width*3;		//缩小/放大了 
		for(j=0;j<new_width;j++)
		{
			dwsrcX=j/s;
			memcpy(pucDest+j*3,pucSrc+dwsrcX*3,3);//数据拷贝
			//从源sourec中复制m个字节到目标destination中 ,(void* destination, void* sourec, unsigned m);
		}
	}
	fseek(fpw2,54,SEEK_SET);			//随机定位,以字节数为1开始定位,后54位 
	fwrite(dest_data,new_width*new_height*3,1,fpw2);
	printf("成功!\n");
	//释放堆空间,关闭文件
	free(dest_data);
	free(src_data);
	fclose(fpr1);
	fclose(fpw2);
}
int main(int argc, char* argv[])
{
	BITMAPFILEHEADER old_head;
	BITMAPINFOHEADER old_info;//定义原图片信息的两个结构体 
	memset(&old_head,0,sizeof(BITMAPFILEHEADER));
	memset(&old_info,0,sizeof(BITMAPINFOHEADER));//用memset函数进行初始化 
  	double s = atof(argv[2])/100.0;//把字符串转化为double型
  	printf("%lf\n",s);
	Bmp_Bigger_And_Smaller(old_head,old_info, s,argv[1],argv[3]);
	return 0;
}

测试数据

上面是在命令行运行内容即相应生成文件的截图,下面依次是原图,缩小为一半的图及放大二倍的图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第5题
5.原题:编写一个程序,可以在命令行输入参数,完成指定文件的压缩解压
命令行参数如下
rle file1 –c(-d) file2
第一个参数为可执行程序名称,第二个参数为原始文件名,第三个参数为压缩或解压缩选项,第四个参数为新文件名
问题分析:这道题要求是用RLE压缩解压算法,所以先要对其原理有一个大概的了解。
RLE全称(run-length encoding),翻译为游程编码,又译行程长度编码,又称变动长度编码法(run coding),在控制论中对于二值图像而言是一种编码方法,对连续的黑、白像素数(游程)以不同的码字进行编码。游程编码是一种简单的非破坏性资料压缩法,其好处是加压缩和解压缩都非常快。其方法是计算连续出现的资料长度再进行压缩。其优点是无损压缩,既节省了磁盘空间又不损失任何图像数据。但是当内容像ABCABCABC的话使用这种算法文件会增大,就是1A1B1C1A1B1C1A1B1C了,会变得更长,就达不到压缩的效果了。
解决方案:由于上学期做过字符串压缩和解压的题目,所以感觉其核心思想的实现还是比较简单的,不熟悉的还是一些文件操作,在通过查阅书籍资料后了解了一些基本文件操作函数,然后这道题就比较简单了,具体的内容基本都写在代码的注释里面了(由于压缩和解压极其相似,所以只写了一个函数的注释)
算法描述:主要还是RLE算法本身吧, RLE是一种简单的压缩算法,主要用于压缩图像中连续的重复的颜色块。当然RLE并不是只能应用于图像压缩上,RLE能压缩任何二进制数据。原始图像文件的数据有一个特点,那就是有大量连续重复的颜色数据,RLE正好是用来压缩有大量连续重复数据的压缩编码,但对于其他二进制文件而言,由于文件中相同的数据出现概率较少,使用RLE压缩这些数据重复性不强的文件效果不太理想,有时候压缩后的数据反而变大了。RLE压缩方案是一种极其成熟的压缩方案,其特点是无损失压缩。
源程序

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void zip(char *filename,char *outfile)
{//压缩函数 
	FILE *in, *out;//定义指向文件的指针 
	int filelen;//每一个字符连续重复出现的次数 
	char cur,tmp;	
	if(!(in=fopen(filename,"rb")))//以二进制方式打开只读文件(原文件必须存在) 
		printf("文件打开失败\n");//若文件不存在则进行提示 
	else
	{
		out=fopen(outfile,"wb");/*二进制方式打开只写文件
		(若文件不存在则会进行创建,若存在则会覆盖原内容)*/ 
		cur=fgetc(in);//读取第一个字符,返回读取到的字符
		tmp=cur;
		filelen=1;
		while(!feof(in))//读到文件末尾时结束循环 
		{
			cur=fgetc(in);
			if(cur==tmp){
				filelen++;
			}//进行RLE压缩 
			else{
				fputc(filelen+'0',out);//写一个字符(此处是计数数字),若失败则返回EOF 
				fputc(tmp,out);//同上,此处是原字符 
				tmp=cur;
				filelen=1;
			}
		}
	}
	fclose(in);
	fclose(out);//关闭文件 
}
void unzip(char *filename,char *outfile)
{//解压函数 ,注释会基本和上面一样 ,不再赘述 
	FILE *in, *out;
	int filelen;
	char cur;
	if(!(in=fopen(filename,"rb")))
		printf("文件打开失败\n");
	else
	{
		out = fopen(outfile,"wb");
		while(!feof(in)){
			filelen = fgetc(in)-'0';
			if(feof(in)) break;
			cur = fgetc(in);
			while(filelen--)
				fputc(cur,out);
		}
	}
	fclose(in);
	fclose(out);
}
int main(int argc,char *argv[])
{//通过判断命令行参数进行相应操作 
	if(!strcmp(argv[2], "-d"))
	{
		unzip(argv[1], argv[3]);
		printf("decompress finished\n");
	}
	else if(!strcmp(argv[2],"-c"))
	{
		zip(argv[1], argv[3]);
		printf("compress finished\n");
	}
	else
		printf("输入参数有误,请重新检查,-c : compress; -d : decompress\n"); 
	return 0;
}

测试数据
第一个是图片压缩解压的测试:
这个是图片进行压缩解压之后与原图的对比
第二个是txt文本的测试,可以看到由43字节压缩到了18字节:
在这里插入图片描述
再展示一下无损压缩,可以看到里面的数据并没有改变:
在这里插入图片描述
第三个是音频文件的测试,这个就只能看一下文件大小来标明它压缩解压后没变了,具体的音频已经通过视频的形式单独发给老师了:
在这里插入图片描述

第6题
6.原题:编写一个程序模拟图书管理系统。用户分为管理员和读者两类,分别显示不同文本格式菜单,通过菜单项对应数字进行选择。读者菜单包括借书、还书、查询等功能。管理员菜单包括图书和读者信息录入、修改和删除。图书信息至少应包括:编号、书名、数量,读者信息至少应包括:编号、姓名、所借图书。可根据图书名称或编号进行图书信息查询,可查询某本书现在被哪些读者借走。命令行参数如下:
Libsim –a(-u) xxxx
第一个参数为可执行程序名称;第二个参数为用户身份,-a表示管理员,-u表示读者;第三个参数为用户名。
问题分析:由于录入的图书信息及读者信息至少包含三项,故使用结构体对其进行定义。然后如果进行整体考虑的话可能会比较复杂,所以对要实现的每一个功能定义相应的函数,再通过输入提示信息进行与之对应的函数调用。在做的时候先不考虑命令行参数,等做好了再在main函数里面加入,稍加修改即可。
解决方案:自己在编这道题的时候一共用了两天时间,使用的都是一些基本的东西,通过结构体定义书籍和读者信息,其中由于要查看读者借阅的书籍信息,故需要进行结构体嵌套。然后需要记录现有读者和书籍的数量,这样便于遍历书籍和读者信息。然后定义管理员菜单和读者菜单,并把里面的功能选项定义成其他函数。返回对应菜单的操作则通过while循环实现(这个功能好像在改成命令行操作没有作用了,之前我在编译器操作的时候需要起始界面选择身份,然后用这个返回起始菜单)。然后需要考虑输入的读者/书籍编号是否匹配,这个可以通过遍历操作加strcmp的返回值进行判断。其他的就是需要注意一些相应数据的更新,诸如读者/书籍的数量、书籍的种类、读者借阅书籍的更新等等。还有一个没有解决的问题:我刚开始在做这个系统的时候用的gets输入,然后在运行的时候发现被跳过了,后来换成scanf输入才可以输入。(追加补充一下:这个问题现在解决了,是因为在根据提示信息输入数字后又按了回车,然后scanf不会读入回车,它会留在缓冲区,导致接下来的gets函数读入的是那个回车,所以需要先清空一下缓冲区)
算法描述:在和同学交流的时候,发现很多人都用了链表,我觉得好像没有必要,然后主体就用数组+结构体+字符串那块的两个函数(strcmp判断字符串是否相同,strcpy用来传递字符串)实现的。细节上还加入了清屏的操作,以及用memset函数初始化数组,其他的就没什么了。
补充:上面是第一次验收时的思考过程,然后由于后来老师要求必须使用文件操作,所以后来又加入了文件操作的相关内容,不过对整体的框架影响不大。经过添加100多行代码对源程序进行了修改,加入了六个函数,分别是读入\更新读者、书籍、借阅信息。
源程序

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int readerlen=0,booklen=0;//定义读者数量和书籍的种类 

 
typedef struct BOOK{//书籍信息 
	char booknum[200];//编号 
	char bookname[200];//书名 
	int bookamount;//数量 
};
typedef struct READER{//读者信息 
	char readernum[200];//编号 
	char readername[200];//用户名
	int borrownum;//借阅书籍的数量,此处默认一个人同一种书只能借阅一本 
	BOOK books[100];//借阅的书籍 
};


BOOK book[200];
READER reader[200];//声明全局变量 

FILE *readerlist;
FILE *booklist;
FILE *borrowlist; 

 
void login();//登录界面 
void adminmenu();//管理员菜单 
void readermenu();//读者菜单 
void addreader();//添加用户 
void addbook();//添加书籍 
void changebook();//修改书籍信息 
void deletebook();//删除书籍信息 
void inquire();//查询书籍信息 
void traverse();//遍历所有书籍 
void borrow();//借阅图书 
void returnbook();//归还图书
void readbooklist();//读入图书列表
void readborrowlist();//读入借阅信息 
void readreaderlist();//读入读者列表
void updatebooklist();//更新图书列表
void updatereaderlist();//更新读者列表
void updateborrowlist();//更新借阅信息 


void readbooklist()//读入图书列表 
{
	booklist=fopen("booklist.txt","r");//打开只读文件 
    while(fscanf(booklist,"%s%s%d\n",book[booklen].booknum,book[booklen].bookname,&book[booklen].bookamount)!=EOF)
	{
    	booklen++;//将文件中的信息读入书籍结构体 
    }
    fclose(booklist);//关闭文件 
}


void readreaderlist()//读入读者列表 
{//与读入图书列表相似 
	readerlist=fopen("readerlist.txt","r");
	while(fscanf(readerlist,"%s%s%d",reader[readerlen].readernum,reader[readerlen].readername,&reader[readerlen].borrownum)!=EOF)
	{
		readerlen++;
	}
	fclose(readerlist);
}


void readborrowlist()//读入借阅信息 
{ 
	for(int i=0;i<readerlen;i++)
    {
 		reader[i].borrownum=0;
  		for(int j=0;j<100;j++)
  		{
  			memset(&reader[i].books[j].booknum,0,sizeof(reader[i].books[j].booknum));
  			memset(&reader[i].books[j].bookname,0,sizeof(reader[i].books[j].bookname));
  			reader[i].books[j].bookamount=0;
		}
 	}//先将读者借阅书籍的信息初始化 
    borrowlist=fopen("borrowlist.txt","r");
    char bookname[200];
    char readername[200];
    while(fscanf(borrowlist,"%s%s\n",bookname,readername)!=EOF)
    {//读入文件信息,思路类似上面的读入读者信息 
		for(int i=0;i<readerlen;i++)
		{
   			if(strcmp(reader[i].readername,readername)==0)
			{
       			strcpy(reader[i].books[reader[i].borrownum].bookname,bookname);
        		reader[i].borrownum++;
    		}
   		}
   	}
	fclose(borrowlist);
}


void updatebooklist()//更新图书列表 
{
	booklist=fopen("booklist.txt","w");//打开只写文件 
    for(int i=0;i<booklen;i++)
	{//将结构体信息写入文件 
        fprintf(booklist,"%s %s %d\n",book[i].booknum,book[i].bookname,book[i].bookamount);
    }
    fclose(booklist);//关闭文件 
}


void updatereaderlist()//更新读者列表 
{//同上 
	readerlist=fopen("readerlist.txt","w");
	for(int i=0;i<readerlen;i++)
	{
		fprintf(readerlist,"%s %s %d\n",reader[i].readernum,reader[i].readername,reader[i].borrownum);
	}
	fclose(readerlist);
}


void updateborrowlist()//更新借阅信息 
{//同上 
	borrowlist=fopen("borrowlist.txt","w");
	for(int i=0;i<readerlen;i++)
	{
		for(int j=0;j<reader[i].borrownum;j++)
		{
    		fprintf(borrowlist,"%s %s\n",reader[i].books[j].bookname,reader[i].readername);
  		}
	}
	fclose(borrowlist);
}


void adminmenu()//管理员菜单 
{
	int a=1; 
	while(a)
	{//定义非零常量a,使其在未选择返回的情况下一直处于菜单页面 
		int choice;
		printf("1.录入用户信息\n");
		printf("2.录入图书信息\n");
		printf("3.修改图书信息\n");
		printf("4.删除图书信息\n");
		printf("5.信息查询\n");
		printf("6.返回\n");
		printf("请选择你的操作:\n");
		scanf("%d",&choice);
		switch(choice)
		{
			case 1: addreader(); break;
			case 2: addbook(); break;
			case 3: changebook(); break;
			case 4: deletebook(); break;
			case 5: inquire(); break; 
			case 6: a=0; break;//将非零常量a改为0,从而达到退出while循环的目的 
		}
	}
}


void readermenu()//用户菜单 
{//思路基本和管理员菜单一致 
	int a=1;
	while(a)
	{
		int choice;
		printf("1.查询图书\n");
		printf("2.借阅图书\n");
		printf("3.归还图书\n");
		printf("4.返回\n");
		printf("请选择你的操作:\n");
		scanf("%d",&choice);
		switch(choice)
		{
			case 1: traverse(); break;
			case 2: borrow(); break;
			case 3: returnbook(); break;
			case 4: a=0; break;
		}
	}
}


void addreader()//添加用户信息 
{
	char str[10];
	int a=readerlen,i;
	system("cls");
	printf("请输入你要添加的用户编号\n");
	scanf("%s",str);
	for(i=0;i<a;i++)
	{
		if(strcmp(str,reader[i].readernum)==0)
		{
			printf("该编号已被使用!\n");
			return;
		}
	}//添加用户信息的时候要注意是否重复 
	if(a<200)
	{//如果不重复,且不超过定义的最多用户数量,则可以进行添加 
		strcpy(reader[a].readernum,str);
		printf("请输入该用户的用户名\n");
		scanf("%s",reader[a].readername);
		reader[a].borrownum=0;
		for(int j=0;j<reader[a].borrownum;j++)
		{
			memset(&reader[a].books[j].booknum,0,sizeof(reader[a].books[j].booknum));
			memset(&reader[a].books[j].bookname,0,sizeof(reader[a].books[j].bookname));
			reader[a].books[j].bookamount=0;
		}
		printf("添加成功!\n");
		readerlen++;//读者数量要相应地进行更新 
	}
	return;
}


void addbook()//添加图书信息 
{//思路同添加用户函数 
	char str[10];
	int a=booklen,i;
	system("cls");
	printf("请输入你要添加的图书编号\n");
	scanf("%s",str);
	for(i=0;i<booklen;i++)
	{
		if(strcmp(str,book[i].booknum)==0)
		{
			printf("该编号已被使用!\n");
			return;
		}
	}
	if(a<100)
	{
		strcpy(book[a].booknum,str);
		printf("请输入该书籍的书名\n");
		scanf("%s",book[a].bookname);
		printf("请输入该书籍的数量\n");
		scanf("%d",&book[a].bookamount);
		printf("添加成功!\n");
		booklen++;
	}
	return; 
}


void changebook()//修改图书信息 
{//通过重新输入要修改的变量进行修改 
	int i,choice,a=booklen;
	char num[10];
	system("cls");
	printf("请输入您要修改的图书编号\n");
	scanf("%s",num);
	for(i=0;i<a;i++)
	{
		if(strcmp(book[i].booknum,num)==0)
		{
			printf("编号:%s,书名:%s,数量:%d\n",book[i].booknum,book[i].bookname,book[i].bookamount);
			//打印对应编号书籍的信息,然后根据提示进行修改 
			printf("请选择您要修改的内容\n");
			printf("1.编号\n");
			printf("2.书名\n");
			printf("3.数量\n");
			scanf("%d",&choice);
			switch(choice)
			{
				case 1:{
					printf("请输入新的编号\n");
					scanf("%s",book[i].booknum);
					break;
				}
				case 2:{
					printf("请输入新的书名\n");
					scanf("%s",book[i].bookname);
					break;
				}
				case 3:{
					printf("请输入新的数量\n");
					scanf("%d",&book[i].bookamount);
					break;
				}
			}
		}
	}
	return;
}


void deletebook()//删除已有图书 
{/*删除函数的思路就是通过寻找要删除的书籍,然后将其后面的书籍前移 
同时注意原来的最后一本书的信息要进行清空*/ 
	int i,j,a=booklen;
	char str[10];
	system("cls");
	printf("请输入您想要删除的图书编号\n");
	scanf("%s",str);
	for(i=0;i<a;i++)
	{
		if(strcmp(book[i].booknum,str)==0)
		{
			for(j=i;j<a;j++)
			{
				book[j]=book[j+1];
			}
		}
	}
	memset(book[a].bookname,0,sizeof(book[a].bookname));
	memset(book[a].booknum,0,sizeof(book[a].booknum));
	book[a].bookamount=0;
	printf("删除成功!\n");
	booklen--;//图书种类进行更新 
}


void inquire()//查询图书信息 
{
	char str[10];
	int i,j,a=booklen,b=readerlen;
	system("cls");
	printf("请输入您要查询的图书编号或书名\n");
	scanf("%s",str);
	for(i=0;i<a;i++)
	{//对现有图书一一进行匹配,匹配成功后输出该书籍信息 
		if((strcmp(book[i].booknum,str)==0)||(strcmp(book[i].bookname,str)==0))
		{
			printf("该图书编号:%s,书名:%s,数量:%d\n",book[i].booknum,book[i].bookname,book[i].bookamount);
			for(j=0;j<b;j++)
			{
				if(strcmp(reader[j].books[reader[j].borrownum].bookname,book[i].bookname)==0)
				{//输出借阅该书籍的读者的用户名 
					printf("%s借阅了该书籍\n",reader[j].readername);
				}
			}
			return;
		}
	}
	printf("未找到该书籍,即将返回!\n");
	return;	
}


void traverse()//遍历图书信息 
{
	int i,a=booklen;
	system("cls");
	for(i=0;i<a;i++)
	{//将所有图书信息输出,方便读者借阅 
		printf("图书编号:%s,书名:%s,数量:%d\n",book[i].booknum,book[i].bookname,book[i].bookamount);
	}
}


void borrow()//借阅图书 
{
	char str[10],s[10];
	int a=booklen,i,j,b=readerlen;
	system("cls");
	printf("请输入您的用户编号或用户名\n");
	scanf("%s",s);
	for(j=0;j<b;j++)
	{
		if((strcmp(reader[j].readernum,s)==0)||(strcmp(reader[j].readername,s)==0))
		{
			break;
		}
	}
	printf("请输入你要借阅图书的编号或书名\n");
	scanf("%s",str);
	for(i=0;i<a;i++)
	{
		if((strcmp(book[i].booknum,str)==0)||(strcmp(book[i].bookname,str)==0))
		{
			if(book[i].bookamount>0)
			{
				book[i].bookamount--;//借阅成功后注意将该书的数量进行相应更新 
				reader[j].books[reader[j].borrownum]=book[i];//将该书信息传入读者借阅书籍部分
				reader[j].books[reader[j].borrownum].bookamount++;
				reader[j].borrownum++;
				printf("借阅成功!\n");
			}
			else{
				printf("该书籍库存量不足,请以后再尝试\n");//注意借阅前提是库存量大于0 
			}
			return;
		}
	}
	printf("未找到该书籍,即将返回!\n");
	return;
}


void returnbook()//归还图书
{//思路和借书的情况差不多 
	char s[10],str[10];
	int a=booklen,b=readerlen,i,j,k,m;
	system("cls");
	printf("请输入您的用户编号或用户名\n");
	scanf("%s",s);
	for(j=0;j<b;j++)
	{
		if((strcmp(reader[j].readernum,s)==0)||(strcmp(reader[j].readername,s)==0))
		{
			break;
		}
	}
	printf("请输入你要归还图书的编号或书名\n");
	scanf("%s",str);
	for(i=0;i<a;i++)
	{
		if((strcmp(book[i].booknum,str)==0)||(strcmp(book[i].bookname,str)==0))
		{
			book[i].bookamount++;//书籍库存量更新 
			for(k=0;k<reader[j].borrownum;k++)
			{
				if((strcmp(reader[j].books[k].booknum,str)==0)||(strcmp(reader[j].books[k].bookname,str)==0))
				{
					break;
				}
			}
			printf("归还成功!\n");
			for(m=k;m<reader[j].borrownum-1;m++)
			{
				strcpy(reader[j].books[m].booknum,reader[j].books[m+1].booknum);
				strcpy(reader[j].books[m].bookname,reader[j].books[m+1].bookname);
				reader[j].books[m].bookamount=reader[j].books[m+1].bookamount;
			}
			strcpy(reader[j].books[reader[j].borrownum].booknum,"\0");
			strcpy(reader[j].books[reader[j].borrownum].bookname,"\0");
			reader[j].books[reader[j].borrownum].bookamount--;
			reader[j].borrownum--;
			return;
		}
	}
	printf("未找到该书籍,即将返回!\n");
	return;
}


int main(int argc,char *argv[])
{
	readbooklist(); 
	readreaderlist();
	readborrowlist(); //读入三个已建立的文件信息 
	if((strcmp(argv[1],"-a")==0)&&(strcmp(argv[2],"123456")==0))
	{
		adminmenu();
		updatebooklist();
		updatereaderlist();
		updateborrowlist();//更新文件信息 
	}
	else if(strcmp(argv[1],"-u")==0)
	{
		for(int i=0;i<readerlen;i++)
		{
			if(strcmp(argv[2],reader[i].readername)==0)
			{
				readermenu();
			}
		}
		updatebooklist();
		updatereaderlist();
		updateborrowlist();//更新文件信息 
	}
	return 0;
}

测试数据
下面第一张图片是初始文件,然后后面展示了使用每一种功能后的结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
《课程设计》感想
总的来说通过完成《课程设计》这门课布置的作业,自己对C语言的掌握更深入了一些。上学期虽然听老师讲了文件操作之类的东西,但是当时自己也仅限于在学校的oj和智慧教育平台上进行练习,而那里面又没有与之相关的题目,所以也只是大概的留下了一个印象,等到实际操作时才知道自己完全不会,然后通过这篇博客了解了相关知识,才得以解决问题(原文链接:https://blog.csdn.net/afei__/article/details/81835684)除了文件操作,还有命令行参数也不熟悉,通过百度大概了解了它是什么意思,然后在编写程序的时候都是循序渐进,先编好不带命令行参数的程序,再对main()函数进行修改。
然后想再谈谈单独对个别题的想法:
第一个想说的就是第一题,对我来说感触就是有时候把数学里面学到的知识和编程结合起来真的能大大简化代码。拿这道题来讲,我用的三角形的行列式的面积公式,在计算方面就比那些用Helen公式的要快(省去了四次开平方运算)。可能这道题略微简单,数学方法对其优化不太明显,再以之前做过的一道洛谷题目为例(题目名称UVA11437 Triangle Fun),题目本身在洛谷是一道普及/提高+的题,而洛谷为其定义的算法标签为动态规划,但是里面涉及到一些三角形方面的知识,我通过找其内在联系仅用了20行的C语言代码就AC了,所以感触颇深。
第二个想说的是第六题,可能是因为第一次接触这中题目,所以刚开始做的时候显得不知所措,不知道从何下手,以至于最开始也就写了存储书籍信息和读者信息的结构体。后来在草稿纸上写了一个简单的登录页面,根据身份不同又设计了管理员菜单和读者菜单,这样思路才清晰了起来。然后给我的感触就是越是复杂的题,在编写程序之前写一个伪代码或者流程图对自己的帮助就越大(平时自己编写的程序最多也就在100行左右,所以一直没有这个习惯,经常是想到哪儿码到哪儿,然后运行的时候出了问题再去调试),所以要尽早养成这个好习惯。

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值