“双重”任务 - 应该避免吗?

时间:2010-11-13 11:14:05

标签: coding-style

考虑你有一些像

这样的表达方式
i = j = 0

假设您的选择语言定义明确。将它分成两个表达式(如

)通常会更好
i = 0
j = 0

我有时会在库代码中看到这一点。它在简洁性方面似乎并没有给你带来太大的好处,并且不应该比两个语句更好地执行(尽管这可能与编译器有关)。那么,是否有理由使用一个而不是另一个?或者只是个人偏好?我知道这听起来像个愚蠢的问题,但现在很长时间困扰着我: - )。

6 个答案:

答案 0 :(得分:11)

曾几何时存在性能差异,这是使用这种分配的原因之一。编译器会将i = 0; j = 0;转换为:

load 0
store [i]
load 0
store [j]

因此,您可以使用i = j = 0来保存指令,因为编译器会将其转换为:

load 0
store [j]
store [i]

现在编译器可以自己进行这种类型的优化。此外,由于当前CPU一次运行多个指令,因此无法再简单地按指令数量来衡量性能。一个动作不依赖于另一个动作的结果的指令可以并行运行,因此对每个变量使用单独值的版本实际上可能更快。

关于编程风格,您应该使用最能表达代码意图的方式。

例如,当您只想清除某些变量时,可以链接分配,并在值具有特定含义时将其分配。特别是如果将一个变量设置为该值的含义与将另一个变量设置为相同的值不同。

答案 1 :(得分:4)

这取决于语言。在高度面向对象的语言中,双重赋值会导致同一个对象被分配给多个变量,因此一个变量的变化会反映在另一个变量中。

$ python -c 'a = b = [] ; a.append(1) ; print b'
[1]

答案 2 :(得分:4)

这两种形式反映了对作业的不同观点。

第一种情况将赋值(至少是内部赋值)视为运算符(返回值的函数)。

第二种情况将赋值视为语句(执行某些操作的命令)。

在某些情况下,作为运算符的赋值具有它的重点,主要是为了简洁,或者在期望结果的上下文中使用。不过我觉得很困惑。有几个原因:

  • 赋值运算符基本上是副作用运算符,现在为编译器优化它们是个问题。在C和C ++等语言中,它们会导致许多未定义行为案例或未经优化的代码。
  • 目前还不清楚应该归还什么。赋值运算符是否应返回已分配的值,或者是否应返回已存储的位置的地址。根据具体情况,其中一个可能有用。
  • 使用+=之类的复合作业,情况会更糟。目前还不清楚操作员是否应该返回初始值,合并结果,甚至是存储它的位置。

作为语句的赋值有时会导致中间变量,但这是我看到的唯一缺点。很明显,编译器知道如何有效地优化连续的这样的陈述。

基本上,我会尽可能避免分配为运营商。提出的案例非常简单,并不是真的令人困惑,但作为一般规则,我仍然更喜欢。

i = 0
j = 0

i, j = 0, 0

支持并行分配的语言。

答案 3 :(得分:3)

首先,在语义层面,这取决于您是想要说ij是相同的值,还是恰好两者都具有相同的值。

例如,如果ij是2D数组的索引,则它们都从零开始。 j = i = 0表示i从零开始,ji开始。如果你想从第二行开始,你不一定要从第二列开始,所以我不会在同一个语句中初始化它们 - 行和列的索引独立地发生在从零开始。

此外,在ij表示复杂对象而不是整数变量的语言中,或者赋值可能导致隐式转换的语言中,它们不等效:

#include <iostream>

class ComplicatedObject
{
public:
    const ComplicatedObject& operator= ( const ComplicatedObject& other ) {
        std::cout << "    ComplicatedObject::operator= ( const ComplicatedObject& )\n";
        return *this;
    }
    const ComplicatedObject& operator= ( int value ) {
        std::cout << "    ComplicatedObject::operator= ( int )\n";
        return *this;
    }

};

int main ()
{
    {
        // the functions called are not the same
        ComplicatedObject i;
        ComplicatedObject j;

        std::cout << "j = i = 0:\n";
        j = i = 0;

        std::cout << "i = 0; j = 0:\n";
        i = 0;
        j = 0;
    }

    {
        // the result of the first assignment is 
        // effected by implicit conversion 
        double j;
        int i;

        std::cout << "j = i = 0.1:\n";
        j = i = 0.1;

        std::cout << "    i == " << i << '\n'
                  << "    j == " << j << '\n'
                  ;

        std::cout << "i = 0.1; j = 0.1:\n";
        i = 0.1;
        j = 0.1;

        std::cout << "    i == " << i << '\n'
                  << "    j == " << j << '\n'
                  ;
    }

}

答案 4 :(得分:2)

大多数人会发现两种可能性同样可读。其中一些人会对这两种方式都有个人偏好。但有些人乍一看可能会被“双重任务”弄糊涂。我个人喜欢单独的方法,因为

  • 100%可读
  • 与双变种
  • 相比,它并不是真正的冗长
  • 它让我忘记了= operator
  • 的关联规则

答案 5 :(得分:1)

第二种方式更具可读性和清晰度,我更喜欢它。

但是我尽量避免“双重”声明:

int i, j;

而不是

int i;
int j;
如果他们连续上去的话。特别是在MyVeryLong.AndComplexType

的情况下