Uma divisão que perde a vírgula, uma concatenação que transforma 1 + 2 em "12" e um texto que só fica certo com a cultura certa: as operações sobre os valores também têm armadilhas.
Na aula anterior, "Cada Valor Tem um Tipo", aprendemos a guardar dados e escolher o tipo adequado para cada um. Terminamos com um pedido da Livraria que tinha tudo fixo no código: título, preço e quantidade. Hoje vamos fazer contas e comparações com esses valores, montar textos bem formatados e, principalmente, conversar com o usuário. No fim da aula, o pedido deixa de ser fixo e passa a perguntar ao cliente o que ele quer comprar.
Operadores aritméticos
Os cinco operadores básicos são os que você espera: +, -, *, / e % (resto da divisão). A surpresa está na divisão:
Console.WriteLine(7 / 2); // 3
Console.WriteLine(7 / 2.0); // 3.5
Console.WriteLine(7 % 2); // 1
Quando os dois operandos são inteiros, a divisão é inteira: o resultado é truncado e a parte fracionária se perde. Basta que um dos lados seja fracionário para a divisão ser real. É o mesmo comportamento do C, e a mesma fonte de bugs: int media = soma / quantidade; descarta as casas decimais em silêncio. Se você quer a média com vírgula, converta antes de dividir: (double)soma / quantidade.
O resto (%) tem o sinal do número dividido: -17 % 5 dá -2, e não 3. Isso importa quando você usa % para testar se um número é ímpar. n % 2 == 1 falha para ímpares negativos. n % 2 != 0 funciona sempre.
Dividir por zero também depende do tipo:
double infinito = 1.0 / 0; // ∞: não é erro para double
double indefinido = 0.0 / 0; // NaN ("Not a Number")
int zero = 0;
int quebra = 10 / zero; // lança DivideByZeroException
Com double, o padrão IEEE 754 define resultados especiais, e o programa segue rodando com um valor esquisito que pode se espalhar pelos cálculos seguintes. Com inteiros e decimal, o programa para com uma exceção. Nenhum dos dois é "melhor": o importante é saber qual acontece e verificar o divisor quando ele puder ser zero.
Atribuição composta, incremento e precedência
Operações sobre a própria variável têm atalhos:
int estoque = 10;
estoque -= 3; // o mesmo que estoque = estoque - 3; agora 7
estoque *= 2; // 14
estoque++; // 15
estoque--; // 14
++ e -- existem em duas formas. x++ (pós-fixado) usa o valor atual e depois incrementa. ++x (pré-fixado) incrementa antes. A diferença só aparece quando o incremento está no meio de uma expressão maior, e o conselho mais útil é não colocá-lo no meio de expressões. Use x++; sozinho numa linha, e a dúvida desaparece.
A precedência segue a matemática: *, / e % antes de + e -. 2 + 3 * 4 dá 14. Na dúvida, use parênteses: eles não custam nada e deixam a intenção explícita.
Comparações e lógica
Os operadores de comparação (==, !=, <, >, <=, >=) sempre produzem um bool. Para combinar condições existem && (E), || (OU) e ! (NÃO):
int quantidade = 4;
decimal subtotal = 239.60m;
bool ganhaDesconto = quantidade >= 3 && subtotal > 100m;
bool freteGratis = subtotal >= 200m || quantidade >= 5;
bool pagaFrete = !freteGratis;
Console.WriteLine(ganhaDesconto); // True
Console.WriteLine(freteGratis); // True
Console.WriteLine(pagaFrete); // False
&& e || fazem curto-circuito: se o lado esquerdo já decide o resultado, o direito nem é avaliado. Em divisor != 0 && total / divisor > 10, a divisão só acontece se o divisor não for zero. A ordem das condições, portanto, não é só estética: ela protege o código. (Existem também & e | para booleanos, que avaliam os dois lados sempre. Raramente você vai querer isso.)
O operador condicional e o ??
Para escolher entre dois valores numa expressão, existe o operador condicional ?::
decimal percentual = quantidade >= 3 ? 0.10m : 0m;
string rotulo = quantidade == 1 ? "exemplar" : "exemplares";
Leia assim: "a condição é verdadeira? então isto; senão, aquilo". É ótimo para escolhas simples entre dois valores. Para decisões com mais caminhos, o if e o switch, que formalizamos na próxima aula, são mais legíveis.
Outro operador aparece o tempo todo com entrada de usuário: o ??, que fornece um valor substituto quando algo é nulo.
string nome = Console.ReadLine() ?? "";
Console.ReadLine() pode devolver null, por exemplo quando a entrada foi redirecionada de um arquivo que acabou. O ?? diz: "se vier nulo, use a string vazia". O tratamento completo de nulos tem uma aula própria na Fase 3.
Strings: juntando e formatando texto
Strings podem ser concatenadas com +, mas cuidado com a ordem de avaliação:
Console.WriteLine("Total: " + 1 + 2); // Total: 12
Console.WriteLine(1 + 2 + " itens"); // 3 itens
A expressão é avaliada da esquerda para a direita. No primeiro caso, "Total: " + 1 já vira a string "Total: 1", e o 2 é colado depois. No segundo, 1 + 2 é soma de inteiros antes de encontrar a string. Esse tipo de surpresa é o motivo pelo qual o C# moderno prefere a interpolação:
string titulo = "Dom Casmurro";
int quantidade = 3;
decimal total = 161.73m;
Console.WriteLine($"{quantidade} x {titulo}: R$ {total:F2}");
O $ antes das aspas ativa a interpolação. Qualquer expressão entre chaves é avaliada e inserida no texto. Depois dos dois-pontos vem o formato:
decimal preco = 1234.5m;
DateTime hoje = new DateTime(2026, 9, 24);
Console.WriteLine($"{preco:F2}"); // 1234,50 (duas casas decimais)
Console.WriteLine($"{preco:N2}"); // 1.234,50 (com separador de milhar)
Console.WriteLine($"{0.15:P0}"); // 15%
Console.WriteLine($"{hoje:dd/MM/yyyy}"); // 24/09/2026
(As saídas acima assumem um sistema configurado em português. Já vamos resolver isso.)
Também dá para alinhar o texto, informando a largura depois de uma vírgula. Largura positiva alinha à direita, e negativa, à esquerda. É perfeito para tabelas no terminal:
Console.WriteLine($"{"Título",-25}{"Preço",10}");
Console.WriteLine($"{"Dom Casmurro",-25}{59.90m,10:F2}");
Console.WriteLine($"{"O Cortiço",-25}{42.50m,10:F2}");
Título Preço
Dom Casmurro 59,90
O Cortiço 42,50
Caracteres especiais, caminhos e textos longos
Dentro de uma string comum, a barra invertida inicia uma sequência de escape: \n (nova linha), \t (tabulação), \" (aspas) e \\ (a própria barra). Isso atrapalha caminhos do Windows. Para eles existem as strings literais, com @, que desligam os escapes:
string comEscape = "C:\\livros\\catalogo.txt";
string literal = @"C:\livros\catalogo.txt"; // mesmo conteúdo, mais legível
Para textos longos, com várias linhas e aspas no meio (JSON, SQL, HTML), o C# 11 trouxe as strings cruas, delimitadas por três aspas ou mais:
string json = """
{
"titulo": "Dom Casmurro",
"autor": "Machado de Assis"
}
""";
Console.WriteLine(json);
Nada é escapado, as aspas internas ficam como estão, e a indentação à esquerda da linha das aspas finais é removida automaticamente. Elas também aceitam interpolação: $"""...""". Você vai agradecer por esse recurso quando escrevermos SQL na Fase 5.
Métodos úteis de string
Strings vêm com dezenas de operações prontas. As mais usadas:
string entrada = " Dom Casmurro ";
string titulo = entrada.Trim(); // remove espaços das pontas: "Dom Casmurro"
Console.WriteLine(titulo.Length); // 12
Console.WriteLine(titulo.ToUpper()); // DOM CASMURRO
Console.WriteLine(titulo.Contains("Cas")); // True
Console.WriteLine(titulo.StartsWith("Dom")); // True
Console.WriteLine(titulo.IndexOf(' ')); // 3: posição do primeiro espaço
Console.WriteLine(titulo.Substring(0, 3)); // Dom
Console.WriteLine(titulo.Replace("Dom", "Senhor")); // Senhor Casmurro
Console.WriteLine(titulo[0]); // D: o caractere na posição 0
Lembre-se da aula anterior: strings são imutáveis. titulo.ToUpper() não altera titulo. Ela devolve uma string nova, que você precisa guardar se quiser usá-la.
Duas diferenças importantes em relação a outras linguagens. Primeiro, em C# == compara o conteúdo das strings, e não o endereço (ao contrário do Java). Segundo, a comparação é sensível a maiúsculas: "dom" == "Dom" é false. Para ignorar a caixa, diga isso explicitamente:
bool iguais = string.Equals("dom casmurro", "Dom Casmurro",
StringComparison.OrdinalIgnoreCase); // True
Para testar se o usuário deixou um campo em branco, use string.IsNullOrWhiteSpace(texto). Ele trata de uma vez o nulo, a string vazia e a string só com espaços.
A cultura: por que o mesmo programa mostra números diferentes
Na aula anterior vimos que "3,14" e "3.14" são lidos de forma diferente conforme o idioma do sistema. O mesmo vale para a formatação: {total:F2} mostra 161,73 num computador em português e 161.73 num em inglês. Servidores Linux e contêineres, onde muitos programas C# rodam, costumam estar em inglês ou numa cultura neutra.
Quando o programa é feito para um público brasileiro, a forma mais simples de ter um comportamento previsível é fixar a cultura no início:
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("pt-BR");
decimal total = 161.73m;
Console.WriteLine($"{total:C}"); // R$ 161,73
O formato C (moeda) usa o símbolo e as convenções da cultura atual. Repare no using System.Globalization; no topo: os using implícitos do projeto não incluem esse namespace, então precisamos declará-lo. Voltaremos a este tema com calma. Um sistema multilíngue precisa de mais cuidado do que "fixar pt-BR". Mas, para os programas de console do curso, esta linha resolve.
Juntando tudo: um pedido interativo
A Livraria agora conversa com o cliente. Vamos usar o if de maneira informal, como já fizemos com o TryParse. Ele é formalizado na próxima aula.
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("pt-BR");
Console.Write("Título do livro: ");
string titulo = (Console.ReadLine() ?? "").Trim();
Console.Write("Preço unitário (ex.: 59,90): ");
bool precoOk = decimal.TryParse(Console.ReadLine(), out decimal preco);
Console.Write("Quantidade: ");
bool quantidadeOk = int.TryParse(Console.ReadLine(), out int quantidade);
if (string.IsNullOrWhiteSpace(titulo) || !precoOk || !quantidadeOk || preco <= 0 || quantidade <= 0)
{
Console.Error.WriteLine("Dados inválidos. Tente novamente.");
return 1;
}
decimal subtotal = preco * quantidade;
decimal percentual = quantidade >= 3 ? 0.10m : 0m; // desconto só a partir de 3 unidades
decimal desconto = subtotal * percentual;
decimal total = subtotal - desconto;
string rotulo = quantidade == 1 ? "exemplar" : "exemplares";
Console.WriteLine();
Console.WriteLine($"""
Pedido
------
{quantidade} {rotulo} de "{titulo}"
Subtotal: {subtotal,12:C}
Desconto: {desconto,12:C} ({percentual:P0})
Total: {total,12:C}
""");
return 0;
Uma execução:
Título do livro: Dom Casmurro
Preço unitário (ex.: 59,90): 59,90
Quantidade: 3
Pedido
------
3 exemplares de "Dom Casmurro"
Subtotal: R$ 179,70
Desconto: R$ 17,97 (10%)
Total: R$ 161,73
Repare no que usamos:
??eTrim()para limpar a entrada;TryParsepara não quebrar com dados inválidos;- operadores lógicos com curto-circuito para validar tudo numa condição só;
- o operador condicional para escolher o desconto e o plural;
- uma string crua interpolada para montar o resumo sem nenhum
\n.
O problema que ficou pendente na aula passada está resolvido: o desconto só vale a partir de três unidades.
Fontes e leituras recomendadas
- Operadores aritméticos (Microsoft Learn): https://learn.microsoft.com/dotnet/csharp/language-reference/operators/arithmetic-operators. Traz divisão, resto, estouro e os casos especiais de ponto flutuante.
- Interpolação de strings: https://learn.microsoft.com/dotnet/csharp/language-reference/tokens/interpolated. Cobre a sintaxe completa, alinhamento e formatos.
- Cadeias de formato numérico padrão: https://learn.microsoft.com/dotnet/standard/base-types/standard-numeric-format-strings. É a tabela de
C,F,N,Pe os demais especificadores. - Literais de string crua: https://learn.microsoft.com/dotnet/csharp/language-reference/tokens/raw-string. Mostra as regras de indentação e de interpolação das strings com três aspas.
- Práticas recomendadas para comparar strings: https://learn.microsoft.com/dotnet/standard/base-types/best-practices-strings. Explica quando usar comparação ordinal e quando usar a sensível à cultura, e por que isso importa.
Exercícios
Exercício 1
Sem rodar, preveja o resultado de cada linha e depois confira: 17 / 5, 17 % 5, 17 / 5.0, -17 % 5.
Ver resposta
✓ Resposta: 3, 2, 3,4 e -2 (num sistema em inglês, o terceiro aparece como 3.4). 17 / 5 é divisão entre inteiros, e a parte fracionária de 3,4 é descartada. 17 % 5 é o resto: 17 = 5 × 3 + 2. 17 / 5.0 tem um operando double, então a divisão é real. -17 % 5 dá -2 porque, em C#, o resto sempre tem o sinal do dividendo: -17 = 5 × (-3) + (-2).
Exercício 2
Escreva um programa que leia um número inteiro e imprima "par" ou "ímpar" usando o operador condicional. O programa deve funcionar corretamente para números negativos.
Ver resposta
✓ Resposta:
Console.Write("Número: ");
if (!int.TryParse(Console.ReadLine(), out int n))
{
Console.Error.WriteLine("Número inválido.");
return 1;
}
string paridade = n % 2 == 0 ? "par" : "ímpar";
Console.WriteLine($"{n} é {paridade}.");
return 0;
O teste usa n % 2 == 0, que funciona para qualquer sinal. Se o teste fosse n % 2 == 1 para "ímpar", o número -3 seria classificado errado, porque -3 % 2 dá -1, e não 1.
Exercício 3
Imprima uma tabela alinhada com três livros da Livraria, com colunas de título (alinhado à esquerda, 30 caracteres), autor (à esquerda, 20 caracteres) e preço (à direita, 10 caracteres, formato moeda).
Ver resposta
✓ Resposta:
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("pt-BR");
Console.WriteLine($"{"Título",-30}{"Autor",-20}{"Preço",10}");
Console.WriteLine(new string('-', 60));
Console.WriteLine($"{"Dom Casmurro",-30}{"Machado de Assis",-20}{59.90m,10:C}");
Console.WriteLine($"{"O Cortiço",-30}{"Aluísio Azevedo",-20}{42.50m,10:C}");
Console.WriteLine($"{"Vidas Secas",-30}{"Graciliano Ramos",-20}{38.00m,10:C}");
new string('-', 60) cria uma string com 60 hífens, um jeito prático de desenhar a linha separadora. As larguras somam 60 (30 + 20 + 10), e é por isso que a linha tem esse tamanho. Um detalhe honesto: se um título passar de 30 caracteres, a interpolação não o corta, e a tabela desalinha. Tratar isso exige decisões, que ficam para a próxima aula.
Exercício 4
Explique a diferença de saída entre estas duas linhas e reescreva a primeira para que imprima "Total: 3".
Console.WriteLine("Total: " + 1 + 2);
Console.WriteLine(1 + 2 + " itens");
Ver resposta
✓ Resposta: A primeira imprime Total: 12 e a segunda, 3 itens. O + é avaliado da esquerda para a direita. Na primeira linha, "Total: " + 1 produz a string "Total: 1", e somar 2 a uma string significa concatenar. Na segunda, 1 + 2 é uma soma de inteiros (3), e só então o resultado é concatenado ao texto. Para corrigir a primeira, agrupe a soma com parênteses, "Total: " + (1 + 2), ou, melhor ainda, use interpolação: $"Total: {1 + 2}". Na interpolação, a expressão entre chaves é sempre avaliada inteira antes de virar texto, e a ambiguidade desaparece.
Exercício 5
Leia o nome completo de uma pessoa e imprima: o nome sem espaços nas pontas, o nome em maiúsculas, o primeiro nome e a quantidade de caracteres (sem contar os espaços das pontas). Se a pessoa digitar só um nome, o primeiro nome é o próprio nome.
Ver resposta
✓ Resposta:
Console.Write("Nome completo: ");
string nome = (Console.ReadLine() ?? "").Trim();
int espaco = nome.IndexOf(' ');
string primeiroNome = espaco >= 0 ? nome.Substring(0, espaco) : nome;
Console.WriteLine($"Nome: {nome}");
Console.WriteLine($"Maiúsculas: {nome.ToUpper()}");
Console.WriteLine($"Primeiro nome: {primeiroNome}");
Console.WriteLine($"Caracteres: {nome.Length}");
IndexOf(' ') devolve a posição do primeiro espaço ou -1 se não houver nenhum. O operador condicional trata os dois casos: com espaço, Substring(0, espaco) pega os caracteres do início até antes do espaço; sem espaço, o nome inteiro já é o primeiro nome. Sem essa verificação, Substring(0, -1) lançaria uma exceção quando alguém digitasse um nome só. É o tipo de caso de borda que vale procurar sempre que você usa uma posição devolvida por IndexOf.