我在读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++;

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


当前回答

因为当你有两个没有{}的语句时,很容易漏掉一个问题。让我们假设代码是这样的。

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;

看起来不错。它的问题很容易被忽略,特别是当包含代码的函数非常大的时候。问题是,失败是无条件的。您可以很容易地想象这是多么令人沮丧(让您问为什么上次hash_update总是失败,毕竟在hash_update函数中一切看起来都很好)。

然而,这并不意味着我支持在所有地方都添加{}(在我看来,到处都看到{}很烦人)。虽然这可能会导致问题,但在我自己的项目中却从来没有,因为我个人的编码风格禁止没有{}的条件,当它们不在同一行时(是的,我同意我的编码风格是非常规的,但我喜欢它,并且我在为其他项目贡献时使用项目的代码风格)。这使得下面的代码很好。

if (something) goto fail;

但下一个不是。

if (something)
    goto fail;

其他回答

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

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

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

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


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

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

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

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

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

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++中运行过三元运算符,我不知道这是否有效,但它确实存在。

简而言之:

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

当你完成它时,最好将指针设置为NULL。

下面是一个例子:

A类行为如下:

分配一块内存 然后一段时间后,它删除这块内存,但不将指针设置为NULL

B类做以下事情

分配内存(在这个实例中,它得到的内存块恰好与类a删除的内存块相同)

在这一点上,类A和类B都有指向同一个内存块的指针,就类A而言,这块内存块不存在,因为它已经用完了。

考虑以下问题:

如果在类a中有一个逻辑错误,导致它写入现在属于类B的内存呢?

在这个特定的实例中,您不会得到一个糟糕的访问异常错误,因为内存地址是合法的,而类A现在有效地破坏了类B数据。

类B可能最终崩溃,如果它遇到意外值,当它崩溃时,很有可能,当问题出现在类a时,您将花费相当长的时间在类B中查找这个bug。

如果您已经将删除的内存指针设置为NULL,那么只要类A中的任何逻辑错误试图写入NULL指针,您就会得到一个异常错误。

如果您担心在指针第二次为NULL时使用双重删除会出现逻辑错误,那么可以为此添加assert。

我正在处理的代码库被那些病态地厌恶大括号的人分散在代码中,对于后来的人来说,它确实可以对可维护性产生影响。

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

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秒左右的时间,并且可以为您节省至少几分钟的调试时间,为什么不选择减少歧义性呢?在我看来这是虚假的节约。