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 três 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, como trocar && por &.
- Os três operadores:
&&(e),||(ou) e!(não), e a tabela-verdade de cada um. - Que, para o C, qualquer número diferente de zero é "verdadeiro", e que a resposta é sempre
1ou0. - O curto-circuito: por que
b != 0 && a / b > 2nunca divide por zero. - A diferença entre
&&e&, e entre||e|. - Por que
&&vem antes de||, e as armadilhas!x == 5ex == 1 || 2.
Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
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 1 (sim) quando… | Exemplo |
|---|---|---|---|
&& | E (AND) | os dois lados são verdadeiros | convite && documento |
|| | OU (OR) | pelo menos um lado é verdadeiro | aniversariante || amigo |
! | NÃO (NOT) | o valor é falso (ele inverte) | !menor |
O && são dois "e comercial" grudados. O || são duas barras verticais (no teclado brasileiro, a barra vertical costuma ficar na tecla à esquerda do Z, com Shift). O ! é o ponto de exclamação, escrito antes do valor, como em != ("não igual").
1. A tabela-verdade
Como cada lado só pode ser verdadeiro (1) ou falso (0), dá para listar todas as combinações numa tabela, a tabela-verdade. O programa abaixo calcula cada uma:
#include <stdio.h>
int main(void) {
printf(" a b | a && b | a || b\n");
printf(" 0 0 | %d | %d\n", 0 && 0, 0 || 0);
printf(" 0 1 | %d | %d\n", 0 && 1, 0 || 1);
printf(" 1 0 | %d | %d\n", 1 && 0, 1 || 0);
printf(" 1 1 | %d | %d\n", 1 && 1, 1 || 1);
printf("!0 = %d !1 = %d\n", !0, !1);
return 0;
}
$ gcc -std=c23 -Wall -Wextra tabela.c -o tabela $ ./tabela a b | a && b | a || b 0 0 | 0 | 0 0 1 | 0 | 1 1 0 | 0 | 1 1 1 | 1 | 1 !0 = 1 !1 = 0
Leia as colunas assim:
&&só dá 1 na última linha, quando os dois são 1. É exigente.||só dá 0 na primeira linha, quando os dois são 0. É generoso.!troca: o que era 0 vira 1, o que era 1 vira 0.
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.
Juntando comparações de verdade
Na prática, os lados de um && ou de um || são comparações (aula Operadores de Comparação). 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?":
#include <stdio.h>
int main(void) {
int nota = 15;
int idade = 70;
bool estudante = false;
printf("Nota válida? %d\n", nota >= 0 && nota <= 10);
printf("Meia-entrada? %d\n", estudante || idade >= 65);
printf("Paga inteira? %d\n", !(estudante || idade >= 65));
return 0;
}
$ gcc -std=c23 -Wall -Wextra faixa.c -o faixa $ ./faixa Nota válida? 0 « 15 >= 0 é 1, mas 15 <= 10 é 0: 1 && 0 dá 0 Meia-entrada? 1 « não é estudante (0), mas tem 65 ou mais (1): 0 || 1 dá 1 Paga inteira? 0 « 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. Os parênteses em volta de cada comparação são opcionais (as comparações são feitas antes do&&; veja Precedência). - bool estudante = false;
- Um
boolC23 já é um "sim ou não", então pode ir direto num||, sem comparar com nada. - !( … )
- Os parênteses aqui 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 (0 ou 1) e veja quando a pessoa entra. Depois troque o && por || e compare.
#include <stdio.h>
int main(void) {
int convite = 1;
int documento = 0;
printf("Entra na festa? %d\n", convite && documento);
printf("Esqueceu algo? %d\n", !convite || !documento);
return 0;
}
2. Para o C, todo número diferente de zero é "sim"
Os operadores lógicos não exigem 0 e 1 nos lados. Eles aceitam qualquer número e seguem uma regra simples:
0é falso;- qualquer outro valor (5, −3, 1000) é verdadeiro.
A resposta, porém, é sempre 1 ou 0, um int, como nas comparações:
#include <stdio.h>
int main(void) {
int a = 5;
int b = -3;
int c = 0;
printf("%d\n", a && b);
printf("%d\n", a && c);
printf("%d\n", c || b);
printf("%d\n", !a);
printf("%d\n", !c);
printf("%d\n", !!a);
return 0;
}
$ gcc -std=c23 -Wall -Wextra numeros.c -o numeros $ ./numeros 1 « 5 é "sim", -3 é "sim": a resposta é 1 (e não 5, nem -3) 0 « c é 0, "não" 1 « b é "sim" 0 « não 5 = não "sim" = 0 1 « não 0 = 1 1 « !!a: dois "não" seguidos transformam qualquer número em 0 ou 1
É por isso que você vai ver muito código C assim: if (estoque) no lugar de if (estoque != 0), e if (!estoque) no lugar de if (estoque == 0). As duas formas fazem a mesma coisa. Para quem está começando, a forma com a comparação escrita (estoque != 0) costuma ser mais fácil de ler; reserve if (x) e if (!x) para variáveis que são mesmo "sim ou não", como um bool.
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é falso, a resposta é 0 de qualquer jeito, eBnem é calculado. - Em
A || B, seAé verdadeiro, a resposta é 1 de 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. Lembre que dividir um inteiro por zero derruba o programa (aula Operadores Aritméticos):
#include <stdio.h>
int main(void) {
int a = 10;
int b = 0;
printf("Com proteção: %d\n", b != 0 && a / b > 2);
printf("Sem proteção: %d\n", a / b > 2 && b != 0);
return 0;
}
$ gcc -std=c23 -Wall -Wextra div.c -o div $ ./div Com proteção: 0 Exceção de ponto flutuante (imagem do núcleo gravada) ./div $ echo $? 136
- Linha 6: o C calcula
b != 0. Comobé 0, dá 0 (falso). O&&já sabe que a resposta é 0 e não faz a divisão. O programa mostraCom proteção: 0. - Linha 7: as mesmas duas perguntas, na ordem trocada. Agora o lado esquerdo é
a / b > 2, que é calculado primeiro: 10 dividido por 0. O processador se recusa, e o sistema encerra o programa com a mensagem "Exceção de ponto flutuante" (o nome é histórico: vale também para divisão de inteiros). O código de saída 136 indica essa morte. A segunda linha nunca aparece.
Repare que o gcc não avisou nada: ele não sabe, ao compilar, 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:
#include <stdio.h>
int main(void) {
int pessoas = 0;
int pizzas = 3;
printf("%d\n", pessoas == 0 || pizzas / pessoas < 1);
return 0;
}
$ gcc -std=c23 -Wall -Wextra ou.c -o ou $ ./ou 1 « pessoas == 0 já é verdade: a divisão por zero não foi feita
O curto-circuito aparece em todo programa C de verdade: "o índice está dentro do array e o elemento é 0?" (Arrays), "o ponteiro não é nulo e o valor apontado é positivo?" (Ponteiro Nulo). Sempre a pergunta que protege primeiro.
4. && não é & (e || não é |)
O C também tem & e |, com um símbolo só. Eles são operadores de bits: trabalham com cada dígito binário do número, um por um (aula Operadores de Bits). Com 0 e 1 eles parecem iguais aos lógicos, mas com outros números a diferença aparece:
#include <stdio.h>
int main(void) {
int a = 1;
int b = 2;
printf("a && b = %d\n", a && b);
printf("a & b = %d\n", a & b);
printf("a || b = %d\n", a || b);
printf("a | b = %d\n", a | b);
return 0;
}
$ gcc -std=c23 -Wall -Wextra bits.c -o bits $ ./bits a && b = 1 « 1 é "sim" e 2 é "sim": sim a & b = 0 « 01 & 10 em binário: nenhum bit em comum, dá 0! a || b = 1 a | b = 3 « 01 | 10 = 11 em binário, que é 3
Dois números "verdadeiros" (1 e 2) deram 0 com o &. Além disso, & e | não fazem curto-circuito: calculam sempre os dois lados. Trocar b != 0 && a / b > 2 por b != 0 & a / b > 2 volta a dividir por zero.
Quando os dois lados são comparações, o gcc desconfia da troca e avisa:
$ gcc -std=c23 -Wall -Wextra ebits.c -o ebits
ebits.c: In function ‘main’:
ebits.c:6:11: warning: suggest parentheses around comparison in operand of ‘&’ [-Wparentheses]
6 | if (x > 0 & y > 0) {
| ~~^~~
"Sugiro parênteses em volta da comparação usada no &." Na prática, o recado é: "você queria &&?". O Clang do editor não dá esse aviso: mais um motivo para conferir o símbolo com cuidado. Regra simples: para juntar perguntas, sempre dois símbolos: && e ||.
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). Como pouca gente lembra disso, o gcc avisa quando você mistura os dois sem parênteses:
#include <stdio.h>
int main(void) {
int idade = 70;
int estudante = 0;
int doador = 0;
printf("%d\n", estudante && doador || idade >= 65);
printf("%d\n", !idade == 18);
printf("%d\n", idade == 18 || 21);
return 0;
}
$ gcc -std=c23 -Wall -Wextra mistura.c -o mistura
mistura.c: In function ‘main’:
mistura.c:7:30: warning: suggest parentheses around ‘&&’ within ‘||’ [-Wparentheses]
7 | printf("%d\n", estudante && doador || idade >= 65);
| ~~~~~~~~~~^~~~~~~~~
mistura.c:8:27: warning: comparison of constant ‘18’ with boolean expression is always false [-Wbool-compare]
8 | printf("%d\n", !idade == 18);
| ^~
mistura.c:8:27: warning: logical not is only applied to the left hand side of comparison [-Wlogical-not-parentheses]
mistura.c:8:20: note: add parentheses around left hand side expression to silence this warning
8 | printf("%d\n", !idade == 18);
| ^~~~~~
| ( )
$ ./mistura
1
0
1
Três linhas, três problemas diferentes:
- Linha 7:
estudante && doador || idade >= 65 - "Sugiro parênteses em volta do
&&dentro do||." O C leu(estudante && doador) || idade >= 65. Pode ser o que você queria, ou não: escreva os parênteses para deixar claro, por exemplo(estudante && doador) || idade >= 65ouestudante && (doador || idade >= 65). As duas dão respostas diferentes (aqui, 1 e 0). - Linha 8:
!idade == 18 - A intenção era "idade não é 18". Mas o
!pega só oidade: o C calculou(!idade) == 18, isto é,0 == 18. Os avisos dizem: "o não lógico só é aplicado ao lado esquerdo da comparação" e "comparar 18 com uma expressão booleana dá sempre falso". Escrevaidade != 18(ou!(idade == 18)). - Linha 9:
idade == 18 || 21 - A intenção era "idade é 18 ou 21". Mas o C não "distribui" o
idade ==: ele leu(idade == 18) || 21, e 21 é diferente de zero, então é sempre "sim". O gcc não avisa. O Clang do editor avisa:use of logical '||' with constant operand [-Wconstant-logical-operand]("uso de||com uma constante"). Escreva a pergunta completa dos dois lados:idade == 18 || idade == 21.
Missão: a Ana quer conferir se uma nota digitada está entre 0 e 10 (a entrada preparada é 15). Ela escreveu a faixa como na matemática, e o Clang do editor recusa. Conserte com &&, sem avisos. Depois teste com outras entradas (7, 0, −1, 10).
15 é válida? 0
#include <stdio.h>
int main(void) {
int nota = 0;
if (scanf("%d", ¬a) != 1) {
return 1;
}
bool valida = 0 <= nota <= 10;
printf("%d é válida? %d\n", nota, valida);
return 0;
}
#include <stdio.h>
int main(void) {
int nota = 0;
if (scanf("%d", ¬a) != 1) {
return 1;
}
bool valida = nota >= 0 && nota <= 10;
printf("%d é válida? %d\n", nota, valida);
return 0;
}
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 <) |
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).
Palavras no lugar dos símbolos: and, or, not C95
O cabeçalho <iso646.h> define nomes em inglês para os operadores (foi criado para teclados que não tinham & ou |):
#include <iso646.h>
#include <stdio.h>
int main(void) {
int idade = 20;
int tem_titulo = 1;
printf("%d\n", idade >= 18 and tem_titulo);
printf("%d\n", idade < 16 or not tem_titulo);
return 0;
}
$ gcc -std=c23 -Wall -Wextra iso.c -o iso $ ./iso 1 0
Funciona, mas é raro em C: quase todo código usa &&, || e !, e é isso que você vai encontrar nos livros, fóruns e projetos. Use os símbolos. (Em C++ essas palavras existem sem #include; em C, sem o <iso646.h>, and é só um nome desconhecido.)
Quando dá errado
Exceção de ponto flutuante (imagem do núcleo gravada) numa condição com divisã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 &&. No editor do guia aparece a mesma mensagem e o código de saída 136.
Como consertar: coloque a proteção à esquerda e use &&: b != 0 && a / b > 2.
warning: suggest parentheses around ‘&&’ within ‘||’
O que significa: você misturou && e || sem parênteses. O C faz o && primeiro, o que pode não ser o que você queria. No Clang: '&&' within '||' [-Wlogical-op-parentheses].
Como consertar: escreva os parênteses do jeito que você quer que a pergunta seja lida.
warning: logical not is only applied to the left hand side of comparison
O que significa: em !x == 5, o ! pegou só o x.
Como consertar: x != 5, ou !(x == 5).
warning: suggest parentheses around comparison in operand of ‘&’
O que significa: você juntou duas comparações com & (ou |), um símbolo só. Isso é operação de bits, sem curto-circuito.
Como consertar: use && (ou ||).
O que significa: provavelmente você escreveu x == 1 || 2 ou cor == 'a' || 'e'. O lado direito é um número diferente de zero, logo é sempre "sim".
Como consertar: repita a comparação: x == 1 || x == 2.
Referência
| Operador | Nome | Resposta | Curto-circuito |
|---|---|---|---|
a && b | E | 1 se os dois são diferentes de 0; senão 0 | Se a é 0, b não é calculado |
a || b | OU | 1 se pelo menos um é diferente de 0; senão 0 | Se a não é 0, b não é calculado |
!a | NÃO | 1 se a é 0; senão 0 | — |
!!a | "normalizar" | 0 continua 0; qualquer outro vira 1 | — |
a & b, a | b | E / OU de bits | outro número (bit a bit) | Não: calcula sempre os dois lados |
and, or, not | nomes de <iso646.h> | iguais a &&, ||, ! | igual |
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 1 só se a pessoa tem convite e documento:
2. Pelo menos um
Complete para responder 1 se o cliente é estudante ou idoso:
3. A proteção primeiro
Complete para nunca dividir por zero:
4. Quanto vale 7 && -2?
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. O programa lê a idade, se é estudante (1 ou 0) e o total. Troque o 0 marcado pela pergunta completa, com || e &&, e sem avisos (dica: o Clang avisa quando você mistura os dois sem parênteses, e aqui os parênteses mudam a resposta). A entrada preparada é um estudante de 20 anos com compra de 30 reais, que não tem desconto:
Desconto: 0
#include <stdio.h>
int main(void) {
int idade = 0;
int estudante = 0;
double total = 0;
if (scanf("%d %d %lf", &idade, &estudante, &total) != 3) {
return 1;
}
bool desconto = 0; // troque 0 pela pergunta completa
printf("Desconto: %d\n", desconto);
return 0;
}
#include <stdio.h>
int main(void) {
int idade = 0;
int estudante = 0;
double total = 0;
if (scanf("%d %d %lf", &idade, &estudante, &total) != 3) {
return 1;
}
bool desconto = (estudante || idade >= 65) && total > 50;
printf("Desconto: %d\n", desconto);
return 0;
}
Teste outras entradas: 70 0 80 (idosa, compra de 80: tem desconto), 20 1 60 (estudante, compra de 60: tem desconto), 30 0 100 (nem estudante nem idoso: não tem). Experimente tirar os parênteses e veja o aviso e a resposta mudarem com a entrada 20 1 30.
Resumo
&&(e): 1 só se os dois lados forem verdadeiros.||(ou): 1 se pelo menos um for.!(não): inverte.- 0 é falso; qualquer outro número é verdadeiro. A resposta é sempre um
int, 1 ou 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) são operadores de bits: outro resultado e sem curto-circuito.&&vem antes de||; ao misturar, use parênteses (suggest parentheses around ‘&&’ within ‘||’).!x == 5é(!x) == 5: escrevax != 5.x == 1 || 2é sempre verdade: escrevax == 1 || x == 2.- De Morgan:
!(a && b)=!a || !b;!(a || b)=!a && !b.