C++ Operadores Lógicos

Na aula anterior, cada comparação fazia uma pergunta: "a idade é maior ou igual a 18?". Mas as regras da vida real costumam ter várias condições: "pode entrar se for maior de idade e tiver ingresso"; "ganha desconto se for estudante ou idoso"; "o alarme toca se não houver senha". Os operadores lógicos juntam perguntas de sim ou não numa pergunta maior. São só três, mas aparecem em quase todo programa.

Você vai aprender
  • Os três operadores: && ("e"), || ("ou") e ! ("não").
  • As palavras equivalentes and, or e not.
  • A tabela-verdade: a resposta de cada operador para cada combinação.
  • O curto-circuito: por que o C++ às vezes nem olha o lado direito, e como isso evita uma divisão por zero.
  • A diferença entre & e && (a ideia).
  • Por que opcao == 1 || 2 é sempre verdadeiro.

Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp, com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa. Os programas usam std::boolalpha para mostrar true/false em vez de 1/0 (veja Operadores de Comparação).


A ideia: o porteiro da festa

A Ana vai dar uma festa e deixou três instruções para o porteiro:

  • E: "Só entra quem é maior de idade e tem convite." As duas coisas precisam ser verdade. Faltou uma, não entra.
  • OU: "Ganha bebida grátis quem fizer aniversário hoje ou trouxer um prato." Basta uma das duas (e, se tiver as duas, ganha também).
  • NÃO: "Entra quem não estiver na lista de bloqueados." Inverte a resposta: estar na lista (sim) vira não entrar.
OperadorPalavraNomeVerdadeiro quando…
a && ba and bE lógicoos dois são verdadeiros
a || ba or bOU lógicopelo menos um é verdadeiro
!anot aNÃO lógico (negação)a é falso

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 ! é a exclamação, que você já viu no != ("não igual").


1. Os três operadores

#include <iostream>

int main() {
    int idade = 20;
    bool tem_ingresso = false;
    std::cout << std::boolalpha;
    std::cout << (idade >= 18 && tem_ingresso) << '\n';
    std::cout << (idade >= 18 || tem_ingresso) << '\n';
    std::cout << !tem_ingresso << '\n';
}
$ g++ -std=c++23 -Wall -Wextra logicos.cpp -o logicos
$ ./logicos
false    « maior de idade (true) E tem ingresso (false): falta um, false
true     « maior de idade (true) OU tem ingresso (false): basta um, true
true     « NÃO tem ingresso: o false virou true
idade >= 18 && tem_ingresso
Duas perguntas ligadas pelo "e". A comparação idade >= 18 é feita primeiro (comparações têm prioridade maior que && e ||), e o resultado (true) é combinado com tem_ingresso (false).
tem_ingresso
Uma variável bool já é uma resposta de sim ou não. Não precisa escrever tem_ingresso == true: só o nome basta.
!tem_ingresso
O ! vai na frente do valor que ele inverte, grudado.
os parênteses de fora
Necessários por causa do cout (o << é mais forte que && e ||). Fora do cout, como em bool pode = idade >= 18 && tem_ingresso;, não precisa.

A faixa de valores, do jeito certo

Na aula de comparação você viu que 0 <= nota <= 10 não funciona. Com o &&, você faz as duas perguntas separadas e exige as duas:

#include <iostream>

int main() {
    int nota = 15;
    std::cout << std::boolalpha;
    std::cout << (0 <= nota && nota <= 10) << '\n';
    nota = 7;
    std::cout << (0 <= nota && nota <= 10) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra faixa2.cpp -o faixa2
$ ./faixa2
false    « 15 não está entre 0 e 10
true     « 7 está

Escrever o menor valor à esquerda e o maior à direita (0 <= nota && nota <= 10) deixa a expressão parecida com a da matemática, com a nota "no meio". Para o contrário, "fora da faixa", use o ||: nota < 0 || nota > 10.

Experimente: mude a idade e o convite e veja quem entra na festa. Tente achar uma combinação em que entra, uma em que não entra e uma em que ganha desconto sem poder entrar.

#include <iostream>

int main() {
    int idade = 16;
    bool convite = true;
    std::cout << std::boolalpha;
    std::cout << "Entra na festa? " << (idade >= 18 && convite) << '\n';
    std::cout << "Desconto (menor que 18 ou 60+)? " << (idade < 18 || idade >= 60) << '\n';
    std::cout << "Sem convite? " << !convite << '\n';
}

2. A tabela-verdade

Como cada lado só pode ser true ou false, existem só quatro combinações possíveis. A tabela-verdade mostra a resposta de cada operador para todas elas. Este programa monta a tabela de verdade (com saída real):

#include <iostream>

int main() {
    std::cout << std::boolalpha;
    std::cout << "A     B     | A && B  A || B\n";
    std::cout << false << ' ' << false << " | " << (false && false) << "   " << (false || false) << '\n';
    std::cout << false << ' ' << true  << "  | " << (false && true)  << "   " << (false || true)  << '\n';
    std::cout << true  << "  " << false << " | " << (true && false)  << "   " << (true || false)  << '\n';
    std::cout << true  << "  " << true  << "  | " << (true && true)   << "    " << (true || true)   << '\n';
}
$ g++ -std=c++23 -Wall -Wextra tabela.cpp -o tabela
$ ./tabela
A     B     | A && B  A || B
false false | false   false
false true  | false   true
true  false | false   true
true  true  | true    true

Para decorar:

  • && só dá true numa linha: quando tudo é true. Um false estraga tudo.
  • || só dá false numa linha: quando tudo é false. Um true salva tudo.
  • ! é o mais simples: !true é false e !false é true.
O "ou" do C++ inclui o "os dois"

No português, "café ou chá?" costuma querer dizer "um ou outro, não os dois". O || não é assim: quando os dois lados são verdadeiros, ele dá true (última linha da tabela). Se você precisa do "um ou outro, mas não os dois" (o "ou exclusivo"), compare os dois bool com !=: a != b.

Negar uma pergunta composta

Para inverter uma pergunta inteira, coloque-a entre parênteses e o ! na frente: !(chovendo || frio) é "nem chovendo nem frio". Existe uma regra (as leis de De Morgan) que diz como "abrir" esse parêntese: o ! entra em cada pedaço e o operador troca (|| vira && e vice-versa):

#include <iostream>

int main() {
    bool chovendo = false;
    bool frio = true;
    std::cout << std::boolalpha;
    std::cout << !(chovendo || frio) << '\n';
    std::cout << (!chovendo && !frio) << '\n';
    std::cout << !(chovendo && frio) << '\n';
    std::cout << (!chovendo || !frio) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra morgan.cpp -o morgan
$ ./morgan
false    « !(chovendo || frio)
false    « !chovendo && !frio: mesma resposta
true     « !(chovendo && frio)
true     « !chovendo || !frio: mesma resposta

Você não precisa decorar a regra; o importante é saber que !(a || b) não é !a || !b. Na dúvida, escreva com parênteses e teste.


3. As palavras and, or e not

O C++ aceita palavras no lugar dos símbolos. Elas são exatamente o mesmo operador, só com outra grafia:

#include <iostream>

int main() {
    int idade = 20;
    bool tem_ingresso = false;
    std::cout << std::boolalpha;
    std::cout << (idade >= 18 and tem_ingresso) << '\n';
    std::cout << (idade >= 18 or tem_ingresso) << '\n';
    std::cout << (not tem_ingresso) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra palavras.cpp -o palavras
$ ./palavras
false
true
true

Mesmas respostas do logicos.cpp. As palavras existem desde o primeiro padrão do C++ (C++98) e são palavras reservadas: não precisam de #include e não podem ser nomes de variável (veja Nomes de Variáveis). Elas foram criadas para teclados antigos que não tinham & ou |.

FormaPrósContras
&& || !É o que você vai ver em 99% do código C++ (e em C, Java, JavaScript…).O ! é pequeno e passa despercebido; && se confunde com &.
and or notLê como português/inglês; not é difícil de não ver.Pouco comum; algumas equipes estranham.

Qual usar: este guia usa os símbolos, por serem o padrão que você vai encontrar em todo lugar. As palavras não estão erradas: escolha uma forma e mantenha no programa inteiro.


4. Curto-circuito: o lado direito pode nem ser olhado

Olhe de novo a tabela-verdade. Se o lado esquerdo de um && é false, a resposta já é false, não importa o lado direito. E se o lado esquerdo de um || é true, a resposta já é true. O C++ aproveita isso: nesses casos, ele nem calcula o lado direito. Isso se chama avaliação em curto-circuito.

Vendo o curto-circuito acontecer

Para ver o que é calculado, vamos usar uma pequena função "espiã" que mostra uma mensagem toda vez que é chamada e devolve a resposta que mandarmos. Funções têm aula própria (Funções); por enquanto, basta saber que pergunta("A", false) escreve (avaliei: A) na tela e responde false.

#include <iostream>

bool pergunta(const char* texto, bool resposta) {
    std::cout << "  (avaliei: " << texto << ")\n";
    return resposta;
}

int main() {
    std::cout << std::boolalpha;
    std::cout << "Teste 1, &&:\n";
    bool r1 = pergunta("A", false) && pergunta("B", true);
    std::cout << "resultado: " << r1 << '\n';

    std::cout << "Teste 2, ||:\n";
    bool r2 = pergunta("A", true) || pergunta("B", false);
    std::cout << "resultado: " << r2 << '\n';

    std::cout << "Teste 3, &&:\n";
    bool r3 = pergunta("A", true) && pergunta("B", false);
    std::cout << "resultado: " << r3 << '\n';
}
$ g++ -std=c++23 -Wall -Wextra espiao.cpp -o espiao
$ ./espiao
Teste 1, &&:
  (avaliei: A)          « A é false: com &&, a resposta já é false
resultado: false        « o B nunca foi avaliado!
Teste 2, ||:
  (avaliei: A)          « A é true: com ||, a resposta já é true
resultado: true         « o B de novo foi pulado
Teste 3, &&:
  (avaliei: A)          « A é true: com &&, ainda depende do B
  (avaliei: B)          « então o B foi avaliado
resultado: false

A ordem também é garantida: o lado esquerdo sempre é avaliado primeiro.

Para que serve: a guarda

O curto-circuito permite escrever uma guarda: uma pergunta à esquerda que protege uma conta perigosa à direita. Na aula de Operadores Aritméticos, dividir um inteiro por zero quebrou o programa. Veja:

#include <iostream>

int main() {
    int pessoas = 0;
    int pizzas = 8;
    std::cout << std::boolalpha;
    bool sobra = pessoas != 0 && pizzas / pessoas > 2;
    std::cout << sobra << '\n';
}
$ g++ -std=c++23 -Wall -Wextra curto.cpp -o curto
$ ./curto
false    « pessoas != 0 deu false, e a divisão nunca aconteceu

Agora a mesma pergunta, com os lados trocados (curto2.cpp):

$ grep sobra curto2.cpp
    bool sobra = pizzas / pessoas > 2 && pessoas != 0;
    std::cout << sobra << '\n';
$ g++ -std=c++23 -Wall -Wextra curto2.cpp -o curto2
$ ./curto2
Exceção de ponto flutuante (imagem do núcleo gravada) ./curto2
$ echo $?
136

Para o &&, as duas versões são "a mesma pergunta" (na matemática, a ordem do "e" não importa). Para o programa, não: na segunda, a divisão por zero acontece antes de a guarda ser consultada, e o programa quebra com SIGFPE. Regra: a guarda vem sempre à esquerda.


5. & × && (e | × ||)

O C++ também tem & e |, com um caractere só. Eles são operadores de bits: trabalham com cada bit (cada 0 e 1 interno) dos números. A ideia, sem entrar nos bits:

#include <iostream>

int main() {
    int a = 2;
    int b = 1;
    std::cout << std::boolalpha;
    std::cout << (a && b) << '\n';
    std::cout << (a & b) << '\n';
    std::cout << (a || b) << '\n';
    std::cout << (a | b) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra ecomercial.cpp -o ecomercial
$ ./ecomercial
true     « 2 e 1 são diferentes de zero (verdadeiros): true && true
0        « & compara bit a bit: 2 (binário 10) & 1 (binário 01) = 0
true
3        « | junta os bits: 10 | 01 = 11, que é 3

Repare que && e || sempre respondem um bool, enquanto & e | respondem um número. Com &&, 2 e 1 são "verdadeiro e verdadeiro"; com &, deram 0, que significa "falso". Trocar um pelo outro dá respostas erradas sem nenhum erro de compilação.

Mesmo com bool, onde a resposta coincide, há outra diferença: & e | não fazem curto-circuito. A mesma espiã do teste 1, com &:

$ grep 'bool r' espiao2.cpp
bool pergunta(const char* texto, bool resposta) {
    bool r = pergunta("A", false) & pergunta("B", true);   « a linha que mudou
$ g++ -std=c++23 -Wall -Wextra espiao2.cpp -o espiao2
$ ./espiao2
Com &:
  (avaliei: A)
  (avaliei: B)          « o B foi avaliado mesmo com A falso
resultado: false

O g++ não avisou nada; o Clang do editor do guia avisa: use of bitwise '&' with boolean operands [-Wbitwise-instead-of-logical], "uso do & de bits com operandos booleanos", e sugere &&. Para condições, use sempre && e ||. Os operadores de bits têm usos próprios (guardar várias opções num número só, por exemplo) que não aparecem neste nível.


Quando dá errado

opcao == 1 || 2 é sempre verdadeiro
$ cat ouerrado.cpp
#include <iostream>

int main() {
    int opcao = 5;
    std::cout << std::boolalpha;
    std::cout << (opcao == 1 || 2) << '\n';
    std::cout << (opcao == 1 || opcao == 2) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra ouerrado.cpp -o ouerrado
$ ./ouerrado
true     « 5 é 1 ou 2? o programa diz que sim!
false    « agora sim

O que aconteceu: em português, "a opção é 1 ou 2" subentende "a opção é 2". O C++ não subentende nada: ele leu (opcao == 1) || 2. O lado direito é o número 2 sozinho, e qualquer número diferente de zero conta como true. Então a pergunta inteira é sempre verdadeira. Nem o g++ nem o Clang avisam.

Como resolver: repita a variável em cada comparação: opcao == 1 || opcao == 2.

O programa quebrou numa conta que tinha uma "guarda"

O que aconteceu: a guarda estava do lado direito, como em pizzas / pessoas > 2 && pessoas != 0. O lado esquerdo é avaliado primeiro, então a conta perigosa rodou antes da conferência.

Como resolver: coloque a conferência à esquerda: pessoas != 0 && pizzas / pessoas > 2.

Resultado estranho com & ou |

O que aconteceu: você usou o operador de bits (um caractere) em vez do lógico (dois caracteres). Com números, a resposta é outra (2 & 1 é 0); com funções, o curto-circuito some.

Como resolver: && e ||, sempre dobrados, em condições.

warning: suggest parentheses around ‘&&’ within ‘||’

O que aconteceu: você misturou && e || sem parênteses, como em vip || cupom && total > 100. O && é feito primeiro, o que nem sempre é o que você queria.

Como resolver: coloque parênteses mostrando o que vai junto. A aula Precedência mostra esse caso com saídas.


Referência

OperadorPalavraResultadoCurto-circuito
a && bandtrue só se os dois forem trueSe a é false, não avalia b
a || borfalse só se os dois forem falseSe a é true, não avalia b
!anotO contrário de a—
a & b, a | bbitand, bitorUm número (bit a bit)Não fazem curto-circuito

Prioridade: ! antes de tudo; depois as comparações (< > == …); depois &&; por último ||. Tabela completa em Precedência.


Exercícios

1. E

Complete para pode_dirigir ser verdadeiro só quando a idade for pelo menos 18 e a pessoa tiver carteira:

bool pode_dirigir = idade >= 18 tem_carteira;

2. Ou

Complete para fim_de_semana ser verdadeiro se dia for 6 ou 7 (repita a variável!):

bool fim_de_semana = dia == 6 ;

3. Não

Complete com o símbolo que inverte a resposta, para aberto ser o contrário de fechado:

bool aberto = fechado;

4. Quanto vale (true || false) && !true?

5. Em x != 0 && 100 / x > 5, com x valendo 0, o que acontece?

6. Com int cor = 3;, quanto vale (cor == 1 || 2)?

Missão final: a bilheteria do cinema

Missão: troque cada false pela condição certa. Meia-entrada: menores de 18 ou pessoas com 60 anos ou mais ou estudantes. Entrada proibida: menores de 12 e não acompanhados. Com os valores do programa, a saída deve ser:

Meia-entrada: true
Entrada proibida: false
#include <iostream>

int main() {
    int idade = 10;
    bool estudante = false;
    bool acompanhado = true;
    bool meia = false;        // troque pela condição
    bool proibida = false;    // troque pela condição
    std::cout << std::boolalpha;
    std::cout << "Meia-entrada: " << meia << '\n';
    std::cout << "Entrada proibida: " << proibida << '\n';
}
#include <iostream>

int main() {
    int idade = 10;
    bool estudante = false;
    bool acompanhado = true;
    bool meia = idade < 18 || idade >= 60 || estudante;
    bool proibida = idade < 12 && !acompanhado;
    std::cout << std::boolalpha;
    std::cout << "Meia-entrada: " << meia << '\n';
    std::cout << "Entrada proibida: " << proibida << '\n';
}

Resumo

  • && (e, and): verdadeiro só se tudo for verdadeiro. || (ou, or): verdadeiro se pelo menos um for. ! (não, not): inverte.
  • Faixa de valores: 0 <= x && x <= 10. Fora da faixa: x < 0 || x > 10.
  • O || inclui "os dois". Para "um ou outro, não os dois", compare bool com !=.
  • !(a || b) é !a && !b; !(a && b) é !a || !b.
  • Curto-circuito: o lado esquerdo é avaliado primeiro; se ele já decide a resposta, o direito é pulado. Use isso para guardas: x != 0 && 100 / x > 5.
  • & e | (um caractere) são de bits: dão números e não fazem curto-circuito. Em condições, use && e ||.
  • x == 1 || 2 é sempre verdadeiro; escreva x == 1 || x == 2.