Для бинарных операторов у нас есть как побитовые, так и логические операторы:
& bitwise AND
| bitwise OR
&& logical AND
|| logical OR
NOT (унарный оператор) ведет себя по-другому, хотя. Существует ~ для побитового и! для логики.
Я признаю, что NOT - это унарная операция, в отличие от AND и OR, но я не могу придумать причину, по которой дизайнеры решили отклониться от принципа, что single - побитовое, а double - логично, и вместо этого выбрали другой символ. Я думаю, вы могли бы прочитать это неправильно, как двойная побитовая операция, которая всегда возвращала бы значение операнда. Но это не кажется мне реальной проблемой.
Есть ли причина, по которой я скучаю?
~~
тогда более логичным для логического НЕ, если бы вы следовали шаблону, согласно которому логический оператор является удвоением побитового оператора?!!foo
это весьма распространенная (не распространенная?) Идиома. Она нормализует аргумент «ноль или ненулевое значение» для0
или1
.Ответы:
Странно, но история языка программирования в стиле C начинается не с C.
Деннис Ричи хорошо объясняет проблемы рождения Си в этой статье .
При чтении становится очевидным, что C унаследовал часть своего языкового дизайна от своего предшественника BCPL , и особенно от операторов. Раздел «Неонатальный С» вышеупомянутой статьи объясняет, как BCPL
&
и|
были обогащены двумя новыми операторами&&
и||
. Причины были:==
a
находитсяfalse
вa&&b
,b
не оценивается).Интересно, что это дублирование не создает никакой двусмысленности для читателя:
a && b
не будет неверно истолковано какa(&(&b))
. С точки зрения синтаксического анализа также нет никакой двусмысленности: это&b
могло бы иметь смысл, если быb
было l-значением, но это был бы указатель, тогда как побитовое значение&
потребовало бы целочисленного операнда, поэтому логический AND был бы единственным разумным выбором.BCPL уже используется
~
для побитового отрицания. Таким образом, с точки зрения последовательности, его можно было бы удвоить,~~
чтобы придать ему логический смысл. К сожалению, это было бы крайне неоднозначно, поскольку~
является унарным оператором: это~~b
также может означать~(~b))
. Вот почему другой символ должен был быть выбран для отсутствующего отрицания.источник
(t)+1
том , что добавление(t)
и1
или это слепок+1
типуt
? C ++ дизайн должен был решить проблему с тем, чтобы лекс шаблоны содержали>>
правильно. И так далее.&&
как один&&
токен, а не как два токена&
, потому чтоa & (&b)
интерпретация не является разумной вещью, поэтому человек никогда бы не подумал об этом и был бы удивлен компилятор рассматривает это какa && b
. В то время как!(!a)
и то и другое!!a
могут иметь значение для человека, поэтому для компилятора плохая идея разрешить неоднозначность с помощью произвольного правила уровня токенизации.!!
не только возможно / разумно написать, но каноническая идиома "преобразовать в логическое значение".--a
vs-(-a)
, оба из которых допустимы синтаксически, но имеют разную семантику.Это не принцип в первую очередь; как только вы поймете это, это станет более логичным.
Лучший способ думать о
&
против&&
не двоичный и логический . Лучше всего думать о них как о страстных и ленивых .&
Оператор выполняет левую и правую сторону , а затем вычисляет результат.&&
Оператор выполняет левую сторону, а затем выполняет только с правой стороны , если это необходимо , чтобы вычислить результат.Более того, вместо того, чтобы думать о «двоичном» и «логическом», подумайте о том, что на самом деле происходит. «Бинарная» версия просто выполняет логическую операцию над массивом логических выражений, который был упакован в слово .
Итак, давайте это вместе. Имеет ли смысл выполнять ленивую операцию с массивом логических значений ? Нет, потому что нет "левой стороны", чтобы проверить в первую очередь. Сначала нужно проверить 32 «левых стороны». Таким образом, мы ограничиваем ленивые операции одним булевым значением, и именно отсюда ваша интуиция, что одна из них является «двоичной», а другая «булевой», но это является следствием дизайна, а не самого проекта!
И когда вы думаете об этом таким образом, становится понятно, почему нет
!!
и нет^^
. Ни у одного из этих операторов нет свойства, которое можно пропустить, анализируя один из операндов; нет "ленивых"not
илиxor
.Другие языки делают это более ясным; некоторые языки используют
and
для обозначения «нетерпеливый» иand also
«ленивый», например. И другие языки также сделать его более ясным , что&
и&&
не «двоичный» и «Boolean»; например, в C # обе версии могут принимать логические значения в качестве операндов.источник
&
и&&
. В то время как усердие одна из отличий&
и&&
,&
ведет себя совершенно иначе, чем рьяная версия&&
, особенно в тех языках, где&&
поддерживаются типы, отличные от выделенного логического типа.1 & 2
имеет совершенно другой результат1 && 2
.bool
типа в C имеет эффект " зацепки" . Нам нужны и то,!
и другое,~
потому что одно означает «обрабатывать int как один логический тип», а другое означает «обрабатывать int как упакованный массив логических значений». Если у вас есть отдельные типы bool и int, то у вас может быть только один оператор, который, на мой взгляд, был бы лучше, но мы опоздали почти на 50 лет. C # сохраняет этот дизайн для ознакомления.TL; DR
C унаследовал операторы
!
and~
от другого языка. Оба&&
и||
были добавлены годами позже другим человеком.Длинный ответ
Исторически, C развивался из ранних языков B, который был основан на BCPL, который был основан на CPL, который был основан на Algol.
Алгол , прадедушка C ++, Java и C #, определил истину и ложь таким образом, который стал понятен программистам: «Значения истины, которые рассматриваются как двоичное число (истина соответствует 1 и ложь - 0), такой же, как внутренняя интегральная стоимость ». Однако одним из недостатков этого является то, что логическое и побитовое не может быть одной и той же операцией: на любом современном компьютере
~0
равно -1, а не 1, и~1
равно -2, а не 0. (Даже на каком-то шестидесятилетнем мэйнфрейме, где~0
представляет - 0 илиINT_MIN
,~0 != 1
на каждом процессоре когда - либо делал, и стандарт языка C потребовал его в течение многих лет, в то время как большинство его дочерние языков не удосужилось поддержка знаковой-и величина или one's-комплемента на всех.)Алгол работал над этим, используя разные режимы и по-разному интерпретируя операторы в логическом и интегральном режимах. То есть побитовая операция была для целочисленных типов, а логическая - для логических типов.
BCPL имел отдельный логический тип, но один
not
оператор , как для побитового, так и для логического типа. Способ, которым этот ранний предшественник Си сделал эту работу:(Вы заметите, что термин rvalue эволюционировал для обозначения чего-то совершенно другого в языках семейства C. Сегодня мы бы назвали это «представлением объекта» в C.)
Это определение позволит логически и поразрядно не использовать одну и ту же инструкцию машинного языка. Если бы C пошел по этому пути, заголовочные файлы по всему миру сказали бы
#define TRUE -1
.Но язык программирования B был слабо типизирован и не имел булевых или даже типов с плавающей точкой. Все было эквивалентом
int
в его преемнике, C. Это сделало для языка хорошей идеей определить, что происходит, когда программа использует значение, отличное от true или false, в качестве логического значения. Сначала он определил истинное выражение как «не равное нулю». Это было эффективно на миникомпьютерах, на которых он работал, с нулевым флагом ЦП.В то время была альтернатива: те же процессоры также имели отрицательный флаг, и значение истинности BCPL было равно -1, так что B мог бы вместо этого определить все отрицательные числа как истинные, а все неотрицательные числа как ложные. (Есть один остаток этого подхода: UNIX, разработанная одними и теми же людьми в одно и то же время, определяет все коды ошибок как отрицательные целые числа. Многие из ее системных вызовов возвращают одно из нескольких различных отрицательных значений при сбое.) Так что будьте благодарны: это могло быть и хуже!
Но определение «
TRUE
как»1
и «FALSE
как»0
в B означало, что тождествоtrue = ~ false
больше не сохранялось, и оно отбросило строгую типизацию, которая позволяла Algol устранять неоднозначность между битовыми и логическими выражениями. Это потребовало нового логического оператора not, и разработчики выбрали!
, возможно, потому, что уже не равное было!=
, которое выглядит как вертикальная черта через знак равенства. Они не следовали тому же соглашению, что&&
и||
потому что ни один еще не существовал.Возможно, они должны иметь:
&
оператор в B сломан, как задумано. В B и в C,1 & 2 == FALSE
хотя1
и2
являются истинными значениями, и нет никакого интуитивного способа выразить логическую операцию в B. Это была одна ошибка, которую C пытался частично исправить, добавив&&
и||
, но основной проблемой в то время было наконец, установите короткое замыкание на работу и заставьте программы работать быстрее. Доказательством этого является то, что нет^^
:1 ^ 2
истинное значение, хотя оба его операнда истинны, но оно не может извлечь выгоду из короткого замыкания.источник
~0
(все биты установлены) - это отрицательный ноль дополнения (или представление ловушки). Знак / величина~0
является отрицательным числом с максимальной величиной.