04. Entrada e Saída de Dados e Pré-Processador
Domina as diretivas do pré-processador (#define, #include e bibliotecas .h), as funções fundamentais de E/S pela consola (getchar, putchar, printf, scanf), formatação avançada de campos e precisão, e exercícios práticos com soluções interativas.
Entrada e Saída de Dados e Diretivas do Pré-Processador
Nesta aula vamos explorar os mecanismos através dos quais um programa em C comunica com o mundo exterior. Com base nos tópicos da apresentação Aula 4 — Entrada e Saída de Dados, irás compreender o papel do pré-processador e das bibliotecas de cabeçalho (.h), o funcionamento de funções de caractere único (getchar e putchar) e dominar as funções de entrada e saída formatada (printf e scanf), incluindo o controlo de larguras de campo, precisão decimal e a gestão do buffer do teclado.
No final, poderás resolver vários exercícios práticos com soluções interativas ocultas, que podes revelar com um simples clique.
1. Diretivas para o Pré-Processador
Sempre que um programa em C é compilado, o processo inicia-se com duas operações fundamentais:
- Pré-processamento: O pré-processador lê o código-fonte e executa todas as instruções que lhe são especificamente dirigidas (as diretivas de pré-processamento).
- Compilação propriamente dita: O código expandido resultante é traduzido em código-máquina.
As diretivas de pré-processamento não fazem parte da gramática da linguagem C, mas ampliam as suas capacidades e conferem ao programador controlo sobre o processo de compilação.
Regras Sintáticas das Diretivas
- Começam sempre pelo símbolo cardinal (
#). - Nunca terminam em ponto e vírgula (
;).
2. As Diretivas #define e #include
A Diretiva #define (Constantes Simbólicas e Macros)
A diretiva #define serve para definir macros. Um caso particular muito frequente é a definição de constantes simbólicas:
#define NOTA_MAX 20
#define PI 3.14159265
#define NOME_CURSO "Linguagem C"
- Como funciona? Uma constante simbólica não existe fisicamente numa posição de memória RAM. Antes de o compilador iniciar a análise do código, o pré-processador efetua uma substituição textual direta de todas as ocorrências de
NOTA_MAXpelo texto20. - Vantagens: Facilita a manutenção do código (alterar o valor num único local) e torna a leitura mais expressiva e legível.
A Diretiva #include
A diretiva #include instrui o pré-processador a substituir a própria linha pelo conteúdo integral do ficheiro designado. Permite separar o protótipo de funções e tipos da sua implementação real, sendo os ficheiros habitualmente designados por header files (com extensão .h).
Existem duas formas de sintaxe:
// 1. Delimitado por < > :
#include <stdio.h>
#include <math.h>
// 2. Delimitado por " " :
#include "meu_modulo.h"
#include "utilitarios/geometria.h"
| Sintaxe | Local de Procura do Ficheiro | Utilização Típica |
|---|---|---|
<ficheiro.h> |
Diretórios padrão do sistema / do compilador | Ficheiros de cabeçalho da biblioteca padrão do C |
"ficheiro.h" |
Diretório de trabalho atual do projeto (ou caminho relativo/absoluto) | Ficheiros de cabeçalho desenvolvidos pelo programador |
Principais Ficheiros de Cabeçalho da Biblioteca Padrão
Ficheiro .h |
Conteúdo e Finalidade Principal |
|---|---|
<stdio.h> |
Protótipos de funções de entrada/saída de dados e fluxos (stdin, stdout, stderr). |
<stdlib.h> |
Funções utilitárias: conversões de tipos (atoi, atof), geração de números aleatórios (rand), controlo de processos (exit) e alocação dinâmica. |
<string.h> |
Tratamento e manipulação de cadeias de caracteres (strlen, strcpy, strcat, strcmp). |
<ctype.h> |
Teste e classificação de caracteres (isalpha, isdigit, isupper, tolower). |
<limits.h> |
Definição de constantes com os limites numéricos da máquina (INT_MAX, INT_MIN, CHAR_BIT). |
<math.h> |
Funções matemáticas (sqrt, pow, sin, cos, fabs, floor, ceil). (Nota: ao compilar no Linux com GCC, adiciona a flag -lm). |
<malloc.h> |
Protótipos para alocação e gestão de memória dinâmica. |
3. Entrada e Saída pela Consola
Ao contrário de outras linguagens (como Python ou Pascal), a linguagem C não possui instruções nativas de E/S na sua sintaxe. Todas as operações de entrada e saída são asseguradas por funções da biblioteca padrão declaradas em <stdio.h>:
getchar()eputchar(): Leitura e escrita de caracteres individuais.printf()escanf(): Saída e entrada com formatação avançada.
4. Leitura e Escrita de um Único Caractere: getchar e putchar
Quando precisamos de processar texto caractere a caractere, o uso de getchar() e putchar() é mais simples e rápido do que scanf e printf:
A Função getchar()
- É invocada sem qualquer argumento:
ch = getchar(); - Devolve um valor inteiro correspondente ao código ASCII do caractere introduzido na consola (ou a constante
EOFem caso de fim de ficheiro).
A Função putchar(ch)
- Recebe como argumento o caractere a apresentar:
putchar(ch); - Escreve esse caractere na saída padrão e devolve o mesmo valor escrito.
#include <stdio.h>
int main(void) {
int ch;
printf("Digita uma letra e pressiona [ENTER]: ");
ch = getchar(); // Lê o primeiro caractere da consola
printf("O caractere digitado foi: ");
putchar(ch); // Escreve o caractere
putchar('\n'); // Nova linha
return 0;
}
5. Saída Formatada: A Função printf()
A função printf() apresenta dados no ecrã de acordo com um formato especificado:
int printf(const char *string_de_formatacao, arg1, arg2, ...);
- Valor de Retorno: Devolve um número inteiro correspondente ao total de caracteres efetivamente escritos no ecrã.
- Primeiro Argumento (
string_de_formatacao): Obrigatório. Contém o texto fixo, sequências de escape (ex.:\n,\t) e especificadores de formato (iniciados por%). - Deve existir uma correspondência de tipo e ordem entre cada especificador e os argumentos seguintes:
int num = 5;
printf("Mostrar %d valores: %d e %f\n", 2, num, num / 2.0);
// Saída: Mostrar 2 valores: 5 e 2.500000
Especificadores de Formato Mais Usados
| Especificador | Significado / Tipo de Dado |
|---|---|
%d ou %i |
Inteiro em decimal com sinal (int) |
%u |
Inteiro em decimal sem sinal (unsigned int) |
%o |
Inteiro em formato octal (sem sinal) |
%x ou %X |
Inteiro em formato hexadecimal (sem sinal, minúsculas ou maiúsculas) |
%c |
Um único caractere (char) |
%s |
Cadeia de caracteres (string terminada em \0) |
%f |
Vírgula flutuante decimal (float ou double) |
%e ou %E |
Vírgula flutuante em notação científica |
%p |
Endereço de memória / apontador |
%hd |
Inteiro curto (short int) |
%ld |
Inteiro longo (long int) |
%% |
Imprime o símbolo literal % |
Alinhamento e Largura Mínima de Campo
Podemos indicar uma largura mínima de caracteres antes do especificador:
%10d: Reserva no mínimo 10 colunas, com alinhamento à direita.%-10d: Reserva no mínimo 10 colunas, com alinhamento à esquerda (usando o sinal-).%3d: Reserva 3 colunas (se o número precisar de mais, o campo expande automaticamente sem cortar).
#include <stdio.h>
int main(void) {
int valor = 123;
printf("O valor %10d fica alinhado à direita\n", valor);
printf("O valor %-10d fica alinhado à esquerda\n", valor);
printf("O valor %3d fica completo\n", valor);
return 0;
}
Saída Produzida:
O valor 123 fica alinhado à direita
O valor 123 fica alinhado à esquerda
O valor 123 fica completo
Controlo de Precisão Decimal
Em números de ponto flutuante, a precisão controla a quantidade de casas decimais após a vírgula:
#include <stdio.h>
#include <math.h> // Para sqrt()
int main(void) {
double r = sqrt(2.0); // Raiz quadrada de 2 ≈ 1.41421356...
printf("Raiz de 2 = %.20f\n", r);
printf("Raiz de 2 = %.20f\n", (float)r);
printf("Raiz de 2 = %2.10f\n", r);
printf("Raiz de 2 = %10.2f\n", r);
printf("Raiz de 2 = %10f\n", r);
printf("Raiz de 2 = %10.0f\n", r);
return 0;
}
Saída Produzida:
Raiz de 2 = 1.41421356237309510000
Raiz de 2 = 1.41421353816986080000
Raiz de 2 = 1.4142135624
Raiz de 2 = 1.41
Raiz de 2 = 1.414214
Raiz de 2 = 1
6. Entrada Formatada: A Função scanf()
A função scanf() permite ler dados a partir da entrada padrão (stdin), convertendo-os automaticamente de texto para o tipo de dado indicado:
int scanf(const char *string_de_controlo, &arg1, &arg2, ...);
O Operador de Endereço &
Os argumentos do scanf() devem ser endereços de memória onde os dados lidos serão armazenados. Para variáveis primitivas, prefixamos a variável com o operador & (&num).
Exemplos de Utilização de scanf
int num, a, b;
unsigned int u;
// 1. Leitura de inteiro em base decimal
scanf("%d", &num);
// 2. Leitura de inteiro com limite máximo de 4 dígitos
scanf("%4d", &num);
// 3. Leitura de número sem sinal (unsigned)
scanf("%u", &u);
// 4. Leitura de número em base octal
scanf("%o", &num);
// 5. Leitura de número em base hexadecimal
scanf("%x", &num);
// 6. Leitura de dois inteiros separados obrigatoriamente por vírgula no terminal
scanf("%d,%d", &a, &b);
O Buffer do Teclado e a Leitura de Caracteres (" %c")
Ao ler números com %d ou %f, o scanf ignora automaticamente quebras de linha (\n) ou espaços anteriores. No entanto, ao usar %c, qualquer caractere pendente no buffer (como o \n da tecla Enter anterior) é consumido de imediato!
Para descartar caracteres brancos residuais antes de ler um caractere, coloca um espaço antes do %c:
char opcao;
printf("Deseja continuar (s/n)? ");
scanf(" %c", &opcao); // O espaço antes de %c limpa o buffer!
7. Exercícios Práticos com Soluções Interativas
Experimenta resolver os exercícios no teu computador antes de clicares para ver a solução!
Exercício 1: Eco em Maiúsculas com getchar e putchar
Enunciado: Escreve um programa que leia um caractere do utilizador através de getchar(). Se o caractere for uma letra minúscula ('a' a 'z'), converte-o para maiúscula e imprime-o com putchar(). Caso contrário, imprime o caractere inalterado.
👉 Ver Solução Proposta
#include <stdio.h>
int main(void) {
int c;
printf("Digita uma letra: ");
c = getchar();
// Se for minúscula, converte subtraindo ('a' - 'A') = 32
if (c >= 'a' && c <= 'z') {
c = c - ('a' - 'A');
}
printf("Resultado: ");
putchar(c);
putchar('\n');
return 0;
}
Explicação:
getchar()lê o caractere diretamente do buffer de entrada.- A expressão
c - ('a' - 'A')retira 32 unidades ao código ASCII, transformando minúsculas em maiúsculas sem necessidade de bibliotecas adicionais. putchar()escreve o resultado no ecrã.
Exercício 2: Tabela de Preços e IVA com Alinhamento em Colunas
Enunciado: Define constantes simbólicas #define IVA 0.23 e #define LOJA "TechStore". Cria um programa que apresente uma fatura simples com 3 artigos (nome, preço base, taxa de IVA e preço final). Os dados devem ser alinhados em colunas perfeitas utilizando as larguras mínimas do printf.
👉 Ver Solução Proposta
#include <stdio.h>
#define IVA 0.23f
#define LOJA "TechStore Informática"
int main(void) {
printf("==================================================\n");
printf(" FATURA - %s\n", LOJA);
printf("==================================================\n");
printf("%-18s %10s %8s %10s\n", "ARTIGO", "PREÇO", "TAXA", "TOTAL");
printf("--------------------------------------------------\n");
// Artigo 1: Teclado Mecânico
float p1 = 49.90f;
printf("%-18s %10.2f %7.0f%% %10.2f\n", "Teclado Mecanico", p1, IVA * 100, p1 * (1.0f + IVA));
// Artigo 2: Rato Óptico
float p2 = 19.50f;
printf("%-18s %10.2f %7.0f%% %10.2f\n", "Rato Optico", p2, IVA * 100, p2 * (1.0f + IVA));
// Artigo 3: Monitor 27"
float p3 = 189.00f;
printf("%-18s %10.2f %7.0f%% %10.2f\n", "Monitor 27 pol", p3, IVA * 100, p3 * (1.0f + IVA));
printf("==================================================\n");
return 0;
}
Explicação:
%-18s: Alinha o nome do artigo à esquerda num campo reservado de 18 caracteres.%10.2f: Reserva 10 posições alinhadas à direita com exatamente 2 casas decimais.%%: Imprime o símbolo de percentagem%sem que o compilador o interprete como especificador.
Exercício 3: Leitura Estruturada de Data e Hora com scanf
Enunciado: Escreve um programa que solicite ao utilizador a introdução de uma data e hora no formato estrito DD/MM/AAAA HH:MM (ex.: 25/12/2026 19:30). O programa deve validar se todos os 5 campos foram introduzidos corretamente utilizando o valor de retorno de scanf e, em caso afirmativo, imprimir cada componente isolada com zeros à esquerda (%02d).
👉 Ver Solução Proposta
#include <stdio.h>
int main(void) {
int dia, mes, ano, hora, minuto;
printf("Introduz a data e hora (DD/MM/AAAA HH:MM): ");
// scanf retorna o número de variáveis preenchidas com sucesso
int lidos = scanf("%d/%d/%d %d:%d", &dia, &mes, &ano, &hora, &minuto);
if (lidos == 5) {
printf("\n✓ Entrada válida registada com sucesso:\n");
printf(" - Dia: %02d\n", dia);
printf(" - Mês: %02d\n", mes);
printf(" - Ano: %04d\n", ano);
printf(" - Horas: %02d\n", hora);
printf(" - Minuto: %02d\n", minuto);
} else {
printf("\n✗ Erro: Formato inválido! Esperado DD/MM/AAAA HH:MM (lidos apenas %d campos).\n", lidos);
}
return 0;
}
Explicação:
- Na string de controlo
"%d/%d/%d %d:%d", os caracteres literais/e:obrigam o utilizador a digitar essas pontuações exatas como separadores. scanfdevolve5se todos os inteiros forem capturados com sucesso.- O especificador
%02dgarante o preenchimento com zero à esquerda (ex.: dia5é impresso como05).
Resumo da Aula 04
- O pré-processador executa antes da compilação:
#definecria constantes simbólicas textuais e#includeimporta ficheiros de cabeçalho (.h). <header.h>procura nos diretórios de sistema/compilador;"header.h"procura no diretório do projeto.getchar()eputchar()operam sobre caracteres únicos com rapidez e simplicidade.printf()devolve o total de caracteres escritos e permite calibrar larguras de campo (%10d,%-10d), bases (%d,%o,%x) e precisão (%.2f).scanf()requer o operador¶ variáveis primitivas e devolve a quantidade de campos lidos com sucesso.- Para evitar o consumo indevido de
\nresidual no buffer ao ler caracteres, usa-se" %c"com espaço prévio.