Зачем использовать присвоение в условии?

80

На многих языках задания легальны в условиях. Я так и не понял причину этого. Зачем писать:

if (var1 = var2) {
  ...
}

вместо:

var1 = var2;
if (var1) {
  ...
}
Lajos
источник
Действительно ли этот вопрос не зависит от языка? На какие языки это влияет? Я знаю, что это C и C ++, что еще?
Wolf

Ответы:

115

Это более полезно для циклов, чем для операторов if.

while( var = GetNext() )
{
  ...do something with var 
}

Что в противном случае пришлось бы написать

var = GetNext();
while( var )
{
 ...do something
 var = GetNext();
}
Джеральд
источник
14
Я согласен, но написал бы: while ((var = GetNext ())! = 0) {...}, не задумываясь, отчасти потому, что GCC жалуется, если я этого не сделаю с моими параметрами компиляции по умолчанию.
Джонатан Леффлер
1
Правда. Предполагая, что вы используете язык, поддерживающий объявления цикла, и вы еще не используете var вне цикла.
Джеральд,
1
в некоторых языках, таких как ANSI C, правила области видимости не допускают такой подход, @KerrekSB
wirrbel
7
@wirrbel: Вы имеете в виду ANSI C от 1989 года? Например, когда у них были паровые машины и перфокарты? Это может быть так, но актуально ли это?
Kerrek SB
3
Это предупреждение вызвано не тем, что с присваиванием какое-то время что-то не так, а тем, что выполнение присваивания, когда вы действительно намеревались провести сравнение, является распространенной ошибкой. Если вы хотите избавиться от предупреждения, вы можете сделать JS-эквивалент того, что предлагает @Jonathan Leffler. Лично я, вероятно, сделал бы это в любом случае из-за того, насколько я не доверяю правдивым правилам JavaScript :) Это также следует отметить в некоторых языках, таких как C # и Java, что это единственный способ.
Джеральд
33

Я считаю его наиболее полезным в цепочках действий, которые часто включают обнаружение ошибок и т. Д.

if ((rc = first_check(arg1, arg2)) != 0)
{
    report error based on rc
}
else if ((rc = second_check(arg2, arg3)) != 0)
{
    report error based on new rc
}
else if ((rc = third_check(arg3, arg4)) != 0)
{
    report error based on new rc
}
else
{
    do what you really wanted to do
}

Альтернатива (без использования присваивания в условии):

rc = first_check(arg1, arg2);
if (rc != 0)
{
    report error based on rc
}
else
{
    rc = second_check(arg2, arg3);
    if (rc != 0)
    {
        report error based on new rc
    }
    else
    {
        rc = third_check(arg3, arg4);
        if (rc != 0)
        {
            report error based on new rc
        }
        else
        {
            do what you really wanted to do
        }
    }
}

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

Проверки ошибок может быть также «действия» - first_action(), second_action(), third_action()- конечно, а не только чеки. То есть это могут быть проверенные шаги в процессе, которым управляет функция. (Чаще всего в коде, с которым я работаю, функции соответствуют строкам проверок предварительных условий или выделению памяти, необходимому для работы функции, или аналогичным строкам).

Джонатан Леффлер
источник
Да, на самом деле я сделал это, чтобы избежать чрезмерной вложенности, а затем поискал в Интернете, чтобы увидеть, было ли это обычной практикой. В конце концов, я отказался от этого, потому что на самом деле у меня был только один случай, но это похоже на законную причину для включения задания в условие.
Ибрагим
Коллеги ненавидят это! Для меня, по крайней мере, в большинстве случаев, это гораздо удобнее в обслуживании, чем гигантское дерево или множество возвращений. Я предпочитаю одиночный возврат из функции ...
Натан
Разумный пример, блоки-сопровождающие делают шаблон очевидным, не требующим пояснений. Но, ну, внутриблочный («псевдо») код выглядит немного некрасиво, а обратный стиль (размещение того, что вы действительно хотели сделать в конце) не так уж хорош.
Wolf
31

Это более полезно, если вы вызываете функцию:

if (n = foo())
{
    /* foo returned a non-zero value, do something with the return value */
} else {
    /* foo returned zero, do something else */
}

Конечно, вы можете просто поставить n = foo (); в отдельном заявлении тогда if (n), но я думаю, что приведенная выше идиома довольно читабельна.

Крис Янг
источник
3
Я думаю, вы должны заключить его в круглые скобки, иначе gcc будет жаловаться: warning: предлагайте круглые скобки вокруг присваивания, используемого как значение истины [-Wparentheses] Вот так: if ((n = foo ())) {// что-то делаем с возвращаемым значением } else {// foo вернул ноль / NULL, сделаем что-нибудь еще}
Фабио Поцци
23

Это может быть полезно, если вы вызываете функцию, которая возвращает либо данные для работы, либо флаг, указывающий на ошибку (или на то, что все готово).

Что-то вроде:

while ((c = getchar()) != EOF) {
    // process the character
}

// end of file reached...

Лично мне эта идиома не очень нравится, но иногда альтернатива бывает уродливее.

Майкл Берр
источник
13

GCC может помочь вам обнаружить (с -Wall), если вы непреднамеренно попытаетесь использовать присвоение в качестве значения истины, в случае, если он рекомендует вам написать

if ((n = foo())) {
   ...
}

Т.е. используйте дополнительные скобки, чтобы указать, что это действительно то, что вы хотите.

JesperE
источник
1
Хорошо знать. Надеюсь, ваши коллеги прочтут это так же.
Wolf
1
Мне? Боже, я бы никогда не стал использовать присвоение как значение истины. Я имел в виду то, что рекомендует GCC.
JesperE
GCC также может компилировать Фортран . Может быть, прямо о языке программирования?
Питер Мортенсен
9

Идиома более полезна, когда вы пишете whileцикл вместо ifоператора. Что касается ifутверждения, вы можете разбить его, как вы описываете. Но без этой конструкции вам либо пришлось бы повторяться:

c = getchar();
while (c != EOF) {
    // ...
    c = getchar();
}

или используйте структуру из полутора петель:

while (true) {
    c = getchar();
    if (c == EOF) break;
    // ...
}

Я обычно предпочитаю форму в виде полуторных петель.

Грег Хьюгилл
источник
1
Для whileпетли предпочтительнее использовать:do { c = getchar(); ... } while (c != EOF);
Scott S
И forвместо for (char c = getchar(); c != EOF; c= getchar()) { /* do something with c */ }этого можно использовать петли: - это самый краткий, и forстилистически он всегда говорит мне «петля». Небольшой недостаток в том, что вам нужно указать функцию, возвращающуюся cдважды.
Scott S
4

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

Хью Аллен
источник
1
Это постоянная борьба за принудительное кодирование без присваиваний в if (), while () и других подобных операторах. C / C ++ печально известны своими побочными эффектами, которые часто оказываются ошибками, особенно когда мы добавляем операторы ++ и -.
Alexis Wilke
1
Первая ссылка бесполезна: C или C ++ вроде бы не такие.
Wolf
3

В PHP, например, это полезно для просмотра результатов базы данных SQL:

while ($row = mysql_fetch_assoc($result)) {
    // Display row
}

Это выглядит намного лучше, чем:

$row = mysql_fetch_assoc($result);
while ($row) {
    // Display row
    $row = mysql_fetch_assoc($result);
}
Джереми Рутен
источник
4
У этого также есть изящный побочный эффект, заключающийся в том, что $ row не живет за пределами этого цикла и, следовательно, имеет право на сборку мусора раньше (в языках, которые в любом случае используют сборку мусора).
Даррен Гривз,
2

Другое преимущество возникает при использовании gdb. В следующем коде код ошибки неизвестен, если мы выполняем пошаговое выполнение.

while (checkstatus() != -1) {
    // process
}

Скорее

while (true) {
    int error = checkstatus();
    if (error != -1)
        // process
    else
        //fail
}

Теперь во время одного шага мы можем узнать код ошибки возврата из checkstatus ().

плазмиксы
источник
1
Вы имеете в виду, что это может быть полезно для временных изменений при отладке кода?
Wolf
0

Я считаю это очень полезным с функциями, возвращающими необязательные параметры ( boost::optionalили std::optionalв C ++ 17):

std::optional<int> maybe_int(); // function maybe returns an int

if (auto i = maybe_int()) {
    use_int(*i);
}

Это уменьшает объем моей переменной, делает код более компактным и не мешает читабельности (я считаю).

То же самое с указателями:

int* ptr_int();

if (int* i = ptr_int()) {
    use_int(*i);
}
Жюльен-Л
источник
Кстати, если вы хотите ограничить объем переменных, вы можете использовать if с инициализаторами :)
cigien
-3

Причина:

  1. Улучшение производительности (иногда)

  2. Меньше кода (всегда)

Возьмем пример: существует метод, someMethod()и в ifусловии, что вы хотите проверить, равно ли возвращаемое значение метода null. Если нет, вы собираетесь снова использовать возвращаемое значение.

If(null != someMethod()){
    String s = someMethod();
    ......
    //Use s
}

Это снизит производительность, поскольку вы дважды вызываете один и тот же метод. Вместо этого используйте:

String s;
If(null != (s = someMethod())) {
    ......
    //Use s
}
Гангадхар
источник
Что это за язык? Он не компилируется на C # или C ++ (заглавные буквы «Если»).
Питер Мортенсен