【问题标题】:How are if statements in C syntactically unambiguous?C 中的 if 语句在语法上如何明确?
【发布时间】:2016-01-16 20:08:14
【问题描述】:

我对 C 了解不多,但我了解基础知识,并且据我所知:

int main() {
  if (1 == 1) printf("Hello World!\n");
  return 0;
}

int main() {
  if (1 == 1) 
    printf("Hello World!\n");
  return 0;
}

int main() {
  if (1 == 1) {
    printf("Hello World!\n");
  }
  return 0;
}

在语法上都是完全等价的。陈述属实;字符串被打印;大括号(显然)是可选的。

有时,尤其是在 SO 上,我会看到类似以下内容:

int main() {
  if (1 == 1)
    printf("one is one\n");
  printf("is this inside the if statement??/who kn0WS\n");
  return 0;
}

由于CodeGolf 的权力,我被引导相信C 与空格无关;词法分析器将标记分解为其组成部分并去除字符串外部的空白。

(我的意思是,每个语句都使用分号的全部原因是解析器可以去除 \n\t 文字空格,并且仍然知道每个语句的结束位置,对吗? ?)

那么,如果要忽略空格,如何能够明确地解析前面的代码(或者也许有人可以提出一个更好的例子来说明我的意思)?

如果 C 程序员想要使用依赖于空格的 Python 语法编写,他们为什么要编写 C,以及为什么在任何地方教 C 的地方都教它可以编写词法歧义(对我、程序员和计算机而言)像这样的陈述?

【问题讨论】:

  • 你为什么不阅读 C11 草案标准,6.8.4 Selection statements(可能还有6.8.2 Compound statement),看看你能不能弄明白?

标签: c syntax lexical-analysis


【解决方案1】:
if (1 == 1)
  printf("one is one\n");
printf("is this inside the if statement??/who kn0WS\n");

第二个printf() 不应该在if 语句内部执行。

原因是上一行以分号结尾,表示要执行的if-block结束。

(我的意思是,每个语句上都使用分号的全部原因 是这样解析器可以去除 \n、\t、文字空格并且仍然知道在哪里 每个语句都结束了,对吧??)

那么如何才能明确地解析前面的代码 (或者也许有人可以想出一个更好的例子来说明我的意思), 如果要忽略空格?

解析示例

if (1 == 1) // if - () - 语句(或块)跟随,跳过所有空格

// 没有找到{ -> 单条语句,扫描到;(外引号/cmets)

printf("one is one\n"); // ;遇到,if-block结束

没有大括号,只有一个语句属于 if-block

但是,正如已经说过的,使用大括号是一个好习惯。如果您稍后添加一个语句(例如一个快速的临时 printf()),它将始终位于块内。

特殊情况

int i = 0;
while(i++ < 10);
    printf("%d", i);

这里printf() 只会执行一次。标记while()末尾的;

如果是空语句,最好使用:

while(i++ < 10)
    ;

为了明确意图(或者,也可以使用空块{})。

【讨论】:

  • 那么无括号的 if 块最多可以包含一个语句吗?
  • 是的 - 但最好使用牙套(俗话说 - 总是使用牙套,这样你就永远不会被裤子夹住)。
  • 换行符 ; 是一个空块 {}
【解决方案2】:

在 C 中,if 语句恰好在真值表达式之后使用语句,而不考虑缩进。通常为了清楚起见,该语句是缩进的,但 C 忽略缩进。无论如何,您的任何示例都没有歧义。

在 C 和许多其他语言中,模棱两可的是“悬空的 else”。例如,假设您有一个嵌套的if 语句,在第二个语句之后有一个else。它可以分组为:

if (expr)
    if (expr)
        statement
    else
        statement

或者它可以分组为:

if (expr)
    if (expr)
        statement
else
    statement

这两者之间的唯一区别是它们的缩进方式,C 忽略了这一点。在这种情况下,歧义通过使用第一种解释来解决,即else 语句绑定到最近的前面if 语句。要实现第二种解释,需要花括号:

if (expr) {
    if (expr)
        statement
}
else
    statement

但是,即使在第一种情况下,最好包含花括号,即使它们不是必需的:

if (expr) {
    if (expr)
        statement
    else
        statement
}

【讨论】:

  • 大部分来自golang,我希望他们需要的。
  • @cat: ...Python 通过要求局部一致的缩进避免了这个问题。剥皮的方法不止一种……哦,对不起。解决这个问题的方法不止一种。
  • @JonathanLeffler 确实如此,但现在只有当我可以使用带有大括号和可选静态类型的 Python 时(另外,感谢您的笑声:P)
【解决方案3】:

tl;dr 唯一的模糊之处在于人类阅读的难度。从编译器的角度来看,语法非常明确。

if 语句之后只有两种(可编译且语法上可接受的)可能性:

  1. 大括号,如

    if(x) {
        DoFoo();
    }
    // or
    if(x) { DoFoo(); }
    

    在这种情况下,如果满足条件,{...} 中的任何内容都会执行。

  2. 没有大括号,如

    if(x)
        DoFoo();
    // or
    if(x) DoFoo();
    

    在这种情况下,只有满足条件时才会执行下一条语句

C 与空格无关是正确的。因此,省略大括号会导致一些棘手的错误。例如,在这段代码中,DoBar() 无论是否满足条件都会执行:

if(x)
    DoFoo();
    DoBar();

大括号的使用不一致也很容易导致无效代码。例如,从人类的角度来看,这看起来是有效的(一目了然),但事实并非如此:

if(x)
    DoFoo();
    DoBar();
else
    DoBaz();

从编译器的角度来看,您发布的所有示例都不是模棱两可的,但从人类的角度来看,没有大括号的版本令人困惑。省略大括号经常会导致难以发现的错误。

【讨论】:

  • @TomKarzes 已修复。谢谢。
【解决方案4】:

没有大括号,它只是 if 之后的下一条语句。空格无关紧要

始终使用牙套是一种很好的做法,可以让生活更轻松。缩进也很好。这样代码就很容易阅读,并且当人们在 if 之后添加/删除语句时不会导致错误

【讨论】:

    【解决方案5】:

    可读性是声明中唯一的歧义:

    if (1 == 1)
      printf("one is one\n");
    printf("is this inside the if statement??/who kn0WS\n");
    

    if(...) 语句之后的第一个语句应该执行的唯一时间是它被评估为 TRUE。

    大括号,{...} 有助于消除可读性歧义,

    if (1 == 1)
    {
         printf("one is one\n");
    }
    printf("is this inside the if statement??/who kn0WS\n");
    

    但是语法规则还是一样的。

    意见不一,但我总是选择使用大括号。
    在编写代码时不使用它们很好。但是在路上,你只知道有人会出现并在第一个语句下添加另一个语句并期望它被执行。

    【讨论】:

      【解决方案6】:

      一般来说,在单条指令的语句或循环中,大括号是可选的;相反,如果您有两个或更多说明,则必须添加它们。 例如:

      for(i = 0; i < 2; i++)
         for(j = 0; j < 4; j++)
            If(...)
               printf(..);
            else
               printf(..);
      

      相当于:

      for(i = 0; i < 2; i++)
         {
               for(j = 0; j < 4; j++)
               {
                   If(...)
                   {
                       printf(..);
                    }
                    else
                   {
                       printf(..);
                   }
              }
         }
      

      您可能会注意到,这更多地与代码的缩进有关。就我个人而言,如果我有一条指令,我不会使用大括号,因为这样做会使你的代码更短更清晰。

      【讨论】:

      • 如果您在大括号上使用 K&R 样式(例如 if (cond) {} else { ),它就不会那么长了...
      • @JohnHascall 你的意思是例如:if(..) {..} 在一行吗?
      • 在评论格式中很难成功传达,但在一行中简短的if (cond) statement;就是一个例子。四行的if (cond) { statement1; statement2; } 是另一个。五行上的if (cond) { statement1; } else { statement2; } 是另一个。
      • @JohnHascall 我更喜欢我写的那个,因为一眼就能看出是否缺少大括号,以及大括号内的说明指的是哪些。顺便说一句,我知道 K&R 风格,但就像我写的那样,在这些简单的情况下,我认为个人喜好会引导讨论
      • 我更喜欢 K&R,因为它允许您一次查看更多代码,我发现在某些情况下这对理解很有帮助。但是,理性的人可以不同意...
      【解决方案7】:

      使用大括号的另一个原因是,一个简单的错字可能会让你很难受:

      #include <stdio.h>
      int main(void) {
        if (0 == 1)
          printf("zero is one\n"),
        printf("is this inside the if statement?? /who kn0WS\n");
        return 0;
      }
      

      仔细看……

      【讨论】:

      • 确实!我也看到你偷偷摸摸地试图解除我的三元组,但没有结束;-)
      • Trigraphs 永远不应该从它们来自的任何孔口被黑暗喷射......
      • 我听说人们称它们为“关于 C 的最聪明/最有用的东西之一”,但除了让程序员感到困惑之外,我看不出它们有什么用途。
      • 标准组最终意识到他们是多么愚蠢,并想出了不那么邪恶的二合字母。现在,如果他们只放逐三元组。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多