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.

Você vai aprender
  • 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 > 2 nunca divide por zero.
  • A diferença entre && e &, entre || e |, e o "ou exclusivo" ^.
  • Por que && vem antes de ||, a forma moderna nota is >= 0 and <= 10 e 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:

OperadorNomeResponde true quando…Exemplo
&&E (AND)os dois lados são trueconvite && documento
||OU (OR)pelo menos um lado é trueaniversariante || 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á True na última linha, quando os dois são true. É exigente.
  • || só dá False na primeira linha, quando os dois são false. É generoso.
  • ! troca: o que era false vira true, 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 que nota aparece duas vezes: em C# não existe 0 <= nota <= 10 com o sentido da matemática (é o erro CS0019). As comparações são feitas antes do &&, então não precisam de parênteses (aula Precedência).
bool estudante = false;
Um bool já é 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ó ao estudante. 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, se A é false, a resposta é false de qualquer jeito, e B nem é calculado.
  • Em A || B, se A é true, a resposta é true de qualquer jeito, e B nem é 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
  1. Linha 3: o C# calcula b != 0. Como b é 0, dá false. O && já sabe que a resposta é false e não faz a divisão. O programa mostra Com proteção: False.
  2. 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));
Você vai usar isso muito

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 estaEm 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.

A resposta está errada, e não há erro nem aviso

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

OperadorNomeRespostaCurto-circuito
a && bEtrue se os dois são trueSe a é false, b não é calculado
a || bOUtrue se pelo menos um é trueSe a é true, b não é calculado
!aNÃOo contrário de a—
a ^ bOU exclusivotrue se exatamente um é trueNão (precisa dos dois)
a & b, a | bE / OU sem atalhoiguais a && / || com boolNão: calculam sempre os dois lados
x is >= 0 and <= 10padrão C# 9true 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:

bool entra = convite documento;

2. Pelo menos um

Complete para responder true se o cliente é estudante ou idoso:

bool meia = estudante idoso;

3. A proteção primeiro

Complete para nunca dividir por zero:

bool muito = && a / b > 2;

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): true só se os dois lados forem true. || (ou): true se pelo menos um for. ! (não): inverte.
  • Só bool entra: if (estoque) com um int dá 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 || 21 não compila (CS0019): escreva idade == 18 || idade == 21, ou idade is 18 or 21.
  • De Morgan: !(a && b) = !a || !b; !(a || b) = !a && !b.