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.
- 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 == ben & 1 == 0não fazem o que parecem, e os avisos do g++ para cada um. - Por que
std::cout << a & bdá 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ível | Operadores | O que são | Associatividade |
|---|---|---|---|
| 1 | :: | Escopo (std::cout) ★ | esquerda → direita |
| 2 | x++ x-- f() a[i] . -> | Pós-incremento ★, chamada de função, índice, membro | esquerda → direita |
| 3 | ++x --x +x -x ! ~ (tipo) *p &x sizeof new delete | Pré-incremento ★, sinal ★, "não" ★, cast estilo C ★, ponteiros | direita → 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 | ? : = += -= *= /= %= <<= >>= &= ^= |= throw | Ternário, atribuições ★ | direita → esquerda |
| 17 | , | Vírgula | esquerda → 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
- Unários (
!,-x,++) grudam no valor e vêm antes de tudo. - Depois, as contas:
* / %, depois+ -. - Depois o
<</>>docout/cin. - Depois as comparações: primeiro
< > <= >=, depois== !=. - Depois os de bits
& ^ |(sim, abaixo das comparações: isso causa problemas). - Depois a lógica:
&&antes de||. - 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
¶ estes operandos." E diz quais são: à esquerda, umstd::basic_ostream<char>(é o tipo docout); à direita, umint. - os tracinhos
~~~~~~~~~~~~~~ ^ ~ - Mostram como o C++ agrupou: tudo de
std::coutatéaé o operando esquerdo, obé o direito. Ou seja, ele leu(std::cout << a) & b: primeiro mostrou oa, depois tentou fazer "cout& 3". - note: there are 6 candidates
- Daqui em diante, o g++ lista os 6
&que ele conhece (paraint, parastd::byte, para opções internas docout…) 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)), ex += y -= 2seriax += (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);.
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ão | Precisa de parênteses? | Por quê |
|---|---|---|
a + b * c | Não (se é o que você quer) | Todo mundo conhece a regra da escola. |
(a + b) / 2 | Sim | Sem eles, só o b é dividido. |
x > 0 && y > 0 | Não | Comparações vêm antes da lógica, e isso é natural de ler. |
a || b && c | Sim, sempre | O g++ avisa; deixe claro o que vai junto. |
n & 1 == 0 | Sim, sempre | Bits ficam abaixo das comparações. |
!a == b | Sim (ou use a != b) | O ! gruda só no a. |
std::cout << a + b | Não | Contas vêm antes do <<. |
std::cout << (a > b) | Sim | Comparaçõ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).
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
| Ordem | Grupo | Operadores |
|---|---|---|
| 1º | Unários | x++ 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:
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-Wparenthesese-Wlogical-not-parentheses. - No
cout, só contas vão sem parênteses:std::cout << a & bdá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.