SOP-stdio

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar

Entrada e saída padrão

Explorando printf para mostrar dados na tela

A função printf mostra dados na tela de acordo com um formato (daí seu nome: printf = print formatted, ou imprima formatado). O exemplo mais simples de uso do printf faz com que seja mostrada somente uma constante de texto, como a seguir:

printf("Apenas uma constante de texto ...\n");

O resultado na tela é:

Apenas uma constante de texto ...

Porém printf faz muito mais do que isso. Um segundo uso envolve mostrar dados que misturam constantes de texto e valores numéricos:

int voltas = 15;
float angulo = 3.1416;

printf("Contador de voltas = %d\n", voltas);
printf("Angulo da volta atual = %f radianos\n", angulo);

Na tela será mostrado o seguinte:

Contador  de voltas = 15
Angulo da volta atual = 3.1416 radianos

Esse segundo exemplo mostra como especificar o formato do que deve aparecer na tela. O formato é o primeiro argumento da função printf, e ele contém uma especificação do texto que deve aparecer na tela. Nesse texto podem-se indicar valores a serem substituídos (eles aparecem prefixados com %), os quais devem ser fornecidos nos demais argumentos da função. No primeiro printf do exemplo acima, o formato é:

"Contador de voltas = %d\n"

Nesse texto, toda sequência de caracteres que iniciar com % indica que ali deve-se substituir por um valor. Dependendo da letra que vier depois de %, o tipo de valor será int (inteiro), float (real), double (real com mais casas), char (caractere), string (texto). A tabela abaixo exemplifica os diferentes tipos de valores que podem ser especificados no formato da função printf:

Tipo de dados Formato Exemplo O que aparece na tela
int %d
int horas=2;

printf("Já se passaram %d horas ...\n", horas);
Já se passaram 2 horas ...
char %c
char letra = 'S';

printf("Você digitou a letra %c !\n", letra);
Você digitou a letra S !
float %f
float media = 4.5;

printf("Média calculada=%f\n", media);
Média calculada=4.5
double %g
double angulo = 0.1122333;

printf("Angulo=%g\n", angulo);
Angulo=0.111222333
char * (string) %s
char * nome = "Filomena";

printf("Cara sra. %s ...",  nome);
Cara sra. Filomena ...

Para substituir mais de um valor, basta especificar no formato e fornecer os valores nos demais argumentos:

int hora = 10, minuto = 5, segundo = 0;

printf("Horario: %d:%d:%d\n", hora, minuto, segundo);

... o que faz aparecer na tela:

Horario: 10:5:0

Esse último exemplo poderia ser melhorado, pois o horário ficou esquisito de ler ... normalmente costumamos ver horas, minutos e segundos com dois algarismos, como a seguir:

Horario: 10:05:00

É possível fazer com que printf faça isso automaticamente, bastando especificar o seguinte no formato:

int hora = 10, minuto = 5, segundo = 0;

printf("Horario: %02d:%02d:%02d\n", hora, minuto, segundo);

Note que ao invés de %d se especificou %02d. Esse formato faz com que se mostre um número inteiro com no mínimo dois algarismos. Caso o número tenha somente um algarismo, printf automaticamente adiciona um 0 à esquerda. Isso funciona somente para formatos numéricos.

Além de acrescentar zeros à esquerda de valores numéricos, é possível tabular os valores mostrados de forma que ocupem uma certa quantidade de colunas. Por exemplo, caso se deseje mostrar o seguinte:

Nome         Idade        Sexo
Marcelo      39           M
Marina       9            F
Luis         11           M

... pode-se fazer assim:

printf("%-12s %-12s %s\n", "Nome", "Idade", "Sexo");
printf("%-12s %-12d %c\n", "Marcelo", 39, 'M');
printf("%-12s %-12d %c\n", "Marina", 9, 'F');
printf("%-12s %-12d %c\n", "Luis", 11, 'M');

Note que desta vez adicionou-se após % a sequência -12. Isso indica que o valor a ser mostrado deve ocupar no mínimo 12 posições, preenchendo-se com espaços caso necessário. Veja que os valores foram alinhados à esquerda. Caso se deseje que sejam alinhados à direita, basta fazer assim:

printf("%12s %12s %s\n", "Nome", "Idade", "Sexo");
printf("%12s %12d %c\n", "Marcelo", 39, 'M');
printf("%12s %12d %c\n", "Marina", 9, 'F');
printf("%12s %12d %c\n", "Luis", 11, 'M');

... e assim aparecerá o seguinte na tela:

       Nome        Idade Sexo
    Marcelo           39 M
     Marina            9 F
       Luis           11 M

Explorando scanf para ler dados do teclado

A função scanf possibilita que se leiam valores pelo teclado, de forma a armazená-los em variáveis. Assim como no caso de printf, esses valores podem ser de diferentes tipos e devem ser especificados segundo um formato. No exemplo abaixo a função scanf é usada para ler um número inteiro, o que está especificado no formato "%d":

#include <stdio.h>

int main() {
  int x;

  printf("Digite um número inteiro: ");
  scanf("%d", &x);

  printf("Número digitado=%d\n", x);
}

Quando se usa scanf, as variáveis onde se devem guardar os valores lidos devem ser precedidas pelo caractere &, como destacado abaixo:

  scanf("%d", &x);

O caractere & na frente do identificador de uma variável funciona como um operador. Seu efeito é obter o endereço de memória da variável. Assim, ao se escrever scanf("%d", &x) se faz com que a função scanf receba como parâmetro o endereço de memória da variável x, e não o valor dessa variável. Isso é necessário pois scanf vai guardar um valor na variável x, então deve conhecer seu endereço de memória. Pense na analogia entre variáveis e caixas: o valor de uma variável é o conteúdo da caixa, e o endereço da variável é a própria caixa. Seguindo a analogia, a função scanf vai guardar um novo valor na caixa então precisa ter acesso à caixa. Existem alguma exceções em que não se deve usar o operador &, sendo a mais notória quando o valor a ser lido é do tipo string (texto).

Note que scanf serve somente para ler valores do teclado. Portanto, se for necessário mostrar alguma mensagem antes de ler o valor desejado, deve-se usar printf.

O formato no scanf pode especificar mais de um valor a ser lido, e também como exatamente esses valores devem ser digitados. No exemplo abaixo, deseja-se ler uma data no formato dia/mes/ano. Observe que o formato %d/%d/%d especifica que devem ser digitados três números inteiros separados por um caractere /.

#include <stdio.h>

int main() {
  int dia, mes, ano;

  printf("Data (dia/mes/ano): ");
  scanf("%d/%d/%d", &dia, &mes, &ano);

  printf("Data informada foi dia=%d, mes=%d, ano=%d\n", dia, mes, ano);
}

O problema do scanf quando se digita um valor inválido

Ao se usar scanf, pode ocorrer um problema na leitura de um valor pelo teclado. Veja o exemplo abaixo, em que um usuário deve digitar um número inteiro correspondente a uma opção de um menu de um programa. O programa executa uma ação correspondente a essa opção, e em seguida volta a apresentar o menu e pedir uma nova opção. Porém se o usuário digitar algo que não seja um número inteiro, a interface entra em loop, sempre mostrando o menu de opções.

  int main() {
    int opcao = 0, lidos;

    while (opcao != 3) {
      printf("1. Bla bla bla\n");
      printf("2. GHxxndndcd\n");
      printf("3. Terminar o programa\n");

      printf("Digite uma opção: ");
      lidos = scanf("%d", &opcao);
      printf("Opção escolhida=%d\n", opcao);

      // executa a ação correspondente à opção escolhida

    }
  }

Este video mostra o problema do loop quando se digita um valor inválido.

Lembre que o scanf tenta ler o valor digitado de acordo com a especificação contida no formato (no exemplo, "%d" que corresponde a um número inteiro - maiores detalhes no curso de C da UGMF). Se ele conseguir, retorna a quantidade de valores lidos (no exemplo isto é guardado na variável lidos). Assim, se scanf no exemplo acima não conseguir ler o valor digitado, irá retornar 0. O problema é que neste caso o valor que foi digitado fica pendente em memória, quer dizer, o scanf não descarta esse valor, apesar de não ter conseguido lê-lo. Na próxima vez que o scanf for chamado ele vai tentar novamente ler esse mesmo valor, e se der erro o valor vai continuar pendente. Isto só se reverterá quando o scanf especificar um tipo de dado compatível com o valor pendente, ou então se o formato especificar que se deve descartá-lo. Uma possível correção seria portanto testar se o scanf conseguiu ler o valor, e em caso negativo descartar tudo o que foi digitado. Veja assim o exemplo abaixo com a correção:

  int main() {
    int opcao = 0, n;

    while (opcao != 3) {
      printf("1. Bla bla bla\n");
      printf("2. GHxxndndcd\n");
      printf("3. Terminar o programa\n");

      // o laço abaixo faz com que se repita a leitura pelo teclado até que 
      // se digite um número inteiro.
      n = 0;
      while (n == 0) {
        printf("Digite uma opção: ");
        n = scanf("%d", &opcao);

        // se n == 0, então scanf não conseguiu ler o valor, porque ele não 
        // é do tipo inteiro.
        // Então o valor digitado deve ser descartado. Para descartar tudo 
        // o que foi digitado, deve-se chamar de novo o scanf, mas com o 
        // formato "%*[^\n]". Isto joga fora toda a linha digitada.
        // Uma explicação sobre formatos do scanf está na sua página de 
        // manual (execute "man scanf").
        if (n == 0) {
          n = scanf("%*[^\n]");
        }
      }

      printf("Opção escolhida=%d\n", opcao);

      // executa a ação correspondente à opção escolhida

    }
  }

Com a correção acima, o problema do loop desaparece, o que se pode ver neste outro video.

Esse problema da leitura de valores com scanf pode aparecer em outras partes de um programa. Então seria mais prático se o algoritmo contido no programa acima, que repete a leitura do teclado até que se consiga ler uma valor válido, pudesse ser reaproveitado. Isto é um bom exemplo do uso de funções da linguagem C. Uma função é um algoritmo a ser reutilizado. Como todo algoritmo, deve ter dados de entrada, uma sequência de instruções, e fornecer dados de saída como resultado. Veja o exemplo abaixo, que se baseia na leitura do teclado com scanf:

  // Função meu_scanf:
  // Dados de entrada: parâmetro "char * mensagem" é o texto a ser mostrado 
  // antes de ler do teclado.
  // Dado de saída: meu_scanf devolve (retorna) um número inteiro que 
  // corresponde ao valor lido.

  int meu_scanf(char * mensagem) {
    int x, n;

    do {
      printf(mensagem);
      n = scanf("%d", &x);

      // se n == 0, então scanf não conseguiu ler o valor, porque ele não é do 
      // tipo inteiro. Então o valor digitado deve ser descartado. Para descartar
      // tudo o que foi digitado, deve-se chamar de novo o scanf, mas com o formato
      // "%*[^\n]". Isto joga fora toda a linha digitada.
      // Uma explicação sobre formatos do scanf está na sua página de manual
      // (execute "man scanf").
      if (n == 0) {
        n = scanf("%*[^\n]");
      }
    } while (n == 0);

    // aqui meu_scanf retorna o valor lido, que está contido na variável "x"
    return x;
  }

  int main() {
    int opcao = 0;

    while (opcao != 3) {
      printf("1. Bla bla bla\n");
      printf("2. GHxxndndcd\n");
      printf("3. Terminar o programa\n");

      // chama a função "meu_scanf" para ler um número inteiro do teclado.
      opcao = meu_scanf("Digite uma opção: ");

      printf("Opção escolhida=%d\n", opcao);

      // executa a ação correspondente à opção escolhida

    }
  }