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.

Você vai aprender
  • Os seis operadores: ==, !=, <, >, <= e >=.
  • Que toda comparação responde um bool (true ou false), e como mostrar essa resposta.
  • Por que 0.1 + 0.2 == 0.3 é falso, e como comparar double com 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 <= 10 nã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.

OperadorPerguntaExemploResposta
==É igual a?5 == 5true
!=É diferente de?5 != 5false
<É menor que?3 < 5true
>É maior que?3 > 5false
<=É menor ou igual a?5 <= 5true
>=É maior ou igual a?3 >= 5false

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 << do cout "rouba" os operandos (veja a seção "Quando dá errado").
0 e 1
Por padrão, o cout mostra false como 0 e true como 1.
std::boolalpha
Um "ajuste" do cout (bool alphabetic: bool por extenso). Depois dele, os bool aparecem como true e false. Vale até o fim do programa (ou até um std::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.

Dinheiro sem vírgula

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:

  1. Para comparar, os dois lados precisam ter o mesmo tipo.
  2. Quando um lado é int e o outro é unsigned int, a regra do C++ converte o int para unsigned.
  3. Um unsigned não tem negativos: o −1 vira 4294967295, o maior unsigned int (você viu isso em Conversão de Tipos).
  4. 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") responde true. Existem também std::cmp_equal, std::cmp_greater e 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?!
  1. O C++ faz uma comparação de cada vez, da esquerda para a direita: (0 <= nota) <= 10.
  2. 0 <= 15 dá true.
  3. Sobra true <= 10. O true vira 1, e 1 <= 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).

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

A comparação mudou a variável

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

OperadorPerguntaFunciona comCuidado
a == bIguais?números, char, bool, std::stringNão use com double (tolerância); com char[] compara endereços.
a != bDiferentes?idemidem
a < b, a > bMenor? Maior?idemTextos: ordem dos códigos (maiúsculas antes). int × unsigned: resultado errado com negativos.
a <= b, a >= bMenor ou igual? Maior ou igual?idemO = vem por último.
a <=> bMenor, igual ou maior?C++20, #include <compare>Compare o resultado com 0.
std::abs(a - b) < toldouble "iguais"?#include <cmath>Escolha a tolerância pelo problema.
std::cmp_less(a, b)Menor, sem a armadilha do sinalC++20, #include <utility>, só inteiros

Exercícios

1. Diferente

Complete para perguntar se senha é diferente de 1234:

bool errada = senha 1234;

2. Maior ou igual

Complete para perguntar se nota é maior ou igual a 6:

bool aprovado = nota 6;

3. Mostrar por extenso

Complete para o cout mostrar true/false em vez de 1/0:

std::cout <<

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 um bool.
  • O cout mostra bool como 1/0; com std::boolalpha, como true/false. Comparações dentro do cout vão entre parênteses.
  • double: não use ==; use std::abs(a - b) < tolerancia.
  • std::string compara o conteúdo, em ordem de código: maiúsculas antes de minúsculas, "10" < "9". Arrays de char comparam endereços: use std::string.
  • int × unsigned: o negativo vira um número enorme e -1 < 1u dá false. O -Wsign-compare (do -Wall) avisa. No C++20, std::cmp_less.
  • 0 <= x <= 10 não funciona como na matemática; use 0 <= x && x <= 10.
  • <=> (C++20) responde menor/igual/maior de uma vez; compare o resultado com 0.