在C语言中给数组赋值时出现了意外的结果

5
我正在阅读《C程序设计语言第二版》。我来到了这个练习题[1-13,第1.6节,第24页第二版]。
编写一个程序来打印输入中单词长度的直方图。用水平条形图很容易画出直方图;而垂直方向则更具挑战性。
我认为一切都运行得很好,除了当我尝试确定最后一个单词计数(变量tmp)是否<=10或>10时,将其分配给count[tmp-1]或count[11](如果大于)。就算不打印实际的直方图,现在我只想要一个有效的数组表示。下面是程序运行时的输出。
asdasd 
_________________________________
 1  2  3  4  5  6  7  8  9 10 11 
   (x-axis) Length of words 
---------------------------------
[0 SPACE] [0 NEWLINE] [0 TAB] 
[1 WORD] [0.000 KILOBYTE] [6 CHAR]



6---


ARRAy= 1  1  1  1  1  2  0  0  0  0 

这是我的代码

#include <stdio.h>
#define MAX 10


int main (void) {
    //
    int nc,nw,nt,ns,nl;  //nc = bytes, nw = words, nt = tabs, ns = spaces, nl = newlines
    nc = nw = nt = ns = nl = 0;
    //
    int c;                     //getchar()
    int done = 0;             //don't even know I'm a noob just ignore...
    int tmp = 0;              //last word count (this works well)
    int array[MAX + 1];      //For outputting screen formatters like ---
    int count[11];           //THIS is whats broken random values
    int state = 0;
    int waslast = 0;
    float kbcount = 0;
    for (c = 0; c <= MAX; c++)
    count[c] = 0;

    while (done == 0) {
        c = getchar();
        ++nc;

        if (c == ' ' || c == '\n' || c == '\t') {
            waslast = 1;



            if (c == '\t') {
                ++nt;
                state = tmp;
                tmp = 0;
            }
            else if (c == '\n') {
                ++nl;
                state = tmp;
                tmp = 0;
            }
            else if (c == ' ') {
                ++ns;
                state = tmp;
                tmp = 0;
            }
        }

        if (c == EOF) {
            done = 1;
        }
        else if (c != ' ' && c != '\n' && c != '\t') {
            tmp++;
            state = tmp;
            if (waslast == 1) {
                nw++;
                waslast=0;
            }
            if (nc == 1)
                nw++;
        }

        if (tmp <= 10)
            count[tmp-1]++;       //Completely random assignments
        else
            count[11]++;          //This is broken
    }



    // END WHILE
    //
    //



    printf("\n");
    for (c = 1; c <= MAX + 1; c++) {
        printf("___");

    }

    printf("\n");

    for (c = 1; c <= MAX + 1; c++) {
        array[c] = c;
        printf("%2d ", array[c]);
    }

    printf("\n   (x-axis) Length of words \n");

    for (c = 1; c <= MAX + 1; c++){
        printf("---");
    }

    kbcount = (nc-1)/1024;

    printf("\n[%d SPACE] [%d NEWLINE] [%d TAB] \n[%d WORD] [%.3f KILOBYTE] [%d CHAR]\n\n\n\n%d---\n\n\n",
           ns,nl,nt,nw,kbcount,(nc -(nl+nt+ns))-1,state);

    printf("ARRAy=");
    for (c = 0; c<MAX ;++c)
        printf(" %d ",count[c]);
    return 0;


}

~ ~

3个回答

10

c语言中数组的下标从0开始计数。count[11]++; 超出了该数组的界限。

一个长度为11的数组的有效下标从0到10(包括0和10)。该数组的第11个元素为count[10]


4
您正在犯三个主要错误。前两个是风格上的,使我和其他大多数人不想阅读代码和提供帮助。最后一个是您的逻辑错误...至少有一个。
  1. 花时间输入有意义的变量名。或者至少是缩写的。比如 NumTabs 或者适合您的风格。
  2. 掌握好您的缩进风格。正确的缩进可以更容易地理解程序流程。

因此,您最显眼的错误是当您这样做:

count[11]++;          //This is broken

在代码中,它在更高的位置被定义为:
int count[11];

该数组中有11个整数。但是,你不是从1开始引用它们,而是从0开始。count[0]、count[1]...count[10]都是有效的。如下所示:

0 1 2 3 4 5 6 7 8 9 10

正如你所看到的,尽管只到10,但这是11个数字。如果你想访问count[11],你必须将count声明为

int count[12];

这被称为偏移量错误(off-by-one error)。虽然它是一个相当基础的错误,但不要觉得太糟糕,即使是专家也会犯同样基本错误的(通常更复杂、严重)版本。字符串处理通常也很麻烦。

无论如何,如果这还不够清楚,如果您想在概念上想象一下 - 这不是它的工作方式,但是在概念上 - 您的数组分配了11个int,并且“count”是其指针。所以(pointer+0)将指向第1个整数,这与pointer[0]的含义相同。(pointer+1)也就是说pointer[1]将指向第2个整数。(pointer+5)/pointer[5]将指向第6个整数,(pointer+11)/pointer[11]将指向第12个整数-但实际上只有11个。


抱歉,正如我所说的,这是从“你好,世界”开始的下一步,也是我在stackoverflow上的第一篇帖子。我也不知道count[10]是指0-9还是0-10,但现在我明白了。这本书中没有太多适合新手的信息,但感谢您的帮助。 - user1114092
@user1114092:count[10] 创建了 count[0]count[1]count[2]、……和 count[9]count[10] 是未定义的,但访问它很可能会导致一些不好的事情发生。 - wallyk
@KeithThompson 不需要道歉 - 特别是如果这是你从“Hello World”迈出的第一步。你做得很好!从经验中获得一些建议 - 早期养成好习惯,这样你就不必(痛苦地)学习错误的习惯。mctyler的帖子大大压缩并改变了你的代码,但如果你仔细研究,他也提出了一些好的建议(特别是使用枚举。这是一个相当清晰的简单“有限状态机”的例子),尽管它可能超出了你目前的能力范围。 - std''OrgnlDave
@user1114092 唉,抱歉,我也是 StackOverflow 的新手。我本意是想直接回复你的,而不是 KeithTompson 只是编辑了缩进并发表了评论 :-P (谢谢 Keith!) - std''OrgnlDave

1

你的程序还有三个错误点:

  • 整数除法的结果是一个整数,所以在这里:

    float kbcount = (nc-1)/1024;

变量kbcount被设置为nc - 1除以1024整数结果。

我认为你想要表达的是:

float kbcount = (nc - 1) / 1024.0;

因为其中一个部分(分母)是浮点数,优先级规则将两个部分转换为浮点值,然后除法变成浮点除法,所以结果将是一个带有其小数(十进制)值的浮点值。请阅读《C程序设计语言》第2.7节(第42页)以获得更好的解释。

接下来,

您可以简化逻辑,确定 c 是单词的一部分还是“空格”(空格,制表符,换行符),使您的程序更短,更易于理解。

E.g.:

enum state_t = {whitespace, printable};
...

enum state_t state = whitespace;
while (EOF != (c = getchar())) {
    bytes++;

    if (is_whitespace(c)) {
        if (state == printable) { /* the whitespace ends the 'word' state */
            count[length % MAX]++;
            length = 0;
        }
        ...
        state = whitespace;
    } else {
        if (state == whitespace) {
            /* starting a new word */
            wordcount++;
        }
        length++ /* word length */
        state = printable;
    }
    ...
    /* anything else you want to do */
}
/* done reading standard input (stdin), now print report... */

注意使用更好的变量名会使阅读更加容易。

最后,

  • 你是一个初学者,而不是一个“noob”(新手)。

参考:

//don't even know I'm a noob just ignore...

说实话,每个程序员在开始学习新的编程语言时都会犯很多错误。但这是自然学习过程的一部分。你通过探索、尝试新事物来学习,并发现什么有效,什么无效。这是一个过程,希望你能享受努力解决问题的挑战,不仅要考虑解决问题的方案,还要将解决方案拆分成逻辑上合理的步骤,并用合成语言(在这种情况下是C语言)正确地编写它。

你知道这可能是我见过的最简单的有限状态机设计模式之一。你应该将其概括并制作教程,这个概念和设计模式非常有用。大多数你能找到的教学文献都很糟糕,在我看来。 - std''OrgnlDave
@OrgnlDave,谢谢。我并不是在尝试确保它是一个有限状态机(FSM)设计,这也许是为什么它简单易懂的原因。假期结束后,我会考虑写一篇教程。 - mctylr

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