TL; DR
C ha ereditato gli operatori !
e ~
da un'altra lingua. Tutti e due&&
e ||
sono stati aggiunti anni dopo da una persona diversa.
Risposta lunga
Storicamente, C si è sviluppato dalle prime lingue B, che era basato su BCPL, che era basato su CPL, che era basato su Algol.
Algol , il bisnonno di C ++, Java e C #, ha definito vero e falso in un modo che è diventato intuitivo per i programmatori: "valori di verità che, considerati come un numero binario (vero corrispondente a 1 e falso a 0), è lo stesso del valore integrale intrinseco ”. Tuttavia, uno svantaggio di questo è che logico e bit a bit non può essere la stessa operazione: su qualsiasi computer moderno, è ~0
uguale a -1 anziché a 1 e ~1
uguale a -2 anziché a 0. (Anche su un mainframe di sessant'anni dove ~0
rappresenta - 0 o INT_MIN
, ~0 != 1
su ogni CPU mai realizzata, e lo standard del linguaggio C lo richiede da molti anni, mentre la maggior parte delle sue lingue figlie non si preoccupano nemmeno di supportare il segno-magnitudine o il complemento.
Algol ha risolto il problema disponendo di modalità diverse e interpretando gli operatori in modo diverso in modalità booleana e integrale. Ossia, un'operazione bit a bit era una sui tipi interi e un'operazione logica era una sui tipi booleani.
BCPL aveva un tipo booleano separato, ma un singolo not
operatore , sia per bit che per logico. Il modo in cui questo precursore precoce di C fece quel lavoro fu:
Il valore di vero è uno schema di bit interamente composto da uno; il valore di false è zero.
Nota che true = ~ false
(Noterai che il termine rvalue si è evoluto per significare qualcosa di completamente diverso nei linguaggi della famiglia C. Oggi chiameremmo "la rappresentazione dell'oggetto" in C.)
Questa definizione consentirebbe logicamente e bit a bit di non utilizzare la stessa istruzione in linguaggio macchina. Se C avesse seguito quella strada, direbbero i file header in tutto il mondo#define TRUE -1
.
Ma il linguaggio di programmazione B era tipizzato debolmente e non aveva tipi booleani o nemmeno a virgola mobile. Tutto era l'equivalente del int
suo successore, C. Questo ha reso una buona idea per il linguaggio definire cosa è successo quando un programma ha usato un valore diverso da vero o falso come valore logico. In primo luogo ha definito un'espressione veritiera come "non uguale a zero". Ciò era efficace sui minicomputer su cui era in esecuzione, che aveva un flag zero CPU.
All'epoca c'era un'alternativa: anche le stesse CPU avevano un flag negativo e il valore di verità di BCPL era -1, quindi B avrebbe potuto invece definire tutti i numeri negativi come veri e tutti i numeri non negativi come falsi. (C'è un residuo di questo approccio: UNIX, sviluppato dalle stesse persone contemporaneamente, definisce tutti i codici di errore come numeri interi negativi. Molte delle sue chiamate di sistema restituiscono uno dei diversi valori negativi in caso di fallimento.) Quindi sii grato: avrebbe potuto essere peggio!
Ma definire TRUE
come 1
e FALSE
come 0
in B significava che l'identità true = ~ false
non era più valida, e aveva lasciato cadere la tipizzazione forte che permetteva ad Algol di chiarire le espressioni logiche e bitrate. Ciò ha richiesto un nuovo operatore logico e non, e i progettisti hanno scelto !
, forse perché non era già uguale !=
, che sembra una specie di barra verticale attraverso un segno di uguale. Non hanno seguito la stessa convenzione &&
o ||
perché nessuno dei due esisteva ancora.
Probabilmente avrebbero dovuto: l' &
operatore in B è rotto come previsto. In B e in C, 1 & 2 == FALSE
anche se 1
e 2
sono entrambi valori truthy, e non c'è modo intuitivo per esprimere l'operazione logica in B. Questo è stato un errore C cercato di rimediare in parte aggiungendo &&
e ||
, ma la preoccupazione principale al momento era quello finalmente far funzionare i cortocircuiti e rendere i programmi più veloci. La prova di ciò è che non esiste ^^
: 1 ^ 2
è un valore veritiero anche se entrambi i suoi operandi sono veritieri, ma non può beneficiare del cortocircuito.