Переменное присвоение в условии "если"

Недавно я просто потерял некоторое время, выяснив ошибку в моем коде, вызванную опечаткой:

if(a=b)

вместо:

if(a==b)

Мне было интересно, есть ли какой-либо конкретный случай, который вы бы хотели присвоить переменной переменной в инструкции if, или, если нет, почему компилятор не выдает предупреждение или ошибку?

Ответ 1

if (Derived* derived = dynamic_cast<Derived*>(base)) {
   // do stuff with `derived`
}

Хотя это часто цитируется как анти-шаблон ( "использовать виртуальную отправку!" ), иногда тип Derived имеет функции, которые Base просто не (и, следовательно, разные функции), а это хороший способ включить эту семантическую разницу.

Ответ 2

Вот некоторые истории в рассматриваемом синтаксисе.

В классическом C обработка ошибок часто выполнялась путем написания чего-то вроде:

int error;
...
if(error = foo()) {
    printf("An error occured: %s\nBailing out.\n", strerror(error));
    abort();
}

Или, всякий раз, когда был вызов функции, который мог бы возвращать нулевой указатель, идиома использовалась в обратном направлении:

Bar* myBar;
... //in old C variables had to be declared at the start of the scope
if(myBar = getBar()) {
    //do something with myBar
}

Однако этот синтаксис опасно близок к

if(myValue == bar()) ...

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

if((myBar = getBar())) {  //tells the compiler: Yes, I really want to do that assignment!

Затем появился C99, позволяющий смешивать определения и утверждения, поэтому многие разработчики часто пишут что-то вроде

Bar* myBar = getBar();
if(myBar) {

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

if(Bar* myBar = getBar()) {

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

if(Bar* myBar = getBar()) {
    ...
}
foo(myBar->baz);  //compiler error
//or, for the C++ enthusiasts:
myBar->foo();     //compiler error

Без определения переменной внутри оператора if это условие не будет обнаруживаться.

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

Ответ 3

Оператор присваивания возвращает значение назначенное значение. Поэтому я могу использовать его в такой ситуации:

if(x = getMyNumber())

Затем я присваиваю x значение, возвращаемое getMyNumber, и я проверяю, не ноль.

Избегайте этого, я привел вам пример, чтобы помочь вам понять это.

Изменить: добавление Просто предложение (может показаться).

В избегайте таких ошибок до некоторых расширений, следует писать, если условие if(NULL == ptr) вместо if(ptr == NULL) Потому что, когда вы пропускаете оператор проверки равенства == как оператор =, компилятор будет вызывать ошибку lvalue с if(NULL = ptr), но if(res = NULL) передается компилятором (что не то, что вы имеете в виду) и остается ошибкой в ​​коде для времени выполнения.
По той же причине я предпочитаю писать if(getMyNumber() ==x) вместо if(x == getMyNumber())

Также следует прочитать: Criticism относительно этого типа кода.

Ответ 4

Это зависит от того, хотите ли вы писать чистый код или нет. когда C был впервые разработан, важность чистого кода не были полностью признаны, а составители были очень упрощенными: использование вложенного назначения, как это часто может привести к ускорению код. Сегодня я не могу придумать ни одного случая, когда хороший программист сделал бы это. Это просто делает код менее читаемым и более трудно поддерживать.

Ответ 5

Я столкнулся с ситуацией, когда это было полезно совсем недавно, поэтому я подумал, что опубликую его.

Предположим, вы хотите проверить несколько условий в одном, если, и если какое-либо из условий истинно, вы хотите сгенерировать сообщение об ошибке. Если вы хотите включить в сообщение об ошибке, какое конкретное условие вызвало ошибку, вы можете сделать следующее:

std::string e;
if( myMap[e = "ab"].isNotValid() ||
    myMap[e = "cd"].isNotValid() ||
    myMap[e = "ef"].isNotValid() )
{
    // here, e has the key for which the validation failed
}

Итак, если вторым условием является значение, равное true, e будет равно "cd". Это связано с поведением короткого замыкания ||, которое задано стандартом (если не перегружено). См. этот ответ для получения более подробной информации о коротком замыкании.

Ответ 6

почему компилятор не выдает предупреждение

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

Например, в Visual C++ необходимо включить C4706 (или предупреждения уровня 4 в целом). Я обычно включаю как можно больше предупреждений и делаю код более явным, чтобы избежать ложных срабатываний. Например, если я действительно хотел сделать это:

if (x = Foo()) { ... }

Тогда я бы написал это так:

if ((x = Foo()) != 0) { ... }

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

Единственным недостатком этого подхода является то, что вы не можете использовать его, когда переменная объявлена в условии. То есть вы не можете переписать:

if (int x = Foo()) { ... }

как

if ((int x = Foo()) != 0) { ... }

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

ОБНОВЛЕНИЕ: C++ 17 добавили возможность иметь оператор init в условии для оператора if, что хорошо решает эту проблему (для сравнения, не просто != 0).

if (int x = Foo(); x != 0) { ... }

Ответ 7

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

Обычный шаблон:

if (int x = expensive_function_call())
{
  // ...do things with x
}

Анти-шаблон, где вы ошибочно назначаете вещи:

if (x = 1)
{
  // Always true
}
else
{
  // Never happens
}

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

if (1 = x)
{
  // Compiler error, can't assign to 1
}

= vs. == - это то, что вам нужно для развития глаз. Я обычно помещаю пробел вокруг оператора, поэтому более очевидно, какая операция выполняется, поскольку longname=longername выглядит как t28 > с первого взгляда, но = и == сами по себе явно отличаются.