我会尽力进行翻译。以下是您需要翻译的内容:
我会按照阶段开发解决方案,大致如下。我的解决方案要求参数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);
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);
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
控制前导空格的数量。表达式
nb
和
db
计算输出多少个空格和星号。这些表达式在两个循环中都相同;不同之处在于计数方向(上升还是下降)和斜杠字符参数的顺序。
这将生成以下输出:
\ * /
\ *** /
\*****/
\***/
\*/
/*\
/***\
/*****\
/ *** \
/ * \
\ * /
\ *** /
\ ***** /
\*******/
\*****/
\***/
\*/
/*\
/***\
/*****\
/*******\
/ ***** \
/ *** \
/ * \
\ * /
\***/
\*/
/*\
/***\
/ * \
这证明了这些函数可以处理不同大小的输出请求。
第三阶段 — 单循环
代码的最终版本利用了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);
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时几乎退化的情况):
\ * /
\ *** /
\*****/
\***/
\*/
/*\
/***\
/*****\
/ *** \
/ * \
\ * /
\ *** /
\ ***** /
\*******/
\*****/
\***/
\*/
/*\
/***\
/*****\
/*******\
/ ***** \
/ *** \
/ * \
\ * /
\ *** /
\ ***** /
\ ******* /
\*********/
\*******/
\*****/
\***/
\*/
/*\
/***\
/*****\
/*******\
/*********\
/ ******* \
/ ***** \
/ *** \
/ * \
\ * /
\***/
\*/
/*\
/***\
/ * \
\*/
/*\
所以,以上就是一个完整的三个阶段的开发周期。首先让程序能够工作并证明其可行性。然后再将解决方案更加通用化。
n
的值是否总是奇数?如果不是,您需要找到并显示一个偶数值的n
的预期输出(例如,n = 4
或n = 6
)。 - Jonathan Lefflern
(n = 2
时它是什么样子,或者n
必须是奇数吗?)都能正常工作时,然后您可以添加打印斜杠的代码。您可能需要多个嵌套循环。您必须发现模式,找出如何生成它们,然后生成它们。 - Jonathan Leffler