Operadores
por Huxley SterlingEntenda o conceito
Operadores são os símbolos que combinam valores em expressões. Os aritméticos (+, -, *, /, %) fazem contas, e a precedência decide a ordem: *, / e % vêm antes de + e -, e operadores do mesmo nível são avaliados da esquerda para a direita. Quando a ordem natural não serve, os parênteses mandam: (a + b) / 2 soma antes de dividir.
Para atualizar uma variável a partir do próprio valor, use a atribuição composta: estoque -= 3; é o mesmo que estoque = estoque - 3;, e existem também +=, *=, /= e %=. Os operadores ++ e -- somam ou subtraem 1. A forma pré-fixada (++x) altera e entrega o valor novo; a pós-fixada (x++) entrega o valor antigo e só depois altera.
Os operadores lógicos && e || têm curto-circuito: se o lado esquerdo já decide o resultado, o direito nem é avaliado. Isso permite escrever total > 0 && soma / total > 5 sem risco de dividir por zero.
Por fim, os operadores bit a bit (&, |, ^, ~, <<, >>) trabalham com os bits de um inteiro, de preferência sem sinal. Eles são úteis para guardar várias opções liga/desliga num único número, mas têm precedência baixa: um teste como (flags & BIT) != 0 precisa de parênteses.
*,/e%têm precedência maior que+e-; na dúvida, use parênteses, que também deixam a intenção clara para quem lê.- Divisão inteira com negativos corta em direção ao zero:
-7 / 2dá-3e-7 % 2dá-1. x += 5equivale ax = x + 5; o sinal vem antes do=, porquex =+ 5apenas atribui+5.x++entrega o valor antigo e++xentrega o novo; numa linha sozinha, os dois apenas somam 1.&&e||param assim que o resultado está decidido; em testes de bits, escreva(flags & BIT) != 0, com parênteses.
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01Nota final com precedência e parênteses
// Nota final: precedência dos operadores aritméticos
#include <iostream>
int main() {
int prova = 8;
int trabalho = 6;
int participacao = 10;
// * e / vêm antes de + e -: sem parênteses, só a participação é dividida
int semParenteses = prova + trabalho + participacao / 3;
// Os parênteses mudam a ordem: primeiro a soma, depois a divisão
int comParenteses = (prova + trabalho + participacao) / 3;
std::cout << "Sem parênteses: " << semParenteses << '\n'; // 8 + 6 + 3 = 17
std::cout << "Com parênteses: " << comParenteses << '\n'; // 24 / 3 = 8
// Média ponderada: prova peso 3, trabalho peso 2, participação peso 1
int somaPesada = prova * 3 + trabalho * 2 + participacao * 1; // 46
std::cout << "Média ponderada: " << somaPesada / 6 << '\n'; // 7
// % tem a mesma precedência de * e /
std::cout << "Resto da divisão: " << somaPesada % 6 << '\n'; // 4
// Mesma precedência: avaliação da esquerda para a direita
std::cout << "24 / 3 * 2 = " << 24 / 3 * 2 << '\n'; // (24 / 3) * 2 = 16
// Com negativos, a divisão inteira corta em direção ao zero
int ajuste = -7;
std::cout << "-7 / 2 = " << ajuste / 2 << '\n'; // -3, não -4
std::cout << "-7 % 2 = " << ajuste % 2 << '\n'; // -1: o resto tem o sinal da esquerda
return 0;
}
As duas primeiras contas usam os mesmos números e dão resultados diferentes: sem parênteses, só participacao / 3 é calculado antes das somas (8 + 6 + 3 = 17); com parênteses, a soma vem primeiro e a média fica 8. Na média ponderada, as multiplicações acontecem antes das somas sem nenhum parêntese, e % mostra o que sobrou da divisão por 6.
A linha 24 / 3 * 2 mostra que operadores de mesma precedência seguem da esquerda para a direita. As duas últimas lembram um detalhe da divisão inteira: com números negativos, o C++ corta em direção ao zero, e o resto fica com o sinal do lado esquerdo.
02Estoque de cadernos com atribuição composta e ++
// Estoque de cadernos: atribuição composta, incremento e decremento
#include <iostream>
int main() {
int estoque = 40;
int vendas = 0;
estoque -= 12; // o mesmo que estoque = estoque - 12
vendas += 12; // o mesmo que vendas = vendas + 12
std::cout << "Após a manhã: " << estoque << " em estoque\n"; // 28
estoque += 25; // chegou uma caixa do fornecedor
std::cout << "Após a entrega: " << estoque << '\n'; // 53
// ++ e -- somam ou subtraem 1
--estoque; // uma venda avulsa
++vendas;
std::cout << "Vendas do dia: " << vendas << '\n'; // 13
// Pós-fixado: entrega o valor antigo e depois incrementa
int pedido = 100;
int atendido = pedido++;
std::cout << "Atendido: " << atendido << ", próximo: " << pedido << '\n'; // 100, 101
// Pré-fixado: incrementa primeiro e entrega o valor novo
int senha = ++pedido;
std::cout << "Nova senha: " << senha << '\n'; // 102
// *= e /= também existem; com int, /= continua sendo divisão inteira
int pontos = 1275;
pontos *= 2; // promoção: pontos em dobro (2550)
pontos /= 100; // cada 100 pontos vale R$ 1; sobra descartada (25)
std::cout << "Cashback: R$ " << pontos << '\n';
// %= guarda o resto: cadernos que não completam um pacote de 10
estoque %= 10; // 52 % 10 = 2
std::cout << "Fora dos pacotes: " << estoque << '\n';
return 0;
}
O estoque é atualizado com -=, += e --, sempre a partir do valor atual, sem repetir o nome da variável dos dois lados do =. A parte do pedido mostra a diferença entre as formas do incremento: pedido++ entrega 100 para atendido e só depois vira 101; ++pedido vira 102 antes de entregar o valor para senha.
No cashback, *= dobra os pontos e /= faz uma divisão inteira, descartando os 50 pontos que não completam um real. O %= final guarda o resto da divisão por 10, ou seja, os cadernos que ficam fora dos pacotes.
03Permissões da biblioteca com bits e curto-circuito
// Biblioteca: permissões do leitor com operadores bit a bit
#include <iostream>
int main() {
// Cada permissão ocupa um bit: 1, 2, 4 e 8
constexpr unsigned int EMPRESTAR = 1u << 0; // 0001
constexpr unsigned int RESERVAR = 1u << 1; // 0010
constexpr unsigned int RENOVAR = 1u << 2; // 0100
constexpr unsigned int ACERVO_RARO = 1u << 3; // 1000
// | liga bits: o leitor pode emprestar e reservar
unsigned int leitor = EMPRESTAR | RESERVAR;
std::cout << "Permissões iniciais: " << leitor << '\n'; // 3
// |= acrescenta uma permissão sem mexer nas outras
leitor |= RENOVAR;
std::cout << "Com renovação: " << leitor << '\n'; // 7
// & testa um bit; os parênteses são obrigatórios, pois != vem antes de &
std::cout << std::boolalpha;
std::cout << "Pode renovar: " << ((leitor & RENOVAR) != 0) << '\n'; // true
std::cout << "Acervo raro: " << ((leitor & ACERVO_RARO) != 0) << '\n'; // false
// &= com ~ desliga um bit: reservas suspensas
leitor &= ~RESERVAR;
std::cout << "Sem reserva: " << leitor << '\n'; // 5
// ^= inverte um bit: liga se estava desligado
leitor ^= ACERVO_RARO;
std::cout << "Com acervo raro: " << leitor << '\n'; // 13
// Curto-circuito: se a esquerda do && é falsa, a direita nem roda
int devolvidos = 0;
int atrasados = 0;
if (devolvidos > 0 && atrasados * 100 / devolvidos > 20) {
std::cout << "Taxa de atraso alta\n";
} else {
std::cout << "Taxa de atraso sob controle\n";
}
return 0;
}
Cada permissão é um bit, criado com 1u << n. O | combina permissões, o |= acrescenta uma, o & testa se um bit está ligado e o &= ~ desliga um bit sem tocar nos outros; o ^= inverte um bit. Os parênteses em (leitor & RENOVAR) != 0 são obrigatórios, porque != tem precedência maior que &.
O último if mostra o curto-circuito: como devolvidos > 0 é falso, o && já sabe que o resultado é falso e não avalia atrasados * 100 / devolvidos, que seria uma divisão por zero. Por isso, o teste de proteção sempre vem à esquerda.
Agora é com você
Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.
01Placar do basqueteFácil
Um time de basquete pontuou, nesta ordem, com uma cesta de 2, uma de 3, dois lances livres de 1 ponto e outra cesta de 2. Atualize o placar a cada jogada, conte as jogadas e mostre quanto falta para a meta de 20 pontos.
- Começar o placar e o contador em zero e atualizar o placar com
+=a cada jogada. - Contar as jogadas com
++, uma vez por jogada. - Calcular o que falta para a meta com uma subtração, sem digitar o resultado.
- Reproduzir exatamente a saída esperada.
#include <iostream>
int main() {
constexpr int meta = 20;
int pontos = 0;
int jogadas = 0;
// Registre cada jogada com += e conte com ++
// Mostre pontos, jogadas e quanto falta para a meta
return 0;
}
Pontos: 9
Jogadas: 5
Faltam para 20: 11
Ver dica
Cada jogada mexe em duas variáveis: uma recebe o valor da cesta e a outra só cresce de um em um.
Ver resposta
#include <iostream>
int main() {
constexpr int meta = 20; // pontos que o time quer fazer no quarto
int pontos = 0;
int jogadas = 0;
pontos += 2; // cesta de dois
++jogadas;
pontos += 3; // cesta de três
++jogadas;
pontos += 1; // primeiro lance livre
++jogadas;
pontos += 1; // segundo lance livre
++jogadas;
pontos += 2; // outra cesta de dois
++jogadas;
std::cout << "Pontos: " << pontos << '\n';
std::cout << "Jogadas: " << jogadas << '\n';
std::cout << "Faltam para " << meta << ": " << meta - pontos << '\n';
return 0;
}
Passo a passo
constexpr int meta = 20;guarda a meta do quarto como constante, para que o número 20 apareça num lugar só.pontosejogadascomeçam em zero: os operadores compostos leem o valor atual antes de alterar, então a variável precisa ter um valor inicial.- Cada
pontos += 2;(ou+= 3,+= 1) soma o valor da jogada ao placar; é o mesmo quepontos = pontos + 2;, só que mais curto. ++jogadas;soma 1 ao contador a cada jogada que pontuou. Como o valor não é usado na mesma linha, tanto faz o pré ou o pós-fixado.- Depois das cinco jogadas, o placar vale 2 + 3 + 1 + 1 + 2 = 9 e o contador vale 5.
meta - pontosé calculado direto na saída; como-tem precedência maior que<<, não precisa de parênteses.
Por que funciona
A atribuição composta lê a variável, aplica a operação e guarda o resultado de volta, numa instrução só. O incremento é o caso particular de somar 1. Assim, o placar e o contador acompanham o jogo linha a linha, e o total nunca precisa ser digitado: ele é consequência das jogadas.
Erros comuns
- Inverter o operador:
pontos =+ 2;compila, mas é lido comopontos = +2;e apaga o placar a cada jogada. O sinal da operação vem antes do=. - Não inicializar:
int pontos;seguido depontos += 2;soma 2 a um valor indefinido, e o resultado muda a cada execução. Comece comint pontos = 0;. - Esquecer um
++jogadas: o contador fica em 4 e a saída não bate. Uma forma de evitar é escrever a jogada e o contador sempre em par, como na solução.
Outra forma de resolver
Daria para escrever int pontos = 2 + 3 + 1 + 1 + 2; numa linha. Funciona para um placar fixo, mas a forma com += é a que acompanha um jogo de verdade, em que cada jogada chega em um momento diferente e precisa atualizar o total.
Saída esperada
Pontos: 9
Jogadas: 5
Faltam para 20: 11
02Horário para começar a receitaMédio
Uma receita de pão leva 25 minutos de preparo, 90 de descanso, 2 fornadas de 35 minutos e 10 minutos para preaquecer o forno. Calcule o tempo total, mostre-o em horas e minutos e diga a que horas começar para servir o jantar às 19:00.
- Calcular o tempo total numa única expressão sem parênteses, aproveitando a precedência de
*sobre+. - Converter minutos em horas e minutos com
/e%. - Converter o horário do jantar para minutos (
19 * 60) e subtrair o total para achar o horário de início. - Reproduzir exatamente a saída esperada.
#include <iostream>
int main() {
int preparo = 25;
int descanso = 90;
int fornadas = 2;
int porFornada = 35;
int preaquecer = 10;
// Tempo total, duração em horas e minutos e horário de início
return 0;
}
Tempo total: 195 min
Duração: 3 h 15 min
Começar às 15:45
Ver dica
Trabalhe tudo em minutos; quantas horas cheias cabem num total e o que sobra delas são duas perguntas, cada uma com seu operador.
Ver resposta
#include <iostream>
int main() {
int preparo = 25; // minutos para preparar a massa
int descanso = 90; // massa descansando
int fornadas = 2;
int porFornada = 35; // minutos de forno por fornada
int preaquecer = 10;
// Sem parênteses: * vem antes de +, então só as fornadas são multiplicadas
int total = preparo + descanso + fornadas * porFornada + preaquecer;
// / dá as horas cheias e % os minutos que sobram
int horas = total / 60;
int minutos = total % 60;
// Jantar às 19:00, convertido para minutos desde a meia-noite
int jantar = 19 * 60;
int inicio = jantar - total;
std::cout << "Tempo total: " << total << " min\n";
std::cout << "Duração: " << horas << " h " << minutos << " min\n";
std::cout << "Começar às " << inicio / 60 << ':' << inicio % 60 << '\n';
return 0;
}
Passo a passo
- Cada etapa da receita fica numa variável
int, sempre em minutos, para que todas as contas usem a mesma unidade. - Na expressão do
total, a multiplicaçãofornadas * porFornadaé feita primeiro (70) e só depois as somas: 25 + 90 + 70 + 10 = 195. total / 60divide doisint, então dá as horas cheias (3) e descarta o resto.total % 60devolve exatamente o que a divisão descartou: os 15 minutos.19 * 60converte o horário do jantar para 1140 minutos desde a meia-noite; subtraindo o total, o início fica em 945.- Na última linha,
inicio / 60(15) einicio % 60(45) voltam a ser horas e minutos; como/e%têm precedência maior que<<, não precisam de parênteses.
Por que funciona
A precedência resolve a ordem das contas sem parênteses: multiplicação antes da soma, exatamente como na matemática. Já o par / e % é a ferramenta clássica para quebrar uma quantidade em unidades maiores e menores: o quociente diz quantas horas cheias cabem e o resto diz o que sobrou. Trabalhar tudo em minutos e só converter no fim evita somar horas com minutos por engano.
Erros comuns
- Parênteses no lugar errado:
(preparo + descanso + fornadas) * porFornada + preaquecermultiplica tudo por 35 e dá 4.105 minutos. Os parênteses só são necessários quando você quer mudar a ordem natural. - Somar as fornadas como uma só:
preparo + descanso + porFornada + preaqueceresquece a segunda fornada e mostra 160 minutos. - Usar
doublesem precisar: comdouble total,total / 60vale 3.25 e%nem compila, porque o operador resto só existe para inteiros. - Subtrair horas de minutos:
19 - totalmistura unidades e dá -176. Converta o horário para minutos antes da conta.
Outra forma de resolver
Dá para guardar o início já separado, com int horaInicio = (jantar - total) / 60; e int minutoInicio = (jantar - total) % 60;. Aqui os parênteses são obrigatórios, porque a subtração precisa acontecer antes da divisão. Essa forma vale a pena quando os valores serão reaproveitados em outras contas.
Saída esperada
Tempo total: 195 min
Duração: 3 h 15 min
Começar às 15:45
03Conquistas do jogador em bitsDesafio
Guarde as conquistas de um jogador num unsigned int, um bit para cada: primeira vitória, colecionador, explorador, invicto e mestre. Ele começa com primeira vitória e explorador, ganha colecionador, ganha invicto e perde invicto numa derrota; mostre o estado a cada passo, o total de conquistas, se completou o pacote básico (as três primeiras) e o bônus de 10 moedas dobrado a cada conquista.
- Criar as conquistas como constantes com
1u << ne o pacote básico como máscara montada com|. - Ligar conquistas com
|=, desligar com&= ~e testar bits com&, usando parênteses nas comparações. - Contar as conquistas com
std::popcount(cabeçalho<bit>, C++20) e calcular o bônus com<<. - Mostrar os
boolcomstd::boolalphae reproduzir exatamente a saída esperada.
#include <bit>
#include <iostream>
int main() {
// Constantes das conquistas (um bit cada) e máscara do pacote básico
// Estado inicial e mudanças: colecionador, invicto, derrota
// Total, pacote básico e moedas de bônus
return 0;
}
Conquistas iniciais: 5
Após colecionador: 7
Tem mestre: false
Após invicto: 15
Após a derrota: 7
Total de conquistas: 3
Pacote básico completo: true
Moedas de bônus: 80
Ver dica
Para saber se todas as conquistas de uma máscara estão presentes, compare o resultado do & com a própria máscara, e não com zero.
Ver resposta
#include <bit> // std::popcount (C++20)
#include <iostream>
int main() {
// Cada conquista ocupa um bit
constexpr unsigned int PRIMEIRA_VITORIA = 1u << 0; // 1
constexpr unsigned int COLECIONADOR = 1u << 1; // 2
constexpr unsigned int EXPLORADOR = 1u << 2; // 4
constexpr unsigned int INVICTO = 1u << 3; // 8
constexpr unsigned int MESTRE = 1u << 4; // 16
// Máscara: as três conquistas do pacote básico juntas
constexpr unsigned int BASICO = PRIMEIRA_VITORIA | COLECIONADOR | EXPLORADOR;
std::cout << std::boolalpha;
unsigned int conquistas = PRIMEIRA_VITORIA | EXPLORADOR;
std::cout << "Conquistas iniciais: " << conquistas << '\n';
conquistas |= COLECIONADOR; // liga um bit
std::cout << "Após colecionador: " << conquistas << '\n';
// Parênteses: sem eles, != seria avaliado antes do &
std::cout << "Tem mestre: " << ((conquistas & MESTRE) != 0) << '\n';
conquistas |= INVICTO;
std::cout << "Após invicto: " << conquistas << '\n';
conquistas &= ~INVICTO; // a derrota desliga só o bit do invicto
std::cout << "Após a derrota: " << conquistas << '\n';
int total = std::popcount(conquistas); // quantos bits estão ligados
std::cout << "Total de conquistas: " << total << '\n';
// Todos os bits da máscara precisam estar ligados
std::cout << "Pacote básico completo: " << ((conquistas & BASICO) == BASICO) << '\n';
// Cada conquista dobra o bônus: 10 deslocado 3 bits = 10 * 2 * 2 * 2
int moedas = 10 << total;
std::cout << "Moedas de bônus: " << moedas << '\n';
return 0;
}
Passo a passo
1u << ndesloca o bit 1 para a posiçãon, gerando 1, 2, 4, 8 e 16: cada conquista tem um bit só dela. Oudeixa o valor sem sinal, o tipo indicado para bits.BASICOjunta três conquistas com|numa máscara de valor 7 (bits 0, 1 e 2 ligados).- O jogador começa com
PRIMEIRA_VITORIA | EXPLORADOR, ou seja, 1 + 4 = 5. Com|, bits iguais nunca somam duas vezes. conquistas |= COLECIONADORliga o bit 2 e o valor passa a 7; os bits que já estavam ligados continuam como estavam.(conquistas & MESTRE) != 0isola o bit 16: como ele está desligado, o&dá 0 e a comparação dáfalse.|= INVICTOleva o valor a 15; em seguida,&= ~INVICTOusa o complemento (todos os bits ligados, menos o 8) para desligar só esse bit, e o valor volta a 7.std::popcountconta os bits ligados de um inteiro sem sinal: 3. Já(conquistas & BASICO) == BASICOsó é verdadeiro se todos os bits da máscara estiverem ligados.10 << totaldesloca 10 três bits para a esquerda, o que equivale a multiplicar por 2 três vezes: 80 moedas.
Por que funciona
Um único unsigned int guarda até 32 conquistas, uma por bit. Os operadores bit a bit agem posição por posição: | liga, & com ~ desliga, & com uma máscara testa e << desloca. Como cada conquista tem um bit exclusivo, ligar ou desligar uma nunca afeta as outras, e o estado inteiro do jogador cabe num número que pode ser salvo ou comparado de uma vez.
O teste com máscara generaliza o teste de um bit: comparar com != 0 responde "tem alguma destas?", e comparar com == BASICO responde "tem todas estas?".
Erros comuns
- Esquecer os parênteses no teste:
conquistas & MESTRE != 0é lido comoconquistas & (MESTRE != 0), ou seja,conquistas & 1, que testa a primeira vitória. Envolva o&em parênteses. - Confundir
&&com&:conquistas && MESTREsó pergunta se os dois valores são diferentes de zero e dátrueaqui. Para bits, use&. - Desligar com
^:conquistas ^= INVICTOinverte o bit; se o jogador ainda não fosse invicto, ganharia a conquista na derrota. Para desligar sempre, use&= ~INVICTO. - Somar em vez de
|:conquistas += COLECIONADORfunciona na primeira vez, mas, se o bit já estiver ligado, a soma vira 9 e liga o bit do invicto. O|=é seguro para repetir.
Outra forma de resolver
A biblioteca padrão tem std::bitset<5> (cabeçalho <bitset>), com set, reset, test, count e all, que dão nomes às operações e imprimem os bits como 00111. É preferível quando a legibilidade importa mais do que guardar o estado num inteiro, por exemplo para salvar ou enviar pela rede.
Saída esperada
Conquistas iniciais: 5
Após colecionador: 7
Tem mestre: false
Após invicto: 15
Após a derrota: 7
Total de conquistas: 3
Pacote básico completo: true
Moedas de bônus: 80