Назначение "Double" - следует ли его избегать?

У вас есть выражение, например

i = j = 0

полагая, что это хорошо определено на выбранном вами языке. Было бы лучше разделить это на два выражения типа

i = 0
j = 0

Я иногда вижу это в библиотечном коде. Похоже, вы мало покупаете с точки зрения краткости и не должны выполнять ничего лучше, чем два утверждения (хотя это может быть зависимым от компилятора). Итак, есть ли причина использовать один над другим? Или это просто личное предпочтение? Я знаю, что это звучит как глупый вопрос, но он долго меня беспокоит: -).

Ответ 1

Эти две формы отражают различные точки зрения на назначение.

Первый случай рассматривает назначение (по крайней мере, внутреннее) как оператор (функция возвращает значение).

Второй случай рассматривает назначение как инструкцию (команда что-то делать).

В некоторых случаях, когда назначение как оператор имеет смысл, в основном для краткости, или для использования в контекстах, ожидающих результата. Однако я чувствую, что это сбивает с толку. По нескольким причинам:

  • Оператор присваивания - это в основном операторы побочных эффектов, и в настоящее время это проблема оптимизации их для компиляторов. В таких языках, как C и С++, они приводят ко многим Undefined поведенческим случаям или неоптимизированному коду.
  • Непонятно, что он должен вернуть. Если оператор присваивания возвращает значение, которое было присвоено, или оно должно вернуть адрес места, в котором оно было сохранено. Один или другой может быть полезен, в зависимости от контекста.
  • С композитными присваиваниями, такими как +=, это еще хуже. Неясно, должен ли оператор возвращать начальное значение, комбинированный результат или даже место, в которое оно было сохранено.

Назначение как оператор приводит иногда к промежуточным переменным, но это единственный недостаток, который я вижу. Это понятно, и компиляторы знают, как эффективно оптимизировать последовательные такие утверждения.

В принципе, я бы избегал назначения как оператора, когда это было возможно. Представленный случай очень прост и не очень запутан, но, как правило, я бы предпочел.

i = 0
j = 0

или

i, j = 0, 0

для языков, поддерживающих параллельное назначение.

Ответ 2

Когда-то была разница в производительности, и это одна из причин использования такого рода присвоений. Компиляторы превратили бы i = 0; j = 0; в:

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

Таким образом, вы можете сохранить инструкцию с помощью i = j = 0, поскольку компилятор превратит это в:

load 0
store [j]
store [i]

В настоящее время компиляторы могут сами делать подобные типы оптимизации. Кроме того, поскольку текущие процессоры запускают сразу несколько инструкций, производительность больше не может быть просто измерена в количестве инструкций. Инструкции, в которых одно действие не зависит от результата другого, могут выполняться параллельно, поэтому версия, использующая отдельное значение для каждой переменной, может быть быстрее.

Что касается стиля программирования, вы должны использовать способ, который наилучшим образом отражает намерение кода.

Вы можете, например, связать назначения, когда вы просто хотите очистить некоторые переменные, и сделать их отдельными назначениями, когда значение имеет определенное значение. Особенно, если значение установки одной переменной в значение отличается от установки другой переменной на одно и то же значение.

Ответ 3

Во-первых, на семантическом уровне зависит, хотите ли вы сказать, что i и j являются одним и тем же значением или просто имеют одинаковое значение.

Например, если i и j являются индексами в двумерном массиве, они начинаются с нуля. j = i = 0 говорит, что i начинается с нуля, а j начинается с начала i. Если вы хотите начать со второй строки, вы не обязательно захотите начать со второго столбца, поэтому я бы не инициализировал их как в одном и том же выражении - индексы для строк и столбцов независимо друг от друга начинаются с нуля.

Кроме того, в языках, где i и j представляют сложные объекты, а не интегральные переменные, или если назначение может вызвать неявное преобразование, они не эквивалентны:

#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

Это зависит от языка. В языках с высоким объектно-ориентированным языком двойное присвоение приводит к тому, что один и тот же объект присваивается нескольким переменным, поэтому изменения в одной переменной отражаются в другой.

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

Ответ 5

Большинство людей найдут обе возможности одинаково читаемыми. Некоторые из этих людей будут иметь личное предпочтение в любом случае. Но есть люди, которые могут с первого взгляда запутаться в "двойном задании". Мне лично нравится отдельный подход, bacause

  • Он на 100% читается
  • Это не очень многословно по сравнению с двойным вариантом
  • Это позволяет мне забыть правила ассоциативности для оператора =

Ответ 6

Второй способ более читабельен и ясен, я предпочитаю его.

Однако я стараюсь избегать объявления "double":

int i, j;

вместо

int i;
int j;

если они идут последовательно. Особенно в случае MyVeryLong.AndComplexType