C++ Precedência

Nas aulas anteriores você conheceu muitos operadores: aritméticos, de atribuição, de comparação, lógicos, ++. Numa expressão com vários deles, como a + b * c > d && !e, quem é calculado primeiro? A resposta está em duas regras: a precedência (quais operadores "têm prioridade") e a associatividade (o que fazer quando dois operadores têm a mesma prioridade). Esta aula reúne essas regras numa tabela, mostra os enganos clássicos com saídas e mensagens reais e termina com o conselho mais importante: na dúvida, use parênteses.

Você vai aprender
  • O que são precedência e associatividade.
  • A tabela de precedência do C++, com os operadores que você já conhece destacados.
  • Por que a < b == c, !a == b e n & 1 == 0 não fazem o que parecem, e os avisos do g++ para cada um.
  • Por que std::cout << a & b dá erro de compilação.
  • Como misturar && e || sem surpresas.

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 ordem das operações da escola

Na escola você aprendeu que, em 2 + 3 × 4, a multiplicação vem antes da soma, e a resposta é 14, não 20. Também aprendeu que os parênteses mudam a ordem: (2 + 3) × 4 = 20. O C++ segue a mesma ideia, só que com muito mais operadores, e por isso precisa de uma tabela.

Pense numa fila de prioridade no hospital: quem tem prioridade maior é atendido primeiro, não importa a posição na fila. Entre pacientes de mesma prioridade, vale a ordem de chegada. No C++:

  • Precedência = a prioridade. O operador de precedência maior "pega" seus operandos primeiro. * tem precedência maior que +.
  • Associatividade = o desempate entre operadores de mesma prioridade. A maioria é da esquerda para a direita (10 - 4 - 3 é (10 - 4) - 3); alguns são da direita para a esquerda (a = b = 0 é a = (b = 0)).

O efeito da precedência é agrupar: é como se o C++ colocasse parênteses invisíveis na sua expressão. Entender a precedência é saber onde ele colocaria esses parênteses.


1. Exemplos com contas

#include <iostream>

int main() {
    std::cout << 2 + 3 * 4 << '\n';
    std::cout << (2 + 3) * 4 << '\n';
    std::cout << 10 - 4 - 3 << '\n';
    std::cout << 100 / 10 * 2 << '\n';
    std::cout << 7 / 2 * 2 << '\n';
    std::cout << 7 * 2 / 2 << '\n';
    std::cout << -3 * -2 << '\n';
}
$ g++ -std=c++23 -Wall -Wextra ordem.cpp -o ordem
$ ./ordem
14       « 2 + (3 * 4): precedência
20       « parênteses mandam
3        « (10 - 4) - 3: associatividade da esquerda para a direita
20       « (100 / 10) * 2, e não 100 / (10 * 2)
6        « (7 / 2) * 2 = 3 * 2: a divisão inteira veio primeiro
7        « (7 * 2) / 2 = 14 / 2
6        « (-3) * (-2): o menos unário vem antes de tudo

Os parênteses invisíveis que o C++ colocou estão nas anotações. Nas linhas 5 e 6 a associatividade muda o resultado, porque a divisão de inteiros corta (veja Operadores Aritméticos).

Experimente: antes de rodar, calcule cada linha de cabeça. Depois coloque parênteses em lugares diferentes (por exemplo, 20 - (5 - 2) ou 2 * (3 + 4) * 5) e veja como o resultado muda.

#include <iostream>

int main() {
    std::cout << 20 - 5 - 2 << '\n';
    std::cout << 2 * 3 + 4 * 5 << '\n';
    std::cout << 30 / 5 / 3 << '\n';
    std::cout << 17 % 5 * 2 << '\n';
}

2. A tabela de precedência

Da prioridade mais alta (calculada primeiro) para a mais baixa. Operadores na mesma linha têm a mesma prioridade, e a coluna "Associatividade" diz como desempatar. Os operadores marcados com ★ já apareceram no guia; os outros vêm em aulas futuras, e estão aqui para a tabela ficar completa.

NívelOperadoresO que sãoAssociatividade
1::Escopo (std::cout) ★esquerda → direita
2x++ x-- f() a[i] . ->Pós-incremento ★, chamada de função, índice, membroesquerda → direita
3++x --x +x -x ! ~ (tipo) *p &x sizeof new deletePré-incremento ★, sinal ★, "não" ★, cast estilo C ★, ponteirosdireita → esquerda
4.* ->*Ponteiro para membro (raro)esquerda → direita
5* / %Multiplicação, divisão, resto ★esquerda → direita
6+ -Soma, subtração ★esquerda → direita
7<< >>Deslocamento de bits; no cout/cin, saída e entrada ★esquerda → direita
8<=>Nave espacial (C++20) ★esquerda → direita
9< <= > >=Comparação ★esquerda → direita
10== !=Igualdade ★esquerda → direita
11&E de bits ★esquerda → direita
12^Ou exclusivo de bits ★esquerda → direita
13|Ou de bits ★esquerda → direita
14&&E lógico ★esquerda → direita
15||Ou lógico ★esquerda → direita
16? : = += -= *= /= %= <<= >>= &= ^= |= throwTernário, atribuições ★direita → esquerda
17,Vírgulaesquerda → direita

As palavras and, or e not têm exatamente a precedência de &&, || e !. O ternário ? : tem aula própria: Operador Ternário.

Não precisa decorar: grave o desenho geral

  1. Unários (!, -x, ++) grudam no valor e vêm antes de tudo.
  2. Depois, as contas: * / %, depois + -.
  3. Depois o << / >> do cout/cin.
  4. Depois as comparações: primeiro < > <= >=, depois == !=.
  5. Depois os de bits & ^ | (sim, abaixo das comparações: isso causa problemas).
  6. Depois a lógica: && antes de ||.
  7. Por último, a atribuição: tudo à direita do = é calculado antes de guardar.

Em frase: "unário, conta, cout, compara, bits, lógica, guarda". Essa ordem permite escrever sem parênteses coisas muito comuns, como bool ok = a + b > 10 && c != 0;, que o C++ lê como bool ok = (((a + b) > 10) && (c != 0));.


3. Onde a precedência engana

Agora os casos em que a ordem do C++ não é a que a gente espera. Em quase todos, o g++ dá um aviso da família -Wparentheses (que vem com o -Wall): ele desconfia que você quis outra coisa e sugere parênteses.

a < b == c

#include <iostream>

int main() {
    int a = 5;
    int b = 3;
    int c = 0;
    std::cout << std::boolalpha;
    std::cout << (a < b == c) << '\n';
    std::cout << ((a < b) == c) << '\n';
    std::cout << (a < (b == c)) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra prec1.cpp -o prec1
prec1.cpp: In function ‘int main()’:
prec1.cpp:8:21: warning: suggest parentheses around comparison in operand of ‘==’ [-Wparentheses]
    8 |     std::cout << (a < b == c) << '\n';
      |                   ~~^~~
$ ./prec1
true     « sem parênteses: igual à linha de baixo
true     « (5 < 3) == 0 → false == 0 → true
false    « 5 < (3 == 0) → 5 < false → 5 < 0 → false

O < (nível 9) vem antes do == (nível 10). Então a < b == c é (a < b) == c: o resultado bool da primeira comparação é comparado com um número. Quem escreve isso raramente quer dizer isso. O aviso: "sugiro parênteses em volta da comparação no operando de ==". Escreva os parênteses que você quer, e ele some.

!a == b

#include <iostream>

int main() {
    int estoque = 0;
    int minimo = 5;
    std::cout << std::boolalpha;
    std::cout << (!estoque == minimo) << '\n';
    std::cout << !(estoque == minimo) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra prec2.cpp -o prec2
prec2.cpp: In function ‘int main()’:
prec2.cpp:7:28: warning: logical not is only applied to the left hand side of comparison [-Wlogical-not-parentheses]
    7 |     std::cout << (!estoque == minimo) << '\n';
      |                            ^~
prec2.cpp:7:19: note: add parentheses around left hand side expression to silence this warning
    7 |     std::cout << (!estoque == minimo) << '\n';
      |                   ^~~~~~~~
      |                   (       )
$ ./prec2
false    « (!0) == 5 → true == 5 → 1 == 5 → false
true     « !(0 == 5) → !false → true

A intenção era "o estoque não é igual ao mínimo?". Mas o ! é unário (nível 3) e gruda só no estoque: o C++ calculou !estoque (estoque 0 vira true) e comparou com 5. O aviso explica isso com todas as letras: "o não lógico só é aplicado ao lado esquerdo da comparação", e mostra onde ficariam os parênteses do jeito que o C++ entendeu. Para perguntar "diferente", o mais simples é o operador próprio: estoque != minimo.

n & 1 == 0

Um teste de par com bits (n & 1 é o último bit: 0 para par, 1 para ímpar). Os operadores de bits ficam abaixo das comparações:

#include <iostream>

int main() {
    int n = 4;
    std::cout << std::boolalpha;
    std::cout << (n & 1 == 0) << '\n';
    std::cout << ((n & 1) == 0) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra prec4.cpp -o prec4
prec4.cpp: In function ‘int main()’:
prec4.cpp:6:25: warning: suggest parentheses around comparison in operand of ‘&’ [-Wparentheses]
    6 |     std::cout << (n & 1 == 0) << '\n';
      |                       ~~^~~~
$ ./prec4
0        « n & (1 == 0) → 4 & false → 4 & 0 → 0 (um número, nem é bool!)
true     « (4 & 1) == 0 → 0 == 0 → true

Essa ordem estranha é herança do C dos anos 1970, em que & e | faziam o papel de && e ||. Foi mantida para não quebrar programas antigos. Moral: com &, | e ^, sempre parênteses.

Misturando && e ||

O && (nível 14) vem antes do || (nível 15), assim como a multiplicação vem antes da soma. A regra da loja: "ganha desconto quem é VIP ou tem cupom, e a compra passa de 100". Escrevendo como se fala:

#include <iostream>

int main() {
    bool cliente_vip = true;
    bool tem_cupom = false;
    int total = 50;
    std::cout << std::boolalpha;
    std::cout << (cliente_vip || tem_cupom && total > 100) << '\n';
    std::cout << (cliente_vip || (tem_cupom && total > 100)) << '\n';
    std::cout << ((cliente_vip || tem_cupom) && total > 100) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra prec6.cpp -o prec6
prec6.cpp: In function ‘int main()’:
prec6.cpp:8:44: warning: suggest parentheses around ‘&&’ within ‘||’ [-Wparentheses]
    8 |     std::cout << (cliente_vip || tem_cupom && total > 100) << '\n';
      |                                  ~~~~~~~~~~^~~~~~~~~~~~~~
$ ./prec6
true     « sem parênteses: VIP ganha desconto mesmo comprando 50
true     « é assim que o C++ leu: vip || (cupom && total > 100)
false    « a regra da loja: (vip || cupom) && total > 100

Sem parênteses, o C++ juntou tem_cupom && total > 100 primeiro, e o VIP ganhou desconto numa compra de 50 reais. O aviso "sugiro parênteses em volta do && dentro do ||" aparece justamente porque essa mistura costuma ser engano. Mesmo quando a ordem do C++ é a que você quer, coloque os parênteses: o aviso some e quem lê não precisa lembrar da tabela.


4. O << do cout e a precedência

O << foi criado para deslocar bits, e o cout "pegou ele emprestado" para mostrar valores. A precedência, porém, continua a de deslocamento (nível 7): abaixo das contas, mas acima das comparações, dos operadores de bits e da lógica. Por isso:

#include <iostream>

int main() {
    int a = 6;
    int b = 3;
    std::cout << a + b << '\n';
    std::cout << (a & b) << '\n';
    std::cout << (a > b) << '\n';
    std::cout << (a > b && b > 0) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra prec5.cpp -o prec5
$ ./prec5
9        « a + b sem parênteses funciona: + vem antes de <<
2        « 6 & 3 (110 & 011 = 010)
1
1

A conta a + b funciona sem parênteses. As outras três precisam deles. Veja o que acontece sem os parênteses do &:

$ cat prec3.cpp
#include <iostream>

int main() {
    int a = 6;
    int b = 3;
    std::cout << a & b;
    std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra prec3.cpp -o prec3
prec3.cpp: In function ‘int main()’:
prec3.cpp:6:20: error: no match for ‘operator&’ (operand types are ‘std::basic_ostream<char>::__ostream_type’ {aka ‘std::basic_ostream<char>’} and ‘int’)
    6 |     std::cout << a & b;
      |     ~~~~~~~~~~~~~~ ^ ~
      |               |      |
      |               |      int
      |               std::basic_ostream<char>::__ostream_type {aka std::basic_ostream<char>}
prec3.cpp:6:20: note: there are 6 candidates
    6 |     std::cout << a & b;
      |     ~~~~~~~~~~~~~~~^~~
prec3.cpp:6:20: note: candidate 1: ‘operator&(int, int)’ (built-in)
prec3.cpp:6:20: note: no known conversion for argument 1 from ‘std::basic_ostream<char>::__ostream_type’ {aka ‘std::basic_ostream<char>’} to ‘int’
In file included from /usr/include/c++/15/bits/memory_resource.h:40,
                 from /usr/include/c++/15/string:72,
                 from /usr/include/c++/15/bits/locale_classes.h:42,
                 from /usr/include/c++/15/bits/ios_base.h:43,
                 from /usr/include/c++/15/ios:46,
                 from /usr/include/c++/15/bits/ostream.h:43,
                 from /usr/include/c++/15/ostream:42,
                 from /usr/include/c++/15/iostream:43,
                 from prec3.cpp:1:
/usr/include/c++/15/cstddef:146:3: note: candidate 2: ‘constexpr std::byte std::operator&(byte, byte)’
  146 |   operator&(byte __l, byte __r) noexcept
      |   ^~~~~~~~
/usr/include/c++/15/cstddef:146:18: note: no known conversion for argument 1 from ‘std::basic_ostream<char>::__ostream_type’ {aka ‘std::basic_ostream<char>’} to ‘std::byte’
  146 |   operator&(byte __l, byte __r) noexcept
      |             ~~~~~^~~
/usr/include/c++/15/bits/ios_base.h:86:3: note: candidate 3: ‘constexpr std::_Ios_Fmtflags std::operator&(_Ios_Fmtflags, _Ios_Fmtflags)’
   86 |   operator&(_Ios_Fmtflags __a, _Ios_Fmtflags __b) _GLIBCXX_NOTHROW
      |   ^~~~~~~~
/usr/include/c++/15/bits/ios_base.h:86:27: note: no known conversion for argument 1 from ‘std::basic_ostream<char>::__ostream_type’ {aka ‘std::basic_ostream<char>’} to ‘std::_Ios_Fmtflags’
   86 |   operator&(_Ios_Fmtflags __a, _Ios_Fmtflags __b) _GLIBCXX_NOTHROW
      |             ~~~~~~~~~~~~~~^~~
/usr/include/c++/15/bits/ios_base.h:145:3: note: candidate 4: ‘constexpr std::_Ios_Openmode std::operator&(_Ios_Openmode, _Ios_Openmode)’
  145 |   operator&(_Ios_Openmode __a, _Ios_Openmode __b) _GLIBCXX_NOTHROW
      |   ^~~~~~~~
/usr/include/c++/15/bits/ios_base.h:145:27: note: no known conversion for argument 1 from ‘std::basic_ostream<char>::__ostream_type’ {aka ‘std::basic_ostream<char>’} to ‘std::_Ios_Openmode’
  145 |   operator&(_Ios_Openmode __a, _Ios_Openmode __b) _GLIBCXX_NOTHROW
      |             ~~~~~~~~~~~~~~^~~
/usr/include/c++/15/bits/ios_base.h:192:3: note: candidate 5: ‘constexpr std::_Ios_Iostate std::operator&(_Ios_Iostate, _Ios_Iostate)’
  192 |   operator&(_Ios_Iostate __a, _Ios_Iostate __b) _GLIBCXX_NOTHROW
      |   ^~~~~~~~
/usr/include/c++/15/bits/ios_base.h:192:26: note: no known conversion for argument 1 from ‘std::basic_ostream<char>::__ostream_type’ {aka ‘std::basic_ostream<char>’} to ‘std::_Ios_Iostate’
  192 |   operator&(_Ios_Iostate __a, _Ios_Iostate __b) _GLIBCXX_NOTHROW
      |             ~~~~~~~~~~~~~^~~
In file included from /usr/include/c++/15/format:46,
                 from /usr/include/c++/15/ostream:44:
/usr/include/c++/15/charconv:637:3: note: candidate 6: ‘constexpr std::chars_format std::operator&(chars_format, chars_format)’
  637 |   operator&(chars_format __lhs, chars_format __rhs) noexcept
      |   ^~~~~~~~
/usr/include/c++/15/charconv:637:26: note: no known conversion for argument 1 from ‘std::basic_ostream<char>::__ostream_type’ {aka ‘std::basic_ostream<char>’} to ‘std::chars_format’
  637 |   operator&(chars_format __lhs, chars_format __rhs) noexcept
      |             ~~~~~~~~~~~~~^~~~~

Uma mensagem enorme para um problema de dois parênteses. Ela assusta, mas tem uma estrutura:

error: no match for ‘operator&’
A única linha que importa. "Não existe & para estes operandos." E diz quais são: à esquerda, um std::basic_ostream<char> (é o tipo do cout); à direita, um int.
os tracinhos ~~~~~~~~~~~~~~ ^ ~
Mostram como o C++ agrupou: tudo de std::cout até a é o operando esquerdo, o b é o direito. Ou seja, ele leu (std::cout << a) & b: primeiro mostrou o a, depois tentou fazer "cout & 3".
note: there are 6 candidates
Daqui em diante, o g++ lista os 6 & que ele conhece (para int, para std::byte, para opções internas do cout…) e explica por que nenhum serve. É informação para quem cria seus próprios tipos; para você, agora, pode pular.

A correção: std::cout << (a & b);. O mesmo erro, com operator< no lugar de operator&, aparece com std::cout << idade < 18 (veja Operadores de Comparação). Regra: dentro do cout, contas (+ - * / %) podem ir sem parênteses; qualquer outra coisa (comparação, lógica, bits, ternário, atribuição) vai entre parênteses.


5. Associatividade da direita para a esquerda

Dois grupos da tabela são agrupados ao contrário:

  • Unários (nível 3): !!x é !(!x), e - -x é -(-x). Faz sentido: o operador mais perto do valor age primeiro.
  • Atribuições (nível 16): a = b = c = 0 é a = (b = (c = 0)), e x += y -= 2 seria x += (y -= 2). É isso que permite a atribuição encadeada da aula Operadores de Atribuição.

E como a atribuição tem uma das prioridades mais baixas, em total = preco * quantidade + frete; toda a conta é feita antes de guardar. Por isso nunca é preciso escrever total = (preco * quantidade + frete);.

Precedência não é ordem de execução

A precedência diz como os operandos são agrupados, não em que ordem as partes são calculadas. Em f() + g() * h(), a precedência garante que g() * h() será multiplicado antes de somar, mas não diz se f() é chamada antes ou depois de g(). Só alguns operadores garantem a ordem (o && e o || avaliam a esquerda primeiro, por exemplo; veja o curto-circuito em Operadores Lógicos). É por isso que i++ + i++ é um problema mesmo com a precedência bem definida (aula ++ e --).


Formas diferentes: com ou sem parênteses?

ExpressãoPrecisa de parênteses?Por quê
a + b * cNão (se é o que você quer)Todo mundo conhece a regra da escola.
(a + b) / 2SimSem eles, só o b é dividido.
x > 0 && y > 0NãoComparações vêm antes da lógica, e isso é natural de ler.
a || b && cSim, sempreO g++ avisa; deixe claro o que vai junto.
n & 1 == 0Sim, sempreBits ficam abaixo das comparações.
!a == bSim (ou use a != b)O ! gruda só no a.
std::cout << a + bNãoContas vêm antes do <<.
std::cout << (a > b)SimComparações, lógica e bits vêm depois do <<.

Regra de ouro: se você precisou pensar na tabela para saber o resultado, coloque parênteses. Eles não deixam o programa mais lento e poupam o próximo leitor (que pode ser você daqui a um mês).


Quando dá errado

error: no match for ‘operator&’ (ou operator<, operator&&…) com std::basic_ostream

O que aconteceu: uma comparação, operação lógica ou de bits dentro de um cout sem parênteses. O << pegou o operando antes, e o resto ficou "sobrando" contra o cout.

Como resolver: parênteses em volta da expressão: std::cout << (a & b);. Ignore a lista de candidatos e leia só a primeira linha error:.

warning: suggest parentheses around … [-Wparentheses]

O que aconteceu: você misturou operadores numa ordem que costuma ser engano: && dentro de ||, comparação dentro de &, comparação dentro de ==, atribuição como condição.

Como resolver: coloque parênteses mostrando o agrupamento que você quer. Se o resultado mudar, o aviso acabou de achar um defeito.

warning: logical not is only applied to the left hand side of comparison

O que aconteceu: !x == y. O ! foi aplicado só ao x.

Como resolver: x != y, ou !(x == y).

A média saiu maior do que todas as notas

O que aconteceu: n1 + n2 + n3 / 3 divide só o n3. Nenhum aviso aparece, porque a expressão é perfeitamente comum.

Como resolver: (n1 + n2 + n3) / 3 (e, para ter casas decimais, / 3.0; veja Conversão de Tipos).


Referência rápida

OrdemGrupoOperadores
1ºUnáriosx++ x--, depois ++x --x -x !
2ºMultiplicativos* / %
3ºAditivos+ -
4ºDeslocamento / cout<< >>
5ºComparação<=>, depois < <= > >=, depois == !=
6ºBits&, depois ^, depois |
7ºLógicos&&, depois ||
8ºTernário e atribuição?: = += -= *= /= %= (direita → esquerda)

Exercícios

1. Quanto dá?

Qual é o resultado de 2 + 3 * 4 - 1?

2. Da esquerda para a direita

Qual é o resultado de 40 / 4 / 2 (inteiros)?

3. A média certa

Complete com os parênteses para dividir a soma inteira por 2:

int media = / 2;

4. Como o C++ agrupa a || b && c?

5. Qual destas linhas compila e mostra o resultado da comparação?

6. Com int x = 0;, quanto vale !x == 2?

Missão final: conserte as prioridades

Missão: o programa tem dois defeitos de precedência. A média de 7, 8 e 9 deveria ser 8, e o desconto só vale para quem é VIP ou tem cupom, e comprou mais de 100. Coloque os parênteses certos: a saída deve ser a de baixo, e o compilador não pode dar nenhum aviso.

Média: 8
Desconto: false
#include <iostream>

int main() {
    int n1 = 7;
    int n2 = 8;
    int n3 = 9;
    int media = n1 + n2 + n3 / 3;
    std::cout << "Média: " << media << '\n';

    bool vip = true;
    bool cupom = false;
    int total = 80;
    bool desconto = vip || cupom && total > 100;
    std::cout << std::boolalpha;
    std::cout << "Desconto: " << desconto << '\n';
}
#include <iostream>

int main() {
    int n1 = 7;
    int n2 = 8;
    int n3 = 9;
    int media = (n1 + n2 + n3) / 3;
    std::cout << "Média: " << media << '\n';

    bool vip = true;
    bool cupom = false;
    int total = 80;
    bool desconto = (vip || cupom) && total > 100;
    std::cout << std::boolalpha;
    std::cout << "Desconto: " << desconto << '\n';
}

Resumo

  • Precedência decide quem pega os operandos primeiro; associatividade desempata operadores do mesmo nível. Juntas, elas dizem onde o C++ coloca os "parênteses invisíveis".
  • Ordem geral: unários → * / % → + - → << >> → comparações → bits → && → || → atribuição.
  • Quase tudo é da esquerda para a direita; unários e atribuições são da direita para a esquerda.
  • Armadilhas: a < b == c, !a == b, n & 1 == 0, a || b && c. O g++ avisa com -Wparentheses e -Wlogical-not-parentheses.
  • No cout, só contas vão sem parênteses: std::cout << a & b dá error: no match for ‘operator&’.
  • Precedência agrupa, mas não define a ordem em que as partes são calculadas.
  • Na dúvida, parênteses.