C语言 - 打印特定的图案

3

这个练习要求我创建一个程序,接收一个整数 n 并打印出以下图案,高度为 2*n

\    *    /
 \  ***  /
  \*****/
   \***/
    \*/
    /*\
   /***\
  /*****\
 /  ***  \
/    *    \

我已经完成的工作如下:

void arte(int n)
{
    int i, barrasE, aux, espacobarrasE, ebE, espaco;
    aux = n;

    for(i = 1; i <= n; i++)
    {
       if(aux < n) //Prints the spaces on the superior part.
       {
           espacobarrasE = n - aux;
           for(ebE = 0; ebE < espacobarrasE; ebE++)
               printf(" ");
       }
       for(barrasE = 1; barrasE <= aux; barrasE++) //Prints the backslashes on the superior part.
       {
           printf("\\");
           break;
       }
       for(espaco = 1; espaco < n; espaco++)
       {
           printf(" ");
       }
       aux = aux - 1;
       printf("\n");
   }
}

这只会在顶部打印反斜杠,我不知道如何继续编写代码。我想知道这是否是一个好的方法以及继续编写代码的更好方法。


3
你尝试过使用调试器逐步运行程序吗? - junix
2
请勿尝试嵌入简单文本输出的图片 - 请将文本包含在问题中,就像我为您所做的那样。 n 的值是否总是奇数?如果不是,您需要找到并显示一个偶数值的n的预期输出(例如,n = 4n = 6)。 - Jonathan Leffler
1
我会分两个阶段工作。首先编写代码以生成星号的菱形,忽略斜杠和反斜杠。当您已经为所有值的 nn = 2 时它是什么样子,或者 n 必须是奇数吗?)都能正常工作时,然后您可以添加打印斜杠的代码。您可能需要多个嵌套循环。您必须发现模式,找出如何生成它们,然后生成它们。 - Jonathan Leffler
我是C语言的初学者,不知道如何使用调试器,Junix。 谢谢你,Jonathan。我之前不知道这个。 即使“n”是奇数,它也会乘以2并变成偶数。 - Renan
我不知道如何使用调试器,所以在学会之前请停止尝试“编写程序”。你目前无法开发软件,应该停止尝试。 - ThingyWotsit
2个回答

3
我会尽力进行翻译。以下是您需要翻译的内容:

我会按照阶段开发解决方案,大致如下。我的解决方案要求参数n (代码中使用N)为奇数。问题没有展示如果参数为偶数时该如何呈现结果。这个要求得到了断言的支持。

第一阶段 — 打印行函数

#include <assert.h>
#include <stdio.h>

enum { FSLASH = '/', BSLASH = '\\' };

static inline void repeat_char(int num, char c) { for (int i = 0; i < num; i++) putchar(c); }

static void print_line(int l_blanks, char c1, int i_blanks, char c2, int nc, char c3)
{
    assert(i_blanks % 2 == 0);
    assert(nc % 2 == 1);
    repeat_char(l_blanks, ' ');
    putchar(c1);
    repeat_char(i_blanks, ' ');
    repeat_char(nc, c2);
    repeat_char(i_blanks, ' ');
    putchar(c3);
    //repeat_char(l_blanks, ' ');
    putchar('\n');
}

int main(void)
{
    print_line(0, BSLASH, 4, '*', 1, FSLASH);
    print_line(1, BSLASH, 2, '*', 3, FSLASH);
    print_line(2, BSLASH, 0, '*', 5, FSLASH);
    print_line(3, BSLASH, 0, '*', 3, FSLASH);
    print_line(4, BSLASH, 0, '*', 1, FSLASH);
    print_line(4, FSLASH, 0, '*', 1, BSLASH);
    print_line(3, FSLASH, 0, '*', 3, BSLASH);
    print_line(2, FSLASH, 0, '*', 5, BSLASH);
    print_line(1, FSLASH, 2, '*', 3, BSLASH);
    print_line(0, FSLASH, 4, '*', 1, BSLASH);
    putchar('\n');

    return 0;
}

repeat_char()函数是一个非常简单的小循环,它会打印指定字符指定次数。通过将其声明为static inline,编译器有很大的机会将函数体放到调用代码中,而不是进行函数调用。

print_line()函数将每行字符描述为:

  • 零个或多个前导空格
  • 一个c1字符
  • 零个或多个内部空格(必须是偶数)
  • c2字符出现nc次数(必须是奇数)
  • 再次是零个或多个内部空格
  • 一个c3字符
  • 在其他情况下,可能有必要添加尾随空格;这是被注释掉的
  • 在这些其他情况下,函数不会打印新行

main()函数中的代码手动写出适当的参数并调用该函数。它生成以下输出:

\    *    /
 \  ***  /
  \*****/
   \***/
    \*/
    /*\
   /***\
  /*****\
 /  ***  \
/    *    \

看起来这就是您想要的N=5。但是这些函数的参数列表有很多规律性。肯定有一种方法可以生成用表达式替换这些数字的调用。这个观察结果导致了第二阶段。

第二阶段 - 两个循环

#include <assert.h>
#include <stdio.h>

enum { FSLASH = '/', BSLASH = '\\' };

static inline void repeat_char(int num, char c) { for (int i = 0; i < num; i++) putchar(c); }

static inline int max(int x, int y) { return (x > y) ? x : y; }
static inline int min(int x, int y) { return (x < y) ? x : y; }

static void print_line(int l_blanks, char c1, int i_blanks, char c2, int nc, char c3)
{
    assert(i_blanks % 2 == 0);
    assert(nc % 2 == 1);
    repeat_char(l_blanks, ' ');
    putchar(c1);
    repeat_char(i_blanks, ' ');
    repeat_char(nc, c2);
    repeat_char(i_blanks, ' ');
    putchar(c3);
    //repeat_char(l_blanks, ' ');
    putchar('\n');
}

static void driver_1(int N)
{
    assert(N % 2 == 1 && N > 0);

    for (int i = 0; i < N; i++)
    {
        int nb = max(0, (N-1-2*i)/2);
        int db = min(2*i+1, 2*(N-i)-1);
        print_line(i, BSLASH, 2*nb, '*', db, FSLASH);
    }

    for (int i = N-1; i >= 0; i--)
    {
        int nb = max(0, (N-1-2*i)/2);
        int db = min(2*i+1, 2*(N-i)-1);
        print_line(i, FSLASH, 2*nb, '*', db, BSLASH);
    }
    putchar('\n');
}

int main(void)
{
    int N = 5;
    assert(N % 2 == 1);

    driver_1(N);
    driver_1(N+2);
    driver_1(N-2);

    return 0;
}
repeat_char()print_line()函数与之前没有改变。新的函数driver_1()包含两个循环,一个用来处理具有0、1、… N-1前导空格的行,另一个用来处理具有N-1、N-2、… 0前导空格的行。 min()max()函数再次采用static inline方式,以避免使用时引发函数调用开销。循环索引i控制前导空格的数量。表达式nbdb计算输出多少个空格和星号。这些表达式在两个循环中都相同;不同之处在于计数方向(上升还是下降)和斜杠字符参数的顺序。

这将生成以下输出:

\    *    /
 \  ***  /
  \*****/
   \***/
    \*/
    /*\
   /***\
  /*****\
 /  ***  \
/    *    \

\      *      /
 \    ***    /
  \  *****  /
   \*******/
    \*****/
     \***/
      \*/
      /*\
     /***\
    /*****\
   /*******\
  /  *****  \
 /    ***    \
/      *      \

\  *  /
 \***/
  \*/
  /*\
 /***\
/  *  \

这证明了这些函数可以处理不同大小的输出请求。

第三阶段 — 单循环

代码的最终版本利用了driver_1()中两个循环的对称性,并使用一个循环遍历范围0 .. 2* N - 1,以生成正确的调用print_line()。唯一的变化在于驱动函数,更名为driver_2()(部分原因是它是在单个可执行文件中开发的,该文件还包含driver_1())。同样,repeat_char()print_line()函数保持不变;并且min()max()也被重复使用。

循环使用表达式int i = min(j, 2*N-1-j);确定与driver_1()中的i相对应的值。其中j项递增;2*N-1-j项递减;使用的值是这两个值中较小的那个。测试j == i允许正确选择第一个和最后一个字符。

#include <assert.h>
#include <stdio.h>

enum { FSLASH = '/', BSLASH = '\\' };

static inline void repeat_char(int num, char c) { for (int i = 0; i < num; i++) putchar(c); }

static inline int max(int x, int y) { return (x > y) ? x : y; }
static inline int min(int x, int y) { return (x < y) ? x : y; }

static void print_line(int l_blanks, char c1, int i_blanks, char c2, int nc, char c3)
{
    assert(i_blanks % 2 == 0);
    assert(nc % 2 == 1);
    repeat_char(l_blanks, ' ');
    putchar(c1);
    repeat_char(i_blanks, ' ');
    repeat_char(nc, c2);
    repeat_char(i_blanks, ' ');
    putchar(c3);
    //repeat_char(l_blanks, ' ');
    putchar('\n');
}

static void driver_2(int N)
{
    assert(N % 2 == 1 && N > 0);
    for (int j = 0; j < 2*N; j++)
    {
        int i = min(j, 2*N-1-j);
        int nb = max(0, (N-1-2*i)/2);
        int db = min(2*i+1, 2*(N-i)-1);
        char c1 = (j == i) ? BSLASH : FSLASH;
        char c3 = (j == i) ? FSLASH : BSLASH;
        print_line(i, c1, 2*nb, '*', db, c3);
    }
    putchar('\n');
}

int main(void)
{

    int N = 5;
    assert(N % 2 == 1);

    driver_2(N);
    driver_2(N+2);
    driver_2(N+4);
    driver_2(N-2);
    driver_2(N-4);

    return 0;
}

这将生成输出结果(请注意N=1时几乎退化的情况):
\    *    /
 \  ***  /
  \*****/
   \***/
    \*/
    /*\
   /***\
  /*****\
 /  ***  \
/    *    \

\      *      /
 \    ***    /
  \  *****  /
   \*******/
    \*****/
     \***/
      \*/
      /*\
     /***\
    /*****\
   /*******\
  /  *****  \
 /    ***    \
/      *      \

\        *        /
 \      ***      /
  \    *****    /
   \  *******  /
    \*********/
     \*******/
      \*****/
       \***/
        \*/
        /*\
       /***\
      /*****\
     /*******\
    /*********\
   /  *******  \
  /    *****    \
 /      ***      \
/        *        \

\  *  /
 \***/
  \*/
  /*\
 /***\
/  *  \

\*/
/*\

所以,以上就是一个完整的三个阶段的开发周期。首先让程序能够工作并证明其可行性。然后再将解决方案更加通用化。


2

有很多方法可以实现这一点。其中之一如下所示:


void print_pattern(int n) {

    int padding=0;
    int width=0;
    char char_begin='\\', char_end='/';

    assert(n>0);
    for(int x=0; x<n*2; x++) {
        if(padding==n) {
            padding--;
            char_begin='/';
            char_end='\\';
        }
        width=(n*2)-padding;
        for(int y=0;y<=width; y++) {
            if(y==padding)
                printf("%c",char_begin);
            else
                if (y==width)
                    printf("%c",char_end);
                else 
                    if (y>padding && y>=n-padding && y<=n+padding)
                        printf("*");
                    else
                        printf(" ");
        }
        printf("\n");
        if(x<n)
            padding++;
        else
            padding--;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接