1988 C代码有什么问题?


94

我正在尝试从“ The C Programming Language”(K&R)一书中编译这段代码。它是UNIX程序的基本版本wc

#include <stdio.h>

#define IN   1;     /* inside a word */
#define OUT  0;     /* outside a word */

/* count lines, words and characters in input */
main()
{
    int c, nl, nw, nc, state;

    state = OUT;
    nl = nw = nc = 0;
    while ((c = getchar()) != EOF) {
        ++nc;
        if (c == '\n')
            ++nl;
        if (c == ' ' || c == '\n' || c == '\t')
            state = OUT;
        else if (state == OUT) {
            state = IN;
            ++nw;
        }
    }
    printf("%d %d %d\n", nl, nw, nc);
}

我收到以下错误:

$ gcc wc.c 
wc.c: In function main’:
wc.c:18: error: else without a previous if
wc.c:18: error: expected ‘)’ before ‘;’ token

本书的第二版是1988年出版的,我对C还是很陌生。也许它与编译器版本有关,或者我只是在胡说八道。

我已经在现代C代码中看到了对该main函数的不同用法:

int main()
{
    /* code */
    return 0;
}

这是新标准还是我仍然可以使用无类型的main?


4
不是答案,而是另一段代码,需要仔细研究|| c = '\t')。这看起来与该行上的其他代码相同吗?
2011年

58
32个投票赞成调试+错字问题?!
Lightness Races in Orbit

37
@ TomalakGeret'kal:您知道,旧东西的价值更高(葡萄酒,绘画,C代码)
Sergio Tulentsev 2011年

16
@César:我有权利表达自己的意见,我将感谢您不要试图审查它。碰巧的是,这不是一个用于调试代码和解决印刷错误的网站,而印刷错误是“本地化”的问题,对任何其他人都无济于事。这是一个有关编程语言问题的网站,而不是为您进行基本调试和参考工作的网站。技能水平是完全无关的。阅读FAQ,也许还要阅读这个meta问题
Lightness Races in Orbit

11
@ TomalakGeret'kal当然可以表达您的意见,尽管具有建设性,但我不会审查您的意见。我已经阅读了常见问题解答。我是一个热心的程序员问的是我现在面临实际的问题
塞萨尔

Answers:


247

你的问题是与你的预处理器定义INOUT

#define IN   1;     /* inside a word */
#define OUT  0;     /* outside a word */

请注意,您在每一个中都使用尾随分号。当预处理器扩展它们时,您的代码将大致如下所示:

    if (c == ' ' || c == '\n' || c == '\t')
        state = 0;; /* <--PROBLEM #1 */
    else if (state == 0;) { /* <--PROBLEM #2 */
        state = 1;;

第二个分号导致else之前没有if匹配号,因为您没有使用花括号。所以,从预处理器定义中删除分号INOUT

在这里学到的教训是 预处理程序语句不必以分号结尾。

另外,您应该始终使用大括号!

    if (c == ' ' || c == '\n' || c == '\t') {
        state = OUT;
    } else if (state == OUT) {
        state = IN;
        ++nw;
    }

else上面的代码没有任何歧义。


8
为了清楚起见,问题不在于间距,而在于分号。在预处理程序语句中不需要它们。

@丹感谢您的澄清!分号确实是问题所在!多谢你们!
–César

2
@César:不客气。大胆的建议将希望将来使您摆脱困境,当然对我有所帮助!
user7116 2011年

5
@César:习惯于在宏周围加上括号也是一个好主意,因为您通常希望首先对宏进行评估。在这种情况下,由于值是单个标记,所以没有关系,但是在定义表达式时,忽略括号可能导致意外结果。
styfle

7
“不需要它们”!=“没有它们”。前者永远是正确的;后者取决于上下文,并且在这种情况下是更相关的问题。
Lightness Races in Orbit

63

此代码的主要问题是它不是 K&R的代码。它包括宏定义之后的分号,而本书中没有这些分号,正如其他人指出的那样,它改变了含义。

除非进行更改以尝试理解代码,否则除非您理解它,否则应将其搁置。您只能安全地修改您了解的代码。

这可能只是您的错字,但这确实说明了编程时需要理解和注意细节的需求。


9
对于那些学习编程的人来说,您的建议并不是非常有建设性。修改代码正是您了解编程细节的方式。
user7116 2011年

12
@sixlettervariables:这样做时,您应该知道已经进行了哪些更改,并尽可能少地进行更改。如果OP故意进行了更改,并且进行了尽可能少的更改,那么他可能不会问这个问题,因为他很清楚发生了什么。他本来可以将IN的宏更改为没有错误,然后将OUT的宏更改为具有两个错误,第二个错误就是抱怨他刚刚添加的分号。
jmoreno 2011年

5
除非您错误地认为在预处理程序指令行的末尾包括分号,否则您可能不会知道自己不包括分号。您可以从表面上看待它,可以阅读很多代码,并注意到它们似乎从未出现过。或者,OP可以通过包含它们来搞乱,询问“ bizarre”错误,然后找出:哎呀,预处理器指令不需要分号!这是编程,而不是《惊吓直人》。
user7116 2011年

14
@sixlettervariables:是的,但是当代码不起作用时,显而易见的第一步是“哦,好吧,那么我从C的发明者写的书中无任何原因地更改了代码,问题。那我就撤消。”
Lightness Races in Orbit


34

巨集后不应有任何分号,

#define IN   1     /* inside a word */
#define OUT  0     /* outside a word */

它应该是

if (c == ' ' || c == '\n' || c == '\t')

谢谢,分号是问题。第二个是错字!
–César

21
下次,请直接从文本编辑器粘贴您使用的确切代码。
Lightness Races in Orbit

@ TomalakGeret'kal好吧,我没有,但我会的,但是你是怎么找到的呢?
onemach 2011年

1
@onemach:您说的;是一个拼写错误,不会影响问题,这意味着您的问题而不是您实际使用的代码中的错字。
Lightness Races in Orbit

24

IN和OUT的定义应如下所示:

#define IN   1     /* inside a word  */
#define OUT  0     /* outside a word */

分号引起了问题!解释很简单:IN和OUT都是预处理器指令,从本质上讲,编译器将在源代码中将所有出现的IN替换为1,将所有出现的OUT替换为0。

由于原始代码在1和0之后有分号,因此当代码中替换IN和OUT时,数字后的多余分号产生了无效代码,例如以下行:

else if (state == OUT)

最终看起来像这样:

else if (state == 0;)

但是您想要的是:

else if (state == 0)

解决方案:删除原始定义中的数字后的分号。



7

这不完全是问题,但是的声明main()也已过时,应该像这样。

int main(int argc, char** argv) {
    ...
    return 0;
}

编译器将假定函数的int返回值为w / o one,我确信编译器/链接器将解决缺少argc / argv的声明和缺少返回值的问题,但是它们应该在那里。


3
这是一本好书,据我所知,这是仅有的两本有关C的有价值的书之一。我很确定较新的版本符合ANSI C(可能是C99之前的ANSI C)。关于C的另一本值得一读的书是Peter van der Linden撰写的Expert C Programming Deep C Secrets。
比尔

我从来没有说过。我只是被评论说,为了使其与今天的工作方式保持一致,应该更改主要内容。
比尔

4

尝试在代码块周围添加大括号。在K&R风格可以是不明确的。

看第18行。编译器告诉您问题出在哪里。

    if (c == '\n') {
        ++nl;
    }
    if (c == ' ' || c == '\n' || c == '\t') { // You're missing an "=" here; should be "=="
        state = OUT;
    }
    else if (state == OUT) {
        state = IN;
        ++nw;
    }

2
谢谢!实际上,该代码在第二个情况下无需大括号即可:)
–César

5
+1。不仅模棱两可,而且有些危险。当(如果)if稍后在块中添加一行时,如果由于块已超过一行而忘记添加花括号,则可能需要一段时间来调试该错误...
The111

8
@ The111从未发生过我。我仍然不相信这是一个真正的问题。我使用无括号样式已有十多年了,在扩展块体时,我从未忘记添加括号。
康拉德·鲁道夫

1
@ The111:在这种情况下,花费了一些SO贡献者几分钟的时间:P如果您是一个能够将语句添加到if子句并“忘记”更新括号的程序员,那么您就不是一个非常好的程序员。
Lightness Races in Orbit

3

一个简单的方法是使用括号如{}每个ifelse

if (c == '\n'){
    ++nl;
}
if (c == ' ' || c == '\n' || c == '\t')
{
    state = OUT;
}
else if (state == OUT) {
    state = IN;
    ++nw;
}

2

正如其他答案所指出的那样,问题在于#define分号。为了尽量减少这些问题,我总是更喜欢将数字常量定义为const int

const int IN = 1;
const int OUT = 0;

这样,您就可以摆脱许多问题和可能出现的问题。它受到两方面的限制:

  1. 您的编译器必须支持const-在1988年通常不是真的,但是现在所有常用的编译器都支持它。(AFAIK const是从C ++“借用的”。)

  2. 您不能在某些需要类似字符串的常量的特殊地方使用这些常量。但我认为您的程序并非如此。


我更喜欢另一种方法是枚举-它们可以在特别的地方是可以使用(如数组声明)const int不能在C.
迈克尔·伯尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.