我在读c++老师的课堂笔记,他是这样写的:

使用缩进// OK 永远不要依赖运算符优先级-总是使用括号// OK 总是使用{}块-即使是单行//不可以,为什么?? Const对象在比较的左边// OK 对>= 0的变量使用unsigned,这是个不错的技巧 删除后将指针设置为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++;

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


当前回答

所有其他答案都捍卫了讲师的规则3。

让我说我同意你的观点:这个规则是多余的,我不建议它。确实,如果你总是添加花括号,理论上可以防止错误。另一方面,我在现实生活中从未遇到过这个问题:与其他答案所暗示的相反,我从未忘记在必要时添加花括号。如果使用适当的缩进,一旦多个语句缩进,就需要立即添加花括号。

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

第一个代码很简洁;第二个代码很臃肿。

是的,这可以通过在前一行上加上开大括号在一定程度上得到缓解。所以:如果你坚持使用大括号,至少要把左大括号放在前一行。

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


在最初写答案的时间里,我基本上接受了流行的代码风格,使用大括号,除非我能把整个语句放在前一行。我仍然认为不使用冗余大括号通常更具可读性,而且我仍然从未遇到过由此引起的错误。

其他回答

让我们尝试在增加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中指出了另一个很好的理由。的回答。

我能想到的第三个是嵌套的if:

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

现在,假设您现在想在未满足cond1时执行somethingelse()(新特性)。所以:

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

这显然是错误的,因为else与内部的if相关联。


编辑:既然这引起了一些关注,我将澄清我的观点。我想回答的问题是

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

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

总是使用{}块-即使是单行//不可以,为什么??

我并不是说总是使用{}块。如果这是一个足够简单的条件和行为,那就不要。如果你怀疑有人可能稍后会来修改你的代码来增加功能,那就这么做。

wrt 6:它更安全,因为删除空指针是一个无操作。因此,如果您碰巧两次经过这条路径,您不会因为释放空闲内存或已分配给其他内存而导致内存损坏。

这是静态文件作用域对象和单例对象的主要问题,它们没有非常明确的生命周期,并且已知它们在被销毁后会被重新创建。

在大多数情况下,您可以通过使用auto_ptrs来避免这种需要

有许多可能的方法来编写控制语句;它们的某些组合可能共存而不损害易读性,但其他组合将引起麻烦。的风格

if (condition)
  statement;

可以与其他一些编写控制语句的方法很好地共存,但与其他方法就不那么好了。如果多行受控语句被写成:

if (condition)
{
  statement;
  statement;
}

那么,从视觉上看,哪些if语句控制单行,哪些语句控制多行就很明显了。然而,多行If语句被写成:

if (condition) {
  statement;
  statement;
}

那么,如果构造不添加必要的大括号,那么试图扩展单个语句的可能性就会高得多。

如果代码库大量使用这种形式,那么下一行的单个语句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开头的语句将提供一个清晰的视觉指示,表明有不寻常的地方。

另一个添加花括号的例子。

有一次我在搜索一个bug,发现了这样的代码:

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

如果你逐行阅读这个方法,你会注意到方法中有一个条件,如果不为真就返回。但实际上,它看起来像其他100个简单的事件处理程序,它们基于某些条件设置一些变量。有一天,Fast Coder进来了,在方法的末尾添加了额外的变量设置语句:

{
    ...
    OtherwiseSomeAdditionalStatement3;
    SetAnotherVariableUnconditionally;
}

因此,setanothervariable无条件地在SomeConditionIsMet()时执行,但速度快的家伙没有注意到它,因为所有的行几乎都是相似的大小,即使返回条件垂直缩进,它也不那么明显。

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

if (!SomeConditionIsMet())
{
    return;
}

它是非常明显的,快速编码器一眼就能发现它。

纵观答案,没有人明确地说明了我习惯的那种实践,讲述你的代码的故事:

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 should相同的行上,向其他任何人发出信号:“我只想让这个块永远增加j”。当然,只有在行代码尽可能简单的情况下,这样做才有意义,因为在这里设置断点,正如前面提到的,并不是很有用。

事实上,我刚刚遇到了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++;

这很糟糕,我甚至不知道它是否有效(如预期的那样);不要这样做。新行和大括号有助于区分独立但相关的代码段,就像散文中的逗号或分号一样。上面的块是一个很长的句子,里面有几个从句和一些其他的语句,从来没有中断或暂停来区分不同的部分。

如果你真的想要向其他人发送一个只有一行的任务,请使用三元操作符或?:form:

for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;

但这几乎是代码高尔夫,我认为这不是很好的实践(我不清楚是否应该将j++放在:的一侧)。注意,我以前没有在c++中运行过三元运算符,我不知道这是否有效,但它确实存在。

简而言之:

想象一下您的读者(即维护代码的人)如何解释您的故事(代码)。让他们尽可能清楚地知道。如果您知道新手程序员/学生正在维护这个,甚至可能留下尽可能多的{},这样他们就不会感到困惑。