在单行if或loop中使用大括号(即{})的目的是什么?


320

我正在阅读我的C ++讲师的一些讲义,他写了以下内容:

  1. 使用缩进//确定
  2. 从不依赖运算符优先级-始终使用括号//确定
  3. 始终使用{}块-即使仅一行// 也不行,为什么?
  4. 比较左侧的常量对象//确定
  5. 使用unsigned表示> = 0 //好的技巧
  6. 删除后将Pointer设置为NULL-双重删除保护//不错

第三种技术对我来说还不清楚:将一行放置在a中会得到{ ... }什么?

例如,使用以下奇怪的代码:

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++;

使用第一个版本有什么好处?


256
可读性和可维护性。现在尚不清楚'j ++'语句块属于什么,并且在它之后添加代码不会与if语句相关联。
森林与树木2012年

27
出于种种原因,我总是被告知在这些行中使用大括号{}。它使代码更清晰易读。另外,六个月后的其他人可能需要编辑您的代码,因此清晰度很重要,并且使用花括号,不太可能发生错误。从技术上讲,没有什么比这更正确了,这只是一个良好实践的问题。请记住,一个项目可能包含成千上万行代码,供一些新手学习!
RossC 2012年

30
我不同意6,因为它将隐藏双重删除并可能隐藏逻辑错误。
Luchian Grigore

28
#5可能很棘手-请考虑以下循环:for (unsigned i = 100; i >= 0; --i)
阿奇

36
顺便说一句,(i % 2 == 0)矛盾(2)。您依靠的是运算符优先级,其含义当然是((i % 2) == 0)而不是(i % (2 == 0))。我将规则2归类为“有效情绪,但“总是”是错误的”。
史蒂夫·杰索普

Answers:


508

让我们尝试i在增加时进行修改j

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的答案指出了另一个很好的理由

我能想到的第三个是nested if的:

if (cond1)
   if (cond2) 
      doSomething();

现在,假设您现在想doSomethingElse()何时cond1不满足(新功能)。所以:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

这显然是错误的,因为else与内部联系在一起if


编辑:由于这引起了一些注意,我将澄清我的看法。我正在回答的问题是:

使用第一个版本有什么好处?

我已经描述过了。有一些好处。但是,IMO,“总是”规则并不总是适用。所以我不完全支持

始终使用{}块-即使仅一行//也不行,为什么?

我并不是说总是使用{}块。如果这是一个简单的条件和行为,那就不要。如果您怀疑以后有人会来并更改您的代码以添加功能,请执行。


5
@Science_Fiction:是的,但是如果在i++ 之前 添加j++,那么两个变量在使用时仍将在范围内。
Mike Seymour 2012年

26
这听起来很合理,但是却忽略了编辑器而不是您执行缩进的事实,并且缩进的i++;方式立即显示出它不是循环的一部分。(过去,这可能是一个合理的论点,我已经看到过这样的问题。大约20年前。从那以后。)
James Kanze 2012年

43
@James:这不是事实,但这是您的工作流程。和很多人的工作流程,但不是所有人。我认为将C ++源代码视为纯文本文件而不是强制实施格式设置规则的WYSIWYG编辑器(vi / emacs / Visual Studio)的输出不一定是错误的。因此,此规则与编辑器无关,超出了您的需求,但不超出人们实际用于编辑C ++的范围。因此“防御”。
史蒂夫·杰索普

31
@JamesKanze您是否真的依赖每个人都在强大的IDE中工作的假设?最后的CI写在Nano上。即使考虑到这一点,我倾向于在IDE中关闭的第一件事就是自动缩进-因为IDE倾向于妨碍我的非线性工作流,试图根据不完整的信息纠正我的“错误” 。IDE不能很好地自动缩进每个程序员的自然流。使用这些功能的那些程序员倾向于将其样式合并到他们的IDE中,如果您仅使用一个IDE,但如果您使用多个IDE则不需要太多,这很好。
Rushyo,2012年

10
“这是一个愚蠢的错误,但即使是经验丰富的程序员也可能犯此错误。” –就像我在回答中说的那样,我不相信这一点。我认为这是一个完全人为的案例,实际上并不构成问题。
康拉德·鲁道夫2012年

323

如果不使用{和,很容易不经意地更改带有注释的控制流}。例如:

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()并不总是被调用。

我们的代码库中存在这个问题,有人在发布之前很快就禁用了某些功能。幸运的是,我们在代码审查中发现了它。


3
哦,男孩!它是定义的行为,must_always_do_this();如果您注释// do_something_else();,它将执行。
阿努比斯先生2012年

1
@Supr,因为它是最初写的,他说如果使用花括号很难打破正确的流程,然后给出一个例子,说明在没有正确括

20
前几天我碰到了这个。if(debug) \n //print(info);。基本上拿出了整个图书馆。
凯文(Kevin)

4
Fortunately we caught it in code review.哎哟! 听起来太不对了。Fortunately we caught it in unit tests.会好得多!
2012年

4
@BЈовић但是,如果代码在单元测试中怎么办?头脑陷入困境。(开个玩笑,这是一个旧版应用程序。没有任何单元测试。)
ta.speot.is 2012年

59

我对讲师的能力感到怀疑。考虑他的观点:

  1. 有人真的会写(或想读)(b*b) - ((4*a)*c)吗?一些优先级是显而易见的(或应该是),而多余的括号只会增加混乱。(另一方面,即使不知道不需要括号,也应在不太明显的情况下使用括号。)
  2. 有点。有两种用于格式化条件和循环的广泛使用的约定:
    如果(cond){
        码;
    }
    
    和:
    如果(cond)
    {
        码;
    }
    
    首先,我同意他的看法。开口{不那么明显,所以最好假设它一直在那儿。然而,在第二篇文章中,我(以及我与之合作的大多数人)在为单个陈述省略括号时没有问题。(当然,缩进是系统的,并且您始终使用这种样式。(而且,很多非常好的程序员,编写非常易读的代码,即使在格式化第一种方法时也省略了花括号)。
  3. NO。诸如此类if ( NULL == ptr )的东西丑陋到足以妨碍可读性。直观地编写比较。(这在很多情况下会导致右边的常数。)他的4是个坏建议;任何使代码不自然的东西都会使其可读性降低。
  4. NO。除了int特殊情况外,所有内容均不保留。对于经验丰富的C和C ++程序员,使用unsigned信号位运算符。C ++没有真正的基数类型(或任何其他有效的子范围类型);unsigned由于促销规则,不适用于数值。可能没有数学运算意义的数值(例如序列号)可能是unsigned。但是,我反对这样做,因为它发送了错误的消息:按位运算也没有意义。基本规则是整数类型为int,除非有重要原因,否则要使用其他类型。
  5. NO。系统地进行此操作具有误导性,实际上并不能防止任何事情发生。在严格的OO代码中,delete this;通常是最常见的情况(您不能设置thisNULL),否则,大多数情况delete是在析构函数中的,因此您以后无论如何都无法访问指针。并将其设置为NULL不会对其他任何浮动指针产生任何影响。系统地将指针设置为NULL会给人一种虚假的安全感,实际上并不会给你带来任何好处。

查看任何典型参考文献中的代码。例如,Stroustrup违反了您给出的所有规则,但第一个规则除外。

我建议您另找一位讲师。一个真正知道他在说什么的人。


13
数字4可能很难看,但是有其用途。它试图防止if(ptr = NULL)。我不认为我曾经用过delete this,它比我见过的更普遍吗?我不倾向于认为在使用后将指针设置为NULL并不是一件好事,但YMMV却很糟糕。也许只有我一个人,但他的大多数指导方针似乎并不那么糟糕。
Firedragon

16
@Firedragon:if (ptr = NULL)除非您将其编写为,否则大多数编译器都会发出警告if ((ptr = NULL))。必须同意詹姆斯·坎泽(James Kanze)的观点,NULL首先拥有它的丑陋对我来说绝对不是。
Leo

34
@JamesKanze:我不得不说我不同意你在这里所说的大部分内容,尽管我很欣赏并尊重你提出这些论点的论点。对于有经验的C和C ++程序员,使用无符号信号位运算符。-我完全不同意:使用位运算符表示使用位运算符。对我来说,使用的unsigned指示愿望上,该变量应该仅代表正数程序员的一部分。混合使用带符号的数字通常会导致编译器警告,这可能是讲师想要的。
组件12年

18
对于有经验的C和C ++程序员,是否使用无符号信号位运算符size_t, 任何人?
ta.speot。是2012年

16
@James Kanze,考虑目的。您正在将经验丰富的程序员生成的代码与说明示例进行比较。这些规则由讲师提供,因为它们是他看到他的学生犯的那种错误。有了经验,学生可以放松或无视这些绝对。
约书亚·谢恩·利伯曼

46

所有其他答案都捍卫了讲师的规则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;
}

第一个代码简明扼要;第二个代码is肿。

是的,可以通过在前一行放置开括号在某种程度上减轻这种情况。但是那仍然比没有大括号的代码可读性差。

简而言之:不要编写不必要的代码,这些代码会占用屏幕空间。


26
如果您不相信编写不必要地占用屏幕空间的代码,那么您就没必要在开头加上大括号。我现在可能不得不回避GNU的神圣报复,但说真的-要么您希望代码在垂直方向上紧凑,要么您不想要。而且,如果这样做,则不要做旨在减少代码的垂直紧凑性的事情。但是,正如你说的,已经固定的,你仍然需要删除多余的括号。或者也许只是写成if (a == nullptr) { throw null_ptr_error("a"); }一行。
史蒂夫·杰索普

6
@Steve实际上,出于您所说的原因,我确实将前括号放在上一行。我在这里使用了其他样式,以使差异变得更加明显。
康拉德·鲁道夫2012年

9
+1我完全同意,您的第一个示例无需花括号即可轻松阅读。在第二个示例中,我的个人编码风格是在外部for循环上而不是内部使用大括号。我不同意@SteveJessop的观点,即在垂直紧凑代码方面必须成为极端,而在另一个方面则不同。我省略了带有单衬里的额外括号以减小垂直空间,但是我确实将开口括号放在了新的一行上,因为当对齐括号时,我发现查看范围更容易。目标是可读性,有时这意味着使用更多的垂直空间,而其他时候则意味着使用更少的垂直空间。
Travesty3,2012年

22
“我在现实生活中从未遇到过这个问题”:幸运的您。这样的事情不仅会烫伤您,还会给您90%的三度烧伤(这只是管理层的一部分,需要在深夜进行修复)。
理查德

9
@Richard我根本不买那个。正如我在聊天中所解释的那样,即使应该发生此错误(我不太可能),一旦查看堆栈跟踪,也很容易修复,因为很明显,仅通过查看代码即可发现错误所在。您夸大的说法完全是毫无根据的。
康拉德·鲁道夫2012年

40

我正在研究的代码库中,由于人们对括号的病态厌恶,他们的代码分散了,对于后来出现的人来说,它确实可以对可维护性有所帮助。

我遇到的最常见的问题示例是:

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;

因此,当我想添加一个声明时,如果我不小心的话,我很容易得出结论:

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秒的时间,并且可以节省至少几分钟的调试时间,为什么您选择减少歧义的选项呢?对我来说似乎是虚假的经济。


16
缩进不正确,线条太长而不是花括号不是本例中的问题吗?
Honza Brabec

7
是的,但是遵循仅是“安全”的设计/编码准则,并假设人们也遵循其他准则(例如行长不长),似乎是在自找麻烦。如果括号从一开始就出现,那么在这种情况下不可能以不正确的if块结尾。
2012年

16
如何添加花括号(使其if(really long...editor){ do_foo;}有助于避免这种情况?似乎问题仍然存在。我个人更喜欢在不必要时避免使用花括号,但是这与编写它们所需的时间无关,但可读性降低了由于代码中有两行多余的内容
灰熊

1
好点-我当时认为强制使用花括号也会导致将它们放置在一个明智的位置,但是当然,如​​果有人决心让事情变得困难,可以像在您的示例中那样将它们放在一行中。我以为大多数人不会。
2012年

2
触摸文件时,我要做的第一件事和最后一件事是点击自动格式化按钮。它消除了大多数这些问题。
乔纳森·艾伦

20

我的2c:

使用缩进

明显

从不依赖运算符优先级-始终使用括号

我不会使用“从不”和“总是”这两个词,但总的来说,我认为该规则很有用,在某些语言(Lisp,Smalltalk)中这不是问题。

始终使用{}块-即使是一行

我从来没有这样做,也从来没有一个问题,但是我可以看到它对学生有什么好处,特别是。如果他们以前学习过Python。

比较左侧的常量对象

尤达条件?不谢谢。它会损害可读性。编译代码时只使用最大警告级别。

对大于等于0的变量使用无符号

好。有趣的是,我听说Stroustrup表示不同意。

删除后将指针设置为NULL-双重删除保护

不好的建议!永远不要有指向已删除或不存在的对象的指针。


5
仅为最后一点+1。无论如何,原始指针没有业务拥有内存。
康拉德·鲁道夫2012年

2
关于使用无符号的:不仅是Stroustrup,还有K&R(用C语言),Herb Sutter和(我认为)Scott Meyers。实际上,我从未听说过任何真正了解C ++规则的人都主张使用无符号。
詹姆斯·坎泽

2
@JamesKanze实际上,在同一时间,我听到Stroustrup的意见(2008年在波士顿召开的一次会议),Herb Sutter在场,当场与Bjarne意见不同。
Nemanja Trifunovic

3
只是为了完成“ unsigned已损坏”,问题之一是,当C ++比较大小相似的有符号和无符号类型时,它会在进行比较之前转换为无符号类型。这导致价值的变化。转换为带符号不一定会更好。比较实际上应该“好像”将两个值都转换为一个较大的类型,该类型可以表示这两种类型中的所有值。
詹姆斯·坎泽

1
@SteveJessop我认为您必须在函数返回的上下文中使用它unsigned。我确定他exp(double)返回的值比MAX_INT:-) 没问题。但是,真正的问题再次是隐式转换。int i = exp( 1e6 );是完全有效的C ++。斯特劳斯特鲁普(Stroustrup)实际上提议过时弃用有损隐式转换,但委员会对此并不感兴趣。(一个有趣的问题:会unsigned-> int被认为是有损的。我会同时考虑unsigned-> intint-> unsigned有损。这将大大有助于unsigned确定
James Kanze 2012年

18

它更直观,更容易理解。它使意图很明确。

它确保代码不会破坏当一个新的用户可能在不知不觉中错过{}同时加入了新的代码语句。


Makes the intent clear+1,这可能是最简洁准确的原因。
Qix-蒙尼卡(Monica)

13

为了补充上述非常明智的建议,我在重构某些变得很关键的代码时遇到的一个示例如下:我正在更改一个非常大的代码库,以从一个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允许您在没有括号的地方添加括号的选项,如果您发现自己和我以前的位置相同,我强烈建议您这样做。


5
我曾经看过一些文档,其中包含精美的“ Microsoftligent”。是的,使用全局搜索和替换可能会犯重大错误。这只是意味着必须聪明地而不是microsoftligently地使用全局搜索和替换。
皮特·贝克尔

4
我知道这不是我要进行的事后检验,但是如果您要在源代码上进行文本替换,则应按照与已建立好的文本替换所使用的相同规则进行操作在语言中:宏。您不应该编写宏#define FOO() func1(); \ func2(); (反斜杠后带有换行符),搜索和替换也一样。就是说,我已经看到“总是使用花括号”作为样式规则得到了改进,这恰好是因为它使您不必将所有多语句宏都包装在中do .. while(0)。但我不同意。
史蒂夫·杰索普

2
顺便说一句,就日本虎杖而言,这是“成熟的”:我并不是说我们应该不遗余力地使用宏和文本替换,但我是说当我们这样做时事情,我们应该以一种可行的方式进行操作,而不是仅在将特定样式规则成功地强加于整个代码库的情况下才能进行某些工作:-)
Steve Jessop

1
@SteveJessop还可以为大括号和皮带辩护。如果您必须使用这样的宏(并且在C ++和之前使用过inline),那么您可能应该使它们尽可能地像一个函数一样工作,并在do { ... } while(0)必要时使用技巧(以及很多额外的括号。但这仍然不会如果那是家庭风格,请不要阻止您在任何地方使用牙套(FWIW:我曾在各种家庭风格的地方工作过,涵盖了这里讨论的所有样式。我从来没有发现任何严重的问题。)
詹姆斯·坎泽

1
而且我认为,您使用的样式越多,阅读和编​​辑代码就越仔细。因此,即使您偏爱最容易阅读的内容,也仍然可以成功阅读其他内容。我在一家公司工作过,不同的团队使用不同的“房屋风格”编写了不同的组件,正确的解决方案是在午餐室抱怨效果不佳,而不是尝试创建全局风格:-)
史蒂夫·杰索普

8

我使用的是{}无处不在的情况,除了一些明显的情况。单行是以下情况之一:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 

在返回之前添加一些方法可能会伤害您。缩进表示在满足条件时正在执行return,但是它将始终返回。

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();
    }
}

1
只需在using语句中使用逗号即可,而不必:)
Ry-

1
@minitech这在这里根本行不通–您只能在类型相等时使用逗号,而不能在不相等的类型上使用逗号。卢卡斯(Lukas)的做法是规范的方式,IDE甚至以不同的方式设置格式(请注意,第二种方法没有自动缩进using)。
康拉德·鲁道夫2012年

8

我能想到的最相关的例子:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();

if将与哪个else配对?缩进意味着外部if获取else,但这实际上并不是编译器将如何看待;该 if将得到else和外if没有。您必须知道这一点(或在调试模式下看到它的行为)才能通过检查找出为什么此代码可能未达到您的期望。如果您了解Python,它将变得更加混乱。在这种情况下,您知道缩进定义了代码块,因此您希望它根据缩进进行评估。但是,C#不会给空白带来麻烦。

话虽如此,但我并不完全同意这条“始终使用方括号”规则。它使代码在垂直方向上非常嘈杂,从而降低了快速读取代码的能力。如果声明是:

if(someCondition)
   DoSomething();

...那么应该这样写。语句“始终使用方括号”听起来像“始终用括号括住数学运算”。这将使非常简单的语句a * b + c / d变为((a * b) + (c / d)),从而导致缺少闭包(许多编码器的祸根)的可能性,原因何在?操作的顺序是众所周知的并且是强制执行的,因此括号是多余的。您只需要使用括号来强制执行与通常应用的操作顺序不同的操作:a * (b+c) / d例如。块括号类似;使用它们来定义在与默认值不同且不是“明显”(主观但通常很常识)的情况下要执行的操作。


3
@AlexBrown ...这正是我的意思。OP中规定的规则是“即使使用单行,也总是使用方括号”,出于上述原因,我不同意。括号将对第一个代码示例有所帮助,因为代码不会像缩进那样表现;您必须使用方括号else将第一个if而不是第二个配对。请删除下注。
KeithS 2012年

5

浏览答案时,没有人明确指出我习惯的那种做法,并讲述您的代码故事:

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++;
}

将与if应该j++同一行上发送给其他任何人,“我只希望此块不断增加j。当然,只有在行尽可能简化的情况下才值得这样做,因为如peri所提到的,在此处放置一个断点将不会很有用。

实际上,我刚刚遇到了Twitter Storm API的一部分,该部分在Java中具有这种“一类”的代码,这是该幻灯片的第43页的执行代码的相对摘要

...
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
(1)把该语句在同一行上使得它可读的,而不是更多。特别简单的想法,例如增量,很容易被忽略。不要将它们放在新的一行。(2)当然,您可以将for循环放在一行上,这为什么不起作用?它的工作原理与您可以省略括号的原因相同。换行符在C ++中根本不重要。(3)您的条件运算符示例除了令人恐惧之外,还无效C ++。
康拉德·鲁道夫2012年

@KonradRudolph谢谢,我对C ++有点生疏。我从来没有说过(1)为更具可读性,但它会发出信号,以是这段代码是处于联机一行。(2)我的评论是,我根本无法阅读它,也无法知道它是否有效;这是不执行此操作的示例。(3)谢谢,我已经很长时间没有编写C ++了。我现在修复。
Pureferret 2012年

2
同时在一行中放置一个以上的表达式会使调试代码更加困难。您如何在该行的第二个表达式上设置断点?
皮奥特(霹雳)霹雳州2012年

5

因为当您有两个不带的语句时{},很容易错过一个问题。假设代码看起来像这样。

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;

究竟。只是不要放入(完全不必要的)换行符+缩进,您完全回避了这个问题,每个人总是这么快就提出来。
亚历山大-恢复莫妮卡

4

通过清楚地定义循环和条件块的范围,它使您的代码更具可读性。它还可以避免意外错误。


4

wrt 6:比较安全,因为删除空指针是无操作的。因此,如果您偶然不小心通过了该路径两次,则不会导致内存损坏而释放了空闲的或已分配给其他内容的内存。

对于静态文件作用域对象和单例,它们的生存期不是很清楚,并且已知在销毁它们之后会重新创建,这是一个大问题。

在大多数情况下,可以通过使用auto_ptrs避免此需求


6
如果您碰巧两次经过该路径,则会遇到编程错误。将指针设置为null可以减少此错误的危害,并不能解决根本的问题。
皮特·贝克尔

1
同意,但是我以前曾看到过此建议,并且我相信它是在某些专业编程标准中的。我更多地是在评论海报的教授为什么要提出它,而不是什么时候才有用
汤姆·坦纳

2
遵循皮特·贝克尔(Pete Becker)所说的话:它不能解决根本的问题,但可以掩盖它。(在某些情况下,您需要NULL在删除指针后将其设置为。NULL在这种情况下,如果该指针的正确值是正确的;例如,该指针指向一个缓存的值,并NULL指示一个无效的缓存。但是当您看到有人在设置指向NULL析构函数中最后一行的指针,您想知道他是否知道C ++。)
James Kanze,2012年

4

我喜欢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++;
}

7
我喜欢每个人如何设法忽略它们for (int i = 0; i < 100; i += 2);,以继续关于缩进的争论;-)我们可能有一个完整的独立斗争,如何“最好”地表达“对于i具有一定属性的特定范围内的每个人”的逻辑在C ++中没有循环,使用标准算法filter_iterator和/或的一些恶梦组合counting_iterator
史蒂夫·杰索普

1
同样,如果我们有那样的话,那么我们可能在如何缩进最终的大量单个语句上意见分歧。
史蒂夫·杰索普

2
@Steve,虽然这只是一个例子。该模式有很多合法用途。显然,如果您要计算从1到100可以被2整除的数字,那么您要做的就是100/2。
MikeFHay 2012年

2
当然,我知道,这就是为什么我抽象为“针对i具有特定属性的特定范围内的每个对象”。通常,在SO上,人们很快就会忽略实际问题,而对给出的示例采用完全不同的方法。但是缩进很重要,所以我们不;-)
史蒂夫·杰索普

4

帮助防止上述错误的一种方法是内联不使用花括号时要发生的情况。当您尝试修改代码时,很难注意到这些错误。

if (condition) doSomething();
else doSomethingElse();

if (condition) doSomething();
    doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong

5
第二个选项将产生编译错误,因为else并不与关联if
Luchian Grigore

内联的一个不那么明显的问题是,默认情况下,大多数IDE使用自动格式化实用程序时会将其更改为缩进样式。
Honza Brabec 2012年

@Honza:不过,这是一个充满争议的政治问题。如果我们在代码基础上进行合作,要么必须使用相同的缩进样式直到每个最后细节,要么我们都必须同意不要“仅仅因为”而自动格式化现有代码。如果是前者,则约定的样式仍可以包括此样式,但是您必须配置IDE使其尊重它,或者不使用自动格式化。如果我们永远都使用同一个IDE,那么就可以同意通用格式是“无论我的IDE自动格式化为什么格式”,否则就不太好了。
史蒂夫·杰索普

3

如果您是编译器,则没有任何区别。两者是相同的。

但是对于程序员而言,第一个更清晰,易于阅读且不易出错。


2
{无论如何,除了在自己的线路上打开。
罗布

3

添加花括号的另一个示例。一旦我搜索了一个错误并找到了这样的代码:

void SomeSimpleEventHandler()
{
    SomeStatementAtTheBeginningNumber1;
    if (conditionX) SomeRegularStatement;
    SomeStatementAtTheBeginningNumber2;
    SomeStatementAtTheBeginningNumber3;
    if (!SomeConditionIsMet()) return;
    OtherwiseSomeAdditionalStatement1;
    OtherwiseSomeAdditionalStatement2;
    OtherwiseSomeAdditionalStatement3;
}

如果逐行阅读该方法,您会注意到该方法中存在一个条件,如果条件不正确,则该条件会返回。但实际上,它看起来像100个其他简单事件处理程序,它们根据某些条件设置了一些变量。有一天,Fast Coder出现了,并在方法末尾添加了其他变量设置语句:

{
    ...
    OtherwiseSomeAdditionalStatement3;
    SetAnotherVariableUnconditionnaly;
}

结果,在SomeConditionIsMet()时执行SetAnotherVariableUnconditionnaly,但快速人员没有注意到它,因为所有行的大小几乎都相似,即使返回条件是垂直缩进的,也不太明显。

如果条件返回的格式如下:

if (!SomeConditionIsMet())
{
    return;
}

它非常引人注目,快速编码器将使您一目了然。


如果return在添加函数之前,快速编码器不能麻烦地在函数体内发现语法突出显示的语句,则不应让快速编码器靠近您的代码。您不会因为花括号而阻止此类人绕过您的代码。
cmaster-恢复莫妮卡

@cmaster他不再与我们合作。无论如何,语法高亮显示是好的,但是请记住,有些人看不清(我去年甚至看过一位盲人程序员的帖子)。
Artemix 2014年

2

我认为第一个明确,然后第二个。它提供了关闭指令,很少有代码的感觉是,当代码变得复杂精细{...}有很大帮助,哪怕是endifbegin...end

//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++;

1

完成后,最好将指针设置为NULL。

这是一个示例原因:

A类执行以下操作:

  1. 分配一块内存
  2. 然后过一会儿,它将删除该内存块,但不会将指针设置为NULL

B类执行以下操作

  1. 分配内存(在这种情况下,它恰好被分配了与A类删除的相同的内存块。)

此时,A类和B类都具有指向同一内存块的指针,就A类而言,该内存块不存在,因为它已完成。

考虑以下问题:

如果在A类中发生逻辑错误,导致它写入现在属于B类的内存怎么办?

在这种特定情况下,您不会遇到严重的访问异常错误,因为内存地址是合法的,而类A现在一直在有效破坏类B数据。

如果类B遇到意外的值,它最终可能会崩溃,并且当它确实崩溃时,很可能,当问题出在A类时,您将花费相当长的时间在B类中寻找此错误。

如果将已删除的内存指针设置为NULL,则只要A类中的任何逻辑错误都试图写入NULL指针,就会出现异常错误。

如果您担心指针第二次为NULL时使用double delete导致的逻辑错误,请为此添加断言。

另外:如果您要投反对票,请解释。


2
如果存在逻辑错误,则应该修复它,而不是掩盖它。
uınbɐɥs

@ Barmar,OP说... 6.删除后将Pointer设置为NULL-双重删除保护//不错。有人对不将其设置为Null做出了回应,我是说为什么应该将其设置为NULL,即6.的哪一部分。我设置NULL的答案不适合6?
约翰

1
@Shaquin,您如何建议首先找到这些逻辑错误?一旦删除内存后将指针变量设置为NULL。任何尝试引用NULL指针的尝试都会在您进行非法尝试的行上崩溃到调试器。您可以回溯并查看逻辑错误的位置并解决问题。如果删除内存后未将指针变量设置为NULL,则由于UNAWARE逻辑错误而导致的试图写入此删除的内存的非法尝试可能会成功,因此不会崩溃。它没有掩盖它。
2012年

1

始终使用大括号是非常简单且健壮的规则。但是,当有许多大括号时,代码可能看起来不太雅致。如果规则允许省略花括号,则应该有更详细的样式规则和更复杂的工具。否则,它很容易导致混乱和混乱(不雅致)的代码。因此,将单一样式规则与其余样式指南和使用的工具分开看可能是徒劳的。我将带来关于该规则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时,使工具立即引起抱怨并不难,因此可以及时发现大多数错误。

第三个细节(经常被该规则的反对者遗忘)是由单个分号表示的空语句的混乱性质。大多数具有一定经验的开发人员迟早会因单独放错分号或使用单独分号编写的空语句而感到困惑。在外观上,两个花括号而不是单个分号更容易发现。


1

我必须承认,并非总是{}用于单行,但这是一个好习惯。

  • 假设您编写的代码没有括号,如下所示:

    对于(int i = 0; i <100; ++ i)for(int j = 0; j <100; ++ j)DoSingleStuff();

过了一段时间,您想在j循环中添加其他内容,而只是通过对齐来完成,而忘记添加括号。

  • 内存分配速度更快。可以说您有很大的范围,并在其中创建了大数组(没有数组,new因此它们在堆栈中)。离开作用域后,这些数组将从内存中删除。但是,您可能会在一个地方使用该数组,并且该数组将在堆栈中放置一段时间,并成为某种垃圾。由于堆栈数量有限且非常小,因此可能会超出堆栈大小。因此,在某些情况下,最好写出{}防止这种情况的方法。注意这不是针对单行,而是针对以下情况:

    if(...){// SomeStuff ... {//我们没有if,while等。// SomeOtherStuff} // SomeMoreStuff}

  • 第三种使用方法与第二种相似。它不是为了使堆栈更整洁,而是要打开一些功能。如果您使用mutex长函数,通常最好在访问数据之前和读/写完之后锁定和解锁。注意如果您有自己的东西classstruct使用constructordestructor来锁定内存,则使用这种方式。

  • 更:

    如果(...)如果(...)SomeStuff(); else SomeOtherStuff(); //如果是第二个则转到第二个,但从鳄鱼的角度来看它位于第一个...

总而言之,我不能说始终{}对单个行使用的最佳方法是什么,但是这样做并不坏。

重要编辑 如果您为一行编写编译代码括号,则无济于事,但是,如果您的代码将被解释,则会使代码的运行速度非常缓慢。非常轻微。


1

编写控制语句有多种可能的方法。它们的某些组合可以并存而不会影响可读性,但是其他组合会引起麻烦。样式

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相同的缩进开头,那么将提供清晰的视觉指示,表明存在异常。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.