C++ Operadores de Comparação
A Ana é maior de idade? A nota passou da média? O estoque acabou? A senha está certa? Todo programa que toma decisões começa com uma pergunta de sim ou não, e quase sempre essa pergunta é uma comparação entre dois valores. Nesta aula você vai conhecer os seis operadores de comparação do C++, ver o que eles respondem e cair (de propósito, para aprender) nas três armadilhas clássicas: comparar números com vírgula, comparar textos do jeito antigo e misturar números com e sem sinal.
- Os seis operadores:
==,!=,<,>,<=e>=. - Que toda comparação responde um
bool(trueoufalse), e como mostrar essa resposta. - Por que
0.1 + 0.2 == 0.3é falso, e como comparardoublecom uma tolerância. - Como comparar textos com
std::string, e por que comparar textos do C (char[]) não funciona. - Por que
-1 < 1ué falso em C++. - Por que
0 <= nota <= 10não faz o que parece. - O que é o operador "nave espacial",
<=>, do C++20.
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.
A ideia: a balança de dois pratos
Uma comparação é como uma balança antiga, de dois pratos. Você coloca um valor em cada prato e faz uma pergunta: "estão iguais?", "o da esquerda é mais leve?". A balança não muda os pesos; ela só responde, e a resposta é sempre sim ou não.
Em C++, "sim" é true (verdadeiro) e "não" é false (falso): os dois valores do tipo bool (aula Booleanos). Toda comparação produz um bool.
| Operador | Pergunta | Exemplo | Resposta |
|---|---|---|---|
== | É igual a? | 5 == 5 | true |
!= | É diferente de? | 5 != 5 | false |
< | É menor que? | 3 < 5 | true |
> | É maior que? | 3 > 5 | false |
<= | É menor ou igual a? | 5 <= 5 | true |
>= | É maior ou igual a? | 3 >= 5 | false |
Os sinais de dois caracteres são escritos grudados e nesta ordem: <= e >= (o = por último, como se fala: "menor ou igual"); != (o ! quer dizer "não": "não igual"). E a igualdade tem dois sinais de igual: um só, =, é atribuição, que muda a variável (veja Operadores de Atribuição).
1. Comparar números e ver a resposta
#include <iostream>
int main() {
int idade = 17;
std::cout << (idade == 18) << '\n';
std::cout << (idade != 18) << '\n';
std::cout << (idade < 18) << '\n';
std::cout << (idade > 18) << '\n';
std::cout << (idade <= 17) << '\n';
std::cout << (idade >= 18) << '\n';
std::cout << std::boolalpha;
std::cout << (idade >= 18) << '\n';
bool maior = idade >= 18;
std::cout << "maior de idade? " << maior << '\n';
}
$ g++ -std=c++23 -Wall -Wextra compara.cpp -o compara $ ./compara 0 « 17 == 18? não 1 « 17 != 18? sim 1 « 17 < 18? sim 0 « 17 > 18? não 1 « 17 <= 17? sim 0 « 17 >= 18? não false « a mesma pergunta, agora com std::boolalpha maior de idade? false
- (idade == 18)
- A comparação. Os parênteses são obrigatórios dentro do
cout: sem eles, o<<docout"rouba" os operandos (veja a seção "Quando dá errado"). - 0 e 1
- Por padrão, o
coutmostrafalsecomo0etruecomo1. - std::boolalpha
- Um "ajuste" do
cout(bool alphabetic: bool por extenso). Depois dele, osboolaparecem comotrueefalse. Vale até o fim do programa (ou até umstd::noboolalpha). - bool maior = idade >= 18;
- A resposta de uma comparação pode ser guardada numa variável
bool, para usar depois. Aqui os parênteses não são necessários, porque não há<<na linha.
Na prática, o lugar mais comum de uma comparação é dentro de um if ("se") ou de um while ("enquanto"): if (idade >= 18) { … }. Você vai usar isso em if e else e while. Aqui o foco é a pergunta em si.
Experimente: mude o valor de estoque (tente 0, 3 e 10) e veja as respostas mudarem. Depois acrescente a pergunta "o estoque é maior que 5?".
#include <iostream>
int main() {
int estoque = 3;
std::cout << std::boolalpha;
std::cout << "Acabou? " << (estoque == 0) << '\n';
std::cout << "Tem algum? " << (estoque != 0) << '\n';
std::cout << "Está baixo (até 3)? " << (estoque <= 3) << '\n';
}
2. Comparar double: use uma tolerância
Com números inteiros, == é exato. Com double, não dá para confiar nele. Veja uma conta que qualquer criança acerta:
#include <cmath>
#include <iostream>
int main() {
double a = 0.1 + 0.2;
double b = 0.3;
std::cout << std::boolalpha;
std::cout << (a == b) << '\n';
std::cout.precision(17);
std::cout << a << '\n' << b << '\n';
std::cout << (std::abs(a - b) < 1e-9) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra real.cpp -o real $ ./real false « 0.1 + 0.2 == 0.3? NÃO! 0.30000000000000004 « o que o 0.1 + 0.2 realmente guardou 0.29999999999999999 « o que o 0.3 realmente guardou true « "estão a menos de 0.000000001 de distância?" sim
O double guarda os números em binário (base 2), e números como 0.1 e 0.3 não têm representação exata em binário, assim como 1/3 não tem em decimal (0,3333…). Cada um é guardado como o valor mais próximo possível, e os pequenos erros não se cancelam. O std::cout.precision(17) pediu 17 algarismos para mostrar essa diferença, que normalmente fica escondida. A aula float e double explica em detalhe.
A solução é comparar com uma tolerância: em vez de "são iguais?", pergunte "a distância entre eles é menor que um número bem pequeno?".
- a - b
- A diferença entre os dois. Pode ser negativa.
- std::abs( … )
- O valor absoluto (o número sem o sinal), de
<cmath>. Transforma −0.00000000000000005 em 0.00000000000000005, para a distância ser sempre positiva. - < 1e-9
1e-9é notação científica: 1 × 10−9 = 0.000000001. É a tolerância: se a distância é menor que isso, consideramos iguais.
Qual tolerância usar depende do problema: para dinheiro em reais, 0.001 já basta; para medidas científicas, pode ser bem menor. Com < e > o problema é menor (0.30000000000000004 é mesmo "um pouquinho maior" que 0.3), mas a mesma desconfiança vale quando os valores estão muito próximos.
Uma alternativa muito usada em sistemas de lojas e bancos: guardar dinheiro em centavos, num inteiro. R$ 12,34 vira 1234. Aí o == volta a ser exato.
3. Comparar textos
Com std::string: funciona como você espera
Os seis operadores funcionam com std::string (aula Strings). == compara o conteúdo, letra por letra; < e > comparam em ordem de dicionário:
#include <iostream>
#include <string>
int main() {
std::string a = "Ana";
std::string b = "Ana";
std::string c = "Bruno";
std::string d = "ana";
std::cout << std::boolalpha;
std::cout << (a == b) << '\n';
std::cout << (a < c) << '\n';
std::cout << (d < c) << '\n';
std::cout << (a == "Ana") << '\n';
}
$ g++ -std=c++23 -Wall -Wextra textos.cpp -o textos $ ./textos true « mesmo conteúdo true « "Ana" vem antes de "Bruno" false « "ana" NÃO vem antes de "Bruno"! true « string comparada com texto entre aspas: funciona
A terceira linha surpreende. A "ordem de dicionário" do C++ é, na verdade, a ordem dos códigos dos caracteres (a tabela ASCII, veja Caracteres). Nela, todas as maiúsculas vêm antes de todas as minúsculas: 'B' é 66 e 'a' é 97. Então "ana" < "Bruno" é falso, e "Zebra" < "abacate" é verdadeiro. Pelo mesmo motivo, "Ana" == "ana" é false: maiúscula e minúscula são caracteres diferentes.
Outra consequência: textos com números são comparados caractere por caractere, e não pelo valor. std::string("10") < std::string("9") é verdadeiro, porque o primeiro caractere, '1', vem antes de '9'. Para comparar como números, converta antes (aula Texto ↔ Número).
Com textos do C (char[]): compara o lugar, não o conteúdo
Antes do std::string, textos eram guardados em arrays de char, herança da linguagem C (aula Arrays). Você ainda vai encontrá-los em código antigo. Com eles, o == não compara as letras:
#include <iostream>
int main() {
char a[] = "Ana";
char b[] = "Ana";
std::cout << std::boolalpha << (a == b) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra ponteiros.cpp -o ponteiros
ponteiros.cpp: In function ‘int main()’:
ponteiros.cpp:6:39: warning: comparison between two arrays is deprecated in C++20 [-Warray-compare]
6 | std::cout << std::boolalpha << (a == b) << '\n';
| ~~^~~~
ponteiros.cpp:6:39: note: use unary ‘+’ which decays operands to pointers or ‘&a[0] == &b[0]’ to compare the addresses
$ ./ponteiros
false « mesmo texto, mas "diferentes"
A ideia, por enquanto, é só esta: a e b são duas caixas diferentes na memória, cada uma com sua cópia de "Ana". O == perguntou "é a mesma caixa?" (o mesmo endereço), e não "o conteúdo é igual?". O aviso diz que "comparar dois arrays está obsoleto no C++20", e a nota explica que isso compara "os endereços". Você vai entender endereços na aula Ponteiros. A solução prática: use std::string, e o == compara o conteúdo.
4. A armadilha: números com e sem sinal
O C++ tem inteiros com sinal (int, que aceita negativos) e sem sinal (unsigned int, só 0 e positivos; veja Inteiros). Um número escrito com u no fim, como 1u, é unsigned. Quando você compara um com o outro, acontece isto:
#include <iostream>
int main() {
int saldo = -1;
unsigned int limite = 1;
std::cout << std::boolalpha;
std::cout << (saldo < limite) << '\n';
std::cout << (-1 < 1u) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra sinal.cpp -o sinal
sinal.cpp: In function ‘int main()’:
sinal.cpp:7:25: warning: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare]
7 | std::cout << (saldo < limite) << '\n';
| ~~~~~~^~~~~~~~
sinal.cpp:8:22: warning: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare]
8 | std::cout << (-1 < 1u) << '\n';
| ~~~^~~~
$ ./sinal
false « -1 < 1 é FALSO?!
false
Menos um é menor que um, qualquer criança sabe. Mas o C++ respondeu false duas vezes. O motivo:
- Para comparar, os dois lados precisam ter o mesmo tipo.
- Quando um lado é
inte o outro éunsigned int, a regra do C++ converte ointparaunsigned. - Um
unsignednão tem negativos: o −1 vira 4294967295, o maiorunsigned int(você viu isso em Conversão de Tipos). 4294967295 < 1é falso.
O aviso explica isso: "comparação de expressões inteiras com sinalização diferente: int e unsigned int". O -Wsign-compare vem com o -Wall. Mais um motivo para nunca desligar os avisos. (No editor do guia, o Clang só avisa na linha 7; na linha 8, com números escritos direto, ele fica quieto. O resultado é o mesmo false.)
Você vai encontrar isso na prática quando comparar um int com o tamanho de um texto ou de uma lista (texto.size()), que é unsigned. As soluções:
- Use o mesmo tipo dos dois lados sempre que puder.
- No C++20, a biblioteca
<utility>tem funções que comparam do jeito matemático, mesmo com tipos diferentes:std::cmp_less(saldo, limite)("menor que") respondetrue. Existem tambémstd::cmp_equal,std::cmp_greatere outras. C++20
$ cat cmp.cpp
#include <iostream>
#include <utility>
int main() {
int saldo = -1;
unsigned int limite = 1;
std::cout << std::boolalpha;
std::cout << std::cmp_less(saldo, limite) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra cmp.cpp -o cmp
$ ./cmp
true
5. 0 <= nota <= 10 não é o que parece
Na matemática, escrevemos 0 ≤ nota ≤ 10 para dizer "a nota está entre 0 e 10". Em C++, isso compila, mas significa outra coisa:
#include <iostream>
int main() {
int nota = 15;
std::cout << std::boolalpha;
std::cout << (0 <= nota <= 10) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra faixa.cpp -o faixa
faixa.cpp: In function ‘int main()’:
faixa.cpp:6:29: warning: comparison of constant ‘10’ with boolean expression is always true [-Wbool-compare]
6 | std::cout << (0 <= nota <= 10) << '\n';
| ~~~~~~~~~~^~~~~
faixa.cpp:6:21: warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]
6 | std::cout << (0 <= nota <= 10) << '\n';
| ~~^~~~~~~
$ ./faixa
true « 15 está entre 0 e 10?!
- O C++ faz uma comparação de cada vez, da esquerda para a direita:
(0 <= nota) <= 10. 0 <= 15dátrue.- Sobra
true <= 10. Otruevira 1, e1 <= 10é verdadeiro. Sempre.
Os dois avisos dizem exatamente isso: "comparar a constante 10 com uma expressão booleana dá sempre verdadeiro" e "comparações como X<=Y<=Z não têm o significado da matemática". No editor do guia, o Clang 21 vai além e trata isso como erro: chained comparison 'X <= Y <= Z' does not behave the same as a mathematical expression.
O jeito certo é fazer duas perguntas e juntá-las com "e": 0 <= nota && nota <= 10. O && é assunto da próxima aula, Operadores Lógicos.
6. O operador nave espacial: <=> C++20
O C++20 trouxe um sétimo operador de comparação, o <=>, apelidado de nave espacial (spaceship) pelo formato. Ele faz as três perguntas de uma vez: "menor, igual ou maior?". A resposta não é um bool, e sim um resultado que você compara com zero:
#include <compare>
#include <iostream>
int main() {
int a = 3;
int b = 5;
auto resultado = a <=> b;
std::cout << std::boolalpha;
std::cout << (resultado < 0) << '\n';
std::cout << (resultado == 0) << '\n';
std::cout << (resultado > 0) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra nave.cpp -o nave $ ./nave true « resultado < 0: a é menor que b false « resultado == 0 seria "iguais" false « resultado > 0 seria "a é maior"
O auto deixa o compilador escolher o tipo da variável (aula auto); aqui o tipo é std::strong_ordering, que vem de <compare>. Esse resultado não pode ir direto para o cout (o g++ dá um erro no match for ‘operator<<’); por isso o comparamos com 0. Com -std=c++17, o <=> nem existe, e o g++ se perde: error: expected primary-expression before ‘>’ token.
Para comparar dois números no dia a dia, os seis operadores tradicionais são mais claros. O <=> brilha quando você cria seus próprios tipos e quer que eles possam ser comparados: escrevendo só ele, o C++ gera os outros seis automaticamente. Isso aparece em Sobrecarga de Operadores.
Quando dá errado
error: no match for ‘operator<’ num cout
$ g++ -std=c++23 -Wall -Wextra semparen.cpp -o semparen
semparen.cpp: In function ‘int main()’:
semparen.cpp:5:24: error: no match for ‘operator<’ (operand types are ‘std::basic_ostream<char>::__ostream_type’ {aka ‘std::basic_ostream<char>’} and ‘int’)
5 | std::cout << idade < 18 << '\n';
| ~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~
| | |
| | int
| std::basic_ostream<char>::__ostream_type {aka std::basic_ostream<char>}
semparen.cpp:5:24: note: there are 22 candidates « seguem mais 170 linhas de "candidatos"
O que aconteceu: faltaram os parênteses em volta da comparação. O << tem prioridade maior que o <, então o C++ leu (std::cout << idade) < (18 << '\n'): "o cout é menor que um número?". Os tracinhos embaixo do código mostram exatamente essa divisão: tudo à esquerda do < é o cout (std::basic_ostream), tudo à direita é um int. A aula Precedência explica essa ordem.
Como resolver: std::cout << (idade < 18) << '\n';. Toda comparação dentro de um cout vai entre parênteses.
warning: comparison of integer expressions of different signedness
O que aconteceu: você comparou um int com um unsigned. Se o int for negativo, a resposta sai errada.
Como resolver: use o mesmo tipo dos dois lados, ou std::cmp_less e companhia (C++20). Não ignore esse aviso.
warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning
O que aconteceu: você escreveu uma faixa como na matemática. O resultado é quase sempre true.
Como resolver: 0 <= nota && nota <= 10 (aula Operadores Lógicos).
double "iguais" dão false
O que aconteceu: erros pequenos de arredondamento do double deixaram os valores diferentes na última casa.
Como resolver: std::abs(a - b) < tolerancia, ou trabalhe com inteiros (centavos).
O que aconteceu: você escreveu = no lugar de ==. O g++ avisa com suggest parentheses around assignment used as truth value.
Como resolver: veja a seção = × == da aula de atribuição.
Referência
| Operador | Pergunta | Funciona com | Cuidado |
|---|---|---|---|
a == b | Iguais? | números, char, bool, std::string | Não use com double (tolerância); com char[] compara endereços. |
a != b | Diferentes? | idem | idem |
a < b, a > b | Menor? Maior? | idem | Textos: ordem dos códigos (maiúsculas antes). int × unsigned: resultado errado com negativos. |
a <= b, a >= b | Menor ou igual? Maior ou igual? | idem | O = vem por último. |
a <=> b | Menor, igual ou maior? | C++20, #include <compare> | Compare o resultado com 0. |
std::abs(a - b) < tol | double "iguais"? | #include <cmath> | Escolha a tolerância pelo problema. |
std::cmp_less(a, b) | Menor, sem a armadilha do sinal | C++20, #include <utility>, só inteiros |
Exercícios
1. Diferente
Complete para perguntar se senha é diferente de 1234:
2. Maior ou igual
Complete para perguntar se nota é maior ou igual a 6:
3. Mostrar por extenso
Complete para o cout mostrar true/false em vez de 1/0:
4. Com int x = -5; e unsigned int y = 3;, o que (x < y) responde?
5. Com std::string, qual destas comparações é true?
6. Por que 0.1 + 0.2 == 0.3 é false?
Missão final: o boletim
Missão: o programa lê uma nota (a entrada preparada é 7.5). Troque cada false por uma comparação: "aprovado" é nota maior ou igual a 6; "nota máxima" é nota igual a 10. A saída deve ser:
Aprovado: true Nota máxima: false
#include <iostream>
int main() {
double nota = 0;
std::cin >> nota;
bool aprovado = false; // troque false pela comparação
bool maxima = false; // troque false pela comparação
std::cout << std::boolalpha;
std::cout << "Aprovado: " << aprovado << '\n';
std::cout << "Nota máxima: " << maxima << '\n';
}
#include <iostream>
int main() {
double nota = 0;
std::cin >> nota;
bool aprovado = nota >= 6;
bool maxima = nota == 10;
std::cout << std::boolalpha;
std::cout << "Aprovado: " << aprovado << '\n';
std::cout << "Nota máxima: " << maxima << '\n';
}
Resumo
- Os seis operadores:
==(igual),!=(diferente),<,>,<=,>=. Todos respondem umbool. - O
coutmostraboolcomo 1/0; comstd::boolalpha, comotrue/false. Comparações dentro docoutvão entre parênteses. double: não use==; usestd::abs(a - b) < tolerancia.std::stringcompara o conteúdo, em ordem de código: maiúsculas antes de minúsculas,"10" < "9". Arrays decharcomparam endereços: usestd::string.int×unsigned: o negativo vira um número enorme e-1 < 1udáfalse. O-Wsign-compare(do-Wall) avisa. No C++20,std::cmp_less.0 <= x <= 10não funciona como na matemática; use0 <= x && x <= 10.<=>(C++20) responde menor/igual/maior de uma vez; compare o resultado com 0.