Nesta aula, vamos explorar as funções fundamentais de entrada e saída (E/S) na linguagem C. Essas funções são essenciais para interagir com o usuário, exibindo informações e recebendo dados. Abordaremos printf, scanf, putchar e getchar, além do conceito de buffer, que influencia diretamente o comportamento da E/S. Compreender esses tópicos é crucial para escrever programas que se comuniquem corretamente com o ambiente.

printf e especificadores

A função printf é usada para exibir dados formatados na saída padrão (geralmente o terminal). Ela aceita uma string de formato que contém texto literal e especificadores de formato, que são substituídos pelos valores das variáveis fornecidas. Os especificadores começam com % e indicam o tipo de dado a ser impresso.

Os especificadores mais comuns incluem: %d para inteiros decimais, %f para números de ponto flutuante, %c para caracteres, %s para strings, %x para hexadecimais, entre outros. É possível modificar a formatação com flags, largura, precisão e modificadores de tamanho. Por exemplo, %10d imprime um inteiro com largura mínima de 10 caracteres, alinhado à direita; %.2f imprime um float com duas casas decimais.

#include <stdio.h>

int main() {
    int idade = 25;
    float altura = 1.75;
    char inicial = 'J';
    char nome[] = "João";

    printf("Idade: %d anos\n", idade);
    printf("Altura: %.2f m\n", altura);
    printf("Inicial: %c\n", inicial);
    printf("Nome: %s\n", nome);
    printf("Hex: %x\n", idade);
    return 0;
}

scanf e cuidados

scanf é a função padrão para leitura de dados formatados da entrada padrão (teclado). Ela também usa uma string de formato com especificadores, mas os argumentos devem ser endereços de variáveis (usando o operador &). Por exemplo, scanf("%d", &idade); lê um inteiro e armazena em idade.

É importante tomar cuidados com scanf: ela deixa o caractere de nova linha (\n) no buffer, o que pode causar problemas em leituras subsequentes, especialmente com %c. Além disso, scanf não é segura para strings, pois não verifica o tamanho do buffer, podendo causar estouro. Para strings, é recomendado usar fgets ou especificar largura máxima, como %19s para um array de 20 caracteres. Outro cuidado: a leitura de %c lê qualquer caractere, incluindo espaços e quebras de linha; muitas vezes é necessário limpar o buffer antes.

#include <stdio.h>

int main() {
    int idade;
    char nome[20];
    char sexo;

    printf("Digite sua idade: ");
    scanf("%d", &idade);
    printf("Digite seu nome: ");
    scanf("%19s", nome); // lê até 19 caracteres + '\0'
    // Limpar o buffer de entrada antes de ler caractere
    while(getchar() != '\n'); // descarta o resto da linha
    printf("Digite seu sexo (M/F): ");
    scanf("%c", &sexo);

    printf("\nIdade: %d, Nome: %s, Sexo: %c\n", idade, nome, sexo);
    return 0;
}

putchar/getchar

putchar e getchar são funções mais simples para E/S de um único caractere. putchar(c) escreve o caractere c na saída padrão. getchar() lê um caractere da entrada padrão e o retorna como um int (para poder representar EOF). Elas são úteis para processamento caractere por caractere, como em menus ou leitura de arquivos.

Um exemplo clássico é um programa que copia a entrada para a saída até encontrar EOF (Ctrl+D no Linux, Ctrl+Z no Windows):

#include <stdio.h>

int main() {
    int c;
    printf("Digite caracteres (Ctrl+D para sair):\n");
    while ((c = getchar()) != EOF) {
        putchar(c);
    }
    return 0;
}

Note que getchar retorna int para distinguir caracteres válidos de EOF. A variável c deve ser int, não char.

Buffer

O buffer é uma área de memória temporária usada para armazenar dados antes de serem processados. Em C, a E/S padrão é geralmente bufferizada: os dados são acumulados em um buffer e transferidos em blocos para o dispositivo (terminal, arquivo). Isso melhora a eficiência, mas pode causar comportamentos inesperados, como quando scanf deixa caracteres no buffer.

Existem três tipos de bufferização: sem buffer (unbuffered), com buffer de linha (line-buffered) e com buffer completo (fully-buffered). Para a saída padrão (stdout), é comum ser line-buffered quando conectada a um terminal, ou seja, o buffer é liberado (flushed) quando uma nova linha é encontrada. A entrada padrão (stdin) também é line-buffered, o que explica por que getchar só retorna após pressionar Enter. Para forçar a liberação do buffer, pode-se usar fflush(stdout) ou setbuf.

#include <stdio.h>

int main() {
    printf("Sem newline, sem flush? ");
    // O texto pode não aparecer imediatamente
    fflush(stdout); // força a saída
    getchar(); // espera um caractere
    return 0;
}

Entender o buffer é crucial para evitar problemas como leitura de lixo ou saída não exibida no momento esperado.

Boas práticas

  • Sempre verifique o retorno de scanf para garantir que a leitura foi bem-sucedida.
  • Use fgets para ler strings com segurança, em vez de scanf com %s.
  • Limpe o buffer de entrada após usar scanf se for ler caracteres ou strings com fgets depois.
  • Evite usar fflush(stdin), pois é comportamento indefinido em C padrão; prefira loops com getchar.
  • Para saída imediata sem newline, use fflush(stdout).

Referências

Exercícios

  1. Escreva um programa que leia um número inteiro e um número de ponto flutuante, e os imprima com 3 casas decimais para o float e em formato hexadecimal para o inteiro.
  2. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int inteiro;
        float flutuante;
        printf("Digite um inteiro e um float: ");
        scanf("%d %f", &inteiro, &flutuante);
        printf("Inteiro em hex: %x\n", inteiro);
        printf("Float com 3 casas: %.3f\n", flutuante);
        return 0;
    }
  3. Explique por que o código abaixo pode falhar ao ler um caractere após um número e como corrigi-lo.
    int num;
    char letra;
    scanf("%d", &num);
    scanf("%c", &letra);
  4. ✓ Resposta: O problema é que após ler o inteiro, o caractere de nova linha ('\n') permanece no buffer. O segundo scanf com %c lê esse newline, não a letra desejada. Para corrigir, pode-se adicionar um espaço antes de %c: scanf(" %c", &letra); ou limpar o buffer com while(getchar() != '\n'); antes do segundo scanf.
  5. Usando putchar e getchar, escreva um programa que leia uma linha de texto e a imprima em maiúsculas (assuma que o usuário digita apenas letras minúsculas).
  6. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int c;
        printf("Digite uma linha (minúsculas): ");
        while ((c = getchar()) != '\n' && c != EOF) {
            if (c >= 'a' && c <= 'z')
                putchar(c - 32); // diferença entre maiúscula e minúscula
            else
                putchar(c);
        }
        putchar('\n');
        return 0;
    }
  7. O que é o buffer de saída e como você pode forçar a saída imediata de uma mensagem sem usar uma quebra de linha?
  8. ✓ Resposta: O buffer de saída é uma área de memória onde os dados são armazenados antes de serem enviados ao dispositivo. Para forçar a saída imediata, use fflush(stdout); após a chamada de printf.
  9. Escreva um programa que leia dois números inteiros e exiba a soma, diferença, produto e quociente (com duas casas decimais) usando printf.
  10. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int a, b;
        printf("Digite dois inteiros: ");
        scanf("%d %d", &a, &b);
        printf("Soma: %d\n", a + b);
        printf("Diferença: %d\n", a - b);
        printf("Produto: %d\n", a * b);
        if (b != 0)
            printf("Quociente: %.2f\n", (float)a / b);
        else
            printf("Divisão por zero\n");
        return 0;
    }