为什么C语言在if语句中对简单条件要加括号?

31

听起来很愚蠢,但多年来我一直想不出需要它的用例。快速的谷歌搜索也没有发现有什么有价值的信息。

从记忆中,Bjarne Stroustrup提到过一个用例,但我找不到参考资料了。

那么为什么C语言不能拥有这个特性:

int val = 0;
if val
  doSomehing();
else
  doSomehinglse();

我可以接受“我们懒得为词法分析器添加支持”的理由,我只想弄清楚这个语法是否会破坏其他语言结构。考虑到 C/C++ 有多少奇怪的语法特性,我几乎认为这不会增加太多复杂性。


16
因为这就是语言的定义方式? - cx0der
1
我认为如果有大量嵌套的if / else块,这可能会成为一个问题。 - Hamish Grubijan
3
好问题。而cx0der问:“为什么它被定义成这样?”许多编程语言允许省略括号。在C语言族中是否存在这样的情况,即会导致解析器出现歧义?我想不到任何一个例子。 - jalf
1
@cx0der:许多编译语言都允许这样做。解释型与编译型与此无关。问题在于语言的语法,而不是代码在解析后执行的方式。 - jalf
4
一些其他语言使用“then”来标记条件的结尾,关键词起到括号的作用。丹尼斯·里奇选择不使用关键字来区分条件和操作,而是选择使用括号。一些语言有关键字来引入所有语句;这样的语言不需要括号,因为关键字标志着下一个语句的开始,所以条件和操作的分离是明确的。 - Jonathan Leffler
显示剩余7条评论
13个回答

52
如果在 if 结构中的表达式周围没有括号,那么以下语句的含义是什么?
if x * x * b = NULL;

它是否为真?

if (x*x)
    (*b) = NULL;

还是这样吗?

if (x)
    (*x) * b = NULL;

(当然这些只是愚蠢的例子,由于明显的原因甚至都不起作用,但你懂我的意思)

简而言之:在C语言中需要使用括号以消除任何语法歧义的可能性。


1
...或者 if ( x * x * b = NULL ) ; 程序解析器看起来会没问题,直到它尝试将函数与操作匹配。 - Potatoswatter
5
这并不是一个好的回答。C语言严格规定了运算符的操作顺序。如果那是有效的语法,它将被评估为if(NULL),因为*的优先级高于=,赋值语句的返回值是所分配的值,并且没有逻辑上的理由说明一个在条件语句周围不需要括号的编译器会在if后分隔语句(看看Python语法)。 - Beanz
11
Chris说,LR分析器需要能够决定条件结束的位置和条件块开始的位置。不同的语言有不同的方法,一些需要括号,一些需要换行符,一些使用“then”关键字。你不能回避这个问题。请告诉我一个没有任何机制将条件表达式与条件块分离的语言。如果你仍然不相信我,请尝试使用yacc构建自己的语言语法,你会看到所有出现的问题。 - Tamas Czinege
3
@Chris:即使有一种明确的解析方式,它也会更加晦涩难懂,让人不易阅读,因此不是一个好主意。例如,对于嵌套的 and/or/not 条件,我总是多加括号,即使它们不是必需的。这样可以节省大家思考隐含优先级如何运作的时间。 - Thilo
这种情况听起来像是需要让 if 语句几乎像一个运算符,具有自己的优先级规则。 - Igor Zevaka
显示剩余8条评论

13
告诉我如何解释以下内容:
if x ++ b;

看起来很傻,但是...

if( x ) ++b;
或者
if( x++ ) b;

或许 "x" 有一个重载的运算符,然后...

if( x ++ b){;}

编辑:

正如Martin York所指出的那样,“++”具有更高的优先级,这就是为什么我不得不添加运算符重载提示的原因。在处理现代编译器时,他是绝对正确的,直到您允许包含C ++中的所有重载好处。


4
后缀自增运算符具有更高的优先级,因此它会在最后执行。更好的做法是 if x + b; 是否为 if (x+b) {;} 或者 if (x) {+b;} - Martin York
3
一个更常见且易于理解的写法是 if x - b,因为对大多数人而言,一元减号比一元加号更加熟悉。 - jalf
1
"interpret" 中有两个 "e"。 - Jedidja

5

我认为一个更好的问题应该是“我们为什么需要这样的东西?”而不是“为什么我们没有它?”这会在词法分析器中引入另一个本来不必要的边缘情况,只是为了避免多输入4个字符; 同时增加了规范的复杂性,允许对已经完全支持所有可能情况的简单语法进行例外处理。更不用说这还有歧义的风险,在编程语言中并没有太大的价值。


5
许多语言都允许省略括号,减少噪音总是有助于提高清晰度。假装这是一种不可逾越的障碍有点儿可笑。最后一部分更有趣,你能否想到任何可能会引入歧义的情况?否则,你的答案似乎只是“括号对我来说已经足够好了!别来这里暗示我的语言不完美”。 - jalf
1
当然,这会增加复杂性。编译器必须弄清楚程序员是否给出了一个简单的条件(即bool),还是更复杂的条件;例如,如果用户有一个语句“if someObj doStuff()”,其中someObj具有用于隐式转换为bool的巨大函数,那该怎么办? - Collin Dauphinee
@dauphic:如果你曾经编写过解析器,你就会知道它只有在创建任何以前不存在的歧义时才会引入复杂性。为了使这个答案有意义,它必须显示这样的歧义。如果someObj可以隐式转换为bool,那么编译器将执行该操作,就像在它周围有()一样。只要这是代码可以被解析的唯一方式,它就不会增加任何复杂性。 - jalf
1
@Hostile Fork。不必要的样板代码很糟糕,但过多的表达灵活性也可能很糟糕,即使它不会引入歧义。你正在编写代码,而不是诗歌。没有必要使编译器变得不合理复杂(这很容易导致错误),你也必须考虑维护程序员。在更灵活的语言(如Perl)中,你可以轻松地创建只能写不能读的代码,这变得难以理解。 - Thilo
2
@jalf - “减少噪音总是有助于清晰度”显然是不正确的。举个简单的反例,你认为为什么有些人会添加完全不必要的括号?因为对于某些人来说,冗余的括号有助于清晰度!! - Stephen C
显示剩余3条评论

1
还有一件需要记住的事情:C语言是在磁带存储普遍的时代创建的,因此随机查找或向后浏览当前文件甚至其他文件并不可行(这也解释了为什么你必须在其他东西之前放置一些东西(即前向声明),尽管编译器应该能够自己解决)。

磁带与此无关。编译器设计中的重点始终是线性时间的从左到右处理。许多其他语言也有前向声明规则,不仅仅是磁带时代的语言。 - user207421

0

我也喜欢那种语法,但为了避免解析歧义,必须采用这种形式:

对于多个语句:

if val 
{ 
    doSomething();
    doOthers();
}

对于一个语句:

if val: 
   doSomething();

0

除非你想要大量的空格,否则你需要能够确定条件何时结束。在一些语言中,它是在条件周围使用(),在其他一些语言中,是使用关键字if并像答案中给出的那样。

如果x * x * b = NULL是一个始终计算为false的条件,或者在(x x)的情况下将null赋值给b,编译器无法知道条件何时结束,后续操作开始。因此,需要有一个明确的“后标”告诉编译器何时条件结束。在C(++), Java, c# 和其他语言中,使用()标记条件,在F#中,使用关键字'then',在其他一些语言中,则是使用换行/缩进。


0
根本原因是C和其他语言没有'then'关键字,因此它们需要另一种方式来界定条件表达式。像Pascal和PL/1这样的具有'then'关键字的语言不需要使用括号。同样适用于'while',在Pascal、PL/1等语言中有一个'do'。

实际上,如果编译器能够知道一个表达式何时结束,下一个表达式何时开始,这就足够了。(但正如DrJopeku的答案所示,它并不是这样。) - Paŭlo Ebermann
@Paŭlo Ebermann 当然这已经足够了,这就是“then”关键字提供的功能。如果没有“then”关键字,你会遇到可能的赋值语句,并且需要任意数量的前瞻。从语法角度来看,C中的“(”是多余的,而“)”在功能上与“then”相同。 - user207421

0

这只是一种结束语句条件子句的简单方法。 其他语言使用其他方式,例如“then”或在Python中使用“if condition:(后跟返回)” (在Python中,冒号总是让我困扰,我经常忘记加上它们,结果会出现不明显的错误。)

计算机语法通常需要一些独特的机制来识别语句或表达式的不同部分。在这方面,它与任何其他语言一样好或不好。 C/C++中的悬挂else问题是一个从语法角度来看存在歧义的例子,必须作为特殊情况处理以确保正确操作。(即它增加了编译器实现者的额外工作。)


0

这是因为它帮助程序员让编译器理解表达式的求解顺序(包括算术、逻辑、赋值等类型运算符的顺序),以便得到期望的输出。


-1

我认为你混淆了“方括号”和“圆括号”

这在C语言中是可能的:

void doSomething(){}
void doSomethingElse(){}

void main() {
    int val = 0;
    if( val ) 
        doSomething();
    else
        doSomethingElse();
}

并非所有的“C”语言都强制要求使用括号,并把大括号留作可选项。Go(问题)编程语言正好做了相反的事情

在Issue9中写同样的句子的正确方式是:

if val {
    doSomething();
} else {
    doSomethingElse();
}

这样以下内容就不合法:

if x * x * b = NULL; 

它应该是以下之一:

if x*x {
    *b=NULL;
}

或者 如果 x { xb=NULL; }

这里的大括号消除了由于缺少括号而产生的歧义。


太好了,这只是再次证明了要么是大括号要么是圆括号的概念。 - Igor Zevaka
或者像Python一样(我知道它不是来自“C”家族,但是)使用冒号来分隔句子。它可以是:if x * x: b = None - OscarRyz

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