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

Você vai aprender
  • 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 1 ou 0.
  • O curto-circuito: por que b != 0 && a / b > 2 nunca divide por zero.
  • A diferença entre && e &, e entre || e |.
  • Por que && vem antes de ||, e as armadilhas !x == 5 e x == 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:

OperadorNomeResponde 1 (sim) quando…Exemplo
&&E (AND)os dois lados são verdadeirosconvite && documento
||OU (OR)pelo menos um lado é verdadeiroaniversariante || 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 que nota aparece duas vezes: em C não existe 0 <= nota <= 10 com 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 bool C23 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ó ao estudante. 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, se A é falso, a resposta é 0 de qualquer jeito, e B nem é calculado.
  • Em A || B, se A é verdadeiro, a resposta é 1 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. 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
  1. Linha 6: o C calcula b != 0. Como b é 0, dá 0 (falso). O && já sabe que a resposta é 0 e não faz a divisão. O programa mostra Com proteção: 0.
  2. 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
Você vai usar isso muito

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 >= 65 ou estudante && (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ó o idade: 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". Escreva idade != 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", &nota) != 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", &nota) != 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 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 <)

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

A condição é sempre verdadeira e não há aviso no gcc

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

OperadorNomeRespostaCurto-circuito
a && bE1 se os dois são diferentes de 0; senão 0Se a é 0, b não é calculado
a || bOU1 se pelo menos um é diferente de 0; senão 0Se a não é 0, b não é calculado
!aNÃO1 se a é 0; senão 0—
!!a"normalizar"0 continua 0; qualquer outro vira 1—
a & b, a | bE / OU de bitsoutro número (bit a bit)Não: calcula sempre os dois lados
and, or, notnomes 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:

bool entra = convite documento;

2. Pelo menos um

Complete para responder 1 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. 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: escreva x != 5. x == 1 || 2 é sempre verdade: escreva x == 1 || x == 2.
  • De Morgan: !(a && b) = !a || !b; !(a || b) = !a && !b.