Pour les opérateurs binaires, nous avons à la fois des opérateurs au niveau du bit et des opérateurs logiques:
& bitwise AND
| bitwise OR
&& logical AND
|| logical OR
NOT (un opérateur unaire) se comporte toutefois différemment. Il y a ~ pour bitwise et! pour logique.
Je reconnais que NOT est une opération unaire par opposition à AND and OR, mais je ne vois pas pourquoi les concepteurs ont choisi de s’écarter du principe que simple est synonyme de bitwise et que double est logique ici, et a plutôt opté pour un caractère différent. J'imagine que vous pourriez le lire mal, comme une opération à deux bits qui renverrait toujours la valeur d'opérande. Mais cela ne me semble pas vraiment un problème.
Y a-t-il une raison qui me manque?
~~
pas alors été plus cohérent pour le NOT logique si vous suiviez le modèle selon lequel l'opérateur logique est un doublement de l'opérateur au niveau du bit?!!foo
est un idiome Il normalise non rare (non pas commun) un argument zéro ou non nul?.0
Ou1
.Réponses:
Étrangement, l’histoire du langage de programmation de type C ne commence pas par C.
Dennis Ritchie explique bien les défis de la naissance de C dans cet article .
En le lisant, il devient évident que C a hérité une partie de la conception de son langage de son prédécesseur, BCPL , et en particulier des opérateurs. La section « néonatale C » de l'article mentionné ci - dessus explique comment son BCPL
&
et|
ont été enrichis avec deux nouveaux opérateurs&&
et||
. Les raisons étaient:==
a
estfalse
dansa&&b
,b
n'est pas évalué).Fait intéressant, ce dédoublement ne crée aucune ambiguïté pour le lecteur:
a && b
ne sera pas mal interprété commea(&(&b))
. D'un point de vue analytique , il n'y a pas d'ambiguïté non plus: cela&b
pourrait avoir un sens s'ilb
s'agissait d'une lvalue, mais ce serait un pointeur alors que le bitwise&
nécessiterait un opérande entier, le AND logique serait donc le seul choix raisonnable.BCPL est déjà utilisé
~
pour la négation au niveau du bit. Donc, d’un point de vue de la cohérence, on aurait pu le doubler pour lui~~
donner son sens logique. Malheureusement, cela aurait été extrêmement ambigu, car~
c’est un opérateur unaire: cela~~b
pourrait aussi vouloir dire~(~b))
. C'est pourquoi il a fallu choisir un autre symbole pour la négation manquante.la source
(t)+1
est qu'une addition de(t)
et1
ou est - ce un casting de+1
typet
? La conception C ++ devait résoudre le problème de la syntaxe>>
correcte des modèles . Etc.&&
comme un&&
jeton unique et non comme deux&
jetons, car l'a & (&b)
interprétation n'est pas une chose raisonnable à écrire. Un humain n'aurait jamais voulu dire cela et aurait été surpris par le compilateur le traite commea && b
. Tandis que les deux!(!a)
et!!a
sont des choses possibles pour un humain, c'est donc une mauvaise idée pour le compilateur de résoudre l'ambiguïté avec une règle de niveau de symbolisation arbitraire.!!
n’est pas seulement possible / raisonnable d’écrire, mais l’idiome canonique "convertir en booléen".--a
vs-(-a)
, qui sont tous deux syntaxiquement valables mais ont une sémantique différente.Ce n'est pas le principe en premier lieu; une fois que vous vous en rendez compte, c'est plus logique.
La meilleure façon de penser à
&
vs&&
n'est pas binaire ni booléenne . Le meilleur moyen est de penser à eux comme impatients et paresseux . L'&
opérateur exécute les côtés gauche et droit, puis calcule le résultat. L'&&
opérateur exécute le côté gauche, puis n'exécute le côté droit que si nécessaire pour calculer le résultat.De plus, au lieu de penser à "binaire" et "booléen", pensez à ce qui se passe réellement. La version "binaire" ne fait que l’opération booléenne sur un tableau de booléens qui a été condensé dans un mot .
Alors mettons-le ensemble. Est-il logique de faire une opération paresseuse sur un tableau de booléens ? Non, car il n'y a pas de "côté gauche" à vérifier en premier. Il y a 32 "côtés gauche" à vérifier en premier. Nous limitons donc les opérations paresseuses à un seul booléen, et c’est de là que vient votre intuition selon laquelle l’un d’eux est «binaire» et l’autre «Booléen», mais c’est une conséquence. du design, pas du design lui-même!
Et quand on y pense de cette façon, on comprend pourquoi il n’ya
!!
ni non ni non^^
. Aucun de ces opérateurs n'a la propriété que vous pouvez ignorer en analysant l'un des opérandes; il n'y a pas de "paresseux"not
ouxor
.D'autres langues rendent cela plus clair. certaines langues utilisent
and
le sens "désireux et" maisand also
signifient "paresseux et", par exemple. Et d'autres langues précisent également que&
et&&
ne sont pas "binaires" et "booléennes"; en C # par exemple, les deux versions peuvent prendre les booléens comme opérandes.la source
&
et&&
. Bien que l’empressement soit l’une des différences entre&
et&&
,&
se comporte de manière totalement différente d’une version désireuse de&&
, en particulier dans les langues où&&
les types de support autres que le type booléen dédié sont pris en charge.1 & 2
résultat obtenu est complètement différent1 && 2
.bool
type en C a des effets d'entraînement. Nous avons besoin des deux!
et~
parce que l’un veut dire "traiter un int comme un booléen unique" et l’un pour "traiter un int comme un tableau rempli de booléens". Si vous avez des types bool et int distincts, vous ne pouvez avoir qu'un seul opérateur, ce qui, à mon avis, aurait été la meilleure conception, mais nous avons presque 50 ans de retard pour celui-là. C # conserve cette conception pour la familiarité.TL; DR
C a hérité des opérateurs
!
and~
d’une autre langue. Les deux&&
et||
ont été ajoutés des années plus tard par une personne différente.Longue réponse
Historiquement, C s'est développé à partir des premières langues B, qui étaient basées sur BCPL, qui était basé sur CPL, qui était basé sur Algol.
Algol , l'arrière-petit-père de C ++, Java et C #, a défini vrai et faux de manière intuitive pour les programmeurs: «valeurs de vérité qui, considérées comme un nombre binaire (vrai correspondant à 1 et faux à 0), sont identique à la valeur intrinsèque intrinsèque ». Cependant, un inconvénient de cela est que logique et que bit à bit ne peut pas être la même opération: sur tout ordinateur moderne,
~0
égal à -1 plutôt que 1 et~1
égal à -2 plutôt que 0 (même sur un ordinateur central vieux de soixante ans où~0
représente - 0 ouINT_MIN
,~0 != 1
sur chaque processeur jamais créé, et la norme de langage C l’exige depuis de nombreuses années, alors que la plupart des langues de sa fille ne se soucient même pas de prendre en charge le principe de complémentarité.Algol a contourné cela en ayant différents modes et en interprétant les opérateurs différemment en mode booléen et intégral. C'est-à-dire qu'une opération au niveau des bits était une opération sur les types entiers et une opération logique, une opération sur des types booléens.
BCPL avait un type booléen distinct, mais un seul
not
opérateur , à la fois au niveau du bit et logique. La manière dont ce précurseur précoce de C a réalisé ce travail était:(Vous remarquerez que le terme rvalue a évolué pour signifier quelque chose de complètement différent dans les langues de la famille C. Nous appellerions aujourd'hui cela «la représentation d'objet» en C).
Cette définition permettrait à logique et au bit de ne pas utiliser la même instruction en langage machine. Si C avait choisi cette voie, diraient les fichiers d’en-tête du monde entier
#define TRUE -1
.Mais le langage de programmation B était faiblement typé et n'avait pas de type booléen ni même de type virgule flottante. Tout était l'équivalent de
int
son successeur, C. Cela incitait le langage à définir ce qui se passait lorsqu'un programme utilisait une valeur autre que true ou false comme valeur logique. Il a d'abord défini une expression de vérité comme étant «non égale à zéro». Cette méthode était efficace sur les mini-ordinateurs sur lesquels il fonctionnait, qui présentaient un indicateur de zéro CPU.Il y avait à l'époque une alternative: les mêmes processeurs avaient également un drapeau négatif et la valeur de vérité de BCPL était -1, alors B aurait peut-être défini tous les nombres négatifs comme des vrais et tous les nombres non négatifs comme des faux. (Il y a un reste de cette approche: UNIX, développé par les mêmes personnes en même temps, définit tous les codes d'erreur en tant qu'entiers négatifs. Beaucoup de ses appels système renvoient l'une de plusieurs valeurs négatives différentes en cas d'échec.) Soyez donc reconnaissant: il Aurait pu être pire!
Mais définir
TRUE
comme1
etFALSE
comme0
dans B signifiait que l’identitétrue = ~ false
n’était plus conservée et que le typage fort qui permettait à Algol de ne pas être ambiguïté entre les expressions binaires et logiques disparaissait. Cela nécessitait un nouvel opérateur logique-non, et les concepteurs ont choisi!
, peut-être parce que non-égal-à était déjà!=
, qui ressemble à une barre verticale à travers un signe égal. Ils n'ont pas suivi la même convention&&
ou||
parce que ni l'un ni l'autre n'existaient encore.On peut soutenir qu'ils devraient avoir: l'
&
opérateur en B est cassé comme prévu. En B et en C,1 & 2 == FALSE
même si1
et2
sont deux valeurs véridiques, et il n’existe aucun moyen intuitif d’exprimer l’opération logique en B. C’est une erreur que C a tenté de rectifier en partie en ajoutant&&
et||
, mais la principale préoccupation à l’époque était de: enfin, faites fonctionner les courts-circuits et accélérez les programmes. La preuve est qu'il n'y a pas^^
:1 ^ 2
est une valeur truthy même si ses deux opérandes sont truthy, mais il ne peut pas bénéficier de court-circuit.la source
~0
(tous les bits sont définis) est le complément à zéro négatif de son complément (ou une représentation de piège). Le signe / magnitude~0
est un nombre négatif avec une magnitude maximale.