Operadores, Texto e Conversa com o Usuário

Operadores, Texto e Conversa com o Usuário

Operadores e texto em C#: a divisão inteira que perde a vírgula, o resto com sinal, divisão por zero em double e em int, curto-circuito, ?: e ??, concatenação contra interpolação, formatos e alinhamento, strings cruas, métodos de string, a cultura pt-BR e um pedido interativo.
Linguagem C#

• • 12 min de leitura

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:

  • ?? e Trim() para limpar a entrada;
  • TryParse para 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

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.

Comentários

Mais em Linguagem C#

Do Código-Fonte ao Processo: Instalando o .NET e Entendendo o que Acontece ao Rodar
Do Código-Fonte ao Processo: Instalando o .NET e Entendendo o que Acontece ao Rodar

Instalar o .NET 10 e entender o caminho do código até o processador: SDK e…

Dapper: SQL Seu, Sem a Cerimônia
Dapper: SQL Seu, Sem a Cerimônia

Como o Dapper elimina a cerimônia do ADO.NET sem esconder o SQL: QueryAsync…

Input, física e colisões: fazendo o jogo parecer justo
Input, física e colisões: fazendo o jogo parecer justo

O que faz um jogo parecer justo: ações no lugar de teclas, pulo com buffer…