Für binäre Operatoren haben wir sowohl bitweise als auch logische Operatoren:
& bitwise AND
| bitwise OR
&& logical AND
|| logical OR
NOT (ein unärer Operator) verhält sich jedoch anders. Es gibt ~ für bitweise und! für logisch.
Ich erkenne, dass NOT eine unäre Operation im Gegensatz zu AND und OR ist, aber ich kann mir keinen Grund vorstellen, warum die Designer beschlossen haben, von dem Prinzip abzuweichen, dass single bitweise und double logisch ist, und stattdessen ein anderes Zeichen gewählt haben. Sie könnten es falsch lesen, wie eine doppelte bitweise Operation, die immer den Operandenwert zurückgibt. Das scheint mir aber kein wirkliches Problem zu sein.
Gibt es einen Grund, warum ich vermisse?
~~
logische NICHT dann nicht konsistenter, wenn Sie dem Muster folgen, dass der logische Operator eine Verdoppelung des bitweisen Operators ist?!!foo
ist dies eine nicht ungewöhnliche (nicht gebräuchliche?) Redewendung. Sie normalisiert ein Null- oder Nicht-Null-Argument zu0
oder1
.Antworten:
Seltsamerweise beginnt die Geschichte der Programmiersprache C nicht mit C.
Dennis Ritchie erklärt in diesem Artikel die Herausforderungen von Cs Geburt .
Beim Lesen wird deutlich, dass C einen Teil seines Sprachentwurfs von seinem Vorgänger BCPL und insbesondere von den Operatoren geerbt hat . Der Abschnitt "Neugeborene C" des oben genannten Artikels erklärt, wie BCPL
&
und|
mit zwei neuen Operatoren&&
und angereichert wurden||
. Die Gründe waren:==
a
istfalse
ina&&b
,b
nicht ausgewertet).Interessanterweise erzeugt diese Verdoppelung keine Mehrdeutigkeit für den Leser:
a && b
wird nicht als falsch interpretierta(&(&b))
. Aus der Sicht der Syntaxanalyse gibt es auch keine Mehrdeutigkeit:&b
Wennb
ein l-Wert sinnvoll wäre , wäre dies ein Zeiger, wohingegen bitweise&
ein ganzzahliger Operand erforderlich ist. Daher ist das logische UND die einzig sinnvolle Wahl.BCPL wurde bereits
~
für die bitweise Negation verwendet. Unter dem Gesichtspunkt der Konsistenz wäre es also möglich gewesen, a~~
zu verdoppeln , um ihm seine logische Bedeutung zu verleihen. Dies wäre leider äußerst zweideutig gewesen, da~
es sich um einen unären Operator handelt:~~b
könnte auch bedeuten~(~b))
. Deshalb musste für die fehlende Negation ein anderes Symbol gewählt werden.quelle
(t)+1
ist das eine Ergänzung von(t)
und1
oder ist es eine Besetzung vom+1
Typt
? C ++ Design musste das Problem lösen, wie man Vorlagen, die>>
richtig enthalten, lexiert. Und so weiter.&&
als ein einzelnes&&
Token und nicht als zwei&
Token zu betrachten, da diea & (&b)
Interpretation nicht sinnvoll zu schreiben ist. Ein Mensch hätte das also niemals gemeint und wäre von ihm überrascht worden der Compiler behandelt es alsa && b
. Während die beiden!(!a)
und!!a
sind möglich , Dinge für einen Menschen zu bedeuten, so ist es eine schlechte Idee für die Compiler die Zweideutigkeit mit einer beliebigen tokenization-Level - Regel zu lösen.!!
ist nicht nur möglich / sinnvoll zu schreiben, sondern die kanonische "umwandeln in boolesche" Redewendung.--a
vs-(-a)
, die beide syntaktisch gültig sind, aber unterschiedliche Semantik haben.Das ist nicht das Prinzip an erster Stelle; Sobald Sie das merken, macht es mehr Sinn.
Die bessere Art, an
&
vs zu denken,&&
ist nicht binär und boolesch . Der bessere Weg ist, sie als eifrig und faul zu betrachten . Der&
Bediener führt die linke und rechte Seite aus und berechnet dann das Ergebnis. Der&&
Operator führt die linke Seite und dann die rechte Seite nur dann aus, wenn dies zur Berechnung des Ergebnisses erforderlich ist.Anstatt über "binär" und "boolesch" nachzudenken, sollten Sie darüber nachdenken, was wirklich passiert. Die "binäre" Version führt nur die Boolesche Operation für ein Array von Booleschen Werten aus, die in ein Wort gepackt wurden .
Also lasst es uns zusammensetzen. Ist es sinnvoll, eine verzögerte Operation mit einer Reihe von Booleschen Werten durchzuführen ? Nein, da es keine "linke Seite" gibt, die zuerst überprüft werden muss. Es gibt 32 "linke Seiten", die zuerst überprüft werden müssen. Wir beschränken die faulen Operationen also auf einen einzelnen Booleschen Wert, und daher kommt Ihre Intuition, dass einer von ihnen "binär" und einer "Boolescher Wert" ist, doch das ist eine Konsequenz des Designs, nicht des Designs selbst!
Und wenn man so denkt, wird klar, warum es kein
!!
und kein gibt^^
. Keiner dieser Operatoren verfügt über die Eigenschaft, dass Sie die Analyse eines der Operanden überspringen können. es gibt kein "faul"not
oderxor
.Andere Sprachen machen dies deutlicher. Einige Sprachen
and
bedeuten zum Beispiel "eifrig und", aberand also
auch "faul und". Und andere Sprachen machen es auch klarer&
und&&
sind nicht "binär" und "boolesch"; In C # können beispielsweise beide Versionen Boolesche Werte als Operanden verwenden.quelle
&
und zu denken&&
. Während Eifer einer der Unterschiede zwischen&
und ist&&
,&
verhält es sich ganz anders als eine eifrige Version von&&
, insbesondere in Sprachen, in denen&&
andere Typen als ein dedizierter Boolescher Typ unterstützt werden.1 & 2
ein völlig anderes Ergebnis als1 && 2
.bool
Typ in C wegzulassen, Konsequenzen . Wir brauchen beides!
und~
weil man "ein Int als einen einzelnen Booleschen Wert behandeln" und man "ein Int als ein gepacktes Array von Booleschen Werten behandeln" bedeutet. Wenn Sie getrennte Bool- und Int-Typen haben, können Sie nur einen Operator haben, was meiner Meinung nach das bessere Design gewesen wäre, aber wir sind fast 50 Jahre zu spät dran. C # bewahrt dieses Design für die Vertrautheit.TL; DR
C hat die Operatoren
!
und~
von einer anderen Sprache geerbt . Beide&&
und||
wurden Jahre später von einer anderen Person hinzugefügt.Lange Antwort
Historisch gesehen entwickelte sich C aus den frühen Sprachen B, die auf BCPL beruhten, das auf CPL beruhte, das auf Algol beruhte.
Algol , der Urgroßvater von C ++, Java und C #, definierte wahr und falsch auf eine Weise, die sich für Programmierer intuitiv anfühlte: „Wahrheitswerte, die als Binärzahl betrachtet werden (wahr entspricht 1 und falsch ist 0), sind das gleiche wie der innere Integralwert ”. Ein Nachteil davon ist jedoch, dass logisch und bitweise nicht die gleiche Operation sein kann: Auf jedem modernen Computer ist
~0
-1 anstelle von 1 und~1
-2 anstelle von 0. (Sogar auf einem 60 Jahre alten Mainframe ist~0
- 0 oderINT_MIN
,~0 != 1
auf jeder jemals hergestellten CPU, und der C-Sprachstandard verlangt dies seit vielen Jahren, während sich die meisten seiner Tochtersprachen überhaupt nicht darum kümmern, Vorzeichen und Größe oder das eigene Komplement zu unterstützen.)Algol hat dies umgangen, indem es verschiedene Modi hatte und die Operatoren im Booleschen und im Integralmodus unterschiedlich interpretierte. Das heißt, eine bitweise Operation war eine für Integer-Typen, und eine logische Operation war eine für Boolesche Typen.
BCPL hatte einen separaten Booleschen Typ, aber einen einzelnen
not
Operator , sowohl für bitweise als auch für logische nicht. Die Art und Weise, wie dieser frühe Vorläufer von C diese Arbeit machte, war:(Sie werden feststellen , dass sich der Begriff rvalue in den Sprachen der C-Familie zu einem völlig anderen Begriff entwickelt hat. Wir würden das heute als „Objektrepräsentation“ in C bezeichnen.)
Diese Definition würde es logisch und bitweise ermöglichen, nicht dieselbe maschinensprachliche Anweisung zu verwenden. Wenn C diesen Weg gegangen wäre, würden Header-Dateien auf der ganzen Welt sagen
#define TRUE -1
.Aber die Programmiersprache B war schwach typisiert und hatte weder Boolesche noch Gleitkommatypen. Alles war das Äquivalent zu
int
seinem Nachfolger C. Dies machte es für die Sprache zu einer guten Idee, zu definieren, was passiert ist, wenn ein Programm einen anderen Wert als true oder false als logischen Wert verwendet. Zuerst definierte es einen wahrheitsgemäßen Ausdruck als "ungleich Null". Dies war auf den Minicomputern, auf denen es lief und die ein CPU-Null-Flag hatten, effizient.Damals gab es eine Alternative: Dieselben CPUs hatten auch ein negatives Flag, und der Wahrheitswert von BCPL war -1, sodass B möglicherweise stattdessen alle negativen Zahlen als wahr und alle nicht negativen Zahlen als falsch definiert hat. (Es gibt einen Rest dieses Ansatzes: Viele Systemaufrufe in UNIX, die von denselben Personen zur selben Zeit entwickelt wurden, definieren alle Fehlercodes als negative Ganzzahlen. Viele ihrer Systemaufrufe geben bei einem Fehler einen von mehreren negativen Werten zurück.) Also sei dankbar: es hätte schlimmer kommen können!
Aber die Definition
TRUE
wie1
undFALSE
wie0
in B bedeutet , dass die Identitättrue = ~ false
nicht mehr zu halten, und sie hatte die starke Typisierung fallen gelassen , die Algol eindeutig zu machen zwischen bitweise und logische Ausdrücke erlaubt. Das erforderte einen neuen logisch-nicht-Operator, und die Designer wählten ihn aus!
, möglicherweise, weil es bereits!=
einen ungleichen Operator gab, der durch ein Gleichheitszeichen wie ein vertikaler Strich aussieht. Sie folgten nicht der gleichen Konvention wie&&
oder||
weil es noch keine gab.Möglicherweise sollte dies der Fall sein: Der
&
Operator in B ist fehlerhaft. In B und C,1 & 2 == FALSE
obwohl1
und2
beiden truthy Werte sind, und es gibt keine intuitive Art und Weise die logische Operation in B. um auszudrücken , dass ein Fehler C versuchte , war teilweise zu korrigieren , indem das Hinzufügen&&
und||
, aber das Hauptanliegen war zu der Zeit zu Endlich kann der Kurzschluss funktionieren und Programme können schneller ausgeführt werden. Der Beweis dafür ist, dass es kein^^
: gibt,1 ^ 2
ist ein wahrer Wert, obwohl beide Operanden wahr sind, aber es kann nicht von einem Kurzschluss profitieren.quelle
~0
(alle gesetzten Bits) ist eine negative Null im Komplement (oder eine Trap-Darstellung). Vorzeichen / Größe~0
ist eine negative Zahl mit maximaler Größe.