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. 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 e então precisa ter acesso à essa caixa. Existem algumas 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 pelo 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);
}

Ao executá-lo, o resultado é o seguinte:

aluno@D1:~$ ./data
Data (dia/mes/ano): 6/12/2010
Data informada foi dia=6, mes=12, ano=2010

Ignorando caracteres brancos pendentes

Quando se digita um valor lido por scanf, deve-se teclar Enter para que os valores digitados sejam processados. Isso não é causado pelo scanf em si, mas pela forma com que o sistema operacional trata a interface de texto com o usuário. Os caracteres digitados ficam em uma área de memória intermediária chamada de buffer e que se comporta como FIFO, e são então processados sequencialmente pelo scanf. O processamento usualmente se faz assim:

  1. Descarta caracteres brancos que estejam no início do buffer: espaço, TAB, Newline (Enter), Carriage Return
  2. Interpreta os caracteres correspondentes ao tipo de valor a ser lido, removendo-os do buffer
  3. Para de consumir caracteres do buffer assim que tiver lido os caracteres necessários, ou quando encontrar um caractere incompatível com o tipo a ser lido.

Portanto, normalmente caracteres brancos pendentes não causam problemas, porque ao ler um novo valor scanf descarta do buffer caracteres brancos que porventura estejam pendentes para serem lidos. Porém há um caso em que um comportamento indesejado pode aparecer. Considere o exemplo abaixo:

#include <stdio.h>

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

  printf("Nome: ");
  scanf("%s", nome);

  printf("Idade: ");
  scanf("%d", &idade);

  printf("Sexo (M/F): ");
  scanf("%c", &sexo);

  printf("Resultado: %s tem %d anos e sexo=%c\n", nome, idade, sexo);
}

Se compilar esse programa e executá-lo, verá que ele lê o nome e a idade, mas pula a leitura do sexo, como mostrado abaixo:

Nome: Mariazinha
Idade: 19
Sexo (M/F): Resultado: Mariazinha tem 19 anos e sexo=

Essa anomalia ocorreu porque o caractere Newline pendente no buffer, o qual foi ali inserido quando se digitou o valor da idade, foi lido e interpretado pelo scanf como valor válido. Consequentemente, o valor do Newline (código ASCII 10) foi armazenado na variável sexo, o que não era o comportamento desejado. Isto aconteceu porque o formato %c aceita um caractere simples, e assim é compatível com o caractere Newline. Para fazer com que esse Newline seja descartado e um novo caractere seja lido, deve-se fazer uma alteração sutil no formato especificado no scanf:

  scanf(" %c", &sexo);

Na frente do formato %c deve-se inserir um espaço em branco. Isso força o scanf a descartar quaisquer caracteres brancos que estejam pendentes no buffer. O resultado com essa alteração segue abaixo:

Nome: Mariazinha
Idade: 19
Sexo (M/F): F
Mariazinha tem 19 anos e sexo=F

Limitando a quantidade de caracteres interpretados

Por vezes se deseja limitar a quantidade de caracteres interpretados na leitura de cada valor. Por exemplo, imagine que deve ser lida uma data que está no seguinte formato (A=dígito do ano, M=dígito do mês, D=dígito do dia):

AAAAMMDD

Uma data válida nesse formato é (30/11/2010):

 20101130

Para ler esse valor com scanf, deve-se poder especificar que o número do ano terá exatamente 4 dígitos, e os números de mês e dia 2 dígitos cada. O formato a ser usado deve ser:

#include <stdio.h>

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

  printf("Data (AAAAMMDD): ");
  scanf("%4d%2d%2d", &ano, &mes, &dia);
  printf("Data digitada foi %d/%d/%d\n", dia, mes, ano);
}

Observe que antes da letra que indica o tipo de valor foi inserido um número. Esse número especifica a quantidade máxima de caracteres a ser interpretada para gerar o valor correspondente. Ao executar esse exemplo tem-se este resultado:

Data (AAAAMMDD): 20041011
Data digitada foi 11/10/2004

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

    }
  }