对单行 if 或循环使用大括号(即 )的目的是啥?
Posted
技术标签:
【中文标题】对单行 if 或循环使用大括号(即 )的目的是啥?【英文标题】:What's the purpose of using braces (i.e. ) for a single-line if or loop?对单行 if 或循环使用大括号(即 )的目的是什么? 【发布时间】:2012-08-24 23:06:15 【问题描述】:我正在阅读我的 C++ 讲师的一些讲义,他写了以下内容:
使用缩进 // 确定 永远不要依赖运算符优先级 - 始终使用括号 // OK 始终使用 块 - 即使是单行 // 不行,为什么??? 比较左侧的常量对象 // OK 对 >= 0 的变量使用无符号 // 好技巧 删除后将指针设置为 NULL - 双重删除保护 // 不错
我不清楚第三种技巧:将一条线放入
...
?
以这个奇怪的代码为例:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
并将其替换为:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
使用第一个版本有什么好处?
【问题讨论】:
可读性和可维护性。 'j++' 属于哪个语句块并不是很明显,在它之后添加代码不会与 if 语句相关联。 我总是被告知在这些行中使用花括号 有几个原因。它使代码更清晰易读。此外,其他人在六个月内可能需要编辑您的代码,因此清晰很重要,并且使用大括号不太可能发生错误。从技术上讲,没有什么比这更正确的了,这只是一个良好实践的问题。请记住,一个项目可能有成千上万行代码供新人学习! 我不同意6,因为它会隐藏双重删除并可能隐藏逻辑错误。 #5 可能很棘手 - 考虑一下这个循环:for (unsigned i = 100; i >= 0; --i)
。
顺便说一句,(i % 2 == 0)
与 (2) 相矛盾。您依赖于运算符优先级,其含义当然是((i % 2) == 0)
而不是(i % (2 == 0))
。我会将规则 2 归类为“一种有效的情绪,但‘总是’是错误的”。
【参考方案1】:
当我们增加j
时,让我们尝试修改i
:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
i++;
哦不!来自 Python,这看起来不错,但实际上并非如此,因为它相当于:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
i++;
当然,这是一个愚蠢的错误,但即使是有经验的程序员也会犯。
另一个很好的理由在ta.speot.is's answer 中指出。
第三个我能想到的是嵌套if
的:
if (cond1)
if (cond2)
doSomething();
现在,假设您现在想在不满足 cond1
的情况下使用 doSomethingElse()
(新功能)。所以:
if (cond1)
if (cond2)
doSomething();
else
doSomethingElse();
这显然是错误的,因为else
与内部if
相关联。
编辑:由于这引起了一些关注,我将澄清我的观点。我要回答的问题是:
使用第一个版本有什么好处?
我已经描述过了。有一些好处。但是,IMO,“总是”规则并不总是适用。所以我不完全支持
始终使用 块 - 即使是单行 // 也不行,为什么 ???
我并不是说总是使用 块。如果这是一个足够简单的条件和行为,请不要。如果您怀疑有人稍后可能会进来并更改您的代码以添加功能,请这样做。
【讨论】:
@Science_Fiction: 是的,但是如果你添加i++
之前 j++
,那么这两个变量在使用时仍然在范围内。
这听起来很合理,但忽略了编辑器进行缩进的事实,而不是您,它会立即缩进i++;
,从而立即表明它不是循环的一部分。 (在过去,这可能是一个合理的论点,而我已经看到过这样的问题。大约 20 年前。从那以后就没有了。)
@James:这不是“事实”,但这是您的工作流程。还有很多人的工作流程,但不是每个人。我不认为将 C++ 源代码视为纯文本文件而不是强制格式化规则的 WYSIWYG 编辑器 (vi/emacs/Visual Studio) 的输出是必然错误。因此,此规则与编辑器无关,超出了您的需要,但并未超出人们实际用于编辑 C++ 的内容。因此是“防御性的”。
@JamesKanze 你真的相信每个人都在强大的 IDE 中工作吗?我写的最后一个 C 是在 Nano 中。即便如此,我倾向于在 IDE 中关闭的第一件事是自动缩进 - 因为 IDE 往往会妨碍我的 非线性 工作流程,试图纠正我的基于不完整信息的“错误”。 IDE 并不擅长自动缩进每个程序员的自然流程。那些使用这些功能的程序员倾向于将他们的风格合并到他们的 IDE 中,如果你只使用一个 IDE,这很好,但如果你使用多个 IDE,那就不行了。
“这是一个愚蠢的错误,但即使是有经验的程序员也会犯。” ——就像我在回答中所说的那样,我不相信。我认为这是一个完全人为的案例,在现实中不会造成问题。【参考方案2】:
如果您不使用 和
,很容易意外更改使用 cmets 的控制流。例如:
if (condition)
do_something();
else
do_something_else();
must_always_do_this();
如果你用单行注释注释掉do_something_else()
,你会得到这样的结果:
if (condition)
do_something();
else
//do_something_else();
must_always_do_this();
它可以编译,但must_always_do_this()
并不总是被调用。
我们的代码库中存在这个问题,有人在发布前很快就禁用了某些功能。幸运的是,我们在代码审查中发现了它。
【讨论】:
天啊!如果您评论 //do_something_else();,则must_always_do_this();
将执行定义的行为
@Supr,正如它最初写的那样,他说如果你使用大括号很难打破正确的流程,然后举一个例子说明如果没有正确括住代码是多么容易打破
前几天我遇到了这个问题。 if(debug) \n //print(info);
。基本上拿出了整个图书馆。
Fortunately we caught it in code review.
哎哟!这听起来很不对劲。 Fortunately we caught it in unit tests.
会更好!
@BЈовић 但是如果代码在单元测试中呢?头脑一片混乱。 (开个玩笑,这是一个遗留应用程序。没有单元测试。)【参考方案3】:
我怀疑讲师的能力。考虑到他 分:
-
好的
真的有人会写(或想读)
(b*b) - ((4*a)*c)
吗?
一些优先级是显而易见的(或应该是),额外的括号
只会增加混乱。 (另一方面,你_应该_使用
在不太明显的情况下使用括号,即使您知道它们不是
需要。)
有点像。有两种广泛的格式化约定
条件和循环:
如果(条件)
代码;
和:
如果(条件)
代码;
首先,我同意他的看法。开头
不那么明显,
所以最好假设它总是在那里。然而,在第二个中,我
(以及我共事过的大多数人)对省略没有问题
单个语句的大括号。 (当然,前提是
缩进是系统的,并且您始终使用这种样式。
(还有很多非常优秀的程序员,写的代码可读性很强,省略
即使在格式化第一种方式时也要使用大括号。)
没有。 if ( NULL == ptr )
之类的东西丑到足以阻碍
可读性。直观地写下比较。 (在很多情况下
结果是右边的常数。)他的 4 是不好的建议;任何事物
这使得代码不自然,使其可读性降低。
没有。除了int
之外的任何内容都保留用于特殊情况。到
经验丰富的 C 和 C++ 程序员,unsigned
信号位的使用
运营商。 C++ 没有真正的基数类型(或任何其他
有效子范围类型); unsigned
不适用于数值,
因为促销规则。没有的数值
算术运算是有意义的,就像序列号一样,可以
大概是unsigned
。然而,我会反对它,因为它
发送错误信息:按位运算也没有意义。
基本规则是整数类型是int
,_除非_有一个
使用另一种类型的重要原因。
没有。系统地这样做会产生误导,实际上并没有
防止任何事情。在严格的 OO 代码中,delete this;
通常是
最常见的情况(您不能将this
设置为NULL
),以及
否则,大多数delete
都在析构函数中,因此您无法访问
反正以后指针。并将其设置为 NULL
不会做任何事情
关于任何其他浮动的指针。设置指针
系统性地向NULL
提供一种虚假的安全感,而不是
真的什么都给你买。
查看任何典型参考中的代码。 Stroustrup 违反 例如,除了第一条之外,您给出的每条规则。
我建议你再找一位讲师。一个真正知道什么的人 他在说。
【讨论】:
数字 4 可能很难看,但它是有目的的。它试图阻止 if (ptr = NULL)。我想我从来没有用过delete this
,它比我见过的更常见吗?我不倾向于认为在使用后将指针设置为 NULL 是一件很糟糕的事情,但 YMMV。也许只有我一个人,但他的大部分指导方针似乎都没有那么糟糕。
@Firedragon:大多数编译器都会警告if (ptr = NULL)
,除非你把它写成if ((ptr = NULL))
。必须同意 James Kanze 的观点,首先拥有 NULL
的丑陋让我绝对拒绝。
@JamesKanze:我不得不说我不同意你在这里所说的大部分内容——尽管我很欣赏并尊重你提出的论点。 对于有经验的 C 和 C++ 程序员来说,使用无符号表示位运算符。 - 我完全不同意:使用 位运算符 表示使用位运算符。对我来说,unsigned
的使用表明了程序员的愿望,即变量应该只表示正数。与带符号的数字混合通常会导致编译器警告,这可能是讲师的意图。
对于有经验的 C 和 C++ 程序员来说,使用无符号信号位运算符 还是不要。 size_t
,有人吗?
@James Kanze,考虑一下目的。您正在将经验丰富的程序员生成的代码与教学示例进行比较。这些规则是由讲师提供的,因为它们是他看到学生犯的那种错误。有了经验,学生可以放松或无视这些绝对性。【参考方案4】:
所有其他答案都捍卫了讲师的规则 3。
我同意你的观点:规则是多余的,我不建议这样做。确实,如果您总是添加大括号,它理论上可以防止错误。另一方面,我在现实生活中从未遇到过这个问题:与其他答案所暗示的相反,我从来没有忘记在必要时添加大括号。如果您使用适当的缩进,很明显您需要在多个语句缩进时添加大括号。
“组件 10”的答案实际上突出了唯一可能真正导致错误的情况。但另一方面,通过正则表达式替换代码总是需要非常小心。
现在让我们看看奖牌的另一面:总是使用大括号有缺点吗?其他答案只是忽略了这一点。但是有一个缺点:它占用了大量的垂直屏幕空间,这反过来又会使您的代码不可读,因为这意味着您必须滚动超出必要的范围。
考虑一个开头有很多保护子句的函数(是的,以下是糟糕的 C++ 代码,但在其他语言中这将是很常见的情况):
void some_method(obj* a, obj* b)
if (a == nullptr)
throw null_ptr_error("a");
if (b == nullptr)
throw null_ptr_error("b");
if (a == b)
throw logic_error("Cannot do method on identical objects");
if (not a->precondition_met())
throw logic_error("Precondition for a not met");
a->do_something_with(b);
这是很糟糕的代码,我强烈认为以下代码的可读性要好得多:
void some_method(obj* a, obj* b)
if (a == nullptr)
throw null_ptr_error("a");
if (b == nullptr)
throw null_ptr_error("b");
if (a == b)
throw logic_error("Cannot do method on identical objects");
if (not a->precondition_met())
throw logic_error("Precondition for a not met");
a->do_something_with(b);
同样,短嵌套循环也受益于省略大括号:
matrix operator +(matrix const& a, matrix const& b)
matrix c(a.w(), a.h());
for (auto i = 0; i < a.w(); ++i)
for (auto j = 0; j < a.h(); ++j)
c(i, j) = a(i, j) + b(i, j);
return c;
比较:
matrix operator +(matrix const& a, matrix const& b)
matrix c(a.w(), a.h());
for (auto i = 0; i < a.w(); ++i)
for (auto j = 0; j < a.h(); ++j)
c(i, j) = a(i, j) + b(i, j);
return c;
第一个代码简洁;第二个代码很臃肿。
是的,这可以通过将左大括号放在前一行来缓解在一定程度上。所以:如果你坚持使用花括号,至少把左大括号放在前一行。
简而言之:不要编写占用屏幕空间的不必要代码。
自从最初编写答案以来,我大多接受流行的代码样式并使用大括号,除非我可以将整个单个语句放在前一行。我仍然认为不使用冗余大括号通常更具可读性,并且我仍然没有遇到过由此引起的错误。
【讨论】:
如果您不相信编写不必要地占用屏幕空间的代码,那么您就没有生意将左大括号放在自己的行上。我现在可能不得不躲避 GNU 的神圣复仇,但说真的——要么你希望你的代码垂直紧凑,要么你不。如果你这样做了,不要做仅仅为了让你的代码在垂直方向上不那么紧凑的事情。但正如您所说,修复了该问题后,您仍然也想要删除多余的大括号。或者也许只是将if (a == nullptr) throw null_ptr_error("a");
写成一行。
@Steve 事实上,出于您所说的原因,我确实将左大括号放在了前一行。我在这里使用了另一种样式,以使差异的极端程度更加明显。
+1 我完全同意您的第一个示例在没有大括号的情况下更容易阅读。在第二个示例中,我个人的编码风格是在外部 for 循环而不是内部使用大括号。我不同意@SteveJessop 关于垂直紧凑代码必须是一个极端或另一个极端。我省略了带有单线的额外大括号以减少垂直空间,但我确实将我的开口大括号放在了一个新行上,因为我发现当大括号对齐时更容易看到范围。目标是可读性,有时这意味着使用更多的垂直空间,有时意味着使用更少的空间。
“我在现实生活中从未遇到过这个问题”:你很幸运。这样的事情不仅会烧伤你,还会给你 90% 的三度烧伤(这只是需要在深夜进行修复的几层管理)。
@Richard 我根本不买那个。正如我在聊天中解释的那样,即使这个错误应该发生(我认为不太可能),一旦你查看堆栈跟踪,修复它也是微不足道的,因为只要查看代码就很明显错误出在哪里。你的夸大其词是完全没有根据的。【参考方案5】:
我正在处理的代码库中散布着对大括号有病态厌恶的人的代码,对于后来出现的人来说,它确实可以对可维护性产生影响。
我遇到的最常见的问题示例是这样的:
if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
this_looks_like_a_then-statement_but_isn't;
所以当我出现并希望添加一个 then 语句时,如果我不小心的话,我很容易就这样结束了:
if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
this_looks_like_a_then-statement_but_isn't;
i_want_this_to_be_a_then-statement_but_it's_not;
鉴于添加大括号大约需要 1 秒,并且至少可以为您节省几分钟的混乱调试时间,您为什么不选择减少歧义选项?对我来说似乎是虚假经济。
【讨论】:
这个例子的问题不在于不正确的缩进和太长的行而不是大括号吗? 是的,但是遵循设计/编码准则只是“安全”假设人们也遵循其他准则(例如没有太长的行)似乎是在自找麻烦。如果从一开始就使用大括号,那么在这种情况下就不可能以错误的 if 块结束。 如何添加大括号(使其成为if(really long...editor) do_foo;
可以帮助您避免这种情况?似乎问题仍然存在。我个人更喜欢在不必要时避免使用大括号,但这无关紧要编写它们所需的时间,但由于代码中的两行额外行而降低了可读性。
好点 - 我假设强制使用大括号也会导致它们被放在一个合理的地方,但当然有人决心让事情变得困难可以像你的例子一样把它们放在一起.不过,我想大多数人不会。
触摸文件时我做的第一件事也是最后一件事是点击自动格式化按钮。它消除了大部分这些问题。【参考方案6】:
我的 2c:
使用缩进
显然
永远不要依赖运算符优先级 - 始终使用括号
我不会使用“从不”和“总是”这样的词,但总的来说,我认为这条规则很有用。在某些语言(Lisp、Smalltalk)中,这不是问题。
始终使用 块 - 即使是单行
我从来没有这样做过,也从来没有遇到过任何问题,但我可以看到它对学生有什么好处,尤其是。如果他们以前学过 Python。
比较左侧的常量对象
尤达条件?不谢谢。它损害了可读性。编译代码时只需使用最高警告级别。
对 >= 0 的变量使用无符号数
好的。有趣的是,我听说 Stroustrup 不同意。
删除后将指针设置为 NULL - 双重删除保护
不好的建议!永远不要有指向已删除或不存在的对象的指针。
【讨论】:
+1 仅针对最后一点。无论如何,原始指针没有业务拥有内存。 关于使用无符号:不仅是 Stroustrup,还有 K&R(C 语言)、Herb Sutter 和(我认为)Scott Meyers。事实上,我从未听过真正了解 C++ 规则的人主张使用无符号。 @JamesKanze 事实上,就在我听到 Stroustrup 的意见(2008 年波士顿会议)的同一场合,Herb Sutter 也在场,当场不同意 Bjarne。 只是为了完成“unsigned
被破坏”,问题之一是当C++比较类似大小的有符号和无符号类型时,它会转换为无符号的之前做比较。这会导致价值发生变化。转换为已签名不一定会好得多;比较应该真正发生,“好像”两个值都被转换为一个更大的类型,它可以代表任一类型中的所有值。
@SteveJessop 我认为您必须在返回unsigned
的函数的上下文中使用它。我确信他对exp(double)
返回的值大于MAX_INT
:-) 没有任何问题。但再一次,真正的问题是隐式转换。 int i = exp( 1e6 );
是完全有效的 C++。 Stroustrup 实际上曾一度提议弃用有损隐式转换,但委员会并不感兴趣。 (一个有趣的问题:unsigned
-> int
会被认为是有损的。我会同时考虑 unsigned
-> int
和 int
-> unsigned
有损。这将有很长的路要走unsigned
好的【参考方案7】:
它更直观,更容易理解。它使意图明确。
当新用户在添加新代码语句时可能无意中错过、
时,它可以确保代码不会中断。
【讨论】:
Makes the intent clear
+1,这大概是最简洁准确的理由了。【参考方案8】:
为了补充上述非常明智的建议,我在重构一些代码时遇到的一个例子如下:我正在更改一个非常大的代码库以从一个 API 切换到另一个 API。第一个 API 调用如下设置公司 ID:
setCompIds( const std::string& compId, const std::string& compSubId );
而替换需要两次调用:
setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );
我开始使用非常成功的正则表达式来改变它。我们还通过astyle 传递了代码,这确实使它更具可读性。然后,在审查过程的中途,我发现在某些有条件的情况下,它正在改变这一点:
if ( condition )
setCompIds( compId, compSubId );
到这里:
if ( condition )
setCompId( compId );
setCompSubId( compSubId );
这显然不是我们所需要的。我不得不从头开始再次执行此操作,将替换完全视为一个块内,然后手动更改任何最终看起来很傻的东西(至少它不会不正确。)
我注意到 astyle 现在有选项 --add-brackets
允许您在没有括号的地方添加括号,如果您发现自己的位置与我相同,我强烈建议您这样做。
【讨论】:
我曾经看到一些文档,其中包含了奇妙的词“Microsoftligent”。是的,全局搜索和替换可能会犯重大错误。这只是意味着必须智能地使用全局搜索和替换,而不是智能地使用。 我知道这不是我要执行的事后分析,但是如果您要对源代码进行文本替换,那么您应该按照与一种在语言中已建立良好的文本替换:宏。您不应该编写宏#define FOO() func1(); \ func2();
(反斜杠后有换行符),搜索和替换也是如此。就是说,我已经看到“始终使用大括号”作为样式规则而先进,因为它使您免于将所有多语句宏包装在 do .. while(0)
中。但我不同意。
顺便说一句,在日本虎杖已经成熟的意义上,这是“成熟的”:我并不是说我们应该竭尽全力使用宏和文本替换,但我我说当我们做这样的事情时,我们应该以一种有效的方式去做,而不是做一些只有在一个特定的样式规则成功地应用于整个代码库的情况下才有效的事情:-)
@SteveJessop 也有人会争论牙套和腰带。如果你必须使用这样的宏(我们在 C++ 和 inline
之前使用过),那么你可能应该尽可能让它们像函数一样工作,必要时使用 do ... while(0)
技巧(还有很多额外的括号。但这仍然不会阻止你在任何地方都使用大括号,如果那是房子风格的话。(FWIW:我在不同房子风格的地方工作过,涵盖了这里讨论的所有风格。我从来没有找到是一个严重的问题。)
而且我认为,您使用的样式越多,您阅读和编辑代码就越仔细。因此,即使您偏爱最容易阅读的内容,您仍然可以成功阅读其他内容。我在一家公司工作,不同的团队用不同的“房子风格”编写了不同的组件,正确的解决方案是在午餐室抱怨它没有太大的效果,而不是试图创造一种全球风格:-) 【参考方案9】:
我在所有地方都使用,除了一些很明显的情况。单行就是其中一种情况:
if(condition) return; // OK
if(condition) //
return; // and this is not a one-liner
在返回之前添加一些方法可能会伤害你。缩进表示条件满足时返回正在执行,但会一直返回。
在 C# 中使用语句的其他示例
using (D d = new D()) // OK
using (C c = new C(d))
c.UseLimitedResource();
相当于
using (D d = new D())
using (C c = new C(d))
c.UseLimitedResource();
【讨论】:
只需在using
语句中使用逗号,您不必:)
@minitech 这在这里根本行不通——你只能在类型相等时使用逗号,而不是不相等类型。 Lukas 这样做的方式是规范的方式,IDE 甚至以不同的方式格式化(注意第二个 using
缺少自动缩进)。【参考方案10】:
我能想到的最贴切的例子:
if(someCondition)
if(someOtherCondition)
DoSomething();
else
DoSomethingElse();
else
将与哪个 if
配对?缩进意味着外部if
得到else
,但实际上编译器不会看到它; 内部 if
将获得else
,而外部if
不会。您必须知道这一点(或在调试模式下看到它的行为方式)才能通过检查找出为什么此代码可能没有达到您的期望。如果您了解 Python,它会变得更加混乱;在这种情况下,您知道缩进定义了代码块,因此您希望它根据缩进进行评估。然而,C# 并没有给出关于空白的快速翻转。
现在,也就是说,我并不特别同意这条“始终使用括号”的规则。它使代码在垂直方向上非常嘈杂,从而降低了快速阅读代码的能力。如果语句是:
if(someCondition)
DoSomething();
...那么它应该是这样写的。 “总是使用括号”这句话听起来像“总是用括号括起来数学运算”。这会将非常简单的声明a * b + c / d
变成((a * b) + (c / d))
,从而引入了丢失近端括号的可能性(许多程序员的祸根),这是为了什么?操作顺序是众所周知的并且执行良好,因此括号是多余的。您只能使用括号来强制执行与通常应用不同的操作顺序:例如a * (b+c) / d
。大括号类似;使用它们来定义在与默认值不同且不是“显而易见”(主观,但通常是常识)的情况下您想要做什么。
【讨论】:
@AlexBrown ...这正是我的观点。 OP 中所述的规则是“始终使用括号,即使是单行”,出于我所说的原因,我不同意。括号会对第一个代码示例有所帮助,因为代码的行为不会像缩进的那样;您必须使用括号将else
与第一个if
配对,而不是第二个。请删除反对票。【参考方案11】:
因为当您有两个没有 的语句时,很容易漏掉一个问题。假设代码如下所示。
int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();
if ((err = prepare_hash(hash, &hash_result))) != 0)
goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
goto fail;
goto fail;
if ((err = hash_finish(hash)) != 0)
goto fail;
error = do_important_stuff_with(hash);
fail:
hash_free(hash);
return error;
看起来不错。它的问题很容易被忽略,尤其是当包含代码的函数更大时。问题是goto fail
是无条件运行的。您可以轻松想象这是多么令人沮丧(让您问为什么最后一个 hash_update
总是失败,毕竟在 hash_update
函数中一切看起来都很好)。
但是,这并不意味着我要在任何地方添加(在我看来,到处看到
很烦人)。虽然它可能会导致问题,但它从来没有在我自己的项目中出现过,因为我的个人编码风格禁止没有
的条件,当它们不在同一行时(是的,我同意我的编码风格是非常规的,但我喜欢它,我在为其他项目做贡献时使用项目的代码风格)。这使得下面的代码很好。
if (something) goto fail;
但不是下一个。
if (something)
goto fail;
【讨论】:
没错。只是不要放(完全不必要的)换行符+缩进,你就完全回避了这个每个人总是很快提出来的问题。【参考方案12】:它通过明确定义循环和条件块的范围使您的代码更具可读性。它还可以避免意外错误。
【讨论】:
【参考方案13】:wrt 6:它更安全,因为删除空指针是无操作的。因此,如果您碰巧不小心经过了该路径两次,您不会因为释放空闲的内存或已分配给其他对象的内存而导致内存损坏。
这主要是静态文件范围对象和单例的问题,它们的生命周期不是很明确,并且已知会在它们被销毁后重新创建。
在大多数情况下,您可以通过使用 auto_ptrs 来避免这种情况
【讨论】:
如果您碰巧经过该路径两次,您就会遇到编程错误。将指针设置为 null 以降低此错误的危害并不能解决根本问题。 同意,但是我以前看到过这个推荐,我相信它在一些专业的编程标准中。我更多地评论了为什么海报的教授想出了它,而不是什么时候有什么好处 跟进Pete Becker 所说的:它不能解决根本问题,但可能会掩盖它。 (在某些情况下,您会在删除NULL
后将其设置为指针。如果 NULL
是指针在这些情况下具有的正确值;例如,指针指向缓存值,NULL
表示无效缓存。但是当你看到有人将指向NULL
的指针设置为析构函数的最后一行时,你会怀疑他是否懂 C++。)【参考方案14】:
我喜欢 Luchian 被接受的答案,事实上,我很难学到他是对的,所以我总是使用大括号,即使是单行块也是如此。但是,我个人在编写过滤器时会例外,就像您在示例中一样。这个:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
对我来说看起来很混乱。它将 for 循环和 if 语句分成单独的操作,而实际上您的意图是一个操作:计算所有可被 2 整除的整数。在更具表现力的语言中,可以这样写:
j = [1..100].filter(_%2 == 0).Count
在缺少闭包的语言中,过滤器不能在单个语句中表示,而必须是一个 for 循环后跟一个 if 语句。不过,这仍然是程序员心中的一个动作,我认为应该体现在代码中,像这样:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
【讨论】:
我喜欢每个人都设法忽略for (int i = 0; i < 100; i += 2);
,为了继续关于缩进的争论;-) 我们可能会有一个完全独立的混战,如何“最好”地表达逻辑“对于在特定范围内具有特定属性的每个i
,在 C++ 中没有循环,使用标准算法的一些噩梦组合,filter_iterator
和/或counting_iterator
。
另外,如果我们有,那么我们可能会就如何缩进生成的大量单个语句产生分歧。
@Steve,这只是一个例子。该模式有很多合法用途。显然,如果你想数 1 到 100 中能被 2 整除的数字,你所要做的就是 100/2。
当然,我知道,这就是为什么我抽象为“在特定范围内具有特定属性的每个i
”。只是通常在 SO 上,人们很快就会忽略实际问题,而倾向于对给出的示例采用完全不同的方法。但是缩进是重要的,所以我们不;-)【参考方案15】:
有助于防止上述错误的一个选项是内联您在不使用大括号时想要发生的事情。当您尝试修改代码时,很难不注意到错误。
if (condition) doSomething();
else doSomethingElse();
if (condition) doSomething();
doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong
【讨论】:
第二个选项会产生编译错误,因为else
没有与if
关联。
内联的一个不太明显的问题是大多数 IDE 在使用其自动格式化实用程序时默认将其更改为缩进样式。
@Honza:不过,这是一个高度关注的政治问题。如果我们在代码库上进行合作,要么我们必须使用相同的缩进样式到每一个细节,要么我们必须同意不“仅仅因为”自动格式化现有代码。如果是前者,那么约定的样式仍然可以包含它,但是您必须配置您的 IDE 以尊重它或不使用自动格式化。同意通用格式是“无论我的 IDE 自动格式化为什么”,如果我们都永远使用同一个 IDE,那就太好了,否则就不太好。【参考方案16】:
查看没有人明确说明我习惯的那种做法,讲述你的代码故事:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
变成:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0) j++;
将j++
放在 同一行 上,因为 if 应该向其他任何人发出信号,“我只希望这个块永远增加 j
”。当然,这只有在行尽可能简单的情况下才值得,因为在此处设置断点,正如 peri 所提到的,不会很有用。
事实上,我刚刚运行了 Twitter Storm API 的一部分,它在 java 中有这种“排序”代码,这里是相关的 sn-p 形式的执行代码,在page 43 of this slideshow:
...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...
for 循环块中有两个内容,所以我不会内联该代码。即从不:
int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;
这太糟糕了,我什至不知道它是否有效(按预期); 不要这样做。新行和大括号有助于区分单独但相关的代码片段,就像散文中的逗号或分号一样。上面的块是一个非常长的句子,有几个从句和一些从不中断或暂停以区分不同部分的语句。
如果您真的想给其他人发电报,这只是单行工作,请使用三元运算符或?:
表单:
for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;
但这接近于代码高尔夫,我认为这不是很好的做法(我不清楚是否应该将 j++ 放在 :
的一侧)。 注意我之前没有在 C++ 中运行过三元运算符,我不知道这是否有效,但它确实存在。
简而言之:
想象一下您的读者(即维护代码的人)如何解释您的故事(代码)。尽可能让他们清楚。如果您知道新手编码员/学生正在维护这一点,甚至可以尽可能多地留下,以免他们感到困惑。
【讨论】:
(1) 将语句放在同一行使其更少可读,而不是更多。特别是像增量这样简单的想法很容易被忽视。 不要把它们放在一个新的行上。 (2) 当然你可以把你的for
循环放在一行,为什么不这样呢?它的工作原理与您可以省略大括号的原因相同;换行符在 C++ 中根本不重要。 (3) 您的条件运算符示例,除了可怕之外,是无效的 C++。
@KonradRudolph 谢谢,我对 C++ 有点生疏了。我从来没有说过 (1) 更具可读性,但它表明那段代码意味着是在线的。 (2) 我的评论更多的是我无法阅读它并且知道它有效,无论是完全还是按预期;这是出于这个原因不应该做什么的一个例子。 (3) 谢谢,好久没写C++了。我现在就解决这个问题。
在一行中放置一个以上的表达式也会使调试代码变得更加困难。您如何在该行的第二个表达式上放置断点?【参考方案17】:
如果您是编译器,则没有任何区别。两者都是一样的。
但对于程序员来说,第一个更清晰,易于阅读,不易出错。
【讨论】:
除了在自己的线路上打开
,无论如何。【参考方案18】:
另一个添加花括号的例子。 有一次我正在寻找一个错误并找到这样的代码:
void SomeSimpleEventHandler()
SomeStatementAtTheBeginningNumber1;
if (conditionX) SomeRegularStatement;
SomeStatementAtTheBeginningNumber2;
SomeStatementAtTheBeginningNumber3;
if (!SomeConditionIsMet()) return;
OtherwiseSomeAdditionalStatement1;
OtherwiseSomeAdditionalStatement2;
OtherwiseSomeAdditionalStatement3;
如果您逐行阅读该方法,您会注意到该方法中有一个条件,如果它不为真则返回。但实际上它看起来像 100 个其他简单的事件处理程序,它们根据某些条件设置一些变量。有一天,Fast Coder 进来并在方法末尾添加了额外的变量设置语句:
...
OtherwiseSomeAdditionalStatement3;
SetAnotherVariableUnconditionnaly;
结果 SetAnotherVariableUnconditionnaly 在 SomeConditionIsMet() 时执行,但快速的家伙没有注意到它,因为所有行的大小几乎相似,即使返回条件是垂直缩进的,它也不那么明显。
如果条件返回的格式如下:
if (!SomeConditionIsMet())
return;
非常明显,Fast Coder 一眼就能找到。
【讨论】:
如果您的快速编码器在向其添加内容之前无法在函数主体中发现语法突出显示的return
语句,那么您不应该让快速编码器靠近您的代码。你不会阻止这样的人通过包含大括号来控制你的代码。
@cmaster 他不再和我们一起工作了。无论如何,语法高亮是好的,但要记住有些人看不清楚(我什至看到了一个盲人程序员的帖子)。【参考方案19】:
我认为第一个清晰,然后是第二个。它给人一种关闭指令的感觉,当代码变得复杂时,很少的代码就可以了
//first
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
//second
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
i++;
【讨论】:
【参考方案20】:最好在完成后将指针设置为 NULL。
下面是一个例子:
A 类执行以下操作:
-
分配一块内存
然后过了一段时间,它删除了这块内存,但没有将指针设置为 NULL
B 类执行以下操作
-
分配内存(在这种情况下,它恰好获得了被 A 类删除的相同内存块。)
此时A类和B类都有指向同一个内存块的指针,就A类而言,这个内存块不存在,因为它已经用完了。
考虑以下问题:
如果 A 类中存在逻辑错误导致它写入现在属于 B 类的内存怎么办?
在这种特殊情况下,您不会收到错误的访问异常错误,因为内存地址是合法的,而 A 类现在正在有效地破坏 B 类数据。
如果 B 类遇到意外的值,它最终可能会崩溃,当它崩溃时,很可能,当问题出现在 A 类时,您将花费相当长的时间在 B 类中寻找这个错误。
如果您将已删除的内存指针设置为 NULL,那么只要 A 类中的任何逻辑错误尝试写入 NULL 指针,您就会收到异常错误。
如果你担心指针第二次为NULL时双重删除的逻辑错误,那么为此添加assert。
另外:如果你要投反对票,请解释一下。
【讨论】:
如果出现逻辑错误,应该修复,而不是屏蔽它。 @Barmar,OP 说... 6. 删除后将指针设置为 NULL - 双重删除保护 // 不错。有些人回应不将其设置为 Null,我在说为什么它应该设置为 NULL,6 的哪一部分。我对设置 NULL 的回答不适合 6? @Shaquin,您如何建议首先找到这些逻辑错误?在删除内存后将指针变量设置为 NULL。任何引用 NULL 指针的尝试都会在您进行非法尝试的行上导致调试器崩溃。您可以追溯并查看逻辑错误的位置并修复问题。如果在删除内存后未将指针变量设置为 NULL,则由于 UNAWARE 逻辑错误而导致的非法写入此已删除内存的尝试可能会成功,因此此时不会崩溃。它没有掩盖它。【参考方案21】:总是有花括号是非常简单和健壮的规则。但是代码可能看起来 当有很多大括号时不优雅。如果规则允许省略花括号,那么应该有更详细的样式规则和更复杂的工具。否则很容易导致代码混乱和混乱(不优雅)。因此,将单一样式规则与其他样式指南和使用的工具分开可能是徒劳的。我将带来一些关于该规则#3 的重要细节,这些细节甚至在其他答案中都没有提到。
第一个有趣的细节是,该规则的大多数支持者同意在else
的情况下违反它。换句话说,他们不要求审查此类代码:
// pedantic rule #3
if ( command == Eat )
eat();
else
if ( command == Sleep )
sleep();
else
if ( command == Drink )
drink();
else
complain_about_unknown_command();
相反,如果他们看到它,他们甚至可能会建议这样写:
// not fully conforming to rule #3
if ( command == Eat )
eat();
else if ( command == Sleep )
sleep();
else if ( command == Drink )
drink();
else
complain_about_unknown_command();
这在技术上违反了该规则,因为else
和if
之间没有大括号。当尝试使用无意识的工具将其自动应用于代码库时,规则的这种二元性就会显现出来。确实,何必争辩,就让一个工具自动应用样式吧。
第二个细节(也经常被该规则的支持者忘记)是可能发生的错误绝不是因为违反了该规则#3。实际上,这些几乎总是涉及违反规则#1(没有人反对)。再次从自动化工具的角度来看,制作一个在违反规则#1 时立即抱怨的工具并不难,因此可以及时发现大部分错误。
第三个细节(该规则的反对者经常忘记)是由单个分号表示的空语句的混淆性质。大多数有一定经验的开发人员迟早会被唯一放错位置的分号或使用唯一分号编写的空语句弄糊涂。两个花括号而不是单个分号在视觉上更容易发现。
【讨论】:
【参考方案22】:我不得不承认,单行并不总是使用,但这是一个很好的做法。
假设您编写了一个不带括号的代码,如下所示:
for (int i = 0; i
一段时间后,您想在 j
循环中添加一些其他内容,而您只需通过对齐来完成此操作,而忘记添加括号。
内存释放速度更快。假设您有很大的范围并在内部创建大数组(没有new
,所以它们在堆栈中)。在您离开范围后,这些数组将从内存中删除。但是有可能你在一个地方使用了那个数组,它会在堆栈中一段时间,成为某种垃圾。由于堆栈的大小有限且非常小,因此可能超过堆栈大小。所以在某些情况下最好写 来防止这种情况。 注意这不是针对单行,而是针对这种情况:
如果 (...) //一些东西... //我们没有 if、while 等。 //一些其他的东西 //一些更多的东西
第三种使用方式与第二种类似。它不是为了让堆栈更干净,而是为了打开一些功能。如果您在长函数中使用mutex
,通常最好在访问数据之前和完成读/写之后锁定和解锁。 注意如果您有自己的class
或struct
和constructor
和destructor
,则使用这种方式来锁定内存。
还有什么:
如果 (...) 如果 (...) 一些东西(); 别的 一些其他东西(); //转到第二个 if,但 alligment 显示它在第一个...
总而言之,我不能说,始终将 用于单行的最佳方式是什么,但这样做并没有什么坏处。
重要编辑如果你为一行编写编译代码括号什么都不做,但如果你的代码会被解释,它会稍微减慢代码。非常轻微。
【讨论】:
【参考方案23】:编写控制语句有多种可能的方式;它们的某些组合可以共存而不影响易读性,但其他组合会造成麻烦。风格
if (condition)
statement;
将与其他一些编写控制语句的方式舒适地共存,但与其他方式不那么好。如果多行控制语句写成:
if (condition)
statement;
statement;
那么,哪些if
语句控制单行,哪些语句控制多行,在视觉上会很明显。但是,如果多行 if
语句写成:
if (condition)
statement;
statement;
那么有人试图在不添加必要的大括号的情况下扩展单个语句 if
构造的可能性可能要高得多。
如果代码库大量使用表单,则下一行的单语句 if
语句也可能有问题
if (condition) statement;
我自己的偏好是,将语句放在单独的行中通常会提高可读性,除非有许多 if
语句具有类似的控制块,例如
if (x1 > xmax) x1 = xmax;
if (x1 < xmin) x1 = xmin;
if (x2 > xmax) x2 = xmax;
if (x2 < xmin) x2 = xmin;
etc.
在这种情况下,我通常会在这些 if
语句组之前和之后添加一个空行,以便在视觉上将它们与其他代码分开。拥有一系列以if
开头的语句在同一个缩进处将提供一个清晰的视觉指示,表明存在异常。
【讨论】:
【参考方案24】:在“始终使用牙套”训练营 10 年后,我最近转而不再使用它们。 主要是受到 Bob 叔叔关于如何编写干净代码的一些论点的启发,我现在相信不带大括号编写它们更具可读性。
if(guardClause)
throw new SomeException(..)
鲍勃叔叔认为,在 if/for 语句中编写多行代码是一种潜在的可读性气味。
例如
if(someCondition)
doTechnicalThingX();
doTechnicalThingY();
doTechnicalThingZ();
应该重构为
if(someCondition)
doFunctionalThingA();
不知何故,不把大括号放在那里是有帮助的,因为我得到提醒,我在 if 块中编写了太多代码。
我确实相信代码风格是其他人提到的团队决定。
【讨论】:
我同意...我的大多数条件和循环都是一个衬里。但是目前我工作的最佳实践需要大括号..所以我必须遵循他们的约定。我已经编程超过 25 年了,我不确定这对人们来说什么时候变得很重要。如果您自动格式化代码,90% 的情况下这不是问题。其他 10% 的由牙套引起的事故我只是非常擅长发现。很多程序员都对这条规则充满热情,所以如果你的商店说它的最佳做法,你需要遵循他们的最佳做法。以上是关于对单行 if 或循环使用大括号(即 )的目的是啥?的主要内容,如果未能解决你的问题,请参考以下文章