Operadores
por Tatum WinslowEntenda o conceito
Operadores são os símbolos que fazem contas e combinam valores: +, -, * e / são os aritméticos, e % devolve o resto da divisão. Um detalhe pega muita gente no começo: quando os dois lados de / são inteiros, o resultado também é inteiro e a parte fracionária é descartada, então 7 / 2 vale 3 e 7 % 2 vale 1. Basta um dos lados ser double para a divisão manter as casas decimais.
Para alterar uma variável a partir do valor que ela já tem, existem os operadores de atribuição composta: pontos += 10 é o mesmo que pontos = pontos + 10, e o mesmo vale para -=, *=, /= e %=. Somar ou subtrair 1 é tão comum que ganhou atalhos próprios: ++ e --. Sozinhos numa linha, x++ e ++x fazem a mesma coisa; dentro de uma expressão, o pós-incremento (x++) entrega o valor antigo e o pré-incremento (++x) entrega o valor já somado.
Quando vários operadores aparecem juntos, vale a precedência: *, / e % são avaliados antes de + e -, as comparações vêm depois das contas, e && e || vêm por último. Parênteses mudam essa ordem e deixam a intenção clara para quem lê. Por fim, C# tem operadores para lidar com null: a ?? b devolve a se ele não for nulo e b caso contrário, e a ??= b só atribui b quando a ainda é null.
- Entre inteiros,
/descarta a parte fracionária:1100 / 250dá4, e1100 % 250dá o resto,100. - Em contas com inteiros, multiplique antes de dividir:
sobra * 100 / totaldá a porcentagem, enquantosobra / total * 100costuma dar0. x += 5equivale ax = x + 5; existem também-=,*=,/=e%=.- Dentro de uma expressão,
x++usa o valor antigo e depois soma;++xsoma primeiro e usa o valor novo. valor ?? padraotroca umnullpor um valor padrão, evariavel ??= valorsó atribui se a variável ainda fornull.
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01Receitas de brigadeiro com divisão inteira e resto
// Quantas receitas de brigadeiro cabem no chocolate da cozinha?
const int ChocolatePorReceita = 250; // gramas
const int DocesPorReceita = 30;
const int Bandejas = 4;
int chocolateDisponivel = 1100;
// Entre inteiros, / descarta a parte fracionária e % devolve o resto
int receitasCompletas = chocolateDisponivel / ChocolatePorReceita;
int sobraChocolate = chocolateDisponivel % ChocolatePorReceita;
Console.WriteLine($"Receitas completas: {receitasCompletas}");
Console.WriteLine($"Chocolate que sobra: {sobraChocolate} g");
// * tem precedência sobre +: a multiplicação acontece primeiro
int totalDoces = receitasCompletas * DocesPorReceita + 6; // 6 doces do pote de ontem
Console.WriteLine($"Total de doces: {totalDoces}");
// Dividindo os doces entre as bandejas
Console.WriteLine($"Doces por bandeja: {totalDoces / Bandejas}");
Console.WriteLine($"Doces fora das bandejas: {totalDoces % Bandejas}");
// % também responde "é divisível?": resto zero quer dizer divisão exata
bool divisaoExata = totalDoces % Bandejas == 0;
Console.WriteLine($"Todas as bandejas iguais e sem sobra? {divisaoExata}");
// Multiplicar antes de dividir evita perder tudo na divisão inteira
int percentualErrado = sobraChocolate / ChocolatePorReceita * 100;
int percentualCerto = sobraChocolate * 100 / ChocolatePorReceita;
Console.WriteLine($"Sobra em relação a uma receita: {percentualCerto}% (e não {percentualErrado}%)");
Com 1100 g de chocolate e 250 g por receita, / entre inteiros dá 4 receitas completas e % mostra os 100 g que sobram. O mesmo par de operadores divide os 126 doces entre 4 bandejas (31 em cada, 2 de fora), e totalDoces % Bandejas == 0 mostra o uso clássico do resto: testar se uma divisão é exata.
A última conta mostra por que a ordem importa na divisão inteira: 100 / 250 já vira 0 antes da multiplicação, então o resultado é 0%. Multiplicando primeiro, 10000 / 250 dá os 40% corretos. Repare também em receitasCompletas * DocesPorReceita + 6: a multiplicação acontece antes da soma, sem precisar de parênteses.
02Placar de fliperama com atribuição composta e ++/--
// Placar de uma partida de fliperama com atribuição composta e ++/--
int pontos = 0;
int vidas = 3;
int combo = 0;
pontos += 500; // o mesmo que pontos = pontos + 500
combo++; // pós-incremento numa linha sozinha: só soma 1
pontos += 300;
combo++;
Console.WriteLine($"Pontos: {pontos} | combo: {combo} | vidas: {vidas}");
// Com dois acertos seguidos, o combo dobra o placar
pontos *= 2;
Console.WriteLine($"Combo x2 aplicado: {pontos} pontos");
// O jogador perde uma vida e paga uma penalidade
vidas--;
pontos -= 150;
combo = 0;
Console.WriteLine($"Levou dano! Pontos: {pontos} | vidas: {vidas}");
// Dentro de uma expressão, pré e pós-incremento dão resultados diferentes
int fase = 1;
Console.WriteLine($"Pós-incremento mostra {fase++}, e depois fase vale {fase}");
Console.WriteLine($"Pré-incremento já mostra {++fase}");
// /= e %= também existem
int moedas = 47;
int vidasExtras = moedas / 20;
moedas %= 20; // fica só o que não virou vida extra
vidas += vidasExtras;
Console.WriteLine($"Vidas extras: {vidasExtras} | moedas restantes: {moedas} | vidas: {vidas}");
Os operadores +=, -= e *= atualizam o placar a partir do valor atual, sem repetir o nome da variável dos dois lados. combo++ e vidas-- aparecem sozinhos numa linha, onde só importa o efeito de somar ou subtrair 1.
O trecho da fase mostra a diferença entre as duas formas dentro de uma expressão: fase++ imprime 1 e só depois a variável passa a 2; ++fase soma antes e já imprime 3. No fim, / calcula quantas vidas extras as 47 moedas compram e moedas %= 20 guarda na própria variável só as 7 que sobraram.
03Multa da biblioteca com precedência, ?? e ??=
// Ficha de empréstimo da biblioteca: precedência, ?? e ??=
const int DiasPadrao = 14;
const int MultaPorDiaCentavos = 120;
string? apelidoLeitor = null; // leitor não cadastrou apelido
string nomeLeitor = "Marina Costa";
int? diasRenovados = null; // null: ainda não renovou
// ?? devolve o lado esquerdo se ele não for null; senão, o direito
string exibido = apelidoLeitor ?? nomeLeitor;
Console.WriteLine($"Leitor: {exibido}");
// ??= só atribui quando a variável ainda é null
diasRenovados ??= 0;
Console.WriteLine($"Dias renovados: {diasRenovados}");
diasRenovados ??= 7; // não muda nada: já tem valor
Console.WriteLine($"Depois do segundo ??=: {diasRenovados}");
int diasComLivro = 20;
// Sem parênteses, * e / vêm antes de + e -; parênteses deixam a intenção clara
int prazo = DiasPadrao + diasRenovados.Value;
int diasAtraso = diasComLivro - prazo;
int multa = diasAtraso * MultaPorDiaCentavos + 50; // 50 centavos de taxa fixa
int multaSemParenteses = diasComLivro - prazo * MultaPorDiaCentavos;
int multaComParenteses = (diasComLivro - prazo) * MultaPorDiaCentavos;
Console.WriteLine($"Prazo: {prazo} dias | atraso: {diasAtraso} dias");
Console.WriteLine($"Multa com taxa: R$ {multa / 100},{multa % 100:D2}");
Console.WriteLine($"Sem parênteses: {multaSemParenteses} | com parênteses: {multaComParenteses}");
// Operadores de comparação e lógicos também são operadores: o resultado é bool
bool podeEmprestarOutro = diasAtraso <= 0 || multa < 500;
Console.WriteLine($"Pode emprestar outro livro? {podeEmprestarOutro}");
Como o leitor não tem apelido, apelidoLeitor ?? nomeLeitor usa o nome completo. Já diasRenovados ??= 0 troca o null por 0, e a segunda tentativa, ??= 7, não altera nada porque a variável já tem valor. Depois disso o programa pode usar diasRenovados.Value com segurança.
A comparação entre multaSemParenteses e multaComParenteses mostra a precedência na prática: sem parênteses, prazo * MultaPorDiaCentavos é calculado primeiro e o resultado vira -1660, um valor sem sentido; com parênteses, o atraso é calculado antes e a multa fica em 720 centavos. A última linha lembra que comparações e || também são operadores, avaliados depois das contas.
Agora é com você
Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.
01Horas de estudo da semanaFácil
Some os minutos estudados de segunda a sexta (45, 30, 60, 50 e 25) e mostre o total em minutos, o mesmo tempo em horas e minutos e a média de minutos por dia.
- Acumular o total com o operador
+=, uma linha por dia. - Calcular as horas com
/e os minutos restantes com%, usando só inteiros. - Guardar o valor 60 numa constante com nome.
- Produzir a saída idêntica à esperada.
const int MinutosPorHora = 60;
int totalMinutos = 0;
// acumule os minutos de segunda (45), terça (30), quarta (60), quinta (50) e sexta (25)
// separe horas e minutos
// imprima total, horas e minutos e a média por dia
Total da semana: 210 minutos
Equivale a 3 h 30 min
Média por dia: 42 minutos
Ver dica
Pense em quantas vezes 60 cabe no total e no que sobra depois disso: cada uma dessas perguntas tem um operador próprio.
Ver resposta
// Tempo de estudo da semana convertido em horas e minutos
const int MinutosPorHora = 60;
int totalMinutos = 0;
// += acumula o tempo de cada dia
totalMinutos += 45; // segunda
totalMinutos += 30; // terça
totalMinutos += 60; // quarta
totalMinutos += 50; // quinta
totalMinutos += 25; // sexta
// / dá as horas inteiras e % dá os minutos que sobram
int horas = totalMinutos / MinutosPorHora;
int minutos = totalMinutos % MinutosPorHora;
Console.WriteLine($"Total da semana: {totalMinutos} minutos");
Console.WriteLine($"Equivale a {horas} h {minutos} min");
Console.WriteLine($"Média por dia: {totalMinutos / 5} minutos");
Passo a passo
MinutosPorHoradá nome ao número 60, deixando claro o que as contas fazem.totalMinutoscomeça em0, porque+=precisa de um valor inicial para somar.- Cada
totalMinutos += ...acumula o tempo de um dia; ao final a variável vale210. totalMinutos / MinutosPorHoraé uma divisão entre inteiros, então dá3, as horas completas, sem casas decimais.totalMinutos % MinutosPorHoradevolve o resto dessa divisão,30, que são os minutos que não fecham uma hora.- A média usa a mesma divisão inteira:
210 / 5dá exatamente42.
Por que funciona
Converter minutos em horas e minutos é exatamente a pergunta que / e % respondem juntos: quantas vezes 60 cabe no total e quanto sobra. Como 3 * 60 + 30 volta a dar 210, nenhum minuto se perde. A atribuição composta += deixa o acúmulo legível, uma linha por dia.
Erros comuns
- Esquecer de inicializar o acumulador:
int totalMinutos;seguido detotalMinutos += 45;não compila, com o erro de uso de variável local não atribuída. Comece comint totalMinutos = 0;. - Escrever
totalMinutos =+ 45;: isso compila, mas significatotalMinutos = +45, que substitui o valor em vez de somar, e o total fica25. O operador é+=. - Usar
doublepara as horas:double horas = totalMinutos / 60.0;dá3.5e não separa as horas dos minutos. Para esse formato, use/e%com inteiros.
Outra forma de resolver
Para durações, a biblioteca padrão tem TimeSpan: var tempo = TimeSpan.FromMinutes(totalMinutos); e então tempo.Hours e tempo.Minutes. É preferível quando o programa também soma datas ou passa de 24 horas (aí entra TotalHours); para aprender os operadores, / e % mostram a conta por trás.
Saída esperada
Total da semana: 210 minutos
Equivale a 3 h 30 min
Média por dia: 42 minutos
02Movimento de estoque de cadernosMédio
Uma loja abre o dia com 40 cadernos. Registre, nesta ordem, a venda de 12 unidades, a chegada de 2 caixas de 15, uma devolução e uma avaria, mostrando o estoque após cada movimento; depois calcule a porcentagem do estoque inicial que foi vendida e mostre o resultado da ordem errada de cálculo.
- Usar
-=para as vendas,+=para o lote e++e--para a devolução e a avaria. - Calcular a porcentagem só com inteiros, multiplicando por 100 antes de dividir.
- Mostrar também o resultado de dividir antes de multiplicar, para comparar.
- Produzir a saída idêntica à esperada.
const int EstoqueInicial = 40;
const int UnidadesPorCaixa = 15;
int estoque = EstoqueInicial;
Console.WriteLine($"Abertura: {estoque} unidades");
int vendidos = 12;
// registre vendas, lote, devolução e avaria, imprimindo o estoque após cada uma
// calcule a porcentagem vendida nas duas ordens
Abertura: 40 unidades
Após as vendas: 28
Após o lote: 58
Após a devolução: 59
Após a avaria: 58
Vendido: 30% do estoque inicial
Ordem errada daria: 0%
Ver dica
Na divisão entre inteiros, qualquer conta que dê menos de 1 vira 0; organize a expressão para que a divisão seja o último passo.
Ver resposta
// Movimento de estoque de cadernos ao longo de um dia na loja
const int EstoqueInicial = 40;
const int UnidadesPorCaixa = 15;
int estoque = EstoqueInicial;
Console.WriteLine($"Abertura: {estoque} unidades");
int vendidos = 12;
estoque -= vendidos; // vendas do dia
Console.WriteLine($"Após as vendas: {estoque}");
estoque += 2 * UnidadesPorCaixa; // chegaram 2 caixas
Console.WriteLine($"Após o lote: {estoque}");
estoque++; // um cliente devolveu um caderno
Console.WriteLine($"Após a devolução: {estoque}");
estoque--; // um caderno chegou amassado
Console.WriteLine($"Após a avaria: {estoque}");
// Divisão inteira: 12 / 40 dá 0, então a multiplicação vem antes
Console.WriteLine($"Vendido: {vendidos * 100 / EstoqueInicial}% do estoque inicial");
Console.WriteLine($"Ordem errada daria: {vendidos / EstoqueInicial * 100}%");
Passo a passo
- As constantes
EstoqueInicialeUnidadesPorCaixaguardam os dados fixos; o estoque inicial é reaproveitado no cálculo da porcentagem. estoque -= vendidosdesconta as 12 vendas e deixa28unidades.estoque += 2 * UnidadesPorCaixasoma as duas caixas; a multiplicação é avaliada antes do+=, então entram30unidades.estoque++eestoque--ajustam uma unidade cada; sozinhos na linha, só o efeito de somar ou subtrair importa.vendidos * 100 / EstoqueInicialcalcula1200 / 40, que é exatamente30.- A última linha mostra a ordem errada:
12 / 40já vira0na divisão inteira, e0 * 100continua0.
Por que funciona
Cada movimento do estoque é uma alteração do valor atual, e é para isso que existem -=, +=, ++ e --: o código fica curto e mostra a intenção sem repetir estoque = estoque .... Na porcentagem, a precedência entre * e / é a mesma, então a avaliação segue da esquerda para a direita; por isso escrever a multiplicação primeiro preserva o resultado na divisão inteira.
Erros comuns
- Calcular a porcentagem com
vendidos / EstoqueInicial * 100: compila, mas dá0%, porque a divisão inteira acontece antes. Multiplique por 100 primeiro. - Escrever
estoque + 2 * UnidadesPorCaixa;sozinho numa linha: dá erro de compilação, porque uma expressão que só calcula não é uma instrução válida em C#. Useestoque += .... - Mudar o valor de
EstoqueInicialno meio do programa:EstoqueInicial -= vendidos;não compila, porque constantes não podem ser alteradas. Guarde o valor variável emestoque.
Outra forma de resolver
A porcentagem também pode ser calculada com double: double percentual = (double)vendidos / EstoqueInicial * 100;. É a escolha certa quando a conta não fecha em número inteiro, como 7 de 40 (17.5), mas a forma de exibir o número passa a depender da cultura (vírgula ou ponto decimal).
Saída esperada
Abertura: 40 unidades
Após as vendas: 28
Após o lote: 58
Após a devolução: 59
Após a avaria: 58
Vendido: 30% do estoque inicial
Ordem errada daria: 0%
03Pontuação das fases de um jogo de plataformaDesafio
Escreva uma função local CalcularPontos(string fase, int moedas, int inimigos, int danos, int? bonusTempo) que calcule e imprima os pontos de uma fase: 10 por moeda, 50 por inimigo, menos 5 por dano, mais o bônus de tempo (ausente vale zero) e mais 10% quando não houver dano ou o número de inimigos for par. Some três fases, mostre as vidas extras (uma a cada 1000 pontos), quanto falta para a próxima e grave o total como primeiro recorde.
- Calcular a base da fase numa única expressão, sem parênteses, aproveitando a precedência de
*sobre+e-. - Tratar o bônus ausente com
??e gravar o recorde, que começa comoint? recorde = null, com??=. - Testar a paridade dos inimigos com
%e calcular os 10% com divisão inteira. - Usar as fases Floresta (34, 6, 3, 120), Caverna (21, 9, 7, sem bônus) e Castelo (40, 12, 10, 200) e produzir a saída idêntica à esperada.
const int PontosPorMoeda = 10;
const int PontosPorInimigo = 50;
const int PenalidadePorDano = 5;
const int PontosPorVidaExtra = 1000;
int? recorde = null;
int total = 0;
// some as três fases chamando CalcularPontos
// grave o recorde com ??= e mostre total, vidas extras, quanto falta e o recorde
int CalcularPontos(string fase, int moedas, int inimigos, int danos, int? bonusTempo)
{
// calcule, imprima e devolva os pontos da fase
return 0;
}
Floresta: 819 pontos
Caverna: 625 pontos
Castelo: 1265 pontos
Total: 2709 pontos
Vidas extras: 2
Faltam 291 pontos para a próxima vida
Recorde salvo: 2709
Ver dica
Faça a conta da Floresta no papel antes de programar (base, bônus de tempo e só então os 10%); se o seu programa der 819, a ordem dos operadores está certa.
Ver resposta
// Pontuação das fases de um jogo de plataforma
const int PontosPorMoeda = 10;
const int PontosPorInimigo = 50;
const int PenalidadePorDano = 5;
const int PontosPorVidaExtra = 1000;
int? recorde = null; // nenhum recorde salvo ainda
int total = 0;
total += CalcularPontos("Floresta", 34, 6, 3, 120);
total += CalcularPontos("Caverna", 21, 9, 7, null);
total += CalcularPontos("Castelo", 40, 12, 10, 200);
// ??= grava o primeiro recorde só se ainda não existir nenhum
recorde ??= total;
Console.WriteLine($"Total: {total} pontos");
Console.WriteLine($"Vidas extras: {total / PontosPorVidaExtra}");
Console.WriteLine($"Faltam {PontosPorVidaExtra - total % PontosPorVidaExtra} pontos para a próxima vida");
Console.WriteLine($"Recorde salvo: {recorde}");
int CalcularPontos(string fase, int moedas, int inimigos, int danos, int? bonusTempo)
{
// * vem antes de + e -, então cada produto é calculado primeiro
int pontos = moedas * PontosPorMoeda + inimigos * PontosPorInimigo - danos * PenalidadePorDano;
// Fase sem bônus de tempo (null) vale como zero
pontos += bonusTempo ?? 0;
// Bônus de 10% para fase sem dano nenhum ou com número par de inimigos derrotados
if (danos == 0 || inimigos % 2 == 0)
{
pontos += pontos / 10;
}
Console.WriteLine($"{fase}: {pontos} pontos");
return pontos;
}
Passo a passo
- As constantes guardam as regras de pontuação, e
recordeé umint?que começa emnullpara indicar que ainda não há recorde. - Cada chamada a
CalcularPontosdevolve os pontos da fase, etotal +=os acumula; a fase Caverna recebenullcomo bônus de tempo. - Na função,
moedas * PontosPorMoeda + inimigos * PontosPorInimigo - danos * PenalidadePorDanonão precisa de parênteses: os três produtos são calculados antes da soma e da subtração. pontos += bonusTempo ?? 0troca onullpor zero; sem o??, somar umint?a umintproduziria outroint?e a atribuição não compilaria.- A condição
danos == 0 || inimigos % 2 == 0usa o resto para testar se o número de inimigos é par;%e==são avaliados antes de||. pontos += pontos / 10aplica 10% com divisão inteira: na Floresta,745 / 10dá74, e a fase fecha com819.- Fora da função,
recorde ??= totalsó grava o total porque o recorde ainda eranull. total / PontosPorVidaExtradá as vidas extras ePontosPorVidaExtra - total % PontosPorVidaExtracalcula quanto falta para a próxima:%tem precedência sobre-.
Por que funciona
Toda a pontuação é feita com expressões que dependem da precedência: produtos antes de somas, resto antes da comparação, comparações antes do ||. Os valores que podem não existir (o bônus de tempo e o recorde) são int?, e os operadores ?? e ??= resolvem o null num único ponto, sem if espalhados. A divisão inteira aparece duas vezes de propósito: no bônus de 10%, arredondando para baixo, e na contagem de vidas extras, junto com %.
Erros comuns
- Somar o bônus direto:
pontos += bonusTempo;não compila, porque não existe conversão implícita deint?paraint. UsebonusTempo ?? 0. - Esquecer a precedência no que falta para a vida:
(PontosPorVidaExtra - total) % PontosPorVidaExtradá-709, porque os parênteses obrigam a subtração a acontecer antes do resto. Sem eles,%vem primeiro e o resultado é291. - Calcular os 10% com
pontos * (10 / 100):10 / 100é0em divisão inteira, então o bônus some. Usepontos / 10oupontos * 10 / 100. - Testar par com
inimigos / 2 == 0: a divisão só é zero para 0 ou 1 inimigo, e a fase Castelo perderia o bônus. O teste de paridade usa o resto,%.
Outra forma de resolver
Os ajustes da fase também podem ser escritos como uma única expressão com o ternário: int pontos = baseFase + (bonusTempo ?? 0); return danos == 0 || inimigos % 2 == 0 ? pontos + pontos / 10 : pontos;. Fica mais compacto e combina com funções curtas; a versão com += e if é mais fácil de depurar linha a linha e de imprimir antes de devolver o valor.
Saída esperada
Floresta: 819 pontos
Caverna: 625 pontos
Castelo: 1265 pontos
Total: 2709 pontos
Vidas extras: 2
Faltam 291 pontos para a próxima vida
Recorde salvo: 2709