C# Operadores Lógicos
Na aula anterior você fez perguntas simples: "a nota é maior que 6?". Muitas vezes, porém, a pergunta tem mais de uma parte: "a nota está entre 0 e 10?", "o cliente é estudante ou tem mais de 65 anos?", "o estoque não acabou?". Para juntar perguntas, o C# tem os operadores lógicos: && (e), || (ou) e ! (não). Nesta aula você vai ver o que cada um responde, como o C# economiza trabalho com o curto-circuito (e por que isso evita que o programa quebre) e as confusões mais comuns.
- Os três operadores:
&&(e),||(ou) e!(não), e a tabela-verdade de cada um. - Que eles só aceitam
bool: em C#, um número não vira "sim" ou "não" sozinho. - O curto-circuito: por que
b != 0 && a / b > 2nunca divide por zero. - A diferença entre
&&e&, entre||e|, e o "ou exclusivo"^. - Por que
&&vem antes de||, a forma modernanota is >= 0 and <= 10e os erros reais do compilador.
Todos os programas desta aula foram rodados com o .NET 10 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs.
A ideia: as regras de uma festa
Imagine a portaria de uma festa. O segurança tem regras:
- "Entra quem tem convite e documento." Precisa das duas coisas. Faltou uma, não entra.
- "Ganha pulseira VIP quem é aniversariante ou amigo do dono." Basta uma das duas. Ter as duas também vale.
- "Pode beber cerveja quem não é menor de idade." O "não" inverte a resposta.
Em C#, essas três palavras viram símbolos:
| Operador | Nome | Responde true quando… | Exemplo |
|---|---|---|---|
&& | E (AND) | os dois lados são true | convite && documento |
|| | OU (OR) | pelo menos um lado é true | aniversariante || amigo |
! | NÃO (NOT) | o valor é false (ele inverte) | !menorDeIdade |
O && são dois "e comercial" grudados. O || são duas barras verticais (no teclado brasileiro ABNT2, a barra vertical fica na tecla à esquerda do Z, com Shift). O ! é o ponto de exclamação, escrito antes do valor, como no != ("não igual") da aula Operadores de Comparação.
1. A tabela-verdade
Como cada lado só pode ser true ou false, dá para listar todas as combinações numa tabela, a tabela-verdade. O programa abaixo calcula cada uma:
Console.WriteLine(" a b | a && b | a || b");
Console.WriteLine("false false | " + (false && false) + " | " + (false || false));
Console.WriteLine("false true | " + (false && true) + " | " + (false || true));
Console.WriteLine("true false | " + (true && false) + " | " + (true || false));
Console.WriteLine("true true | " + (true && true) + " | " + (true || true));
Console.WriteLine("!false = " + !false + " !true = " + !true);
$ dotnet run tabela.cs a b | a && b | a || b false false | False | False false true | False | True true false | False | True true true | True | True !false = True !true = False
Leia as colunas assim:
&&só dáTruena última linha, quando os dois sãotrue. É exigente.||só dáFalsena primeira linha, quando os dois sãofalse. É generoso.!troca: o que erafalseviratrue, e vice-versa.
O "ou" do C# é o ou inclusivo: se os dois lados forem verdadeiros, a resposta também é verdadeira. É diferente do "ou" de "quer café ou chá?", em que só se escolhe um. (Para esse "ou um ou outro", existe o ^, mais adiante.)
Juntando comparações de verdade
Na prática, os lados de um && ou de um || são comparações. O exemplo clássico é a faixa: "a nota está entre 0 e 10?" vira "a nota é maior ou igual a 0 e menor ou igual a 10?":
int nota = 15;
int idade = 70;
bool estudante = false;
Console.WriteLine("Nota válida? " + (nota >= 0 && nota <= 10));
Console.WriteLine("Meia-entrada? " + (estudante || idade >= 65));
Console.WriteLine("Paga inteira? " + !(estudante || idade >= 65));
$ dotnet run faixa.cs Nota válida? False « 15 >= 0 é true, mas 15 <= 10 é false: true && false dá false Meia-entrada? True « não é estudante (false), mas tem 65 ou mais (true) Paga inteira? False « o ! inverteu a resposta anterior
- nota >= 0 && nota <= 10
- Duas perguntas completas, uma de cada lado do
&&. Repare quenotaaparece duas vezes: em C# não existe0 <= nota <= 10com o sentido da matemática (é o erroCS0019). As comparações são feitas antes do&&, então não precisam de parênteses (aula Precedência). - bool estudante = false;
- Um
booljá é um "sim ou não", então vai direto num||, sem comparar com nada. - ( … ) em volta de tudo
- Como na aula anterior, os parênteses separam a pergunta do texto que vem antes do
+. - !( … )
- Aqui os parênteses são obrigatórios: sem eles, o
!se aplicaria só aoestudante. O!é "grudento" e pega só o que vem logo depois dele.
Experimente: mude os valores de convite e documento (true ou false) e veja quando a pessoa entra. Depois troque o && por || e compare.
bool convite = true;
bool documento = false;
Console.WriteLine("Entra na festa? " + (convite && documento));
Console.WriteLine("Esqueceu algo? " + (!convite || !documento));
2. Só bool entra
Quem já viu C ou JavaScript conhece o costume de usar um número como "sim ou não" (0 é não, o resto é sim). O C# não aceita isso. Os operadores lógicos e o if exigem um bool de verdade:
int estoque = 3; Console.WriteLine(estoque && true);
$ dotnet run el4.cs /home/ana/cs/el4.cs(2,19): error CS0019: O operador "&&" não pode ser aplicado a operandos dos tipos "int" e "bool" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Parece chato, mas evita um monte de erros. Você precisa escrever a pergunta completa: "o estoque é maior que zero?" é estoque > 0, e aí sim dá para usar: estoque > 0 && true. A regra vale para os três operadores e para o if: if (estoque) dá CS0029 e !estoque dá CS0023 (veja Quando dá errado).
3. Curto-circuito: o C# para assim que sabe a resposta
Pense de novo na portaria. Se a pessoa não tem convite, o segurança nem pede o documento: já sabe que ela não entra. O C# faz igual:
- Em
A && B, seAéfalse, a resposta éfalsede qualquer jeito, eBnem é calculado. - Em
A || B, seAétrue, a resposta étruede qualquer jeito, eBnem é calculado.
Isso se chama avaliação em curto-circuito (short-circuit evaluation). O lado esquerdo é sempre calculado primeiro, e o direito só se for preciso. Parece só uma economia, mas é uma ferramenta de segurança. Dividir um inteiro por zero derruba o programa (aula Operadores Aritméticos):
int a = 10;
int b = 0;
Console.WriteLine("Com proteção: " + (b != 0 && a / b > 2));
Console.WriteLine("Sem proteção: " + (a / b > 2 && b != 0));
$ dotnet run div.cs Com proteção: False Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.<Main>$(String[] args) in /home/ana/cs/div.cs:line 4 $ echo $? 134
- Linha 3: o C# calcula
b != 0. Comobé 0, dáfalse. O&&já sabe que a resposta éfalsee não faz a divisão. O programa mostraCom proteção: False. - Linha 4: as mesmas duas perguntas, na ordem trocada. Agora o lado esquerdo é
a / b > 2, calculado primeiro: 10 dividido por 0. O .NET lança uma exceção,DivideByZeroException("tentativa de dividir por zero"), e o programa termina ali. A mensagem sai em inglês (o .NET no Linux não traduz as exceções), diz a linha (line 4), e o código de saída é 134, o de um programa que morreu com uma exceção não tratada (aula Exceções). A segunda linha nunca aparece.
O compilador não avisou nada: ao compilar, ele não sabe que b vai valer 0. A proteção é responsabilidade sua, e a regra é: a pergunta que protege vem à esquerda.
O mesmo vale com ||, de trás para a frente: "o grupo está vazio ou cada pessoa ganha menos de uma pizza?". Se o grupo está vazio, a divisão nem acontece.
Missão: a Ana quer saber se vai faltar pizza. Com zero pessoas, o programa quebra com DivideByZeroException. Sem apagar nenhuma das duas perguntas, troque a ordem delas para que o curto-circuito proteja a divisão e apareça Pouca pizza? True.
Pouca pizza? True
int pessoas = 0;
int pizzas = 3;
Console.WriteLine("Pouca pizza? " + (pizzas / pessoas < 1 || pessoas == 0));
int pessoas = 0;
int pizzas = 3;
Console.WriteLine("Pouca pizza? " + (pessoas == 0 || pizzas / pessoas < 1));
O curto-circuito aparece em todo programa de verdade: "o índice está dentro do array e o elemento é zero?" (Arrays), "o texto não é null e tem mais de 3 letras?" (null). Sempre a pergunta que protege primeiro.
4. && não é & (e || não é |)
O C# também tem & e |, com um símbolo só. Com bool, eles dão a mesma resposta que && e ||, mas não fazem curto-circuito: calculam sempre os dois lados. Veja o programa da divisão com &:
int a = 10;
int b = 0;
Console.WriteLine("Com &: " + (b != 0 & a / b > 2));
$ dotnet run ebits.cs Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.<Main>$(String[] args) in /home/ana/cs/ebits.cs:line 3
A proteção estava à esquerda, mas o & fez a divisão mesmo assim. O compilador não avisa, porque & com bool é permitido. Com números inteiros, & e | fazem outra coisa: trabalham com cada dígito binário (são operadores de bits, assunto de Operadores). Regra simples: para juntar perguntas, sempre dois símbolos: && e ||.
O "ou exclusivo": ^
Lembra do "quer café ou chá?", em que só se escolhe um? Esse é o ou exclusivo (XOR), e em C# ele é o ^ (acento circunflexo): dá true quando exatamente um dos lados é true.
bool temCafe = true; bool temCha = false; Console.WriteLine(temCafe & temCha); Console.WriteLine(temCafe | temCha); Console.WriteLine(temCafe ^ temCha); Console.WriteLine(true ^ true);
$ dotnet run xor.cs False « & : os dois? não True « | : pelo menos um? sim True « ^ : exatamente um? sim False « ^ com os dois true: não (não é "exatamente um")
O ^ é raro no dia a dia. Você vai usá-lo pouco, mas é bom reconhecê-lo.
5. Misturando &&, || e !
&& vem antes de ||
Assim como na matemática a multiplicação vem antes da soma, em C# o && é calculado antes do ||. Então a || b && c significa a || (b && c):
int idade = 70; bool estudante = false; bool doador = false; Console.WriteLine(estudante && doador || idade >= 65); Console.WriteLine((estudante && doador) || idade >= 65); Console.WriteLine(estudante && (doador || idade >= 65));
$ dotnet run mistura.cs True « sem parênteses: o C# leu (estudante && doador) || idade >= 65 True « a mesma coisa, agora escrita False « outra pergunta: estudante E (doador OU idoso)
A primeira e a segunda linha são a mesma pergunta; a terceira é outra, e dá outra resposta. O compilador do C# não avisa quando você mistura os dois sem parênteses (o gcc do C avisa). Então a boa prática é sua: ao misturar && e ||, escreva os parênteses, mesmo quando eles não mudam nada. Quem lê o código (inclusive você, daqui a um mês) agradece.
O ! pega só o que vem logo depois
O ! é calculado antes de tudo. Por isso !(estudante || idoso) e !estudante || idoso são perguntas diferentes: a primeira é "nem estudante, nem idoso"; a segunda é "não é estudante, ou é idoso".
Missão: a Ana quer conferir se uma nota está entre 0 e 10. Ela escreveu a faixa como na matemática, e o compilador recusa com CS0019. Conserte com duas comparações ligadas por &&. Depois teste com outras notas (7, 0, −1, 10).
15 é válida? False
int nota = 15; bool valida = 0 <= nota <= 10; Console.WriteLine(nota + " é válida? " + valida);
int nota = 15; bool valida = nota >= 0 && nota <= 10; Console.WriteLine(nota + " é válida? " + valida);
Formas diferentes de escrever a mesma pergunta
As leis de De Morgan
Um "não" na frente de um "e" ou de um "ou" pode ser distribuído, desde que você troque o && pelo || (e vice-versa). Essas regras têm o nome do matemático inglês Augustus De Morgan:
| Esta forma… | …é igual a esta | Em palavras |
|---|---|---|
!(a && b) | !a || !b | "não (convite e documento)" = "falta o convite ou falta o documento" |
!(a || b) | !a && !b | "não (estudante ou idoso)" = "nem estudante, nem idoso" |
!(nota >= 0 && nota <= 10) | nota < 0 || nota > 10 | "fora da faixa": o ! também inverte cada comparação (>= vira <) |
Com nota = 12, as duas últimas dão True. Qual usar? A que se lê mais fácil em voz alta. Para "nota inválida", nota < 0 || nota > 10 é mais clara do que !(nota >= 0 && nota <= 10).
Padrões com is, and, or e not C# 9
Desde o C# 9, dá para fazer perguntas sobre uma mesma variável com a palavra is ("é") e as palavras and, or e not. A faixa da nota fica quase em português:
int nota = 7; Console.WriteLine(nota is >= 0 and <= 10); int dia = 7; Console.WriteLine(dia is 6 or 7); Console.WriteLine(dia is not 1);
$ dotnet run padrao.cs True « "nota é maior ou igual a 0 e menor ou igual a 10" True « "dia é 6 ou 7" True « "dia não é 1"
Repare que nota e dia aparecem uma vez só: tudo depois do is fala da mesma variável. Esses and/or/not só existem depois de um is (são padrões, assunto de Pattern Matching); para juntar perguntas sobre variáveis diferentes, continue com && e ||.
Qual usar? Para faixas e listas de valores de uma variável, is … and … é curto e difícil de errar. Para o resto, &&, || e !, que você vai encontrar em todo código C#.
Quando dá errado
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. numa condição
O que significa: a divisão por zero foi calculada porque a pergunta que protege estava do lado errado (a / b > 2 && b != 0) ou porque você usou & no lugar de &&. O código de saída é 134.
Como consertar: a proteção à esquerda e &&: b != 0 && a / b > 2.
error CS0029: Não é possível converter implicitamente tipo "int" em "bool"
int estoque = 3;
if (estoque)
{
Console.WriteLine("Tem produto");
}
$ dotnet run el1.cs /home/ana/cs/el1.cs(2,5): error CS0029: Não é possível converter implicitamente tipo "int" em "bool" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: o if recebeu um número, e o C# não transforma número em bool.
Como consertar: escreva a pergunta: if (estoque > 0).
error CS0023: O operador "!" não pode ser aplicado ao operando do tipo "int"
int idade = 20; Console.WriteLine(!idade == 18);
$ dotnet run el2.cs /home/ana/cs/el2.cs(2,19): error CS0023: O operador "!" não pode ser aplicado ao operando do tipo "int" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: a intenção era "a idade não é 18". Mas o ! pega só o que vem logo depois, o idade, que é um int. Em inglês: Operator '!' cannot be applied to operand of type 'int'.
Como consertar: idade != 18 (ou !(idade == 18)).
error CS0019: O operador "||" não pode ser aplicado a operandos dos tipos "bool" e "int"
int idade = 20; Console.WriteLine(idade == 18 || 21);
$ dotnet run el3.cs /home/ana/cs/el3.cs(2,19): error CS0019: O operador "||" não pode ser aplicado a operandos dos tipos "bool" e "int" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: a intenção era "a idade é 18 ou 21". Mas o C# não "distribui" o idade ==: ele leu (idade == 18) || 21, e 21 não é um bool. (Em C, esse código compila e é sempre verdade; o C# pega o erro.)
Como consertar: a pergunta completa dos dois lados, idade == 18 || idade == 21, ou o padrão idade is 18 or 21.
O que significa: provavelmente você misturou && e || sem parênteses, e o C# fez o && primeiro. Ou trocou !(a || b) por !a || b.
Como consertar: escreva os parênteses do jeito que você quer que a pergunta seja lida, e teste com valores que mudam a resposta.
Referência
| Operador | Nome | Resposta | Curto-circuito |
|---|---|---|---|
a && b | E | true se os dois são true | Se a é false, b não é calculado |
a || b | OU | true se pelo menos um é true | Se a é true, b não é calculado |
!a | NÃO | o contrário de a | — |
a ^ b | OU exclusivo | true se exatamente um é true | Não (precisa dos dois) |
a & b, a | b | E / OU sem atalho | iguais a && / || com bool | Não: calculam sempre os dois lados |
x is >= 0 and <= 10 | padrão C# 9 | true se x casa com o padrão | — |
Ordem de cálculo (do que vem primeiro para o que vem depois): ! → comparações (< <= > >=, depois == !=) → & → ^ → | → && → ||. A tabela completa está em Precedência.
Exercícios
1. Os dois
Complete para responder true só se a pessoa tem convite e documento:
2. Pelo menos um
Complete para responder true se o cliente é estudante ou idoso:
3. A proteção primeiro
Complete para nunca dividir por zero:
4. Em C#, o que acontece com int estoque = 3; if (estoque) { … }?
5. Em pessoas == 0 || pizzas / pessoas < 1, com pessoas valendo 0, o que acontece?
6. A Ana quer saber se dia é 6 ou 7 (fim de semana). Qual está certo?
7. Qual destas é igual a !(a && b)?
Missão final: o desconto da lojinha
Missão: a lojinha da Ana dá desconto quando o cliente é estudante ou tem 65 anos ou mais, mas só se a compra passa de 50 reais. Um estudante de 20 anos comprou 30 reais e o programa deu desconto, o que está errado. Sem trocar os valores, ponha os parênteses no lugar certo para aparecer Desconto? False.
Desconto? False
int idade = 20;
bool estudante = true;
decimal total = 30m;
bool desconto = estudante || idade >= 65 && total > 50m;
Console.WriteLine("Desconto? " + desconto);
int idade = 20;
bool estudante = true;
decimal total = 30m;
bool desconto = (estudante || idade >= 65) && total > 50m;
Console.WriteLine("Desconto? " + desconto);
Depois, teste outros clientes: idade = 70, estudante = false, total = 80m (tem desconto); idade = 30, estudante = false, total = 100m (não tem).
Resumo
&&(e):truesó se os dois lados foremtrue.||(ou):truese pelo menos um for.!(não): inverte.- Só
boolentra:if (estoque)com umintdáCS0029. Escreva a pergunta:estoque > 0. - Curto-circuito: o lado esquerdo é calculado primeiro; o direito só se for preciso. Use para proteger:
b != 0 && a / b > 2. &e|(um símbolo) não fazem curto-circuito.^é o "ou exclusivo".&&vem antes de||, e o C# não avisa: ao misturar, use parênteses.idade == 18 || 21não compila (CS0019): escrevaidade == 18 || idade == 21, ouidade is 18 or 21.- De Morgan:
!(a && b)=!a || !b;!(a || b)=!a && !b.