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.
- Os três operadores:
&&("e"),||("ou") e!("não"). - As palavras equivalentes
and,orenot. - 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.
| Operador | Palavra | Nome | Verdadeiro quando… |
|---|---|---|---|
a && b | a and b | E lógico | os dois são verdadeiros |
a || b | a or b | OU lógico | pelo menos um é verdadeiro |
!a | not a | NÃ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 comtem_ingresso(false). - tem_ingresso
- Uma variável
booljá é uma resposta de sim ou não. Não precisa escrevertem_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 docout, como embool 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átruenuma linha: quando tudo étrue. Umfalseestraga tudo.||só dáfalsenuma linha: quando tudo éfalse. Umtruesalva tudo.!é o mais simples:!trueéfalsee!falseétrue.
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 |.
| Forma | Prós | Contras |
|---|---|---|
&& || ! | É 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 not | Lê 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 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.
& 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
| Operador | Palavra | Resultado | Curto-circuito |
|---|---|---|---|
a && b | and | true só se os dois forem true | Se a é false, não avalia b |
a || b | or | false só se os dois forem false | Se a é true, não avalia b |
!a | not | O contrário de a | — |
a & b, a | b | bitand, bitor | Um 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:
2. Ou
Complete para fim_de_semana ser verdadeiro se dia for 6 ou 7 (repita a variável!):
3. Não
Complete com o símbolo que inverte a resposta, para aberto ser o contrário de 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", compareboolcom!=. !(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; escrevax == 1 || x == 2.