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

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


当前回答

帮助防止在前面的回答中描述的错误的一个选项是内联您希望在不使用大括号时发生的事情。当您尝试修改代码时,很难不注意到错误。

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

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

其他回答

我认为第一个比第二个更清楚。它给人一种结束指令的感觉。使用短代码是可以的,但是当代码变得复杂时,{…}帮助很大,即使它是endif或begin…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++;

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

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

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

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

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

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

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

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

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

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


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

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

下面是一个例子:

A类行为如下:

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

B类做以下事情

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

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

考虑以下问题:

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

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

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

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

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