it-swarm.it

Perché l'operatore logico NOT nei linguaggi in stile C "!" e non "~~"?

Per gli operatori binari abbiamo operatori sia bit a bit che logici:

& bitwise AND
| bitwise OR

&& logical AND
|| logical OR

NOT (un operatore unario) si comporta diversamente. C'è ~ per bitwise e! per logica.

Riconosco che NOT è un'operazione unaria al contrario di AND e OR ma non riesco a pensare a una ragione per cui i designer hanno scelto di deviare dal principio che single è bit a bit e double è logico qui, e è andato invece per un personaggio diverso. Immagino che potresti leggerlo male, come una doppia operazione bit a bit che restituirebbe sempre il valore dell'operando. Ma questo non mi sembra un vero problema.

C'è un motivo per cui mi manca?

42
Martin Maat

Stranamente, la storia del linguaggio di programmazione in stile C non inizia con C.

Dennis Ritchie spiega bene le sfide della nascita di C in questo articolo .

Durante la lettura, diventa evidente che C ha ereditato una parte del suo linguaggio dal suo predecessore BCPL , e in particolare dagli operatori. La sezione "Neonatal C" del suddetto articolo spiega come & E | Di BCPL sono stati arricchiti con due nuovi operatori && E ||. Le ragioni erano:

  • era necessaria una priorità diversa a causa del suo utilizzo in combinazione con ==
  • diversa logica di valutazione: valutazione da sinistra a destra con cortocircuito (ovvero quando a è false in a&&b, b non viene valutato).

È interessante notare che questo raddoppio non crea alcuna ambiguità per il lettore: a && b Non verrà interpretato erroneamente come a(&(&b)). Dal punto di vista dell'analisi, non vi è alcuna ambiguità: &b Potrebbe avere senso se b fosse un valore, ma sarebbe un puntatore mentre il bit a bit & Richiederebbe un operando intero, quindi l'AND logico sarebbe l'unica scelta ragionevole.

BCPL ha già usato ~ Per la negazione bit a bit. Quindi da un punto di vista della coerenza, avrebbe potuto essere raddoppiato per dare un ~~ Per dargli il suo significato logico. Purtroppo questo sarebbe stato estremamente ambiguo poiché ~ È un operatore unario: ~~b Potrebbe anche significare ~(~b)). Questo è il motivo per cui è stato scelto un altro simbolo per la negazione mancante.

110
Christophe

Non riesco a pensare a una ragione per cui i designer hanno scelto di deviare dal principio che single è bit a bit e double è logico qui,

Questo non è il principio in primo luogo; una volta che lo capisci, ha più senso.

Il modo migliore di pensare a & vs && non è binario e Booleano. Il modo migliore è di considerarli come desideroso e pigro. Il & L'operatore esegue il lato destro e sinistro e quindi calcola il risultato. Il && L'operatore esegue il lato sinistro, quindi esegue il lato destro solo se necessario per calcolare il risultato.

Inoltre, invece di pensare a "binario" e "booleano", pensa a ciò che sta realmente accadendo. La versione "binaria" è solo eseguendo l'operazione booleana su un array di booleani che è stato impacchettato in una parola.

Quindi mettiamolo insieme. Ha senso fare un'operazione pigra su un array di booleani? No, perché non esiste un "lato sinistro" da controllare per primo. Ci sono 32 "lati a sinistra" da controllare per primi. Quindi limitiamo le operazioni pigre a un singolo Booleano, ed è da qui la tua intuizione che uno di loro è "binario" e uno è "Booleano", ma che è una conseguenza del design, non del design stesso!

E quando ci pensi in questo modo, diventa chiaro perché non esiste !! e no ^^. Nessuno di questi operatori ha la proprietà che è possibile saltare analizzando uno degli operandi; non c'è "pigro" not o xor.

Altre lingue lo rendono più chiaro; alcune lingue usano and per indicare "desideroso e" ma and also significa "pigro e", ad esempio. E altre lingue rendono anche più chiaro che & e && non sono "binari" e "booleani"; in C # per esempio, entrambe le versioni possono prendere i booleani come operandi.

51
Eric Lippert

TL; DR

C ha ereditato il ! e ~ operatori di 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 #, definito vero e falso in un modo che si è sentito intuitivo per i programmatori: "valori di verità che, considerati come un numero binario (vero corrispondente a 1 e falso a 0), sono gli stessi del valore intrinseco integrale". Tuttavia, uno svantaggio di questo è che logico e bit a bit non può essere il stessa operazione: su qualsiasi computer moderno, ~0 è uguale a -1 anziché 1 e ~1 è uguale a -2 anziché a 0. (Anche su un mainframe di sessant'anni in cui ~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 lingue secondarie non si preoccupa nemmeno di supportare il segno-e-grandezza o il complemento.

ALGOL ha aggirato questo problema avendo diverse modalità e interpretando gli operatori in modo diverso in modalità booleana e integrale. Vale a dire, 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 n singolo not operatore , sia per bit che per logico no. Il modo in cui questo precursore precoce di C fece funzionare quel lavoro fu:

Il valore di vero è un modello 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, i file header in tutto il mondo direbbero #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 di int nel 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". Questo era efficiente 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 definendo TRUE come 1 e FALSE come 0 in B significa che l'identità true = ~ false non era più valido, e aveva lasciato cadere la tipizzazione forte che consentiva ad ALGOL di chiarire le espressioni logiche e bit per bit. Ciò ha richiesto un nuovo operatore logico e non, e i progettisti hanno scelto !, probabilmente perché non uguale a era già !=, che sembra una specie di barra verticale attraverso un segno di uguale. Non hanno seguito la stessa convenzione di && o || perché nessuno dei due esisteva ancora.

Probabilmente, dovrebbero avere: il & l'operatore in B è rotto come previsto. In B e in C, 1 & 2 == FALSE nonostante 1 e 2 sono entrambi valori veritieri e non esiste un modo intuitivo per esprimere l'operazione logica in B. Questo è stato un errore che C ha tentato di correggere parzialmente aggiungendo && e ||, ma la preoccupazione principale all'epoca era finalmente far funzionare i cortocircuiti e rendere i programmi più veloci. La prova di ciò è che non esiste ^^: 1 ^ 2 è un valore sincero anche se entrambi i suoi operandi sono veritieri, ma non può beneficiare del corto circuito.

22
Davislor