这个1988 C代码有什么问题?

Cés*_*sar 95 c

我正在尝试编写"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);
}
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

$ gcc wc.c 
wc.c: In function ‘main’:
wc.c:18: error: ‘else’ without a previous ‘if’
wc.c:18: error: expected ‘)’ before ‘;’ token
Run Code Online (Sandbox Code Playgroud)

本书的第2版是从1988年开始的,我对C很新.也许它与编译器版本有关,或者我只是在胡说八道.

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

int main()
{
    /* code */
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这是一个新标准还是我仍然可以使用无类型主?

use*_*116 247

您的问题在于您的预处理器定义INOUT:

#define IN   1;     /* inside a word */
#define OUT  0;     /* outside a word */
Run Code Online (Sandbox Code Playgroud)

注意你在每个中都有一个尾随的分号.当预处理器扩展它们时,您的代码看起来大致如下:

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

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

这里学到的教训是预处理器语句不必以分号结尾.

此外,你应该总是使用大括号!

    if (c == ' ' || c == '\n' || c == '\t') {
        state = OUT;
    } else if (state == OUT) {
        state = IN;
        ++nw;
    }
Run Code Online (Sandbox Code Playgroud)

else上面的代码中没有悬念模糊.

  • 为清楚起见,问题不是间距,而是分号.您在预处理器语句中不需要它们. (8认同)
  • "不需要他们"!="不应该拥有他们".前者总是如此; 后者是依赖于上下文的,在这种情况下是更相关的问题. (7认同)
  • @César:习惯于将括号括在宏周围也是一个好主意,因为您通常希望首先评估宏.在这种情况下,它并不重要,因为值是单个标记,但是在定义表达式时遗漏parens会导致意外结果. (5认同)
  • @César:不客气.有抱负的建议有望让你在将来摆脱困境,当然帮助了我! (2认同)

jmo*_*eno 63

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

除非在尝试理解代码时进行更改,否则在理解之前应该不管它.您只能安全地修改您理解的代码.

这可能只是你的一个错字,但它确实说明了在编程时理解和关注细节的必要性.

  • @sixlettervariables:是的,但是当代码不起作用时,显而易见的第一步就是"哦,好吧,那么我在没有任何理由的情况下改变了C的发明者在书中写的代码,可能是我只是撤消那个." (14认同)
  • @sixlettervariables:当你这样做时,你应该知道你做了什么改变,并尽可能少地做出改变.如果OP故意做出改变,并尽可能少地改变,他可能不会问这个问题,因为他会清楚地知道发生了什么.他本来会改变IN的宏,没有错误,然后OUT的宏有两个错误,第二个会抱怨他刚刚添加的分号. (12认同)
  • 对于学习编程的人来说,你的建议并不十分有建设性.修改代码正是您理解编程细节的方式. (9认同)
  • 看起来除非你在预处理程序指令行的末尾包含一个分号,否则你可能不会知道你不要包含它们.你可以把它看作面值,你可以阅读很多代码并注意到它们似乎永远不会存在.或者,OP可能会因为包含它们而陷入困境,询问"奇怪"错误,并找出:oops,预处理器指令不需要分号!这是编程,而不是Scared Straight的一集. (5认同)
  • 让我们[在聊天中继续讨论](http://chat.stackoverflow.com/rooms/6174/discussion-between-sixlettervariables-and-tomalak-geretkal) (3认同)
  • @ TomalakGeret'kal:如果他复制并粘贴了代码,他今天就不会对预处理器有太多了解.此外,并不是说这本书没有错误. (2认同)
  • @ TomalakGeret'kal:我认为由于不了解预处理器的重要性或差异,他在转录期间出现了无意的错误.为什么这会变得如此巨大,我不确定. (2认同)
  • @ TomalakGeret'kal:我怀疑这个答案对初露头角的程序员有帮助(虽然这对于在公司编程的人来说是合理的建议).修改代码并遇到问题是最好的学习方法之一.右脑/左脑,除非为他们指出,否则有些人不会注意到. (2认同)

one*_*ach 34

宏之后不应该有任何分号,

#define IN   1     /* inside a word */
#define OUT  0     /* outside a word */
Run Code Online (Sandbox Code Playgroud)

它可能应该是

if (c == ' ' || c == '\n' || c == '\t')
Run Code Online (Sandbox Code Playgroud)

  • 下次请直接从文本编辑器粘贴您使用的_exact_代码. (20认同)

Ósc*_*pez 24

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

#define IN   1     /* inside a word  */
#define OUT  0     /* outside a word */
Run Code Online (Sandbox Code Playgroud)

分号导致问题!解释很简单:IN和OUT都是预处理器指令,实际上编译器会将所有出现的IN替换为1,并且在源代码中将所有出现的OUT替换为0.

由于原始代码在1和0之后有分号,当IN和OUT在代码中被替换时,数字后面的额外分号产生无效代码,例如这一行:

else if (state == OUT)
Run Code Online (Sandbox Code Playgroud)

结束看起来像这样:

else if (state == 0;)
Run Code Online (Sandbox Code Playgroud)

但你想要的是这个:

else if (state == 0)
Run Code Online (Sandbox Code Playgroud)

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


Jay*_*yan 8

如您所见,宏中存在问题.

GCC可以选择在预处理后停止.(-E)此选项对于查看预处理的结果很有用.实际上,如果您使用c/c ++中的大型代码库,该技术是一个重要的技术.通常,makefile将具有在预处理之后停止的目标.

有关快速参考:SO问题涵盖了选项 - 如何在Visual Studio中预处理后查看C/C++源文件?.它从vc ++开始,但也有下面提到的gcc选项.


Bil*_*ill 7

不完全是一个问题,但声明main()也是过时的,它应该是这样的东西.

int main(int argc, char** argv) {
    ...
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译器将承担一个函数int类型的返回值W/O之一,我敢肯定,编译器/连接将解决缺乏声明的argc/argv的和缺乏返回值的,但他们应该在那里.

  • 这是一本好书 - 据我所知,这本书中仅有的两本是关于C的书.我很确定新版本符合ANSI C标准(可能是C99 ANSI C之前的版本).另外一本关于C的书是Peter van der Linden的专家C编程Deep C Secrets. (3认同)

duf*_*ymo 5

尝试在代码块周围添加显式大括号.在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;
    }
Run Code Online (Sandbox Code Playgroud)

  • @The111永远不会发生在我身上.我仍然不相信这是一个真正的问题.十多年来我一直在使用无支撑风格,我从来没有忘记在扩展块体时添加支撑. (8认同)
  • +1.不仅模棱两可,而且有些危险.当(if)你稍后在`if`块中添加一行时,如果你忘记添加大括号,因为你的块现在不止一行,那么调试那个错误可能需要一段时间...... (5认同)
  • 谢谢!实际上,代码在第二个没有括号的情况下工作如果:) (2认同)