Projeto Calculadora
O primeiro projeto: uma calculadora no terminal. A Ana digita um número, a operação e outro número, e o programa mostra a conta. Parece simples, e é por isso que é um ótimo começo: cabe num arquivo só e, mesmo assim, junta quase tudo o que o Nível 1 ensina. No caminho aparecem três problemas de verdade: a pessoa digita uma palavra no lugar do número, alguém divide por zero e o programa precisa repetir sem você rodar de novo.
- Ler o teclado com Console.ReadLine e converter o texto em número (Parse e TryParse).
- Escolher a conta com switch.
- Proteger o programa de entradas erradas com
double.TryParse. - Separar a conta num método que devolve o texto do resultado.
- Repetir com while, break e continue, e contar as contas.
O que vamos construir
A calculadora pronta, na máquina da Ana:
$ dotnet run calculadora.cs === Calculadora da Ana === Digite sair para terminar. Primeiro número: 10 Operação (+ - * /): + Segundo número: 5 10 + 5 = 15 Primeiro número: 8 Operação (+ - * /): / Segundo número: 0 Não dá para dividir por zero. Primeiro número: abc Isso não é um número. Primeiro número: 2,5 Operação (+ - * /): * Segundo número: 4 2,5 * 4 = 10 Primeiro número: sair Até logo! Contas feitas: 3.
- Dois números e a soma.
- A operação: um
switch. - Entrada segura e divisão por zero (
TryParsee o métodoCalcular). - Repetir até "sair" (
while,breakecontinue).
Antes de começar
Você precisa ter visto Variáveis, Ler do Teclado, Interpolação e if e else; para as versões 2 a 4, switch, Métodos e while. O programa é um arquivo só, calculadora.cs, rodado com dotnet run calculadora.cs (veja Sobre os Projetos):
$ mkdir -p cs/calculadora $ cd cs/calculadora
Versão 1 · Dois números e a soma
O começo de qualquer calculadora: perguntar dois números e somar. O Console.ReadLine() devolve sempre um texto (string), mesmo que a Ana digite 10. Para fazer conta, é preciso converter esse texto num número com double.Parse. Usamos double para aceitar números com vírgula, como 2,5.
Console.WriteLine("=== Calculadora da Ana ===");
Console.Write("Primeiro número: ");
double primeiro = double.Parse(Console.ReadLine()!);
Console.Write("Segundo número: ");
double segundo = double.Parse(Console.ReadLine()!);
double soma = primeiro + segundo;
Console.WriteLine($"{primeiro} + {segundo} = {soma}");
| Linha | O que faz |
|---|---|
Console.Write("Primeiro número: "); | Mostra a pergunta sem pular linha (Write, não WriteLine): o cursor fica logo depois dos dois-pontos, esperando a resposta. |
Console.ReadLine() | Espera a Ana digitar e apertar Enter, e devolve o que ela digitou, como texto. |
! | Diz ao compilador "confie em mim, isto não é null". O ReadLine devolve null quando a entrada acaba; sem o !, o compilador avisaria (CS8604). Na versão 3 trocamos isso por algo mais seguro. |
double.Parse(…) | Converte o texto "10" no número 10. Como a máquina da Ana está em português, a casa decimal é a vírgula: 2,5. |
$"{primeiro} + {segundo} = {soma}" | Uma interpolação: cada {…} é trocado pelo valor da variável. |
$ dotnet run calculadora.cs === Calculadora da Ana === Primeiro número: 10 Segundo número: 5 10 + 5 = 15 $ dotnet run calculadora.cs === Calculadora da Ana === Primeiro número: 7,5 Segundo número: 2,25 7,5 + 2,25 = 9,75
Repare que 10.0 + 5.0 aparece como 15, sem o ,0: o C# não mostra casas decimais que são zero.
Missão (versão 1): além da soma, mostre a subtração no formato 10 - 5 = 5. A entrada da missão é 10 e 5 (as respostas não aparecem na saída, porque ninguém as digitou).
Console.WriteLine("=== Calculadora da Ana ===");
Console.Write("Primeiro número: ");
double primeiro = double.Parse(Console.ReadLine()!);
Console.Write("Segundo número: ");
double segundo = double.Parse(Console.ReadLine()!);
double soma = primeiro + segundo;
Console.WriteLine($"{primeiro} + {segundo} = {soma}");
// mostre também a subtração
Console.WriteLine("=== Calculadora da Ana ===");
Console.Write("Primeiro número: ");
double primeiro = double.Parse(Console.ReadLine()!);
Console.Write("Segundo número: ");
double segundo = double.Parse(Console.ReadLine()!);
double soma = primeiro + segundo;
Console.WriteLine($"{primeiro} + {segundo} = {soma}");
Console.WriteLine($"{primeiro} - {segundo} = {primeiro - segundo}");
Versão 2 · A operação: um switch
Agora a Ana escolhe a conta. Entre os dois números, o programa pergunta a operação e guarda o texto ("+", "-", "*" ou "/"). Para escolher o que fazer com cada um, o switch é mais limpo do que quatro if seguidos.
Console.WriteLine("=== Calculadora da Ana ===");
Console.Write("Primeiro número: ");
double primeiro = double.Parse(Console.ReadLine()!);
Console.Write("Operação (+ - * /): ");
string operacao = Console.ReadLine()!;
Console.Write("Segundo número: ");
double segundo = double.Parse(Console.ReadLine()!);
switch (operacao)
{
case "+":
Console.WriteLine($"{primeiro} + {segundo} = {primeiro + segundo}");
break;
case "-":
Console.WriteLine($"{primeiro} - {segundo} = {primeiro - segundo}");
break;
case "*":
Console.WriteLine($"{primeiro} * {segundo} = {primeiro * segundo}");
break;
case "/":
Console.WriteLine($"{primeiro} / {segundo} = {primeiro / segundo}");
break;
default:
Console.WriteLine($"Operação desconhecida: {operacao}");
break;
}
- string operacao = Console.ReadLine()!;
- A operação fica como texto: não há nada para converter.
- switch (operacao)
- "Conforme o valor de
operacao…". O C# compara o texto com cadacase, de cima para baixo. - case "+":
- Se
operacaofor"+", roda as linhas até obreak. - break;
- Sai do
switch. Em C#, todocasecom código tem de terminar combreak(oureturn); veja o cenário abaixo. - default:
- "Nenhum dos anteriores": a Ana digitou outra coisa, como
%oux.
$ dotnet run calculadora.cs === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): / Segundo número: 3 10 / 3 = 3,3333333333333335 $ dotnet run calculadora.cs === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): % Segundo número: 3 Operação desconhecida: %
O 3,3333333333333335 tem um 5 no fim, e não 3: o double guarda os números em base 2 e não consegue representar 10/3 exatamente (veja float, double e decimal). Para uma calculadora, tudo bem; para dinheiro, use decimal.
break: erro CS0163
A Ana apagou sem querer o break do case "+":
$ dotnet run calculadora.cs
/home/ana/cs/calculadora/calculadora.cs(14,5): error CS0163: Controle não pode passar através de um rótulo case ("case "+":") para outro
Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: na linha 14 está o case "+":. Sem o break, o programa "cairia" do case "+" para dentro do case "-" e faria as duas contas (em inglês, Control cannot fall through from one case label to another). Em C e Java isso é permitido e causa defeitos; o C# proíbe.
Como consertar: termine cada case com break;.
$ dotnet run calculadora.cs === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): / Segundo número: 0 10 / 0 = ∞
O que aconteceu: com double, dividir por zero não é erro: o resultado é o valor especial "infinito", que em português aparece como ∞. (Com int ou decimal, a mesma conta lançaria DivideByZeroException.) Para uma calculadora, ∞ não é uma resposta útil. A versão 3 conserta.
FormatException
$ dotnet run calculadora.cs === Calculadora da Ana === Primeiro número: dez Unhandled exception. System.FormatException: The input string 'dez' was not in a correct format. at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value) at System.Double.Parse(String s) at Program.<Main>$(String[] args) in /home/ana/cs/calculadora/calculadora.cs:line 4 $ echo $? 134
O que significa: "o texto 'dez' não está num formato correto". O double.Parse da linha 4 não sabe ler "dez", lança uma exceção, ninguém a trata e o programa cai com o código 134. Uma calculadora que cai com um erro de digitação não serve. A versão 3 também conserta isso.
Missão (versão 2): a calculadora já soma, subtrai e multiplica. Acrescente o case "/" para a divisão (não esqueça o break). A entrada da missão é 10, / e 4.
Console.WriteLine("=== Calculadora da Ana ===");
Console.Write("Primeiro número: ");
double primeiro = double.Parse(Console.ReadLine()!);
Console.Write("Operação (+ - * /): ");
string operacao = Console.ReadLine()!;
Console.Write("Segundo número: ");
double segundo = double.Parse(Console.ReadLine()!);
switch (operacao)
{
case "+":
Console.WriteLine($"{primeiro} + {segundo} = {primeiro + segundo}");
break;
case "-":
Console.WriteLine($"{primeiro} - {segundo} = {primeiro - segundo}");
break;
case "*":
Console.WriteLine($"{primeiro} * {segundo} = {primeiro * segundo}");
break;
// acrescente aqui o case "/"
default:
Console.WriteLine($"Operação desconhecida: {operacao}");
break;
}
Console.WriteLine("=== Calculadora da Ana ===");
Console.Write("Primeiro número: ");
double primeiro = double.Parse(Console.ReadLine()!);
Console.Write("Operação (+ - * /): ");
string operacao = Console.ReadLine()!;
Console.Write("Segundo número: ");
double segundo = double.Parse(Console.ReadLine()!);
switch (operacao)
{
case "+":
Console.WriteLine($"{primeiro} + {segundo} = {primeiro + segundo}");
break;
case "-":
Console.WriteLine($"{primeiro} - {segundo} = {primeiro - segundo}");
break;
case "*":
Console.WriteLine($"{primeiro} * {segundo} = {primeiro * segundo}");
break;
case "/":
Console.WriteLine($"{primeiro} / {segundo} = {primeiro / segundo}");
break;
default:
Console.WriteLine($"Operação desconhecida: {operacao}");
break;
}
Versão 3 · Entrada segura e divisão por zero
Duas mudanças. Primeiro, o double.Parse vira double.TryParse ("tentar converter"): em vez de cair, ele devolve false quando o texto não é um número, e o programa avisa a Ana. Segundo, a conta vai para um método Calcular, que recebe os dois números e a operação e devolve o texto do resultado. Lá dentro fica fácil tratar a divisão por zero.
Console.WriteLine("=== Calculadora da Ana ===");
Console.Write("Primeiro número: ");
if (!double.TryParse(Console.ReadLine(), out double primeiro))
{
Console.WriteLine("Isso não é um número.");
return;
}
Console.Write("Operação (+ - * /): ");
string operacao = Console.ReadLine() ?? "";
Console.Write("Segundo número: ");
if (!double.TryParse(Console.ReadLine(), out double segundo))
{
Console.WriteLine("Isso não é um número.");
return;
}
Console.WriteLine(Calcular(primeiro, operacao, segundo));
static string Calcular(double a, string operacao, double b)
{
switch (operacao)
{
case "+":
return $"{a} + {b} = {a + b}";
case "-":
return $"{a} - {b} = {a - b}";
case "*":
return $"{a} * {b} = {a * b}";
case "/":
if (b == 0)
{
return "Não dá para dividir por zero.";
}
return $"{a} / {b} = {a / b}";
default:
return $"Operação desconhecida: {operacao}";
}
}
- double.TryParse(Console.ReadLine(), out double primeiro)
- Tenta converter o texto. Se der certo, devolve
truee guarda o número na variável novaprimeiro(oout, veja ref, out e in). Se não der, devolvefalse. OTryParseaceitanull, por isso o!sumiu. - if (!…) { …; return; }
- O
!na frente é o "não" (operadores lógicos): "se não conseguiu converter". Oreturntermina o programa ali mesmo. - Console.ReadLine() ?? ""
- Se a entrada acabou e veio
null, usa um texto vazio (o operador??, veja Operadores ??, ?. e ??=). O texto vazio cai nodefault. - static string Calcular(double a, string operacao, double b)
- Um método que devolve (
return) umstring. Os nomesaebsão só dele: oprimeirochega ema, osegundoemb(Parâmetros). - case "+": return …;
- Com
return, obreaknão é necessário: oreturnjá sai do método inteiro. - if (b == 0) return "Não dá para dividir por zero.";
- Confere antes de dividir. Nada de
∞.
$ dotnet run calculadora.cs === Calculadora da Ana === Primeiro número: dez Isso não é um número. $ dotnet run calculadora.cs === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): / Segundo número: 0 Não dá para dividir por zero. $ dotnet run calculadora.cs === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): * Segundo número: 3 10 * 3 = 30
O programa principal ficou curto: perguntar, perguntar, perguntar, mostrar. Toda a regra das contas mora em Calcular, e ele não lê nem escreve nada no console: recebe valores e devolve um resultado. Um método assim é fácil de testar sozinho (veja Testes) e de reaproveitar, como você vai ver na versão 4.
Missão (versão 3): a divisão ainda dá 10 / 0 = ∞. No case "/" do método Calcular, antes do return da divisão, confira if (b == 0) e devolva "Não dá para dividir por zero.". A entrada da missão é 10, / e 0.
Console.WriteLine("=== Calculadora da Ana ===");
Console.Write("Primeiro número: ");
if (!double.TryParse(Console.ReadLine(), out double primeiro))
{
Console.WriteLine("Isso não é um número.");
return;
}
Console.Write("Operação (+ - * /): ");
string operacao = Console.ReadLine() ?? "";
Console.Write("Segundo número: ");
if (!double.TryParse(Console.ReadLine(), out double segundo))
{
Console.WriteLine("Isso não é um número.");
return;
}
Console.WriteLine(Calcular(primeiro, operacao, segundo));
static string Calcular(double a, string operacao, double b)
{
switch (operacao)
{
case "+":
return $"{a} + {b} = {a + b}";
case "-":
return $"{a} - {b} = {a - b}";
case "*":
return $"{a} * {b} = {a * b}";
case "/":
// confira aqui se b é zero
return $"{a} / {b} = {a / b}";
default:
return $"Operação desconhecida: {operacao}";
}
}
Console.WriteLine("=== Calculadora da Ana ===");
Console.Write("Primeiro número: ");
if (!double.TryParse(Console.ReadLine(), out double primeiro))
{
Console.WriteLine("Isso não é um número.");
return;
}
Console.Write("Operação (+ - * /): ");
string operacao = Console.ReadLine() ?? "";
Console.Write("Segundo número: ");
if (!double.TryParse(Console.ReadLine(), out double segundo))
{
Console.WriteLine("Isso não é um número.");
return;
}
Console.WriteLine(Calcular(primeiro, operacao, segundo));
static string Calcular(double a, string operacao, double b)
{
switch (operacao)
{
case "+":
return $"{a} + {b} = {a + b}";
case "-":
return $"{a} - {b} = {a - b}";
case "*":
return $"{a} * {b} = {a * b}";
case "/":
if (b == 0)
{
return "Não dá para dividir por zero.";
}
return $"{a} / {b} = {a / b}";
default:
return $"Operação desconhecida: {operacao}";
}
}
Versão 4 · Repetir até "sair"
Rodar o programa de novo para cada conta é cansativo. Na versão final, tudo fica dentro de um while (true), um laço "para sempre", e a Ana sai digitando sair no lugar do primeiro número. Quando ela erra um número, o programa não termina mais: avisa e pula para a próxima conta. No fim, mostra quantas contas foram feitas.
Console.WriteLine("=== Calculadora da Ana ===");
Console.WriteLine("Digite sair para terminar.");
int contas = 0;
while (true)
{
Console.WriteLine();
Console.Write("Primeiro número: ");
string entrada = Console.ReadLine() ?? "sair";
if (entrada == "sair")
{
break;
}
if (!double.TryParse(entrada, out double primeiro))
{
Console.WriteLine("Isso não é um número.");
continue;
}
Console.Write("Operação (+ - * /): ");
string operacao = Console.ReadLine() ?? "sair";
Console.Write("Segundo número: ");
if (!double.TryParse(Console.ReadLine(), out double segundo))
{
Console.WriteLine("Isso não é um número.");
continue;
}
Console.WriteLine(Calcular(primeiro, operacao, segundo));
contas++;
}
Console.WriteLine($"Até logo! Contas feitas: {contas}.");
static string Calcular(double a, string operacao, double b)
{
switch (operacao)
{
case "+":
return $"{a} + {b} = {a + b}";
case "-":
return $"{a} - {b} = {a - b}";
case "*":
return $"{a} * {b} = {a * b}";
case "/":
if (b == 0)
{
return "Não dá para dividir por zero.";
}
return $"{a} / {b} = {a / b}";
default:
return $"Operação desconhecida: {operacao}";
}
}
- while (true)
- Um laço sem fim: a condição é sempre verdadeira. Quem decide a hora de parar é o
breaklá dentro (veja while). - Console.WriteLine();
- Uma linha em branco entre uma conta e outra, para a tela ficar legível.
- string entrada = Console.ReadLine() ?? "sair";
- Primeiro guardamos o texto, porque ele pode ser
"sair"ou um número. Se a entrada acabar (null, por exemplo com Ctrl+D no terminal), o??faz de conta que a Ana digitousair. Sem isso, o laço pediria números para sempre. - break;
- Sai do
while. O programa continua na linha depois do laço, o "Até logo!". - continue;
- Pula o resto da volta e começa a próxima: a Ana errou o número, então pedimos tudo de novo (break e continue).
- contas++;
- Soma 1 no contador a cada conta que chegou ao
Calcular(++ e --).
$ dotnet run calculadora.cs === Calculadora da Ana === Digite sair para terminar. Primeiro número: 10 Operação (+ - * /): + Segundo número: 5 10 + 5 = 15 Primeiro número: 7 Operação (+ - * /): % Segundo número: 2 Operação desconhecida: % Primeiro número: sair Até logo! Contas feitas: 2.
A conta com % também entrou no contador: ela chegou ao Calcular, que respondeu "Operação desconhecida". Contar só as contas que deram certo é o desafio 2.
Missão (versão 4): o laço já funciona, mas o fim sempre diz Contas feitas: 0.. Conte cada conta com contas++; logo depois do Console.WriteLine(Calcular(…)). A entrada da missão faz duas contas (10 + 5 e 8 * 2) e depois sair.
Console.WriteLine("=== Calculadora da Ana ===");
Console.WriteLine("Digite sair para terminar.");
int contas = 0;
while (true)
{
Console.WriteLine();
Console.Write("Primeiro número: ");
string entrada = Console.ReadLine() ?? "sair";
if (entrada == "sair")
{
break;
}
if (!double.TryParse(entrada, out double primeiro))
{
Console.WriteLine("Isso não é um número.");
continue;
}
Console.Write("Operação (+ - * /): ");
string operacao = Console.ReadLine() ?? "sair";
Console.Write("Segundo número: ");
if (!double.TryParse(Console.ReadLine(), out double segundo))
{
Console.WriteLine("Isso não é um número.");
continue;
}
Console.WriteLine(Calcular(primeiro, operacao, segundo));
// conte a conta
}
Console.WriteLine($"Até logo! Contas feitas: {contas}.");
static string Calcular(double a, string operacao, double b)
{
switch (operacao)
{
case "+":
return $"{a} + {b} = {a + b}";
case "-":
return $"{a} - {b} = {a - b}";
case "*":
return $"{a} * {b} = {a * b}";
case "/":
if (b == 0)
{
return "Não dá para dividir por zero.";
}
return $"{a} / {b} = {a / b}";
default:
return $"Operação desconhecida: {operacao}";
}
}
Console.WriteLine("=== Calculadora da Ana ===");
Console.WriteLine("Digite sair para terminar.");
int contas = 0;
while (true)
{
Console.WriteLine();
Console.Write("Primeiro número: ");
string entrada = Console.ReadLine() ?? "sair";
if (entrada == "sair")
{
break;
}
if (!double.TryParse(entrada, out double primeiro))
{
Console.WriteLine("Isso não é um número.");
continue;
}
Console.Write("Operação (+ - * /): ");
string operacao = Console.ReadLine() ?? "sair";
Console.Write("Segundo número: ");
if (!double.TryParse(Console.ReadLine(), out double segundo))
{
Console.WriteLine("Isso não é um número.");
continue;
}
Console.WriteLine(Calcular(primeiro, operacao, segundo));
contas++;
}
Console.WriteLine($"Até logo! Contas feitas: {contas}.");
static string Calcular(double a, string operacao, double b)
{
switch (operacao)
{
case "+":
return $"{a} + {b} = {a + b}";
case "-":
return $"{a} - {b} = {a - b}";
case "*":
return $"{a} * {b} = {a * b}";
case "/":
if (b == 0)
{
return "Não dá para dividir por zero.";
}
return $"{a} / {b} = {a / b}";
default:
return $"Operação desconhecida: {operacao}";
}
}
A Ana testou um número com ponto, como nas calculadoras de celular em inglês:
$ dotnet run calculadora.cs === Calculadora da Ana === Digite sair para terminar. Primeiro número: 10.5 Operação (+ - * /): + Segundo número: 1 105 + 1 = 106 Primeiro número: sair Até logo! Contas feitas: 1.
O que aconteceu: em português, o ponto separa os milhares (1.000), e o TryParse leu 10.5 como "cento e cinco". Não é um erro do programa: ele seguiu a cultura da máquina. Na tela, porém, o 105 mostra o que aconteceu, e a Ana percebeu. Como consertar: diga na pergunta qual separador usar ("use vírgula"), ou veja o desafio 4.
O programa completo
A versão final é a versão 4, acima. O que o projeto juntou: Console.ReadLine, TryParse, o operador ??, switch, um método com valor de retorno, while, break e continue e um contador com ++.
Rodando com a entrada de um arquivo
Para testar várias contas de uma vez, a Ana guardou as respostas em contas.txt, uma por linha, e mandou o arquivo para o programa com < (veja Sobre os Projetos). As respostas não aparecem, porque ninguém as digitou:
$ cat contas.txt 10 + 5 sair $ dotnet run calculadora.cs < contas.txt === Calculadora da Ana === Digite sair para terminar. Primeiro número: Operação (+ - * /): Segundo número: 10 + 5 = 15 Primeiro número: Até logo! Contas feitas: 1.
Desafios extras
- Mais operações. Acrescente
%(o resto da divisão) e^(potência, comMath.Pow(a, b); veja Math). - Só as contas certas. Faça o contador subir só quando a conta deu certo. Dica: o
Calcularpode devolver também umbool, numa tupla:(bool Ok, string Texto). - Expressão switch. Reescreva o
Calcularcom uma expressão switch:operacao switch { "+" => …, _ => … }. - Ponto ou vírgula. Antes de converter, troque o ponto por vírgula com
entrada.Replace('.', ',')(veja Métodos de String). Que problema isso cria para quem digita1.000? - Uma linha só. Leia a conta inteira numa linha, como
10 + 5, e separe as partes comSplit(' ').
Exercícios
1. O que Console.ReadLine() devolve quando a Ana digita 10 e aperta Enter?
2. Com double, o que 10.0 / 0 mostra na máquina da Ana?
3. Dentro do while (true), o que o continue faz?
4. Por que o C# dá o erro CS0163 quando falta o break num case?
5. Complete a conversão que não lança exceção:
6. Complete o operador que usa "sair" quando o ReadLine devolve null:
Resumo
Console.ReadLine()devolve texto; para fazer conta, converta comdouble.Parseou, melhor,double.TryParse.Parsecom texto errado lançaFormatExceptione o programa cai (código 134);TryParsedevolvefalsee você decide o que fazer.- O
switchescolhe pela operação; cadacasetermina combreakoureturn(senão,CS0163). - Com
double, dividir por zero dá∞; confira antes de dividir. - Um método que recebe valores e devolve o resultado deixa o programa principal curto e a regra fácil de testar.
while (true)combreakecontinuefaz o menu "até sair";?? "sair"evita o laço infinito quando a entrada acaba.
Próximo projeto: Jogo de Adivinhação.