这个C for-loop如何打印文本艺术金字塔?

这是我第一次在这里发帖,希望我做得对。

基本上我需要帮助试图弄清楚我用C编写的一些代码。程序的目的是询问用户0到23之间的数字。然后,根据用户输入的数字,半金字塔将打印(就像旧学校马里奥游戏中的那些)。 我是编程的初学者,仅凭运气得到了我的代码的答案,但现在我无法真正告诉我的for循环如何提供金字塔形象。

#include  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 ); while ( user_i  23 ) { scanf ( "%d", &user_i ); } for ( int tall = 0; tall < user_i; tall++ ) { // this are the two for loops that happened by pure magic, I am still // trying to figure out why are they working they way they are for ( int space = 0; space <= user_i - tall; space++ ) { printf ( " " ); } for ( int hash = 0; hash <= tall; hash++ ) { printf ( "#" ); } // We need to specify the printf("\n"); statement here printf ( "\n" ); } return 0; } 

作为编程的新手,我跟着我对伪代码的了解很少,我似乎无法理解为什么for循环部分的工作原理。 我完全理解while循环(虽然欢迎更正和最佳实践),但for循环逻辑仍然无法避开我,我想在继续之前完全理解它。 任何帮助将不胜感激。

我将解释我将如何理解这段代码的过程,以便我自己习惯使用它。 我会假装我没有读你的描述,所以我从头开始。 这个过程分为几个阶段,我将按照这个阶段进行编号。 我的目标是提供一些使程序更易于阅读的通用技巧。

第1阶段:了解粗糙结构

第一步是概括地理解程序的function,而不会陷入细节困境。 让我们开始阅读主要function的主体。

 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++) { 

这是外部for循环。 我们的神秘整数user_i再次出现,我们有另一个整数tall ,介于0user_i之间。 让我们看看更多代码。

  for (int space = 0; space <= user_i - tall; space++) { printf(" "); } 

这是第一个内循环。 让我们不要陷入这个新整数space的细节或者为什么我们有user_i - tall出现,但是让我们注意foor循环的主体只是打印一个空格。 所以这个for循环只是打印一堆空格的效果。 让我们看看下一个内部for循环。

  for (int hash = 0; hash <= tall; hash++) { printf("#"); } 

这个看起来很相似。 它只打印了一堆哈希。 接下来我们有

  printf("\n"); 

这会打印一个新行。 接下来是

  } return 0; } 

这意味着外部for循环结束,并且在外部for循环结束之后,程序结束。

请注意,我们已经找到了代码的两个主要部分。 第一部分是获取user_i的值,第二部分是外部for循环,必须是此值用于绘制金字塔的位置。 接下来让我们试着找出user_i含义。

第2阶段:发现user_i的含义

现在,因为为外循环的每次迭代打印了一个新行,并且神秘的user_i控制外循环的迭代次数,因此打印了多少个新行,看起来user_i控制金字塔的高度那是创造的。 为了得到确切的关系,我们假设user_i的值为3 ,那么tall将取值0,1和2,因此循环将执行三次,金字塔的高度将为3。 另请注意,如果user_i增加1,则循环将再次执行,金字塔将增加1。 这意味着user_i必须是金字塔的高度。 在我们忘记之前,让我们将变量重命名为pyramidHeight 。 我们的主要function现在看起来像这样:

 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; } 

阶段3:创建一个获得金字塔高度的函数

由于我们理解了代码的第一部分,因此我们可以将其转换为函数而不再考虑它。 这将使代码更容易查看。 由于这段代码负责获得有效高度,让我们调用函数getValidHeight 。 执行此操作后,请注意main方法中的金字塔高度不会更改,因此我们可以将其声明为const int 。 我们的代码现在看起来像这样:

 #include  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; } 

第4阶段:理解内部for循环。

我们知道内部for循环重复打印一个字符,但是多少次? 让我们考虑第一个内部for循环。 打印了多少个空格? 您可能认为通过类比外部for循环有pyramidHeight - tall空间,但是这里我们有space <= pyramidHeight - tall ,其中真正类似的情况是space < pyramidHeight - tall 。 由于我们有<=而不是< ,我们得到额外的迭代,其中space等于pyramidHeight - tall 。 因此我们看到实际上pyramidHeight - tall + 1空格被打印出来。 同样打印tall + 1哈希。

第5阶段:将多个字符的打印移动到自己的function中。

由于很容易理解多次打印字符,因此我们可以将此代码移动到自己的函数中。 让我们看看我们的代码现在是什么样的。

 #include  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:引入变量并为变量选择好名称

我们的mainfunction现在易于阅读。 我们准备开始考虑它实际上做了什么。 for循环的每次迭代都会打印一定数量的空格,然后是一定数量的散列,后跟一个新行。 由于首先打印空格,所以它们都在左边,这就是我们想要得到的图片。

由于新行打印在终端上的旧行下方,因此tall为零的值对应于金字塔的顶行。

考虑到这些因素,让我们为迭代中打印的空格和散列数引入两个新变量numSpacesnumHashes 。 由于这些变量的值在单次迭代中不会改变,我们可以使它们成为常量。 另外,让我们将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阶段:为什么numSpacesnumHashes是什么?

现在一切都在一起。 唯一要弄清楚的是给出numSpacesnumHashes的公式。

让我们从numHashes开始,因为它更容易理解。 当距离顶部的距离为零时,我们希望numHashes为1,并且每当距离顶部的距离为零时,我们希望numHashes增加1,因此正确的公式为numHashes = distanceFromTop + 1

现在为numSpaces 。 我们知道,每当距离顶部的距离增加时,空间就变成一个散列,因此空间减少了一个。 因此, numSpaces的表达式中应该包含numSpaces 。 但是顶行应该有多少个空格? 由于顶行已经有一个哈希值,因此需要进行pyramidHeight - 1哈希,因此必须至少有pyramidHeight - 1空格,以便它们可以变成哈希值。 在代码中,我们在顶行中选择了pyramidHeight + 1空格,这比pyramidHeight - 1多两个,因此具有将整个画面向右移动两个空格的效果。

结论

你只问两个内环如何工作,但我给了一个很长的答案。 这是因为我认为真正的问题并不是你不理解循环如何运作得足够好,而是你的代码难以阅读,所以很难说出什么在做什么。 所以我告诉你我将如何编写程序,希望你会认为它更容易阅读,所以你可以更清楚地看到发生的事情,希望你能学会自己编写更清晰的代码。

我是如何更改代码的? 我更改了变量的名称,因此很清楚每个变量的作用是什么; 我介绍了新的变量,并试图给它们好名字; 我将一些涉及输入和输出的低级代码以及将字符打印一定次数的逻辑移动到他们自己的方法中。 最后一次更改大大减少了main函数中的行数,摆脱了main函数中for循环的嵌套,使得程序的关键逻辑很容易看到。

首先,让我们把循环体取出来。 第一个只打印空格,第二个打印哈希标记。

我们想要打印这样的一行,其中_是一个空格:

 ______###### 

所以神奇的问题是,我们需要打印多少个空格和#s?

在每一行,我们希望打印比之前的行多1个#,并且比之前的行少1个空格。 这是“高”用于外循环的目的。 您可以将其视为“应该在此行上打印的哈希标记的数量”。

要在线上打印的所有剩余字符应为空格。 因此,我们可以获取总行长度(用户输入的数字),并减去此行上的散列标记数量,这就是我们需要的空格数量。 这是第一个for循环中的条件:

 for ( int space = 0; space <= user_i - tall; space++ ) // ~~~~~~~~~~~~~ // Number of spaces == number_user_entered - current_line 

然后我们需要打印哈希标记的数量,它总是等于当前行:

 for ( int hash = 0; hash <= tall; hash++ ) // ~~~~ // Remember, "tall" is the current line 

这一切都在for循环中,每行重复一次。

重命名一些变量并引入一些新名称可以使整个事情更容易理解:

 #include  int main ( void ) { int userProvidedNumber; 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", &userProvidedNumber ); while ( userProvidedNumber < 0 || userProvidedNumber > 23 ) { scanf ( "%d", &userProvidedNumber ); } for ( int currentLine = 0; currentLine < userProvidedNumber; currentLine++ ) { int numberOfSpacesToPrint = userProvidedNumber - currentLine; int numberOfHashesToPrint = currentLine; for ( int space = 0; space <= numberOfSpacesToPrint; space++ ) { printf ( " " ); } for ( int hash = 0; hash <= numberOfHashesToPrint; hash++ ) { printf ( "#" ); } // We need to specify the printf("\n"); statement here printf ( "\n" ); } return 0; } 

一些东西:

  • 考虑“基准检查”这个。 也就是说,自己跟踪循环并绘制空格和散列标记。 考虑使用方格纸。 我已经这样做了15年了,当他们毛茸茸的时候,我仍然不时在纸上追踪。

  • 其中的神奇之处是值“user_i – tall”和“hash <= tall”。 这两个内部for循环的条件是括号中的中间值。 注意他们在做什么:

  • 因为tall从最外层循环“向上”,通过从user_i中减去它,打印空格的循环“向下”。 也就是说,随着时间的推移打印的空间越来越少。

  • 因为高是“上升”,并且因为哈希循环基本上是按原样使用它,所以它也在上升。 也就是说,随你打印更多的哈希标记。

所以真的忽略了大部分代码。 它是通用的:外部循环只是计数,大多数内部循环只进行基本初始化(即space = 0和hash = 0),或基本增量(space ++和hash ++)。

只有内环的中心部分才重要,如上所述,它们分别使用来自最外环的高移动来自行向上和向上递增,因此需要空间和散列标记的组合来制作一半-金字塔。 希望这可以帮助!

这是你的代码,只是重新格式化并删除了评论:

 for(int tall = 0;tall 

并且相同的代码,插入了一些解释:

 // This is a simple loop, it will loop user_i times. // tall will be [0,1,...,(user_i - 1)] inclusive. // If we peek at the end of the loop, we see that we're printing a newline character. // In fact, each iteration of this loop will be a single line. for(int tall=0; tall 

这种类型的小程序是我早期让我着迷的程序。 我认为它们在构建逻辑方面发挥着至关重要的作用,并了解循环将如何,何时,何地以及在哪种情况下是完美的。
理解发生的事情的最好方法是手动调试每个语句。
但为了更好地理解让我们理解如何构建逻辑,我正在做一些小的改动,以便我们能够更好地理解它,

  • n不是在金字塔n =5打印的行
  • '-'替换空格' ' '-' (短划线符号)

现在金字塔看起来像,

  ----# ---## --### -#### ##### 

现在设计循环的步骤,

  • 首先,我们必须打印n行,即5行,因此第一个循环将运行5次。
    for (int rowNo = 0; rowNo < n; rowNo++ )行号rowNo类似于循环中的高号
  • 在每一行中,我们必须打印5字符,但如果我们仔细查看那里的逻辑,我们就会得到我们想要的数字,
    rowNo=0 (这是我们的第一行)我们有4破折号和1散列
    rowNo=1我们有3破折号和2散列
    rowNo=2我们有2破折号和3散列
    rowNo=3我们有1破折号和4哈希值
    rowNo=4我们有0破折号和5哈希值
  • 很少检查可以揭示对于rowNo表示的每一行,我们必须打印n - rowNo - 1短划线-rowNo + 1哈希#
    因此,在我们的第一个for循环中,我们必须有两个循环,一个用于打印破折号,另一个用于打印哈希。
    Dash循环将for (int dashes= 0; dashes < n - rowNo - 1; dashes ++ ) ,此处dashes类似于原始程序中的space
    散列循环将为for (int hash = 0; hash < rowNo + 1; dashes ++ )
  • 我们必须在每一行之后的最后一步打印换行符,以便我们可以移动到下一行。

希望,上面的解释清楚地理解了你所编写的for循环是如何形成的。

在你的程序中,只有很小的变化,然后我的解释,在我的循环中我使用少于<运算符,你使用<=运算符,所以它使一次迭代的差异。

您应该使用indentatation来使代码更具可读性,因此更容易理解。

您的代码所做的是打印user_i行,其中包含user_i-tall+1空格,然后是tall+1哈希值。 因为迭代索引tall随着每次传递而增加,这意味着打印了一个或多个哈希。 它们是正确对齐的,因为也留有空间。 随后你会“生长”一排形成金字塔的哈希。
你在做什么等同于这个伪代码:

 for every i between 0 and user_i do: print " " (user_i-i+1) times print "#" (i+1) times 

分析你的循环:
第一个循环

 for ( int tall = 0; tall < user_i; tall++ ){...} 

正在控制这一行。 第二个循环

 for ( int space = 0; space <= user_i - tall; space++ ){...} 

用空格填充列。
对于每一行,它将使用空格填充所有user_i - tall列。
现在剩余的列由#循环填充

 for ( int hash = 0; hash <= tall; hash++ ){...} 
 #include  // Please note spacing of // - functions braces // - for loops braces // - equations // - indentation int main(void) { // Char holds all the values we want // Also, declaire all your variables at the top unsigned char user_i; unsigned char tall, space, hash; // One call to printf is more efficient printf("Hello there and welcome to the pyramid creator program\n" "Please enter a non negative INTEGER from 0 to 23\n"); // This is suited for a do-while. Exercise to the reader for adding in a // print when user input is invalid. do scanf("%d", &user_i); while (user_i < 0 || user_i > 23); // For each level of the pyramid (starting from the top)... // Goes from 0 to user_i - 1 for (tall = 0; tall < user_i; tall++) { // We are going to make each line user_i + 2 characters wide // At tall = 0, this will be user_i + 1 characters worth of spaces // At tall = 1, this will be user_i + 1 - 1 characters worth of spaces // ... // At tall = user_i - 1, this will be user_i + 1 - (user_i - 1) characters worth of spaces for (space = 0; space <= user_i - tall; space++) printf(" "); // no '\n', so characters print right next to one another // because of using '<=' inequality // \_ // At tall = 0, this will be 0 + 1 characters worth of hashes // At tall = 1, this will be 1 + 1 characters worth of hashes // ... // At tall = user_i - 1, this will be user_i - 1 + 1 characters worth of spaces for (hash = 0; hash <= tall; hash++) printf("#"); // Level complete. Add a newline to start the next level printf("\n"); } return 0; } 

对于为什么会发生这种情况的最简单的答案是因为一个循环打印如下所示的空格 – 。

 ---------- --------- -------- ------- ------ ----- 

等等。 哈希像这样印刷

 ------# -----## ----### ---#### --##### -###### 

因为第二阶段的哈希打印循环需要打印更多的哈希以完成金字塔,所以可以通过两种方法解决,一种是通过复制和粘贴哈希循环两次,或者通过下面的修改使循环运行两次

 for ( int hash = 0; hash <= tall*2; hash++ ) { printf ( "#" ); } 

构建这样的循环的逻辑很简单,最外面的循环将一个换行打印到单独的循环,内循环负责每个行的内容。 空间循环放置空格和散列循环在空格的末尾附加散列。 [我的回答可能是多余的,因为我没有仔细阅读其他答案,它们很长]结果:

  # ### ##### ####### ######### ########### 
 for ( int tall = 0; tall < user_i; tall++ ) { ... } 

我用这样的自然语言来想这个:

  1. int tall = 0; //以high = 0的初始值开头
  2. tall
  3. 高大++; //在每个循环结束时,增加高,然后在执行另一个循环之前重新检查步骤2中的条件

现在,在代码中使用了很好的缩进,可以更容易地看到for循环是如何嵌套的。 内循环将在外循环的每次迭代中运行。