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

时间:2014-01-18 23:26:07

标签: c for-loop

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

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

#include <stdio.h>

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 < 0 || 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循环逻辑仍然无法避开我,我想在继续之前完全理解它。任何帮助将不胜感激。

10 个答案:

答案 0 :(得分:58)

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

第1阶段:了解粗略结构

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

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 loop只是打印一个空格。所以这个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,因此循环将执行三次,金字塔的高度为三。另请注意,如果user_i增加1,则循环将再次执行,金字塔将增加1。这意味着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;
}

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

由于我们理解了代码的第一部分,因此我们可以将其转换为函数而不再考虑它。这将使代码更容易查看。由于此部分代码负责获取有效高度,因此请调用函数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;
}

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

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

第5阶段:将多个字符的打印移动到各自的功能中。

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

#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的值为零对应于金字塔的顶行。

考虑到这些因素,让我们为迭代中打印的空格和散列数引入两个新变量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时增加1,因此正确的公式为numHashes = distanceFromTop + 1

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

结论

你只是问两个内环如何工作,但我给了一个很长的答案。这是因为我认为真正的问题并不在于你没有理解for循环是如何工作的,而是你的代码难以阅读,因此很难分辨出什么在做什么。所以我告诉你我将如何编写程序,希望你会认为它更容易阅读,这样你就能更清楚地看到发生了什么,希望你能学会自己编写更清晰的代码。 / p>

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

答案 1 :(得分:12)

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

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

______######

所以神奇的问题是,我们需要打印多少个空格和#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 <stdio.h>

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

答案 2 :(得分:8)

一些事情:

  • 考虑“替补检查”。也就是说,自己跟踪循环并绘制空格和哈希标记。考虑使用方格纸。我已经这样做了15年了,当他们毛茸茸的时候,我仍然不时地在纸上写下来。

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

  • 因为高从最外面的循环“向上”,通过从user_i中减去它,打印空格的循环是“向下”。也就是说,打印的空间越来越少。

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

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

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

答案 3 :(得分:7)

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

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

同样的代码,插入了一些解释:

// 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<user_i; tall++)
{
    // "For each line, we do the following:"
    //
    // This will loop (user_i - tall + 1) times, each time printing a single space.
    //   As we've seen, tall starts at 0 and increases by 1 per line.
    //   On the first line, tall = 0 and this will loop (user_i + 1) times, printing that many spaces
    //   On the last line, tall = (user_i - 1) and this will loop 0 times, not printing any spaces
    for(int space=0; space<=user_i-tall; space++){ 
        printf(" "); 
    }

    // This will loop (tall + 1)  times, each printing a single hash
    //   On the first line, tall = 0 and this will loop 1 time, printing 1 hash
    //   On the last line, tall = (user_i - 1) and this will loop user_i times, printing that many hashes
    for(int hash=0; hash<=tall; hash++){ 
        printf("#");
    }

    // Finally, we print a newline
    printf("\n");   
}

答案 4 :(得分:4)

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

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

现在金字塔看起来像,

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

现在步骤设计循环,

  • 首先,我们必须打印n行,即5行,这样第一个循环将运行5次。
    for (int rowNo = 0; rowNo < n; rowNo++ )行号rowNo类似于循环中的tall
  • 在每一行中,我们必须打印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循环中,我们必须有两个循环,一个用于打印破折号,另一个用于打印哈希。 短划线循环为for (int dashes= 0; dashes < n - rowNo - 1; dashes ++ ),此处dashes类似于原始程序中的space 散列循环将是for (int hash = 0; hash < rowNo + 1; dashes ++ )
  • 我们必须在每一行之后的最后一步打印换行符,以便我们可以移动到下一行。

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

在你的程序中,我的解释只有一些细微的变化,在我的循环中我使用了少于<运算符而你使用了<=运算符,因此它对一次迭代产生了不同。

答案 5 :(得分:3)

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

您的代码所做的是打印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

答案 6 :(得分:2)

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

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

答案 7 :(得分:2)

#include <stdio.h>

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

答案 8 :(得分:2)

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

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

等等。 哈希像这样打印

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

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

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

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

       #
      ###
     #####
    #######
   #########
  ###########

答案 9 :(得分:1)

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

我用这样的自然语言来思考:

  1. int tall = 0; //以初始值tall = 0
  2. 开头
  3. 高&lt; user_i; //当tall大于user_i时,请在花括号之间进行操作
  4. 高大++; //在每个循环结束时,增加高,然后在执行另一个循环之前重新检查步骤2中的条件
  5. 现在,在代码中使用了很好的缩进,可以更容易地看到for循环是如何嵌套的。内循环将在外循环的每次迭代中运行。