返回语句中的结果周围是否加括号很重要吗?

92

在函数内部,这两个语句有什么区别吗?

bool returnValue = true;
// Code that does something
return(returnValue);

还有这个?

bool returnValue = true;
// Code
return returnValue;

前者在returnValue周围有括号。


谢谢Rob,你成功地抓住了问题的实质。本质上我想知道编译器是否会做任何特殊处理(比如尝试先计算表达式),还是直接忽略它。 - Jose Villalta
1
回答这个问题对于任何 c++ / c 程序员来说都很困难。更具体地说明语言定义会更好,但是9年后我不知道如何解决这个问题。 - Jonas Stein
对于C,这里有一个重复的问题:https://dev59.com/GHVC5IYBdhLWcg3w21Iq - Jonas Stein
10个回答

148

截至C++14,有一个区别。

C++14新增了一种边缘情况,即括号在返回值周围可能改变语义。此代码片段显示了声明两个函数。唯一的区别是返回值周围的括号。

int var1 = 42;
decltype(auto) func1() { return var1; } // return type is int, same as decltype(var1)
decltype(auto) func1() { return(var1); } // return type is int&, same as decltype((var1))

在第一个func1中返回一个int,而在第二个func1中返回一个int&语义上的差异与周围的括号直接相关auto关键字在其最新形式中引入了C++11。在C++语言规范中它被描述为:

指定正在声明的变量的类型将根据其初始化器自动推断出来。对于函数,指定返回类型是尾部返回类型,或将从其返回语句中推断出来(自C++14起)

此外,C++11还引入了在C++语言规范中描述的decltype关键字:
检查实体的声明类型或查询表达式的返回类型。
1. 如果参数是对象/函数的非括号名称,或者是成员访问表达式(object.member或pointer->member),则decltype指定此表达式指定的实体的声明类型。 2. 如果参数是类型为T的其他任何表达式,则: a) 如果表达式的值类别是xvalue,则decltype指定T&&。 b) 如果表达式的值类别是lvalue,则decltype指定T&。 c) 否则,decltype指定T。
请注意,如果对象的名称被括在圆括号中,它将变成一个lvalue表达式,因此decltype(arg)和decltype((arg))通常是不同的类型。
在C++14中,允许使用decltype(auto)作为函数返回类型。原始示例是括号产生语义差异的地方。重新访问原始示例:
int var1 = 42;
decltype(auto) func1() { return var1; } // return type is int, same as decltype(var1)
decltype(auto) func1() { return(var1); } // return type is int&, same as decltype((var1))

decltype(auto)允许函数的尾部返回类型从返回语句中的实体/表达式推导出来。在第一个版本中,return var1;实际上等同于返回类型decltype(var1)(根据规则1是int返回类型),而在第二个情况下,return (var1);实际上等同于decltype((var1))(根据规则2b是int &返回类型)。

括号使返回类型变为int&而不是int,因此改变了语义。故事的寓意是 - "并非所有返回类型上的括号都是相等的"


没有括号的return语句仍然是一个左值表达式,对吗?除非在那种情况下它被视为xvalue。你能解释一下没有括号的return语句的值类别吗? - void.pointer
1
return语句返回其所包含表达式的值,即它捕获表达式中的rvalue并返回。我们从不返回lvalue。可能某些编译器存在与您描述的bug,并且出于您提供的原因,但return(x);等同于return&x;绝不应该是这种情况,也不应该导致返回x的值,但类型为对x的引用。 - Theodore Murdock
4
哇,真是一个丑陋的语言构造。完全不透明。不过写得很好,谢谢你的分享。 - Paul Sanders
8
C++是一种非常有趣的编程语言。但有时这些隐藏的“特性”让我感到很糟糕... - andreee
我们的代码中充斥着那些不必要的括号。我们也经常使用auto。只要我们远离decltype,我们的语义就永远不会改变吗?当我们转移到下一个包含下一个Visual Studio版本的编译器版本时,我担心会出现意外行为。 - OneWorld
这个答案更适用于 https://dev59.com/b1UL5IYBdhLWcg3wOVy-,那里的问题更具体涉及到语言(C++)。 - Jonas Stein

6

没有区别。

使用括号的一个原因是如果您想在返回之前评估表达式,但在您的示例中,没有理由这样做。请参见:

Parenthesis surrounding return values

进行进一步讨论。


3
即使使用复杂的表达方式,这些括号仍然不会导致不同的行为。它们只是使含义(主观上!)更明显,方便人类读者理解。 - aschepler
@Karl “使用括号的一个原因是如果您想在返回之前评估表达式”。您能举个例子吗? - Chris Middleton
3
不,因为这个说法在这个帖子和之前的帖子中一样毫无意义。return m * x + creturn (m * x + c)return ((m * x) + c)等表达方式没有任何实际区别,并且在我看来它也不会更好或更直观。请注意,这里不会添加解释或额外的内容。 - underscore_d

4
上面例子中的括号是多余的,它们被有效地忽略了。
这就像是类似于...
int x = (5);

这里的括号也会被忽略。

1
@John:已经有效地被忽略了。 :) - James
3
+1...这是唯一一个说明括号实际上是多余的并展示它们的使用有点愚蠢的回答。 - Konrad Rudolph
@DrP3pp3r,你在答案中缺少代码示例,并且(未明确但暗示的)事实是,即使使用括号将返回值括起来的人,几乎没有人编写这样的代码,因为广泛认为该示例中的括号实际上非常愚蠢。是的,在答案和我的评论中有很多未明确说明的依赖共享上下文。 - Konrad Rudolph
@KonradRudolph: return是用于像for、if、while或函数等控制结构中的关键词,而它们可能需要类似于参数或表达式的东西。后者需要您在return不需要的地方使用括号。这是语言创作者的任意选择,不是吗? 此外,如果x不是int类型而是类类型:就会想到x.operator=(5)... ;) - DrP3pp3r
@KonradRudolph:这并不含糊。这是一个优先级的问题。否则,使用f x形式的数学教科书也会有同样的问题,不是吗?“数学通常不在函数应用的参数周围使用括号”是不正确的。我无法回忆起任何教授在黑板上或在我的大学(HfT斯图加特,数学系)的脚本中使用f x形式的讲座。每个人都使用f(x)。 - DrP3pp3r
显示剩余14条评论

4

据我所知,没有什么不同。

在C++中,表达式可以采用以下形式:expr(expr)。因此,后者是打字更多的表达式。有关此内容的进一步阅读,请参考语法(查找“expression”)。


David,谢谢你提供的链接。我在Stroustrup的C++书中有语法,但是(我想是因为懒),我不经常看它,现在我把它加入了我的浏览器书签,我可以更频繁地参考它了。 - Jose Villalta

3

不,你的代码没有任何区别。


1

没有区别!!

如果涉及到复杂表达式,人们会使用括号。

顺便说一下,return是一个语句而不是函数。


1

这两者之间没有区别,不过如果加上括号可以使表达更易读和清晰。


1
但它从未这样做过。为什么会这样呢?一个没有括号的表达式有什么难以阅读的地方吗?在我看来,添加括号只会显得凌乱。至于似乎更清晰,人们是否认为return是一个贪婪的运算符,而return m * x + c可能会返回m并丢弃其余部分,或者还有其他原因? - underscore_d

1

你在滥用地减慢编译器的速度!

括号的存在不仅会减慢预处理阶段,还会生成更复杂的抽象语法树:需要更多的内存和计算。


从语义角度来看,它们完全相同。无论是否有括号,return语句都会在返回之前完全评估表达式。


5
我完全同意这个想法。让编译器解析无用的冗余内容是没有任何好处的,这只会让它不断接近宇宙热寂。 - Maxim Egorushkin
那真的会有多大的差别吗?即使在一个大型代码库中? 这是编写代码的好指南吗?让编译器的生活更轻松?我们在谈论超微优化,而不是对人类可察觉的编译时间产生巨大影响。 如果if或循环只包含一个语句,花括号也是不好的吗? 也许我们也不应该在代码中使用注释。对编译器来说是无意义的工作。函数和变量也应该只有一个字符的名称。处理速度更快。对吧? - DrP3pp3r
在我看来,编写代码的主要目标应该是易于理解。 不优化。不论是编译时间还是运行时间。 - DrP3pp3r
@DrP3pp3r:这个回答显然是讽刺的。考虑到预处理器拉入了多少 MB 的头文件,再加上几百个括号在性能方面是毫无意义的。话虽如此,我鼓励大家写出易读的代码,这意味着避免不必要的混乱——在 return 语句中传递的表达式周围加上括号是没有意义的。 - Matthieu M.
@MatthieuM.:没听出讽刺意味,我的错。函数参数周围的括号(数学函数有它们,所以我们也有了,我想),在if、for和while之后也不是必需的,但语言发明者选择把它们放在那里)。它们是杂乱无用的吗? 此外,可读性可能在这里是一个非常主观的事情。也许你看到的是杂乱无章,而其他人看到的是有序的。 也许我们可以采用Alexandrescu的说法:“不要为小事烦恼。” - DrP3pp3r
@DrP3pp3r:实际上,在Rust中,ifforwhile语句中没有圆括号,也不需要。但是函数参数周围的括号更难去掉:f 3 + 4应该解析为f(3) + 4还是f(3 + 4)?可以使用优先级,但优先级规则很容易变得混乱。 - Matthieu M.

0

它们是相同的。 我经常看到括号语法,并且我总是问那些使用它的人:为什么?没有人能够回答为什么他们使用它。

bluntly sum it up,将返回表达式括在括号中的人不太理解类似函数的宏和函数之间的区别,或者对C中的运算符优先级或评估顺序规则感到困惑。从使用括号中没有编码样式的好处。

因此

return value;

比起

更正确。

return (value)

因为后者暗示着你不太知道自己在做什么 :)


为什么?也许是为了某种一致性。if、while和for也不是函数,但括号是必要的。 - DrP3pp3r
@DrP3pp3r return 是一个跳转语句,而不是选择或迭代语句。因此,它应该与 gotobreakcontinue 进行比较,而不是其他任何东西。你在使用 gotobreakcontinue 时是否使用括号?如果是,那么你是如何在语法上管理的呢? - Lundin
我不同意你的比较论点。因为从技术/语法上讲,return是一个跳转语句,它与函数、if语句、while循环和for循环有相似之处:它需要一些参数/表达式。而break和continue则不需要。goto也需要,但不允许使用括号。这是语言设计者的任意设计选择吗?你认为在这里不需要它们吗?函数、if语句、while循环和for循环也不需要。从第二个句子开始,你的回答变成了基于观点的。 - DrP3pp3r
@DrP3pp3r 首先,这个回答是古老的,来自一个不同时间的标准,关于什么是离题的标准也不同。 “你用函数,if语句,while循环和for循环也是一样。” 错误,你需要。 现在,如果您想比较returnif,则return的正式语法为: return expression;if的正式语法是if (expression)。 不管你喜欢与否,这就是语言的设计。 if通过正式语法定义要求括号。 - Lundin
我觉得你误解了我的意思。是的,当然,由于语法的规定,if、for、while需要括号。但这并不是必须定义语法的方式。有足够多的编程语言可以证明这一点。这是C语言创造者的选择。对于返回值,他们做出了不同的决定。并不是他们所有的选择都很好。 而且,有技术定义和人类视角之分。代码是为人类编写和阅读的。你喜欢纯粹主义,而其他人则不喜欢。 - DrP3pp3r

-3

在你的情况下,两者是相同的。


M.M,您能否解释一下您的评论? - Zeshan Khan
1
如果 a5,那么 return a++;return (a++);(两者相同)都会返回 5 - M.M
2
具体来说,后置递增/递减返回其操作数的修改前值,无论加上多少括号或其他徒劳的强制转换都无法改变这一点。 - underscore_d

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