北邮C语言实验指导 实验9 Problem A 神奇的字符画

这是我目前写过最长的代码&&耗时最长的题目,debug了好几天。
不过相应地,收获也很大。
题目是通过技术手段处理了书弄上来的,可能会有个别笔误,请有书的同学还是优先看书P101
思路:初始化动态数组——读入命令——切换到子程序处理——打印
收获和踩过的坑:
1,在多个子程序中操纵同一个表,用动态数组和指针会非常非常方便,操作的逻辑和数组一模一样,可以用下标运算
2,二维动态数组声明时,先声明一个**型的主指针,分配[行数]个*型指针的空间,每个*型指针分配[列数]个变量类型的空间
3,Dev-Cpp有一些莫名其妙的错误,比如这次碰到的声明的全局变量在子程序中无法调用(语法没错)和上次遇到的“假编译”(Dev-Cpp正常进行了编译并在调试控制台输出了结果,但发现程序仍然是之前的,不是这次编译的程序),可以通过转移程序源代码来解决,将源程序另存为到另一个位置,重启Dev-Cpp打开新文件来写。
4,Id returned with status 1错误可以把本代码之前没关掉的程序关掉,可以检查代码中有没有保留字的拼写错误,比如把printf写成print,scanf写成scnaf,都会出现这个错误。
5,不要图省事把i,j作为全局变量,父子程序调用同一个i,j会相互影响。比如在父程序的遍历中用到了i,遍历的过程中调用了子程序,子程序中也用到了i,就会相互影响。
6,输入输出可以在子程序里做,不一定非要在主程序里读入数据后想办法传入子程序。。
7,子程序每次要处理好自己的摊子,有多余的回车要当场读掉,不要影响下一个程序的运行。
8,多个for嵌套时,内部的一定要写初值,不然同样的循环只会执行一次。 因为内部的循环在完成一次后就达到了条件而没有复原,之后主循环虽然在进行,里面的循环不再进行,所以只会循环一次。
9,区分两个for和同一个for两个++
for(i…)
for(j…) 可以遍历整个区域
而for(i,j;…;i++,j++)遍历的是一条直线,因为自加是同步的
10,划线一定要用同一个点去移动,不然不知道移动的方向。横纵坐标组装可能出来的不是原来那条线。
比如之前我想用x1,x2中的较小值作为起点横坐标,较大作为起点纵坐标,y同理,然后用反过来作为终点,发现这条线变了。
11,与划线相对的,对整个矩形区域进行操作时(详见clear函数),可以任意选取两个对角的顶点来遍历,一般取横纵坐标双最小和双最大来作为起点终点
12,写多个类似条件时,同一个元素写在同一个位置而去改变不等号的方向,不容易漏写错写。(详见line函数画斜线部分)。
写了有非常详细的注释,可以对照来看。

#include <stdio.h>
#include <stdlib.h>
enum{HORIZONTAL,VERTICAL,DOWN_RIGHT,DOWN_LEFT,UP_RIGHT,UP_LEFT};
int X,Y;//Y标志着列数,X标志着行数
//注意本题中所有输入的点都是先 列数 后 行数,从第一条LINE命令的起始点看出,所以之后所有的程序中都要先读入列数y,再读入行数x 
void initialize(char **);//初始化画布,使其成为有花边的白板  
void print(char **);//打印整个画布 
void point(char **);//在指定位置写下一个o 
void commandResolver(char [],char**);//命令解释程序,通过读入的关键字切换到对应的处理程序 
void line(char **);//在两点之间划线 
void clear(char **);//将两点确定的矩形区域全部换成空格 
void text(char **);//从指定位置开始写下字符串 
int min(int,int);//计算两个值中的较小值 
int max(int,int);//计算两个值中的较大值 
void debug_printMemory(char **);//debug用,可以打印整个画布的内存情况 

int main(int argc, char const *argv[])
{	
	int n,i,j;char command[10];
	//n标志命令条数,command字符串标志单个命令 
	printf("Please input the size of the picture(length and width):");scanf("%d%d",&Y,&X);
	//Y标志着列数,X标志着行数,原书上对应有误,英文部分应该为(width and length),即先宽度(对应每个横条长度为Y)后长度(对应有X个横条) 
	char **mainPtr=(char**)malloc(sizeof(char*)*(X+2));
	//获得二维动态字符数组的主指针,共有X+2横条,首尾两横条是装饰区 
	for(i=0;i<X+2;i++)
		mainPtr[i]=(char*)malloc(sizeof(char)*(Y+3));
	//给每个指针分配字符存储空间,+3中包括两个装饰区和一个'\0' 
	initialize(mainPtr);
	//初始化画布,使其成为有花边的白板 
	printf("Please input the number of the orders:");
	scanf("%d",&n);
	printf("Please input the orders:\n");
	for(i=0;i<n;i++)
	{
		scanf("%s",command);//读入命令 
		commandResolver(command,mainPtr);//调用命令解释程序 
	}
	
	for(i=0;i<X+2;i++) free(mainPtr[i]);
	free(mainPtr);
	//释放动态数组 
	return 0;
}

void initialize(char **mainPtr)
{	
	int i,j;
	mainPtr[0][0]='+';
	mainPtr[X+1][0]='+';
	mainPtr[0][Y+1]='+';
	mainPtr[X+1][Y+1]='+';
	//四个角写下+ 
	for(i=1;i<X+1;i++)
	{
			mainPtr[i][0]='|';
			mainPtr[i][Y+1]='|';
	}
	//左右两边写下 | 
	for(j=1;j<Y+1;j++)
	{
		mainPtr[0][j]='-';
		mainPtr[X+1][j]='-';
	}
	//上下两边写下 - 
	for(i=1;i<X+1;i++)
		for(j=1;j<Y+1;j++)
			mainPtr[i][j]=' ';
	//将中心区域全部初始化为空格 
	for(i=0;i<X+2;i++)
		mainPtr[i][Y+2]='\0';
	//在每一横行的最后一位写下\0 
}

void commandResolver(char command[],char **mainPtr)
{
	if(strcmp(command,"POINT")==0) point(mainPtr);
	if(strcmp(command,"TEXT")==0) text(mainPtr);
	if(strcmp(command,"LINE")==0) line(mainPtr);
	if(strcmp(command,"CLEAR")==0) clear(mainPtr);
	if(strcmp(command,"PRINT")==0) print(mainPtr);
	//根据命令切换到子程序 
}

void print(char **mainPtr)
{	
	printf("The result is:\n");
	int i;
	for(i=0;i<X+2;i++)
		puts(mainPtr[i]);
	//打印所有横行 
}

void point(char **mainPtr)
{	
	int x,y;
	scanf("%d%d",&y,&x);
	getchar();
	//吃掉输入整数后回车 
	mainPtr[x][y]=(mainPtr[x][y]==' '?'o':'*');
	//也要检测画点的位置之前有没有元素,没有就写o,有就写* 
}

void text(char **mainPtr)
{	
	int x,y,i=0;
	char string[Y];//输入字符串的长度不能超出画布宽度 
	scanf("%d%d ",&y,&x);//注意最后要多写一个空格,不然用gets读入的字符串首位会是空格,由于不知道字符串中有没有空格,不能用scanf读入
	gets(string);
	while(string[i]!='\0'&&y<=Y)//当字符串的长度超出画布右边缘时停止写入 
	{	
		mainPtr[x][y]=(mainPtr[x][y]==' '?string[i]:'*');//原位置是空格就写入字符,否则写入* 
		y++;i++;
	}
}

void line(char **mainPtr)
{
	int x1,y1,x2,y2,state;
	scanf("%d%d%d%d",&y1,&x1,&y2,&x2);//先读入列数,再读入行数 
	int start_y=min(y1,y2),end_y=max(y1,y2);
	int start_x=min(x1,x2),end_x=max(x1,x2);//用于同一横行或同一竖列中确定遍历的方向,从小的元素到大的 
	getchar();//吃掉输入整数之后的回车 
	char *chPtr;//用于表示当前写入位置 
	if(x1==x2) state=HORIZONTAL;
	if(y1==y2) state=VERTICAL;
	if(x1<x2&&y1<y2) state=DOWN_RIGHT;
	if(x1<x2&&y1>y2) state=DOWN_LEFT;
	if(x1>x2&&y1<y2) state=UP_RIGHT;
	if(x1>x2&&y1>y2) state=UP_LEFT;
	//均为以(x1,y1)为起点,(x2,y2)为终点的箭头指向,注意坐标系的x轴正方向向下,y轴的正方向向右(画一下整个数组就知道了) 
	//(x1,y1)和(x2,y2)的有向相对位置只有四种 
	switch(state)
	{
		case HORIZONTAL://画横线 
			for(;start_y<=end_y;start_y++)
			{	
				chPtr=&mainPtr[x1][start_y];
				switch(*chPtr)
				{
					case '-':case '+':break;//根据第三组测试数据,原来有+号时,不改为*号 
					case '|':*chPtr='+';break;
					case ' ':*chPtr='-';break;
					default:*chPtr='*';
				}
				//根据原来位置的元素来确定要写入的内容,注意空格在检测元素里,默认的元素是*,因为除了三个case之外都要写* 
			}
			break;
		case VERTICAL://画竖线 
			for(;start_x<=end_x;start_x++)
			{	
				chPtr=&mainPtr[start_x][y1];
				switch(*chPtr)
				{
					case '|':case'+':break;
					case '-':*chPtr='+';break;
					case ' ':*chPtr='|';break;
					default:*chPtr='*';
				}
			}
			break;
		case DOWN_RIGHT://画y=-x 
			for(;x1<=x2&&y1<=y2;x1++,y1++)//区分两个for和一个for两个++,而且一定要用同一个点去移动,写条件时总把x1,y1写在左边不容易漏情况 
				{
					chPtr=&mainPtr[x1][y1];
					switch(*chPtr)
					{
						case '\\':break;//转义符号\单写时也要写两个 
						case '/':*chPtr='x';break;
						case ' ':*chPtr='\\';break;
						default:*chPtr='*';
					}
				}
			break;
		case DOWN_LEFT://画y=x 
			for(;x1<=x2&&y1>=y2;x1++,y1--)
				{
					chPtr=&mainPtr[x1][y1];
					switch(*chPtr)
					{
						case '/':break;
						case '\\':*chPtr='x';break;
						case ' ':*chPtr='/';break;
						default:*chPtr='*';
				 	}
				}
			break;
		case UP_RIGHT://画y=x 
			for(;x1>=x2&&y1<=y2;x1--,y1++)
				{
					chPtr=&mainPtr[x1][y1];
					switch(*chPtr)
					{
						case '/':break;
						case '\\':*chPtr='x';break;
						case ' ':*chPtr='/';break;
						default:*chPtr='*';
				 	}
				}
			break;
		case UP_LEFT://画y=-x 
			for(;x1>=x2&&y1>=y2;x1--,y1--)
				{
					chPtr=&mainPtr[x1][y1];
					switch(*chPtr)
					{
						case '\\':break;
						case '/':*chPtr='x';break;
						case ' ':*chPtr='\\';break;
						default:*chPtr='*';
					}
				}
			break;
	}
}

void clear(char **mainPtr)
{
	int x1,x2,y1,y2;
	scanf("%d%d%d%d",&y1,&x1,&y2,&x2);
	getchar();
	int start_x=min(x1,x2),end_x=max(x1,x2);
	int start_y=min(y1,y2),end_y=max(y1,y2);
	int start_y_0=start_y;
	//这里矩形的四个点都可以取 
	//重要:多层for嵌套,内部的一定要写初始条件,不然只会循环一次 	
	for(;start_x<=end_x;start_x++)
		for(start_y=start_y_0;start_y<=end_y;start_y++)
				mainPtr[start_x][start_y]=' ';
}

int min(int x,int y){if(x>y) return y;else return x;}
int max(int x,int y){if(x>y) return x;else return y;}

void debug_printMemory(char **mainPtr)
{	
	int i,j;
	for(i=0;i<X+2;i++)
		for(j=0;j<Y+3;j++)
			printf("%3d%c",mainPtr[i][j],j==Y+2?'\n':' ');
}

Problem A:神奇的字符画
问题描述:小王在上小学之前就非常喜欢画画,在学完C语言程序设计“字符数组与字符串”这一章后,他想是不是可以用计算机中的字符来画一幅画呢? 经过思考,他想出了几种用字符表示的图形,但由于字符数组这一章他学得并不到家,所以请帮他实现用计算机画画的想法。
图片由 ASCII字符组成,每个字符为图片最小单位, 字符图片的左上角坐标为(1,1),x轴的正方向向右,y轴的正方向向下。 在图片中,他希望你能用计算机实现以下5个命令:
1)POINT xy:在给定的(x,y)坐标下画一个点。 点用小写字母’o’代表。
2)TEXT x y txt:从给定的(x,y)坐标开始,写一行字符串“txt”。 字符串的第一个字符在(x,y)坐标下,其中“txt”可以替换为任意字符串,并且字符串总是向右书写。
3)LINE x1 y1 x2 y2:在点(x1,y1)与(x2,y2)之间画一条直线。 直线根据方向用以下4种字符代表:横线’一’;竖线’;斜线/’;反斜线’’.
4)CLEAR x1 y2 x2 y2:(x1,y1)与(x2,y2)为矩形的两个顶点, 且这两个顶点在对角线上.该命令的功能是清除点(x1,y1)与点(x2,y2)构成的矩形区域。 被清除的区域用空格填充,并且(x1,y1)与(x2,y2)所在的行与列在被清除的区域内。
5)PRINT:输出当前得到的字符图片。 首先图片要被“+”、“一”、“|”3种符号包围,这3种符号依次代表矩形图片的4个顶点,上下边与左右边。 如程序运行效果所示。
如果在某个单元中要画多个字符,请按照以下规则处理:
1)多个相同的字符画到了同一个区域单位,单位中的字符不发生变化。
2)如果在一个区域中只出现’一’与“|‘两种字符,那么这个区域的字符将变成’+’。
3)如果在一个区域中只出现’/‘与“‘两种字符,那么这个区域的字符将变成’x’。
4)其他的情况,该区域的字符变为’*’.
输入与输出要求:输入两个整数X、Y,代表字符画的尺寸,X代表字符画的宽,Y代表字符画的高,其中1<X,Y<75。笔者注:请忽视这里对X和Y的要求,XY按自己的想法定义,这里仅仅是输入提示,即先读入宽度,再读入高度,变量怎么定义看你自己,不过按照提示来定义很容易想反!我们已经习惯了X行Y列的表达 然后输人一个整数n,代表命令的个数,随后会有n个命令输入,命令只会是上述5种命令之一,命令的参数用一个空格间隔,每个命令占一行。 最后一个命令一定是 PRINT命令。 每个命令一定是正确的命令,命令中出现的坐标与画出的字符一定在字符画的尺寸内. 其中 LINE命令中的两点坐标一定不相同,并一定会画出水平直线,竖直直线,或者45°直线,TEXT命令中的字符串参数中的字符只会是大写字母(‘A’-‘Z’)或者是数组字符(‘0’一’9’)。 输出字符画.

测试数据1:
输入:
20 10
14
LINE 3 2 11 10
LINE 3 10 11 2
LINE 20 3 8 3
TEXT 6 8 TEST
LINE 19 1 19 10
LINE 17 10 17 1
LINE 16 1 16 10
LINE 13 6 20 6
CLEAR 20 5 15 7
LINE 18 1 18 10
TEXT 12 10 NICEPICTURE
POINT 1 1
POINT 3 2
PRINT
输出:(CSDN会吞空格,我就贴图了)
在这里插入图片描述

测试数据2:
输入:
1 1
3
POINT 1 1
CLEAR 1 1 1 1
PRINT

输出:
在这里插入图片描述

测试数据3:
输入:
3 3
7
LINE 2 1 2 3
LINE 1 2 3 2
LINE 2 3 2 1
LINE 3 2 1 2
LINE 2 1 2 3
LINE 1 2 3 2
PRINT

输出:
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值