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

我试图从“C编程语言”(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; } 

这是一个新的标准,还是我仍然可以使用一个无types的主?

你的问题是你的预处理器定义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含义。

这个代码的主要问题是它不是来自K&R的代码。 它包含macros定义之后的分号,这些在本书中不存在,正如其他人指出的那样改变了含义。

除了在尝试理解代码时做出改变之外,您应该保持独立,直到您理解为止。 你只能安全地修改你理解的代码。

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

macros后面不应该有分号,

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

它应该可能是

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

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

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

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

由于原来的代码在1和0之后有一个分号,所以在代码中IN和OUT被replace之后,数字后面的额外分号会产生无效代码,例如:

 else if (state == OUT) 

结束了看起来像这样:

 else if (state == 0;) 

但是你想要的是这样的:

 else if (state == 0) 

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

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

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

编译器将假定一个函数没有一个int的返回值,并且我确定编译器/链接器会在argc / argv缺less声明和缺less返回值的情况下工作,但是它们应该在那里。

正如你所看到的,macros中有一个问题。

GCC可以select在预处理后停止。 (-E)该选项用于查看预处理的结果。 实际上,如果您正在使用c / c ++中的大型代码库,该技术是非常重要的。 通常makefile会在预处理之后停止。

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

尝试围绕代码块添加明确的大括号。 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; } 

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

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

正如其他答案指出的,问题是在#定义和分号。 为了最大限度地减less这些问题,我总是喜欢将常量定义为const int

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

这样你就摆脱了许多问题和可能的问题。 它受到两件事的限制:

  1. 你的编译器必须支持const (在1988年并不是这样),但是现在它被所有常用的编译器支持。 (AFAIK const是从C ++“借来的”。)

  2. 你不能在一些需要类string常量的地方使用这些常量。 但是我认为你的计划并不是这种情况。