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 ~0równa się -1 zamiast 1 i ~1równa się -2 zamiast 0. (Nawet na około sześćdziesięcioletnim komputerze mainframe, gdzie ~0reprezentuje - 0 lub INT_MIN, ~0 != 1na 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 notoperator , zarówno logiczny, jak i bitowy. Sposób, w jaki ten wczesny prekursor C sprawił, że ta praca była:
Wartość prawdy jest wzorem złożonym całkowicie z nich; wartość false wynosi zero.
Zauważ, że true = ~ false
(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 intjego 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 TRUEjako 1i FALSEtak jak 0w B oznaczało, że tożsamość true = ~ falsejuż 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, w 1 & 2 == FALSEchoć 1i 2są 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 ^ 2jest prawdziwą wartością, chociaż oba jej operandy są prawdziwe, ale nie może skorzystać na zwarciu.