Можно ли использовать побитовые операторы вместо логических?
побитовые операторы работают с битами, логические операторы оценивают логические выражения. Пока выражения возвращаются 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>
||
не производит никакого дополнительного ветвления, чтобы иметь возможность пропустить вторую оценку выражения.
ответ-да, можно. Вопрос зачем вы хотите? Я могу назвать несколько причин, по которым вы не должны:
- это может быть очень запутанным для других программистов.
- его легко пропустить, что один из операндов имеет тип
bool
, что может привести к тонким ошибкам. - порядок вычисления операндов не определен.
- это портит правило короткого замыкания.
для иллюстрации последнего точка:
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
и никакие побочные эффекты не включили.