我将解释我如何理解这段代码,以至于我可以自如地使用它。假设我没有阅读您的描述,所以我从头开始。这个过程分为几个阶段,我会在进行时进行编号。我的目标是提供一些通用技巧,使程序更易于阅读。
第一阶段:理解粗略结构
第一步将是了解程序的总体功能,而不陷入细节中。让我们开始阅读主函数的主体部分。
int main(void) {
int user_i;
printf("Hello there and welcome to the pyramid creator program\n");
printf("Please enter a non negative INTEGER from 0 to 23\n");
scanf("%d", &user_i);
到目前为止,我们已经声明了一个整数,并告诉用户输入一个数字,然后使用scanf
函数将整数设置为用户输入的内容。不幸的是,从提示或代码中无法清楚地知道这个整数的目的是什么。让我们继续阅读。
while (user_i < 0 || user_i > 23) {
scanf("%d", &user_i);
}
这里可能要求用户输入其他整数。根据提示,很可能这个语句的目的是确保我们的整数在适当的范围内,并且可以通过检查代码来轻松验证。让我们看看下一行。
for (int tall = 0; tall < user_i; tall++) {
这是外部循环。我们神秘的整数user_i
再次出现,并且我们有另一个整数tall
,它在0
和user_i
之间变化。让我们看看更多代码。
for (int space = 0; space <= user_i - tall; space++) {
printf(" ");
}
这是第一个内部for循环。我们不必深入了解这个新整数space
发生了什么,或者为什么会出现user_i - tall
,但是我们只需要注意,for循环的主体只打印一个空格。因此,这个for循环只是打印了一堆空格。让我们看看下一个内部for循环。
for (int hash = 0; hash <= tall; hash++) {
printf("#");
}
这个看起来很相似。它只是打印一堆哈希标记。接下来我们有
printf("\n");
这个会打印一个新的行。接下来是
}
return 0;
}
这意味着外部的for循环结束了,当外部的for循环结束后,程序就结束了。
请注意,我们已经找到了代码的两个主要部分。第一部分是获取user_i
的值,第二部分是外部的for循环,必须使用该值来绘制金字塔。接下来让我们试着弄清楚user_i
的含义。
Stage 2: 发现user_i
的含义
由于每次外部循环迭代时都会打印一个新行,并且神秘的user_i
控制着外部循环的迭代次数,因此控制着打印多少新行,似乎user_i
控制着创建的金字塔的高度。为了得到确切的关系,让我们假设user_i
的值为3
,那么tall
将取值0、1和2,所以循环会执行三次,金字塔的高度将为三。还要注意,如果user_i
增加了一,那么循环将再次执行,金字塔的高度将增加一。这意味着user_i
必须是金字塔的高度。在我们忘记之前,让我们将变量重命名为pyramidHeight
。现在我们的主要函数看起来是这样的:
int main(void) {
int pyramidHeight;
printf("Hello there and welcome to the pyramid creator program\n");
printf("Please enter a non negative INTEGER from 0 to 23\n");
scanf("%d", &pyramidHeight);
while (pyramidHeight < 0 || pyramidHeight > 23) {
scanf("%d", &pyramidHeight);
}
for (int tall = 0; tall < pyramidHeight; tall++) {
for (int space = 0; space <= pyramidHeight - tall; space++) {
printf(" ");
}
for (int hash = 0; hash <= tall; hash++) {
printf("#");
}
printf("\n");
}
return 0;
}
第三阶段:制作一个获取金字塔高度的函数
我们已经理解了代码的前半部分,现在可以将其移入一个函数中并且不再考虑它。这将使得代码更易于查看。由于这部分代码负责获取有效的高度,让我们将该函数命名为getValidHeight
。在这样做之后,注意到金字塔的高度在main
方法中不会改变,因此我们可以将其声明为const int
。我们的代码现在如下所示:
#include <stdio.h>
const int getValidHeight() {
int pyramidHeight;
printf("Hello there and welcome to the pyramid creator program\n");
printf("Please enter a non negative INTEGER from 0 to 23\n");
scanf("%d", &pyramidHeight);
while (pyramidHeight < 0 || pyramidHeight > 23) {
scanf("%d", &pyramidHeight);
}
return pyramidHeight;
}
int main(void) {
const int pyramidHeight = getValidHeight();
for (int tall = 0; tall < pyramidHeight; tall++) {
for (int space = 0; space <= pyramidHeight - tall; space++) {
printf(" ");
}
for (int hash = 0; hash <= tall; hash++) {
printf("#");
}
printf("\n");
}
return 0;
}
第四阶段:理解内部for循环
我们知道内部for循环重复打印一个字符,但是重复多少次呢?让我们考虑第一个内部for循环。有多少个空格被打印出来了?你可能会认为通过外部for循环的类比,有 pyramidHeight - tall
个空格,但是在这里我们有 space <= pyramidHeight - tall
,真正类比的情况应该是 space < pyramidHeight - tall
。由于我们使用的是 <=
而不是 <
,所以我们会得到一个额外的迭代,其中 space
等于 pyramidHeight - tall
。因此,实际上会打印出 pyramidHeight - tall + 1
个空格。同样地,会打印出 tall + 1
个井号。
第五阶段:将多个字符的打印移入它们自己的函数中
由于多次打印一个字符很容易理解,我们可以将这些代码移入它们自己的函数中。让我们看看现在我们的代码是什么样子。
#include <stdio.h>
const int getValidHeight() {
int pyramidHeight;
printf("Hello there and welcome to the pyramid creator program\n");
printf("Please enter a non negative INTEGER from 0 to 23\n");
scanf("%d", &pyramidHeight);
while (pyramidHeight < 0 || pyramidHeight > 23) {
scanf("%d", &pyramidHeight);
}
return pyramidHeight;
}
void printSpaces(const int numSpaces) {
for (int i = 0; i < numSpaces; i++) {
printf(" ");
}
}
void printHashes(const int numHashes) {
for (int i = 0; i < numHashes; i++) {
printf("#");
}
}
int main(void) {
const int pyramidHeight = getValidHeight();
for (int tall = 0; tall < pyramidHeight; tall++) {
printSpaces(pyramidHeight - tall + 1);
printHashes(tall + 1);
printf("\n");
}
return 0;
}
当我查看main
函数时,我不需要担心printSpaces
如何实际打印空格的细节。我已经忘记它是否使用for
循环或while
循环了。这使我的大脑可以去想其他事情。
第6阶段:引入变量并为变量选择良好的名称
我们的main
函数现在很容易阅读。我们准备开始思考它实际上做了什么。for循环的每次迭代都会打印一定数量的空格,然后是一定数量的井号,最后是一个换行符。由于空格首先被打印,它们都在左边,这正是我们想要得到的图像。
由于新行在终端上打印在旧行下面,因此tall
的值为零对应于金字塔的顶部一行。
考虑到这些因素,让我们引入两个新变量:numSpaces
和numHashes
用于迭代中将要打印的空格和井号的数量。由于这些变量的值在单个迭代中不会更改,因此我们可以将它们作为常量。同时,将tall
的名称(这是一个形容词,因此不适合用作整数的变量名)更改为distanceFromTop
。我们新的主方法看起来像这样:
int main(void) {
const int pyramidHeight = getValidHeight();
for (int distanceFromTop = 0; distanceFromTop < pyramidHeight; distanceFromTop++) {
const int numSpaces = pyramidHeight - distanceFromTop + 1;
const int numHashes = distanceFromTop + 1;
printSpaces(numSpaces);
printHashes(numHashes);
printf("\n");
}
return 0;
}
第7阶段:为什么numSpaces
和numHashes
是它们所代表的意思?
现在一切都开始变得清晰起来了。唯一剩下要解决的问题就是找出给出numSpaces
和numHashes
的公式。
让我们从numHashes
开始,因为它更容易理解。当距离顶部的距离为零时,我们希望numHashes
为1,并且每当距离顶部的距离增加时,希望numHashes
增加1,因此正确的公式是numHashes = distanceFromTop + 1
。
现在考虑numSpaces
。我们知道每当距离顶部增加时,一个空格会变成一个井号,因此少了一个空格。因此,numSpaces
的表达式应该有一个-distanceFromTop
。但是顶行应该有多少个空格呢?由于顶行已经有一个井号,需要制作pyramidHeight-1
个井号,因此必须至少有pyramidHeight-1
个空格,以便将其转换为井号。在代码中,我们选择了pyramidHeight+1
个空格作为顶行,比pyramidHeight-1
多两个空格,因此使整个图像向右移动了两个空格。
结论
你只是询问两个内部循环如何工作,但我给了一个非常长的答案。这是因为我认为真正的问题不是你不理解for循环的工作原理,而是你的代码难以阅读,因此很难确定任何内容的功能。因此,我展示了我会如何编写程序,希望您认为它更易于阅读,因此您能够更清晰地看到发生了什么,并希望您能学会编写更清晰的代码。
我如何更改代码?我更改了变量的名称,以便清楚地了解每个变量的作用;我引入了新变量,并尝试为它们取好名字;并且将一些涉及输入和输出以及打印字符特定次数的逻辑的低级代码移动到它们自己的方法中。这种最后一种更改极大地减少了main
函数中的行数,消除了main
函数中的for循环嵌套,并使程序的关键逻辑易于查看。
scanf
的返回值;你不需要void
,也就是说,int main()
就可以了;还要修复缩进,这样代码更容易阅读。顺便说一句,我给你点赞了。 - Ed Heal