Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Operadores

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.

C++ Fundamentos Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
C++ / Fundamentos

Operadores

por Huxley Sterling
Iniciante

Entenda 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.

↳ Pontos-chave
  • *, / 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 / 2 dá -3 e -7 % 2 dá -1.
  • x += 5 equivale a x = x + 5; o sinal vem antes do =, porque x =+ 5 apenas atribui +5.
  • x++ entrega o valor antigo e ++x entrega 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.cpp
// 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.cpp
// 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

permissoes.cpp
// 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.

↳ Requisitos
  • 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.
código inicial
#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;
}
saída esperada
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

  1. constexpr int meta = 20; guarda a meta do quarto como constante, para que o número 20 apareça num lugar só.
  2. pontos e jogadas começam em zero: os operadores compostos leem o valor atual antes de alterar, então a variável precisa ter um valor inicial.
  3. Cada pontos += 2; (ou += 3, += 1) soma o valor da jogada ao placar; é o mesmo que pontos = pontos + 2;, só que mais curto.
  4. ++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.
  5. Depois das cinco jogadas, o placar vale 2 + 3 + 1 + 1 + 2 = 9 e o contador vale 5.
  6. 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 como pontos = +2; e apaga o placar a cada jogada. O sinal da operação vem antes do =.
  • Não inicializar: int pontos; seguido de pontos += 2; soma 2 a um valor indefinido, e o resultado muda a cada execução. Comece com int 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.

↳ Requisitos
  • 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.
código inicial
#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;
}
saída esperada
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

  1. Cada etapa da receita fica numa variável int, sempre em minutos, para que todas as contas usem a mesma unidade.
  2. Na expressão do total, a multiplicação fornadas * porFornada é feita primeiro (70) e só depois as somas: 25 + 90 + 70 + 10 = 195.
  3. total / 60 divide dois int, então dá as horas cheias (3) e descarta o resto.
  4. total % 60 devolve exatamente o que a divisão descartou: os 15 minutos.
  5. 19 * 60 converte o horário do jantar para 1140 minutos desde a meia-noite; subtraindo o total, o início fica em 945.
  6. Na última linha, inicio / 60 (15) e inicio % 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 + preaquecer multiplica 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 + preaquecer esquece a segunda fornada e mostra 160 minutos.
  • Usar double sem precisar: com double total, total / 60 vale 3.25 e % nem compila, porque o operador resto só existe para inteiros.
  • Subtrair horas de minutos: 19 - total mistura 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.

↳ Requisitos
  • Criar as conquistas como constantes com 1u << n e 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 bool com std::boolalpha e reproduzir exatamente a saída esperada.
código inicial
#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;
}
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
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

  1. 1u << n desloca o bit 1 para a posição n, gerando 1, 2, 4, 8 e 16: cada conquista tem um bit só dela. O u deixa o valor sem sinal, o tipo indicado para bits.
  2. BASICO junta três conquistas com | numa máscara de valor 7 (bits 0, 1 e 2 ligados).
  3. O jogador começa com PRIMEIRA_VITORIA | EXPLORADOR, ou seja, 1 + 4 = 5. Com |, bits iguais nunca somam duas vezes.
  4. conquistas |= COLECIONADOR liga o bit 2 e o valor passa a 7; os bits que já estavam ligados continuam como estavam.
  5. (conquistas & MESTRE) != 0 isola o bit 16: como ele está desligado, o & dá 0 e a comparação dá false.
  6. |= INVICTO leva o valor a 15; em seguida, &= ~INVICTO usa o complemento (todos os bits ligados, menos o 8) para desligar só esse bit, e o valor volta a 7.
  7. std::popcount conta os bits ligados de um inteiro sem sinal: 3. Já (conquistas & BASICO) == BASICO só é verdadeiro se todos os bits da máscara estiverem ligados.
  8. 10 << total desloca 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 como conquistas & (MESTRE != 0), ou seja, conquistas & 1, que testa a primeira vitória. Envolva o & em parênteses.
  • Confundir && com &: conquistas && MESTRE só pergunta se os dois valores são diferentes de zero e dá true aqui. Para bits, use &.
  • Desligar com ^: conquistas ^= INVICTO inverte 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 += COLECIONADOR funciona 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

Últimas lições

Ver o arquivo completo →