Projeto Calculadora
O primeiro projeto do guia: uma calculadora que roda no terminal. Ela pede um número, a operação e outro número, mostra o resultado e pergunta se você quer fazer outra conta. A conta em si é a parte fácil. O desafio é fazer o programa não se perder quando alguém divide por zero, escolhe uma operação que não existe ou digita "seis" no lugar de um número.
- Ler números e caracteres do teclado com scanf (e o espaço mágico do
" %c"). - Escolher a conta com else if e depois com switch.
- Recusar a divisão por zero com if.
- Repetir tudo com do while.
- Ler a linha inteira com fgets e converter com
strtod(Texto ↔ Número) para validar a entrada. - Separar o programa em funções.
O que vamos construir
Esta é a calculadora pronta, rodando na máquina da Ana. Tudo o que aparece depois dos dois-pontos nas perguntas foi digitado por ela:
$ ./calculadora === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): + Segundo número: 5 10 + 5 = 15 Outra conta? (s/n): s Primeiro número: seis "seis" não é um número. Tente de novo. « texto no lugar de número Primeiro número: 6 Operação (+ - * /): % Operação desconhecida: % « operação que não existe Operação (+ - * /): * Segundo número: 1.5 6 * 1.5 = 9 Outra conta? (s/n): s Primeiro número: 2,5 "2,5" não é um número. Tente de novo. « no C, o decimal é com ponto Primeiro número: 2.5 Operação (+ - * /): / Segundo número: 0 Não dá para dividir por zero. « divisão por zero Outra conta? (s/n): n Até logo!
Vamos chegar lá em quatro versões. No fim de cada uma, o programa compila e roda:
- Dois números e um operador (
scanf,double,char,else if). - switch e divisão por zero.
- Repetir até a pessoa sair (
do while). - Validar a entrada (
fgets,strtode funções).
Antes de começar
Você precisa saber criar, compilar e rodar um programa (Olá, Mundo e Compilar com gcc), conhecer double e char e ter visto o básico do scanf. Como fazer cada versão no editor ou no terminal está em Sobre os Projetos. No seu computador, crie a pasta do projeto; o arquivo vai se chamar calculadora.c:
$ mkdir -p c/calculadora $ cd c/calculadora
Versão 1 · Dois números e um operador
A calculadora pede o primeiro número, a operação e o segundo número. Os números são double (com casas decimais), porque a Ana quer fazer contas como 7.5 / 2. A operação é um caractere (+, -, * ou /), então cabe num char. Para escolher a conta, uma escada de else if.
#include <stdio.h>
int main(void) {
double a = 0.0;
double b = 0.0;
char op = ' ';
printf("=== Calculadora da Ana ===\n");
printf("Primeiro número: ");
scanf("%lf", &a);
printf("Operação (+ - * /): ");
scanf(" %c", &op);
printf("Segundo número: ");
scanf("%lf", &b);
if (op == '+') {
printf("%g + %g = %g\n", a, b, a + b);
} else if (op == '-') {
printf("%g - %g = %g\n", a, b, a - b);
} else if (op == '*') {
printf("%g * %g = %g\n", a, b, a * b);
} else if (op == '/') {
printf("%g / %g = %g\n", a, b, a / b);
} else {
printf("Operação desconhecida: %c\n", op);
}
return 0;
}
| Linha | O que faz |
|---|---|
double a = 0.0; | Cria a variável do primeiro número, já com zero. Toda variável começa com um valor: em C, uma variável sem valor inicial guarda lixo (veja Variáveis). |
char op = ' '; | A operação, começando com um espaço. Caracteres vão entre aspas simples: '+'. |
printf("Primeiro número: "); | A pergunta sem \n: o cursor fica na mesma linha, e o que a pessoa digitar aparece logo depois dos dois-pontos. |
scanf("%lf", &a); | Espera a pessoa digitar e apertar Enter, converte o texto num double e guarda em a. No scanf, double é %lf ("long float"); o & entrega o endereço de a, para o scanf saber onde guardar (veja Memória e Endereços). |
scanf(" %c", &op); | Lê um caractere. O espaço antes do %c manda pular espaços e Enter que sobraram na entrada. Sem ele, dá errado (veja o cenário abaixo). |
if (op == '+') { … } else if … | Compara a operação com cada possibilidade, em ordem, e faz a primeira que combinar. O último else pega o que não combinou com nada. |
printf("%g + %g = %g\n", a, b, a + b); | %g mostra um double sem zeros inúteis: 15 em vez de 15.000000, 3.75 em vez de 3.750000 (veja Formatos do printf). |
$ gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora $ ./calculadora === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): + Segundo número: 5 10 + 5 = 15 $ ./calculadora === Calculadora da Ana === Primeiro número: 7.5 Operação (+ - * /): / Segundo número: 2 7.5 / 2 = 3.75
O scanf separa os valores por espaços, não por linhas. Se a Ana digitar 10 * 3 na primeira pergunta, os três valores ficam esperando na entrada, e as perguntas seguintes nem param:
$ ./calculadora === Calculadora da Ana === Primeiro número: 10 * 3 Operação (+ - * /): Segundo número: 10 * 3 = 30
O scanf lê números com ponto: 2.5. Veja o que acontece quando a Ana digita 2,5:
$ ./calculadora === Calculadora da Ana === Primeiro número: 2,5 Operação (+ - * /): Segundo número: Operação desconhecida: ,
O scanf("%lf", &a) leu o 2 e parou na vírgula, que não faz parte de um número. A vírgula e o 5 ficaram esperando na entrada. O scanf(" %c", &op) pegou a vírgula como operação, e o segundo scanf pegou o 5, sem esperar a Ana digitar. Na versão 4 a calculadora vai recusar o 2,5.
" %c": a operação vira um Enter
Este código tem um erro de propósito: scanf("%c", &op);, sem o espaço.
$ gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora $ ./calculadora === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): Segundo número: + Operação desconhecida:
O que aconteceu: quando a Ana digitou 10 e apertou Enter, a entrada recebeu 10\n. O %lf leu o 10 e deixou o \n lá. O %c lê qualquer caractere, inclusive o \n, então a operação virou a quebra de linha, sem a Ana digitar nada. Depois, o %lf do segundo número encontrou o +, que não é número, e desistiu. A mensagem mostra a "operação" invisível: uma linha em branco.
Como consertar: um espaço antes do %c: scanf(" %c", &op);. No scanf, um espaço no formato quer dizer "pule todos os espaços, tabs e Enter que vierem". O %d e o %lf já fazem isso sozinhos; o %c não. Mais em Entrada com scanf.
Missão (versão 1): o programa ainda não lê o segundo número e não sabe subtrair. Peça e leia b com scanf("%lf", &b) e acrescente o else if da subtração. A entrada da missão é 10, -, 4: o resultado deve ser 10 - 4 = 6.
#include <stdio.h>
int main(void) {
double a = 0.0;
double b = 0.0;
char op = ' ';
printf("=== Calculadora da Ana ===\n");
printf("Primeiro número: ");
scanf("%lf", &a);
printf("Operação (+ - * /): ");
scanf(" %c", &op);
// peça e leia o segundo número
if (op == '+') {
printf("%g + %g = %g\n", a, b, a + b);
} else if (op == '*') {
printf("%g * %g = %g\n", a, b, a * b);
} else if (op == '/') {
printf("%g / %g = %g\n", a, b, a / b);
} else {
printf("Operação desconhecida: %c\n", op);
}
return 0;
}
#include <stdio.h>
int main(void) {
double a = 0.0;
double b = 0.0;
char op = ' ';
printf("=== Calculadora da Ana ===\n");
printf("Primeiro número: ");
scanf("%lf", &a);
printf("Operação (+ - * /): ");
scanf(" %c", &op);
printf("Segundo número: ");
scanf("%lf", &b);
if (op == '+') {
printf("%g + %g = %g\n", a, b, a + b);
} else if (op == '-') {
printf("%g - %g = %g\n", a, b, a - b);
} else if (op == '*') {
printf("%g * %g = %g\n", a, b, a * b);
} else if (op == '/') {
printf("%g / %g = %g\n", a, b, a / b);
} else {
printf("Operação desconhecida: %c\n", op);
}
return 0;
}
Versão 2 · switch e divisão por zero
O que acontece se a Ana dividir por zero na versão 1? Na matemática, não existe resposta. No C, com double, existe uma resposta estranha:
$ ./calculadora === Calculadora da Ana === Primeiro número: 1 Operação (+ - * /): / Segundo número: 0 1 / 0 = inf $ ./calculadora === Calculadora da Ana === Primeiro número: -1 Operação (+ - * /): / Segundo número: 0 -1 / 0 = -inf
inf é "infinito", um valor especial que o double sabe guardar. O programa não quebrou, mas a resposta não serve para a Ana. (Com int seria pior: dividir um inteiro por zero derruba o programa; veja Operadores Aritméticos.) Então, antes de dividir, a calculadora confere se b é zero.
Aproveitamos para trocar a escada de else if por um switch, que foi feito para isso: comparar uma variável com vários valores fixos. A conta vai para a variável resultado, e um único printf mostra qualquer operação.
#include <stdio.h>
int main(void) {
double a = 0.0;
double b = 0.0;
char op = ' ';
printf("=== Calculadora da Ana ===\n");
printf("Primeiro número: ");
scanf("%lf", &a);
printf("Operação (+ - * /): ");
scanf(" %c", &op);
printf("Segundo número: ");
scanf("%lf", &b);
double resultado = 0.0;
switch (op) {
case '+':
resultado = a + b;
break;
case '-':
resultado = a - b;
break;
case '*':
resultado = a * b;
break;
case '/':
if (b == 0) {
printf("Não dá para dividir por zero.\n");
return 1;
}
resultado = a / b;
break;
default:
printf("Operação desconhecida: %c\n", op);
return 1;
}
printf("%g %c %g = %g\n", a, op, b, resultado);
return 0;
}
- switch (op)
- "Conforme o valor de
op…". Oswitchsó aceita valores inteiros, e umcharé um inteiro pequeno (o código do caractere). - case '+':
- "…se for
'+', comece aqui." - break;
- "Terminei, saia do
switch." Sem ele, o programa continua nocaseseguinte (veja o cenário abaixo). - if (b == 0) { … return 1; }
- Dentro do
case '/': se o divisor é zero, avisa e encerra o programa com o código de saída 1, que diz ao sistema que algo deu errado (0 é "tudo certo"). - default:
- "Se não for nenhum dos anteriores." Mostra o erro e também sai com 1.
- printf("%g %c %g = %g\n", a, op, b, resultado);
- Mostra a conta com a operação escolhida:
%cé o formato de umchar.
$ gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora $ ./calculadora === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): / Segundo número: 4 10 / 4 = 2.5 $ ./calculadora === Calculadora da Ana === Primeiro número: 1 Operação (+ - * /): / Segundo número: 0 Não dá para dividir por zero. $ echo $? 1 « o código de saída do return 1 $ ./calculadora === Calculadora da Ana === Primeiro número: 7 Operação (+ - * /): % Segundo número: 2 Operação desconhecida: % $ echo $? 1
O echo $? é um comando do terminal que mostra o código de saída do último programa. Não se preocupe com ele; é só para você ver o 1 do return 1 (mais em Encerrar o Programa).
break: a conta "escorrega" para o próximo case
Este código tem um erro de propósito: o break depois da soma foi apagado.
case '+':
resultado = a + b;
case '-':
resultado = a - b;
break;
$ gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora
calculadora.c: In function ‘main’:
calculadora.c:19:23: warning: this statement may fall through [-Wimplicit-fallthrough=]
19 | resultado = a + b;
| ~~~~~~~~~~^~~~~~~
calculadora.c:20:9: note: here
20 | case '-':
| ^~~~
$ ./calculadora
=== Calculadora da Ana ===
Primeiro número: 10
Operação (+ - * /): +
Segundo número: 5
10 + 5 = 5
O que significa: "esta instrução pode cair para o próximo" (fall through, "cair através"). Sem o break, depois de somar o programa continuou no case '-' e trocou o resultado por 10 - 5. A conta "10 + 5 = 5" está errada e o programa rodou sem reclamar. Só o aviso do -Wextra denunciou o problema: por isso o guia sempre compila com ele (veja Avisos).
Como consertar: um break; no fim de cada case. Veja mais em switch.
Missão (versão 2): esta versão divide por zero e mostra 1 / 0 = inf. Dentro do case '/', antes da conta, confira se b == 0: se for, mostre Não dá para dividir por zero. e saia com return 1;. A entrada da missão é 1, /, 0.
#include <stdio.h>
int main(void) {
double a = 0.0;
double b = 0.0;
char op = ' ';
printf("=== Calculadora da Ana ===\n");
printf("Primeiro número: ");
scanf("%lf", &a);
printf("Operação (+ - * /): ");
scanf(" %c", &op);
printf("Segundo número: ");
scanf("%lf", &b);
double resultado = 0.0;
switch (op) {
case '+':
resultado = a + b;
break;
case '-':
resultado = a - b;
break;
case '*':
resultado = a * b;
break;
case '/':
// antes de dividir, confira se b é zero
resultado = a / b;
break;
default:
printf("Operação desconhecida: %c\n", op);
return 1;
}
printf("%g %c %g = %g\n", a, op, b, resultado);
return 0;
}
#include <stdio.h>
int main(void) {
double a = 0.0;
double b = 0.0;
char op = ' ';
printf("=== Calculadora da Ana ===\n");
printf("Primeiro número: ");
scanf("%lf", &a);
printf("Operação (+ - * /): ");
scanf(" %c", &op);
printf("Segundo número: ");
scanf("%lf", &b);
double resultado = 0.0;
switch (op) {
case '+':
resultado = a + b;
break;
case '-':
resultado = a - b;
break;
case '*':
resultado = a * b;
break;
case '/':
if (b == 0) {
printf("Não dá para dividir por zero.\n");
return 1;
}
resultado = a / b;
break;
default:
printf("Operação desconhecida: %c\n", op);
return 1;
}
printf("%g %c %g = %g\n", a, op, b, resultado);
return 0;
}
Versão 3 · Repetir até a pessoa sair
Até agora, a calculadora faz uma conta e termina. Para fazer outra, a Ana tem de rodar o programa de novo. A versão 3 pergunta "Outra conta?" e repete enquanto a resposta for s. Como a calculadora sempre faz pelo menos uma conta, o laço certo é o do while: ele roda o bloco primeiro e testa a condição depois.
Duas mudanças no switch: cada case mostra o seu resultado, e a divisão por zero não encerra mais o programa com return 1 (isso pularia o "Outra conta?"). Ela só avisa, e o laço continua.
#include <stdio.h>
int main(void) {
char continuar = 's';
printf("=== Calculadora da Ana ===\n");
do {
double a = 0.0;
double b = 0.0;
char op = ' ';
printf("Primeiro número: ");
scanf("%lf", &a);
printf("Operação (+ - * /): ");
scanf(" %c", &op);
printf("Segundo número: ");
scanf("%lf", &b);
switch (op) {
case '+':
printf("%g + %g = %g\n", a, b, a + b);
break;
case '-':
printf("%g - %g = %g\n", a, b, a - b);
break;
case '*':
printf("%g * %g = %g\n", a, b, a * b);
break;
case '/':
if (b == 0) {
printf("Não dá para dividir por zero.\n");
} else {
printf("%g / %g = %g\n", a, b, a / b);
}
break;
default:
printf("Operação desconhecida: %c\n", op);
}
printf("Outra conta? (s/n): ");
if (scanf(" %c", &continuar) != 1) {
continuar = 'n'; // a entrada acabou: sai do laço
}
} while (continuar == 's' || continuar == 'S');
printf("Até logo!\n");
return 0;
}
- do { … } while (continuar == 's' || continuar == 'S');
- "Faça isto; depois, se a resposta for
souS, faça de novo." O||é o "ou" (veja Operadores Lógicos). Repare no;depois dowhile (…): nodo whileele é obrigatório. - double a = 0.0; (dentro do do)
- As variáveis da conta agora nascem dentro do laço: cada conta começa do zero (veja Escopo). Só
continuarfica fora, porque owhilelá embaixo precisa dela. - if (scanf(" %c", &continuar) != 1)
- O
scanfdevolve quantos valores conseguiu ler. Se a entrada acabou (a pessoa apertou Ctrl+D), ele devolveEOF, que não é 1: então o programa sai do laço em vez de repetir para sempre.
$ gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora $ ./calculadora === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): + Segundo número: 5 10 + 5 = 15 Outra conta? (s/n): s Primeiro número: 3 Operação (+ - * /): * Segundo número: 4 3 * 4 = 12 Outra conta? (s/n): s Primeiro número: 1 Operação (+ - * /): / Segundo número: 0 Não dá para dividir por zero. Outra conta? (s/n): n Até logo!
$ ./calculadora === Calculadora da Ana === Primeiro número: dez Operação (+ - * /): Segundo número: Operação desconhecida: d Outra conta? (s/n): Até logo!
O que aconteceu: o %lf não conseguiu ler dez e deixou o texto na entrada; a ficou com 0. O " %c" da operação pegou o d. O %lf do segundo número esbarrou no e e também falhou. O " %c" do "Outra conta?" pegou o e, que não é s, e o programa terminou sem a Ana ter digitado nada além de dez.
Como consertar: o scanf lê "o que der" e deixa o resto. Para validar, é melhor ler a linha inteira e só depois ver se ela é um número. É a versão 4.
Missão (versão 3): o programa faz uma conta, pergunta "Outra conta?", mas não repete. Coloque tudo, de double a até a leitura de continuar, dentro de do { … } while (continuar == 's' || continuar == 'S');. A entrada da missão faz 10 + 5, responde s, faz 3 * 4 e responde n.
#include <stdio.h>
int main(void) {
char continuar = 's';
printf("=== Calculadora da Ana ===\n");
double a = 0.0;
double b = 0.0;
char op = ' ';
printf("Primeiro número: ");
scanf("%lf", &a);
printf("Operação (+ - * /): ");
scanf(" %c", &op);
printf("Segundo número: ");
scanf("%lf", &b);
switch (op) {
case '+':
printf("%g + %g = %g\n", a, b, a + b);
break;
case '-':
printf("%g - %g = %g\n", a, b, a - b);
break;
case '*':
printf("%g * %g = %g\n", a, b, a * b);
break;
case '/':
if (b == 0) {
printf("Não dá para dividir por zero.\n");
} else {
printf("%g / %g = %g\n", a, b, a / b);
}
break;
default:
printf("Operação desconhecida: %c\n", op);
}
printf("Outra conta? (s/n): ");
if (scanf(" %c", &continuar) != 1) {
continuar = 'n'; // a entrada acabou
}
// coloque tudo, de "double a" até aqui, dentro de um do { ... } while
printf("Até logo!\n");
return 0;
}
#include <stdio.h>
int main(void) {
char continuar = 's';
printf("=== Calculadora da Ana ===\n");
do {
double a = 0.0;
double b = 0.0;
char op = ' ';
printf("Primeiro número: ");
scanf("%lf", &a);
printf("Operação (+ - * /): ");
scanf(" %c", &op);
printf("Segundo número: ");
scanf("%lf", &b);
switch (op) {
case '+':
printf("%g + %g = %g\n", a, b, a + b);
break;
case '-':
printf("%g - %g = %g\n", a, b, a - b);
break;
case '*':
printf("%g * %g = %g\n", a, b, a * b);
break;
case '/':
if (b == 0) {
printf("Não dá para dividir por zero.\n");
} else {
printf("%g / %g = %g\n", a, b, a / b);
}
break;
default:
printf("Operação desconhecida: %c\n", op);
}
printf("Outra conta? (s/n): ");
if (scanf(" %c", &continuar) != 1) {
continuar = 'n'; // a entrada acabou: sai do laço
}
} while (continuar == 's' || continuar == 'S');
printf("Até logo!\n");
return 0;
}
Versão 4 · Validar a entrada com fgets e strtod
A ideia da versão final: nunca ler o número direto do teclado. Primeiro, a calculadora lê a linha inteira que a pessoa digitou, como texto, com fgets. Depois, tenta converter o texto em número com strtod ("string to double", texto para double). Se sobrar alguma coisa que não é número (seis, 2,5, 12abc), ela recusa e pergunta de novo.
Como agora há três perguntas com o mesmo jeito de ler (número, operação, "outra conta?"), o código ficou repetitivo. Hora de criar funções: cada uma faz uma coisa e tem um nome que diz o quê.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Mostra a pergunta e lê uma linha inteira em "linha".
// Se a entrada acabou (Ctrl+D), encerra o programa.
void ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
printf("\nAté logo!\n");
exit(0);
}
linha[strcspn(linha, "\n")] = '\0'; // tira o Enter do fim
}
// Repete a pergunta até a pessoa digitar um número válido.
double ler_numero(const char *pergunta) {
char linha[100];
while (1) {
ler_linha(pergunta, linha, sizeof linha);
char *fim = NULL;
double numero = strtod(linha, &fim);
if (fim != linha && *fim == '\0') {
return numero; // converteu tudo: é um número
}
printf("\"%s\" não é um número. Tente de novo.\n", linha);
}
}
// Repete a pergunta até a pessoa digitar + - * ou /.
char ler_operacao(void) {
char linha[100];
while (1) {
ler_linha("Operação (+ - * /): ", linha, sizeof linha);
if (strlen(linha) == 1 && strchr("+-*/", linha[0]) != NULL) {
return linha[0];
}
printf("Operação desconhecida: %s\n", linha);
}
}
int main(void) {
char resposta[100] = "s";
printf("=== Calculadora da Ana ===\n");
while (resposta[0] == 's' || resposta[0] == 'S') {
double a = ler_numero("Primeiro número: ");
char op = ler_operacao();
double b = ler_numero("Segundo número: ");
if (op == '/' && b == 0) {
printf("Não dá para dividir por zero.\n");
} else {
double r = 0.0;
switch (op) {
case '+': r = a + b; break;
case '-': r = a - b; break;
case '*': r = a * b; break;
case '/': r = a / b; break;
}
printf("%g %c %g = %g\n", a, op, b, r);
}
ler_linha("Outra conta? (s/n): ", resposta, sizeof resposta);
}
printf("Até logo!\n");
return 0;
}
| Trecho | O que faz |
|---|---|
#include <stdlib.h> e <string.h> | stdlib.h traz strtod e exit; string.h traz strcspn, strlen e strchr (veja Funções de String). |
void ler_linha(const char *pergunta, char *linha, int tamanho) | Mostra a pergunta e lê uma linha em linha. Recebe o tamanho do array para o fgets nunca escrever além dele. |
fgets(linha, tamanho, stdin) == NULL | O fgets devolve NULL quando a entrada acabou (Ctrl+D). Aí a calculadora se despede e encerra com exit(0). |
linha[strcspn(linha, "\n")] = '\0'; | O fgets guarda o Enter (\n) no fim da linha. strcspn acha a posição do \n, e o '\0' corta a string ali. |
double numero = strtod(linha, &fim); | Converte o começo da linha num double e aponta fim para o primeiro caractere que não entrou no número. Em "12abc", fim aponta para o a. |
fim != linha && *fim == '\0' | Duas condições: fim != linha = "converteu pelo menos um caractere" (em "seis", fim fica no começo); *fim == '\0' = "não sobrou nada depois do número". |
while (1) { … return numero; … } | Um laço "para sempre", que só termina pelo return, quando o número é válido. |
strlen(linha) == 1 && strchr("+-*/", linha[0]) != NULL | A operação tem de ser exatamente um caractere, e esse caractere tem de estar na string "+-*/". O strchr procura um caractere numa string e devolve NULL se não achar. |
char resposta[100] = "s"; | O "Outra conta?" também é lido com fgets. Começa com "s" para o while entrar na primeira vez. |
scanf e fgets?
O scanf("%lf") deixa o Enter na entrada. Se um fgets vier depois, ele lê esse Enter que sobrou e devolve uma linha vazia, sem esperar a pessoa. Por isso a versão 4 lê tudo com fgets: os números, a operação e o "Outra conta?". Mais em Ler uma Linha.
$ gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora $ ./calculadora === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): + Segundo número: 5 10 + 5 = 15 Outra conta? (s/n): s Primeiro número: seis "seis" não é um número. Tente de novo. Primeiro número: 6 Operação (+ - * /): % Operação desconhecida: % Operação (+ - * /): * Segundo número: 1.5 6 * 1.5 = 9 Outra conta? (s/n): s Primeiro número: 12abc "12abc" não é um número. Tente de novo. « sobrou "abc" depois do 12 Primeiro número: 12 Operação (+ - * /): x Operação desconhecida: x Operação (+ - * /): / Segundo número: 0 Não dá para dividir por zero. Outra conta? (s/n): n Até logo!
Missão (versão 4): a função ler_numero converte com strtod, mas aceita qualquer coisa: abc vira 0. Passe um char *fim para o strtod e só devolva o número se fim != linha && *fim == '\0'; senão, mostre "abc" não é um número. Tente de novo. (com o texto digitado) e deixe o laço repetir a pergunta. A entrada da missão é abc, 10, +, 5, n.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Mostra a pergunta e lê uma linha inteira em "linha".
// Se a entrada acabou (Ctrl+D), encerra o programa.
void ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
printf("\nAté logo!\n");
exit(0);
}
linha[strcspn(linha, "\n")] = '\0'; // tira o Enter do fim
}
// Repete a pergunta até a pessoa digitar um número válido.
double ler_numero(const char *pergunta) {
char linha[100];
while (1) {
ler_linha(pergunta, linha, sizeof linha);
double numero = strtod(linha, NULL);
// confira se a linha inteira era um número;
// se não era, mostre a mensagem e repita a pergunta
return numero;
}
}
// Repete a pergunta até a pessoa digitar + - * ou /.
char ler_operacao(void) {
char linha[100];
while (1) {
ler_linha("Operação (+ - * /): ", linha, sizeof linha);
if (strlen(linha) == 1 && strchr("+-*/", linha[0]) != NULL) {
return linha[0];
}
printf("Operação desconhecida: %s\n", linha);
}
}
int main(void) {
char resposta[100] = "s";
printf("=== Calculadora da Ana ===\n");
while (resposta[0] == 's' || resposta[0] == 'S') {
double a = ler_numero("Primeiro número: ");
char op = ler_operacao();
double b = ler_numero("Segundo número: ");
if (op == '/' && b == 0) {
printf("Não dá para dividir por zero.\n");
} else {
double r = 0.0;
switch (op) {
case '+': r = a + b; break;
case '-': r = a - b; break;
case '*': r = a * b; break;
case '/': r = a / b; break;
}
printf("%g %c %g = %g\n", a, op, b, r);
}
ler_linha("Outra conta? (s/n): ", resposta, sizeof resposta);
}
printf("Até logo!\n");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Mostra a pergunta e lê uma linha inteira em "linha".
// Se a entrada acabou (Ctrl+D), encerra o programa.
void ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
printf("\nAté logo!\n");
exit(0);
}
linha[strcspn(linha, "\n")] = '\0'; // tira o Enter do fim
}
// Repete a pergunta até a pessoa digitar um número válido.
double ler_numero(const char *pergunta) {
char linha[100];
while (1) {
ler_linha(pergunta, linha, sizeof linha);
char *fim = NULL;
double numero = strtod(linha, &fim);
if (fim != linha && *fim == '\0') {
return numero; // converteu tudo: é um número
}
printf("\"%s\" não é um número. Tente de novo.\n", linha);
}
}
// Repete a pergunta até a pessoa digitar + - * ou /.
char ler_operacao(void) {
char linha[100];
while (1) {
ler_linha("Operação (+ - * /): ", linha, sizeof linha);
if (strlen(linha) == 1 && strchr("+-*/", linha[0]) != NULL) {
return linha[0];
}
printf("Operação desconhecida: %s\n", linha);
}
}
int main(void) {
char resposta[100] = "s";
printf("=== Calculadora da Ana ===\n");
while (resposta[0] == 's' || resposta[0] == 'S') {
double a = ler_numero("Primeiro número: ");
char op = ler_operacao();
double b = ler_numero("Segundo número: ");
if (op == '/' && b == 0) {
printf("Não dá para dividir por zero.\n");
} else {
double r = 0.0;
switch (op) {
case '+': r = a + b; break;
case '-': r = a - b; break;
case '*': r = a * b; break;
case '/': r = a / b; break;
}
printf("%g %c %g = %g\n", a, op, b, r);
}
ler_linha("Outra conta? (s/n): ", resposta, sizeof resposta);
}
printf("Até logo!\n");
return 0;
}
O programa completo
Esta é a calculadora final, a mesma da versão 4. Copie, compile com gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora e use à vontade:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Mostra a pergunta e lê uma linha inteira em "linha".
// Se a entrada acabou (Ctrl+D), encerra o programa.
void ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
printf("\nAté logo!\n");
exit(0);
}
linha[strcspn(linha, "\n")] = '\0'; // tira o Enter do fim
}
// Repete a pergunta até a pessoa digitar um número válido.
double ler_numero(const char *pergunta) {
char linha[100];
while (1) {
ler_linha(pergunta, linha, sizeof linha);
char *fim = NULL;
double numero = strtod(linha, &fim);
if (fim != linha && *fim == '\0') {
return numero; // converteu tudo: é um número
}
printf("\"%s\" não é um número. Tente de novo.\n", linha);
}
}
// Repete a pergunta até a pessoa digitar + - * ou /.
char ler_operacao(void) {
char linha[100];
while (1) {
ler_linha("Operação (+ - * /): ", linha, sizeof linha);
if (strlen(linha) == 1 && strchr("+-*/", linha[0]) != NULL) {
return linha[0];
}
printf("Operação desconhecida: %s\n", linha);
}
}
int main(void) {
char resposta[100] = "s";
printf("=== Calculadora da Ana ===\n");
while (resposta[0] == 's' || resposta[0] == 'S') {
double a = ler_numero("Primeiro número: ");
char op = ler_operacao();
double b = ler_numero("Segundo número: ");
if (op == '/' && b == 0) {
printf("Não dá para dividir por zero.\n");
} else {
double r = 0.0;
switch (op) {
case '+': r = a + b; break;
case '-': r = a - b; break;
case '*': r = a * b; break;
case '/': r = a / b; break;
}
printf("%g %c %g = %g\n", a, op, b, r);
}
ler_linha("Outra conta? (s/n): ", resposta, sizeof resposta);
}
printf("Até logo!\n");
return 0;
}
Repare no que o projeto juntou: scanf, else if, switch, do while, while, fgets, strtod, funções com parâmetros e retorno. Nenhum desses assuntos é difícil sozinho; o projeto mostra como eles se encaixam.
Rodando no laboratório
A sessão do começo da página, na máquina da Ana, com a versão final:
$ gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora $ ./calculadora === Calculadora da Ana === Primeiro número: 10 Operação (+ - * /): + Segundo número: 5 10 + 5 = 15 Outra conta? (s/n): s Primeiro número: seis "seis" não é um número. Tente de novo. Primeiro número: 6 Operação (+ - * /): % Operação desconhecida: % Operação (+ - * /): * Segundo número: 1.5 6 * 1.5 = 9 Outra conta? (s/n): s Primeiro número: 2,5 "2,5" não é um número. Tente de novo. Primeiro número: 2.5 Operação (+ - * /): / Segundo número: 0 Não dá para dividir por zero. Outra conta? (s/n): n Até logo!
Desafios extras
- Resto da divisão. Acrescente a operação
%. Cuidado: o%do C só funciona com inteiros. Paradouble, usefmod(a, b), do<math.h>(veja Matemática). E o resto da divisão por zero também não existe. - Potência. Acrescente
^compow(a, b), também do<math.h>. - Histórico. Guarde os resultados num array
double historico[100]e mostre todos antes do "Até logo!" (veja Arrays). - Duas casas decimais. Mostre o resultado com
%.2fno lugar de%ge compare10 / 3nos dois formatos. - Números estranhos. O
strtodaceita1e3(mil),infenan. Experimente na calculadora. Como você recusariainfenan? (Dica:isfinite, do<math.h>.)
$ gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora /usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccua0ZF3.o: na função "main": calculadora.c:(.text+0x39e): referência não definida para "fmod" collect2: error: ld returned 1 exit status $ gcc -std=c23 -Wall -Wextra calculadora.c -o calculadora -lm $ ./calculadora === Calculadora da Ana === Primeiro número: 7.5 Operação (+ - * /): % Segundo número: 2 7.5 % 2 = 1.5 Outra conta? (s/n): n Até logo!
O que significa: o código compilou, mas o ligador (ld, a etapa que junta o seu programa com as bibliotecas) não achou a função fmod: "referência não definida" (em inglês, undefined reference to). No Linux, as funções de math.h ficam numa biblioteca separada, a libm, que o gcc não junta sozinho.
Como consertar: acrescente -lm ("link math", junte a biblioteca de matemática) no fim do comando. O editor do guia já junta a libm sozinho, por isso a missão abaixo funciona sem ele. Veja Matemática e Bibliotecas.
Desafio 1 no editor: acrescente a operação % com fmod. Mude quatro lugares: o #include <math.h>, a string "+-*/" de ler_operacao, o switch e o if da divisão por zero (que agora vale para / e para %). Com a entrada da missão, a calculadora deve mostrar 10 % 3 = 1, 7.5 % 2 = 1.5 e recusar 7 % 0.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Mostra a pergunta e lê uma linha inteira em "linha".
// Se a entrada acabou (Ctrl+D), encerra o programa.
void ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
printf("\nAté logo!\n");
exit(0);
}
linha[strcspn(linha, "\n")] = '\0'; // tira o Enter do fim
}
// Repete a pergunta até a pessoa digitar um número válido.
double ler_numero(const char *pergunta) {
char linha[100];
while (1) {
ler_linha(pergunta, linha, sizeof linha);
char *fim = NULL;
double numero = strtod(linha, &fim);
if (fim != linha && *fim == '\0') {
return numero; // converteu tudo: é um número
}
printf("\"%s\" não é um número. Tente de novo.\n", linha);
}
}
// Repete a pergunta até a pessoa digitar + - * ou /.
char ler_operacao(void) {
char linha[100];
while (1) {
ler_linha("Operação (+ - * /): ", linha, sizeof linha);
if (strlen(linha) == 1 && strchr("+-*/", linha[0]) != NULL) {
return linha[0];
}
printf("Operação desconhecida: %s\n", linha);
}
}
int main(void) {
char resposta[100] = "s";
printf("=== Calculadora da Ana ===\n");
while (resposta[0] == 's' || resposta[0] == 'S') {
double a = ler_numero("Primeiro número: ");
char op = ler_operacao();
double b = ler_numero("Segundo número: ");
if (op == '/' && b == 0) {
printf("Não dá para dividir por zero.\n");
} else {
double r = 0.0;
switch (op) {
case '+': r = a + b; break;
case '-': r = a - b; break;
case '*': r = a * b; break;
case '/': r = a / b; break;
}
printf("%g %c %g = %g\n", a, op, b, r);
}
ler_linha("Outra conta? (s/n): ", resposta, sizeof resposta);
}
printf("Até logo!\n");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
// Mostra a pergunta e lê uma linha inteira em "linha".
// Se a entrada acabou (Ctrl+D), encerra o programa.
void ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
printf("\nAté logo!\n");
exit(0);
}
linha[strcspn(linha, "\n")] = '\0'; // tira o Enter do fim
}
// Repete a pergunta até a pessoa digitar um número válido.
double ler_numero(const char *pergunta) {
char linha[100];
while (1) {
ler_linha(pergunta, linha, sizeof linha);
char *fim = NULL;
double numero = strtod(linha, &fim);
if (fim != linha && *fim == '\0') {
return numero; // converteu tudo: é um número
}
printf("\"%s\" não é um número. Tente de novo.\n", linha);
}
}
// Repete a pergunta até a pessoa digitar + - * ou /.
char ler_operacao(void) {
char linha[100];
while (1) {
ler_linha("Operação (+ - * /): ", linha, sizeof linha);
if (strlen(linha) == 1 && strchr("+-*/%", linha[0]) != NULL) {
return linha[0];
}
printf("Operação desconhecida: %s\n", linha);
}
}
int main(void) {
char resposta[100] = "s";
printf("=== Calculadora da Ana ===\n");
while (resposta[0] == 's' || resposta[0] == 'S') {
double a = ler_numero("Primeiro número: ");
char op = ler_operacao();
double b = ler_numero("Segundo número: ");
if ((op == '/' || op == '%') && b == 0) {
printf("Não dá para dividir por zero.\n");
} else {
double r = 0.0;
switch (op) {
case '+': r = a + b; break;
case '-': r = a - b; break;
case '*': r = a * b; break;
case '/': r = a / b; break;
case '%': r = fmod(a, b); break;
}
printf("%g %c %g = %g\n", a, op, b, r);
}
ler_linha("Outra conta? (s/n): ", resposta, sizeof resposta);
}
printf("Até logo!\n");
return 0;
}
Exercícios
1. Por que a calculadora usa scanf(" %c", &op), com um espaço antes do %c?
2. Na versão 2, a Ana apagou o break depois de resultado = a * b;. O que acontece com 6 * 2?
3. Com strtod(linha, &fim) e linha igual a "12abc", para onde aponta fim?
4. Por que a versão 3 usa do while e não while?
5. Complete o formato do scanf que lê um double na variável a:
6. No terminal, que opção você acrescenta ao fim do comando do gcc quando aparece referência não definida para "fmod"?
Resumo
scanf("%lf", &a)lê umdouble;scanf(" %c", &op)lê um caractere, e o espaço pula o Enter que sobrou.- O
switchcompara uma variável com vários valores; cadacasetermina combreak, e o-Wextraavisa quando falta. - Dividir um
doublepor zero dáinf: confira o divisor antes. do { … } while (…);repete pelo menos uma vez; confira o retorno doscanfpara não repetir para sempre quando a entrada acaba.- Para validar: leia a linha com
fgets, converta comstrtod(linha, &fim)e aceite só sefim != linha && *fim == '\0'. fmod,powe as outras demath.hpedem-lmno gcc.
Próximo projeto: Jogo de Adivinhação.