Можно ли использовать побитовые операторы вместо логических?

побитовые операторы работают с битами, логические операторы оценивают логические выражения. Пока выражения возвращаются bool, почему мы не используем побитовые операторы вместо логических?

в этом примере я использую побитовые вместо логичного:

#include <iostream>


int main(){

    int age;
    std::cin >> age;

    if( (age < 0) | (age > 100) ) // eg: -50: 1 | 0 = 1
        std::cout << "Invalid age!" << std::endl;

//  if( (age < 0) || (age > 100) )
//      std::cout << "Invalid age!" << std::endl;

  return 0;
}

5 ответов


один возможный ответ:оптимизация. Например:

if ((age < 0) | (age > 100))

предположим, что age = -5, нет необходимости оценивать (age > 100) так как первое условие удовлетворено (-5<0). Тем не менее, предыдущий код будет оценивать (age > 100) выражение, которое не является необходимым.

С:

if ((age < 0) || (age > 100))

только первая часть будет оцениваться.

Примечание: As @Lundin упоминается в комментариях, иногда | быстрее || из-за точности ветвления для второго варианта (и проблема неправильного прогноза). Поэтому в случаях, когда другое выражение настолько дешево, параметр | option мая будет быстрее. Таким образом, единственный способ узнать в этих случаях-проверить код на целевой платформе.


самый важный ответ-избежать неопределенного поведения и ошибок:

вы можете представьте себе этот код:

int* int_ptr = nullptr;
if ((int_ptr != nullptr) & (*int_ptr == 5))

этот код содержит неопределено поведение. Однако, если вы замените & С &&, неопределенное поведение больше не существует.


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

int b = foo(1); // returns 0x1
int c = foo(2); // returns 0x2

чем следующие условия приводят к следующим:b && c == true, а b & c == 0

if (b && c)
{
  // This block will be entered
}

if (b & c)
{
  // This block won't
}

существует явная разница между || и |.

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

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

что означает этот код, как f1() || f2() всегда будет оценивать этот псевдо код:

if(f1() != 0)
{
  f2();
}

, тогда как f1() | f2() будет выполнять обе функции, в неопределенном порядке, что программист может не знать.

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


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

в выражение (age < 0) || (age > 100) второе условие (age > 100) будет рассчитываться только если (age < 0) is false. Для такого выражения компилятор создает такой код:

cmpl   x0,-0x4(%rbp)                 
js     1004010f9 <main+0x19>  // <-- Skip right expr evaluation if left true
cmpl   x64,-0x4(%rbp)               
jle    100401100 <main+0x20>

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


ответ-да, можно. Вопрос зачем вы хотите? Я могу назвать несколько причин, по которым вы не должны:

  1. это может быть очень запутанным для других программистов.
  2. его легко пропустить, что один из операндов имеет тип bool, что может привести к тонким ошибкам.
  3. порядок вычисления операндов не определен.
  4. это портит правило короткого замыкания.

для иллюстрации последнего точка:

bool f1() {
    cout << "f1" << endl;
    return true;
}

bool f2() {
    cout << "f2" << endl;
    return true;
}

int main() {
    if (f1() || f2()) {
        cout << "That was for ||" << endl;
    }
    if (f1() | f2()) {
        cout << "That was for |" << endl;
    }
    return 0;
}

он печатает:

f1
That was for ||
f1
f2
That was for |

предполагая, что f2 могут иметь значительные побочные эффекты (if (okToDestroyWorld && destroyWorld()) ...), разница может быть огромной. Это не удивило бы программиста Java (где | и || фактически определено для логических значений спецификацией языка), но это не обычная практика в C++.

я могу придумать только одну причину использовать побитовый оператор для булевых: если вам нужен XOR. Нет ^^ оператора, так if (a ^ b) это нормально как a и b are bool и никакие побочные эффекты не включили.