【问题标题】:"Double" assignment - should it be avoided?“双重”分配——应该避免吗?
【发布时间】:2010-11-13 11:14:05
【问题描述】:

假设你有一些像

这样的表达式
i = j = 0

假设这是在您选择的语言中明确定义的。把它分成两个表达式通常会更好吗

i = 0
j = 0

我有时会在库代码中看到这一点。就简洁性而言,它似乎并没有给您带来太多好处,并且不应该比这两个语句更好(尽管这可能取决于编译器)。那么,有理由使用其中一个吗?还是只是个人喜好?我知道这听起来像是一个愚蠢的问题,但它困扰了我很长时间:-)。

【问题讨论】:

    标签: coding-style


    【解决方案1】:

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

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

    【讨论】:

    • @kriss:但问题不在于语法。
    • 我指出,单行组合赋值的等价物不是将相同的常量分配给两个对象。这依赖于分配结果是分配给第一个变量的值的约定。这是许多可能的选择。在支持赋值时隐式转换的语言(如 C++)中,返回值是否应该是执行转换之前或之后的对象甚至不明显(我相信这是第一个,但必须检查标准参考文档当然)。
    【解决方案2】:

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

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

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

    load 0
    store [j]
    store [i]
    

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

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

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

    【讨论】:

      【解决方案3】:

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

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

      第二种情况将赋值视为一个语句(做某事的命令)。

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

      • 赋值运算符基本上是副作用运算符,现在为编译器优化它们是一个问题。在 C 和 C++ 等语言中,它们会导致许多未定义行为情况或未优化的代码。
      • 尚不清楚它应该返回什么。赋值运算符应该返回分配的值,还是应该返回它已存储的位置的地址。一种或另一种可能有用,具体取决于上下文。
      • 对于像+= 这样的复合分配,情况更糟。目前还不清楚运算符是否应该返回初始值、组合结果,甚至是存储到的位置。

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

      基本上,我会尽可能避免作为运算符进行赋值。所呈现的案例非常简单,并没有真正令人困惑,但作为一般规则,我仍然更喜欢。

      i = 0
      j = 0
      

      i, j = 0, 0
      

      对于支持并行分配的语言。

      【讨论】:

        【解决方案4】:

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

        例如,如果ij 是二维数组的索引,它们都从零开始。 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'
                          ;
            }
        
        }
        

        【讨论】:

          【解决方案5】:

          大多数人会发现这两种可能性同样具有可读性。其中一些人对任何一种方式都有个人偏好。但是有些人乍一看可能会对“双重分配”感到困惑。我个人喜欢单独的方法,因为

          • 100% 可读
          • 与双重变体相比,它并不是很冗长
          • 它让我忘记了 = 运算符的关联规则

          【讨论】:

            【解决方案6】:

            第二种方式更易读,更清晰,我更喜欢。

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

            int i, j;
            

            而不是

            int i;
            int j;
            

            如果他们连续进行。尤其是MyVeryLong.AndComplexType

            【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-04-06
            • 2014-10-24
            • 2012-10-14
            • 2012-01-25
            • 1970-01-01
            • 2013-08-27
            • 2012-02-25
            相关资源
            最近更新 更多