W przypadku operatorów binarnych mamy zarówno operatory bitowe, jak i logiczne:
& bitwise AND
| bitwise OR
&& logical AND
|| logical OR
NIE (jednoargumentowy operator) zachowuje się jednak inaczej. Jest ~ za bitowe i! dla logiki.
Rozumiem, że NIE jest operacją jednoargumentową w przeciwieństwie do AND i OR, ale nie mogę wymyślić powodu, dla którego projektanci postanowili odejść od zasady, że single jest bitowe, a double logiczne, i zamiast tego wybrali inną postać. Wydaje mi się, że można go źle odczytać, jak operację podwójnie bitową, która zawsze zwraca wartość argumentu. Ale to nie wydaje mi się prawdziwym problemem.
Czy brakuje mi powodu?
~~
nie byłoby bardziej spójne dla logicznego NOT, jeśli podążasz za wzorem, że operator logiczny jest podwojeniem operatora bitowego?!!foo
jest to nierzadki (niezbyt często?) Idiom. Normalizuje zero lub niezerowy argument na0
lub1
.Odpowiedzi:
O dziwo, historia języka programowania w stylu C nie zaczyna się od C.
Dennis Ritchie wyjaśnia również wyzwaniom urodzenia C w tym artykule .
Czytając go, staje się oczywiste, że C odziedziczył część swojego projektu językowego od swojego poprzednika BCPL , a zwłaszcza od operatorów. Sekcja „Noworodkowy C” wyżej wymienionego artykułu wyjaśnia, w jaki sposób BCPL
&
i|
zostały wzbogacone o dwóch nowych operatorów&&
i||
. Przyczyny były:==
a
jestfalse
włączonaa&&b
,b
nie jest analizowana).Co ciekawe, to podwojenie nie powoduje żadnych dwuznaczności dla czytelnika:
a && b
nie będzie interpretowane jakoa(&(&b))
. Z parsującego punktu widzenia nie ma również dwuznaczności:&b
może mieć sens, jeślib
byłaby wartością, ale byłby to wskaźnik, podczas gdy bitowe&
wymagałoby operandu całkowitego, więc logiczne AND byłoby jedynym rozsądnym wyborem.BCPL jest już używany
~
do bitowej negacji. Tak więc z punktu widzenia spójności można było podwoić, aby nadać~~
logiczne znaczenie. Niestety byłoby to niezwykle dwuznaczne, ponieważ~
jest to jednoargumentowy operator:~~b
może również oznaczać~(~b))
. Właśnie dlatego musiał zostać wybrany inny symbol brakującej negacji.źródło
(t)+1
że jest to dodatek(t)
i1
czy jest to obsada+1
typut
? Projekt w C ++ musiał rozwiązać problem leksykania szablonów zawierających>>
poprawnie. I tak dalej.&&
jako pojedynczy&&
token, a nie jako dwa&
tokeny, ponieważa & (&b)
interpretacja nie jest rozsądną rzeczą do napisania, więc człowiek nigdy by nie miał tego na myśli i byłby zaskoczony kompilator traktuje to jaka && b
. Zważywszy, że zarówno!(!a)
i!!a
możliwe są rzeczy dla człowieka znaczyć, więc jest to zły pomysł, kompilator rozwiązać niejednoznaczność z dowolnej reguły tokenizacja poziomu.!!
jest nie tylko możliwe / rozsądne do napisania, ale kanoniczny idiom „konwersja na boolean”.--a
vs-(-a)
, które są poprawne składniowo, ale mają inną semantykę.Po pierwsze, to nie jest zasada; kiedy sobie to uświadomisz, ma to większy sens.
Lepszym sposobem myślenia o
&
vs&&
nie jest binarny i logiczny . Lepszym sposobem jest postrzeganie ich jako chętnych i leniwych .&
Operator wykonuje lewej i prawej stronie, a następnie oblicza wynik.&&
Operator wykonuje lewą stronę, a następnie wykonuje prawą stronę tylko jeśli jest to konieczne, aby obliczyć wynik.Co więcej, zamiast myśleć o „binarnym” i „logicznym”, pomyśl o tym, co naprawdę się dzieje. Wersja „binarna” po prostu wykonuje operację boolowską na tablicy logicznej, która została upakowana w słowo .
Złóżmy to razem. Czy ma sens robienie leniwej operacji na szeregu booleanów ? Nie, ponieważ nie ma „lewej strony” do sprawdzenia w pierwszej kolejności. Najpierw są 32 „lewe strony”. Ograniczamy więc leniwe operacje do jednego logicznego loga i stąd twoja intuicja, że jedna z nich jest „binarna”, a druga „logiczna”, ale jest to konsekwencja projektu, a nie samego projektu!
A kiedy pomyślisz o tym w ten sposób, staje się jasne, dlaczego nie ma
!!
i nie ma^^
. Żaden z tych operatorów nie ma właściwości, którą można pominąć analizując jeden z operandów; nie ma „leniwego”not
lubxor
.Inne języki wyjaśniają to; niektóre języki
and
oznaczają na przykład „chętny i”, aleand also
na przykład „leniwy i”. Inne języki również to wyjaśniają&
i&&
nie są „binarne” ani „logiczne”; na przykład w języku C # obie wersje mogą przyjmować booleany jako operandy.źródło
&
i&&
. Podczas gdy zapał jest jedną z różnic między&
i&&
,&
zachowuje się zupełnie inaczej niż chętna wersja&&
, szczególnie w językach, w których&&
obsługuje typy inne niż dedykowany typ logiczny.1 & 2
ma zupełnie inny wynik niż1 && 2
.bool
typu w C wywołuje efekt domina. Potrzebujemy obu,!
a~
ponieważ jeden oznacza „traktuj int jako pojedynczy log boolowski”, a drugi oznacza „traktuj int jako spakowaną tablicę boolean”. Jeśli masz oddzielne typy bool i int, możesz mieć tylko jednego operatora, co moim zdaniem byłoby lepszym projektem, ale spóźniliśmy się prawie o 50 lat. C # zachowuje ten projekt dla znajomości.TL; DR
C odziedziczył operatory
!
i~
z innego języka. Zarówno&&
i||
dodano lat później przez inną osobę.Długa odpowiedź
Historycznie C rozwijał się z wczesnych języków B, które były oparte na BCPL, która była oparta na CPL, która była oparta na Algolu.
Algol , prawnuk C ++, Java i C #, zdefiniował prawdę i fałsz w sposób, który stał się intuicyjny dla programistów: „wartości prawdy, które, uważane za liczbę binarną (prawda odpowiada 1, a fałsz 0), to samo co wewnętrzna wartość całkowita ”. Jednak jedną wadą tego jest to, że logiczne i bitowe nie może być tą samą operacją: na każdym nowoczesnym komputerze
~0
równa się -1 zamiast 1 i~1
równa się -2 zamiast 0. (Nawet na około sześćdziesięcioletnim komputerze mainframe, gdzie~0
reprezentuje - 0 lubINT_MIN
,~0 != 1
na każdym procesorze, jaki kiedykolwiek wyprodukowano, a standard języka C wymagał tego od wielu lat, podczas gdy większość jego języków potomnych nie zadaje sobie nawet trudu, aby obsługiwać znak i wielkość lub uzupełnienie.)Algol obejrzał ten problem, mając różne tryby i różnie interpretując operatory w trybie logicznym i integralnym. Oznacza to, że operacja bitowa dotyczyła typów całkowitych, a operacja logiczna dotyczyła typów logicznych.
BCPL miał osobny typ logiczny, ale pojedynczy
not
operator , zarówno logiczny, jak i bitowy. Sposób, w jaki ten wczesny prekursor C sprawił, że ta praca była:(Zauważysz, że termin wartość ewoluował, aby oznaczać coś zupełnie innego w językach rodziny C. Dzisiaj nazwalibyśmy to „reprezentacją obiektu” w C.)
Ta definicja pozwoliłaby logicznie i bitowo nie używać tej samej instrukcji języka maszynowego. Gdyby C poszedł tą drogą, pliki nagłówkowe powiedziałby cały świat
#define TRUE -1
.Ale język programowania B był słabo napisany i nie miał typów boolowskich ani nawet zmiennoprzecinkowych. Wszystko było równoważne z
int
jego następcą, C. To sprawiło, że dobrym pomysłem było, aby język zdefiniował, co się stanie, gdy program użyje wartości innej niż prawda lub fałsz jako wartości logicznej. Najpierw zdefiniował prawdziwe wyrażenie jako „nie równe zero”. Było to skuteczne na minikomputerach, na których działało, które miały flagę zero procesora.Istniała wówczas alternatywa: te same procesory miały również flagę ujemną, a wartość prawdy BCPL wynosiła -1, więc B mógł zamiast tego zdefiniować wszystkie liczby ujemne jako prawdziwe, a wszystkie nieujemne jako fałsz. (Jest jedna pozostałość tego podejścia: wiele wywołań systemowych w systemie UNIX, opracowanych przez te same osoby w tym samym czasie, definiuje wszystkie kody błędów jako liczby całkowite ujemne. Wiele wywołań systemowych zwraca jedną z kilku różnych wartości ujemnych po awarii.) bądźcie wdzięczni: mogło być gorzej!
Ale zdefiniowanie
TRUE
jako1
iFALSE
tak jak0
w B oznaczało, że tożsamośćtrue = ~ false
już nie zachowała, i porzuciło silne pisanie, które pozwoliło Algolowi rozróżnić wyrażenia bitowe i logiczne. Wymagało to nowego logicznego operatora, który nie jest logiczny, a projektanci wybrali!
, być może dlatego, że już nie był równy!=
, który wygląda jak pionowy pasek przez znak równości. Oni nie poszli tą samą konwencję,&&
albo||
dlatego, że ani jedno, jeszcze istniał.Zapewne powinny:
&
operator w B jest uszkodzony zgodnie z przeznaczeniem. W B i C, w1 & 2 == FALSE
choć1
i2
są obie wartości truthy, i nie ma intuicyjny sposób wyrazić logiczną operację w B. To była jedna pomyłka C starał się częściowo naprawić dodając&&
i||
, ale głównym problemem w tym czasie był na w końcu doszło do zwarcia i przyspieszyło działanie programów. Dowodem na to jest to, że nie ma^^
:1 ^ 2
jest prawdziwą wartością, chociaż oba jej operandy są prawdziwe, ale nie może skorzystać na zwarciu.źródło
~0
(wszystkie ustawione bity) to zero ujemne dopełniacza (lub reprezentacja pułapki). Znak / jasność~0
jest liczbą ujemną o maksymalnej wielkości.