Para operadores binários, temos operadores lógicos e bit a bit:
& bitwise AND
| bitwise OR
&& logical AND
|| logical OR
NOT (um operador unário) se comporta de maneira diferente. Existe ~ para bit a bit e! para lógico.
Reconheço que NOT é uma operação unária, em oposição a AND e OR, mas não consigo pensar em uma razão pela qual os designers escolheram se desviar do princípio de que único é bit a bit e o dobro é lógico aqui, e optou por um personagem diferente. Eu acho que você pode ler errado, como uma operação bit a bit dupla que sempre retornaria o valor do operando. Mas isso não me parece um problema real.
Existe uma razão pela qual estou perdendo?
~~
, então, não ter sido mais consistente para NOT lógico, se você seguir o padrão que o operador lógico é uma duplicação do operador bit a bit?!!foo
é um não-incomum (não não é comum) idioma Normaliza um argumento de zero-ou-não-zero para?.0
Ou1
.Respostas:
Estranhamente, a história da linguagem de programação no estilo C não começa com C.
Dennis Ritchie explica bem os desafios do nascimento de C neste artigo .
Ao lê-lo, torna-se óbvio que C herdou parte de seu design de linguagem do seu BCPL predecessor , e especialmente dos operadores. A seção “Neonatal C” do referido artigo explica como BCPL da
&
e|
foram enriquecidos com dois novos operadores&&
e||
. Os motivos foram:==
a
estáfalse
dentroa&&b
,b
não é avaliado).Curiosamente, essa duplicação não cria nenhuma ambiguidade para o leitor:
a && b
não será mal interpretada comoa(&(&b))
. Do ponto de vista da análise, também não há ambiguidade:&b
poderia fazer sentido seb
fosse um valor l, mas seria um ponteiro, enquanto o bit a bit&
exigiria um operando inteiro, portanto o AND lógico seria a única opção razoável.BCPL já usado
~
para negação bit a bit. Portanto, do ponto de vista da consistência, poderia ter sido duplicado para dar uma~~
para dar seu significado lógico. Infelizmente, isso teria sido extremamente ambíguo, pois~
é um operador unário:~~b
também pode significar~(~b))
. É por isso que outro símbolo teve que ser escolhido para a negação que faltava.fonte
(t)+1
que é uma adição(t)
e1
ou é uma conversão+1
para digitart
? O design do C ++ teve que resolver o problema de como lexar modelos contendo>>
corretamente. E assim por diante.&&
como um único&&
token e não como dois&
tokens, porque aa & (&b)
interpretação não é uma coisa razoável de se escrever, então um humano nunca teria significado isso e ficado surpreso com o compilador tratando comoa && b
. Considerando que tanto!(!a)
e!!a
são coisas possíveis para um ser humano para dizer, por isso é uma má idéia para o compilador para resolver a ambiguidade com uma regra de nível tokenization arbitrária.!!
não é apenas possível / razoável escrever, mas o idioma canônico "converter em booleano".--a
vs-(-a)
, os quais são válidos sintaticamente, mas têm semânticas diferentes.Esse não é o princípio em primeiro lugar; uma vez que você percebe isso, faz mais sentido.
A melhor maneira de pensar em
&
vs&&
não é binário e booleano . A melhor maneira é pensar neles como ansiosos e preguiçosos . O&
operador executa os lados esquerdo e direito e calcula o resultado. o&&
operador executa o lado esquerdo e, em seguida, executa o lado direito somente se necessário para calcular o resultado.Além disso, em vez de pensar em "binário" e "booleano", pense no que realmente está acontecendo. A versão "binária" está apenas executando a operação booleana em uma matriz de booleanos que foi compactada em uma palavra .
Então, vamos montar isso. Faz algum sentido fazer uma operação lenta em uma matriz de booleanos ? Não, porque não há "lado esquerdo" para verificar primeiro. Existem 32 "lados esquerdos" para verificar primeiro. Portanto, restringimos as operações preguiçosas a um único booleano, e é daí que a sua intuição de que uma delas é "binária" e outra é "booleana", mas isso é uma consequência do design, não do design em si!
E quando você pensa dessa maneira, fica claro por que não existe
!!
e não^^
. Nenhum desses operadores tem a propriedade que você pode pular analisando um dos operandos; não há "preguiçoso"not
ouxor
.Outros idiomas tornam isso mais claro; algumas línguas costumam
and
significar "ansioso e", masand also
significa "preguiçoso e", por exemplo. E outros idiomas também deixam mais claro que&
e&&
não são "binários" e "Booleanos"; em C #, por exemplo, ambas as versões podem usar os booleanos como operandos.fonte
&
e&&
. Embora a ânsia seja uma das diferenças entre&
e&&
,&
comporta-se de maneira completamente diferente de uma versão ansiosa de&&
, principalmente em idiomas onde&&
suporta tipos diferentes de um tipo booleano dedicado.1 & 2
tem um resultado completamente diferente de1 && 2
.bool
tipo em C tem efeitos indiretos . Precisamos de ambos!
e~
porque um significa "tratar um int como um único booleano" e um significa "tratar um int como uma matriz compactada de booleanos". Se você tiver tipos bool e int separados, poderá ter apenas um operador, o que, na minha opinião, teria sido o melhor design, mas estamos quase 50 anos atrasados nesse. C # preserva esse design para familiaridade.TL; DR
C herdou os operadores
!
e~
de outro idioma. Ambos&&
e||
foram adicionados anos depois por uma pessoa diferente.Resposta longa
Historicamente, C se desenvolveu a partir dos idiomas iniciais B, que eram baseados no BCPL, que era baseado no CPL, que era baseado no Algol.
Algol , o bisavô de C ++, Java e C #, definiu true e false de uma maneira que pareceu intuitiva aos programadores: “valores de verdade que, considerados como um número binário (true correspondente a 1 e false a 0), são o mesmo que o valor integral intrínseco ”. No entanto, uma desvantagem disso é que lógica e bit a bit não podem ser a mesma operação: em qualquer computador moderno,
~0
é igual a -1 em vez de 1 e~1
é igual a -2 em vez de 0. (Mesmo em um mainframe de sessenta anos, onde~0
representa - 0 ouINT_MIN
,~0 != 1
em todas as CPUs já fabricadas, e o padrão da linguagem C exige isso há muitos anos, enquanto a maioria das linguagens filhas nem se dá ao trabalho de suportar sinal e magnitude ou complemento de alguém.)Algol contornou isso, tendo diferentes modos e interpretando operadores de maneira diferente nos modos booleano e integral. Ou seja, uma operação bit a bit era uma em tipos inteiros e uma operação lógica era uma em tipos booleanos.
O BCPL tinha um tipo booleano separado, mas um único
not
operador , tanto para bits quanto lógicos, não. A maneira como esse precursor de C fez esse trabalho foi:(Você observará que o termo rvalue evoluiu para significar algo completamente diferente nas linguagens da família C. Nós hoje chamaríamos isso de "a representação do objeto" em C.)
Essa definição permitiria lógico e bit a bit não usar a mesma instrução de linguagem de máquina. Se C seguisse esse caminho, diriam os arquivos de cabeçalho em todo o mundo
#define TRUE -1
.Mas a linguagem de programação B era de tipo fraco e não possuía tipos de pontos booleanos ou mesmo de ponto flutuante. Tudo era equivalente
int
em seu sucessor, C. Isso tornou uma boa idéia para a linguagem definir o que aconteceu quando um programa usava um valor diferente de verdadeiro ou falso como valor lógico. Ele primeiro definiu uma expressão de verdade como "diferente de zero". Isso foi eficiente nos minicomputadores em que era executado, que tinham um sinalizador de CPU zero.Havia, na época, uma alternativa: as mesmas CPUs também tinham uma flag negativa, e o valor de verdade do BCPL era -1; portanto, B poderia ter definido todos os números negativos como verdade e todos os números não negativos como falsidade. (Existe um remanescente dessa abordagem: o UNIX, desenvolvido pelas mesmas pessoas ao mesmo tempo, define todos os códigos de erro como números inteiros negativos. Muitas chamadas do sistema retornam um dos vários valores negativos diferentes na falha.) Portanto, seja grato: ele Poderia ter sido pior!
Mas definir
TRUE
como1
eFALSE
como0
em B significava que a identidadetrue = ~ false
não era mais válida, e abandonara a forte digitação que permitia a Algol desambiguar entre expressões bit a bit e expressões lógicas. Isso exigiu um novo operador lógico-não, e os designers escolheram!
, possivelmente porque já não era igual a!=
, que parece uma barra vertical através de um sinal de igual. Eles não seguiram a mesma convenção&&
ou||
porque ainda não existia.Indiscutivelmente, eles deveriam ter: o
&
operador em B está quebrado como projetado. Em B e C,1 & 2 == FALSE
embora1
e2
são ambos valores truthy, e não há nenhuma maneira intuitiva para expressar a operação lógica em B. Esse foi um erro C tentou retificar em parte, adicionando&&
e||
, mas a principal preocupação na época era a finalmente, faça um curto-circuito para funcionar e torne os programas mais rápidos. A prova disso é que não existe^^
:1 ^ 2
é um valor verdadeiro, embora ambos os operandos sejam verdadeiros, mas não pode se beneficiar de um curto-circuito.fonte
~0
(todos os bits definidos) é o zero negativo do complemento de uma pessoa (ou uma representação de interceptação). Sinal / magnitude~0
é um número negativo com magnitude máxima.