这是我目前写过最长的代码&&耗时最长的题目,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
输出: