Programar em C/Entrada e saída simples

Entrada e saída simples

editar

Se você pensar bem, perceberá que um computador é praticamente inútil se não tiver nenhuma maneira de interagir com o usuário. Por exemplo, se você abrir um processador de texto, nada irá acontecer até que você abra um arquivo ou digite algum texto no teclado. Mas, da mesma maneira, é necessário que o computador forneça informação também: como você poderia saber se uma tarefa foi concluída?

As trocas de informação entre o computador e o usuário são chamadas entrada e saída (input e output, em inglês). Entrada é a informação fornecida a um programa; saída é a informação fornecida pelo programa. É comum referir-se aos dois termos simultaneamente: entrada/saída ou E/S (I/O, em inglês).

Frequentemente são usados os termos "saída padrão" (standard output, stdout) e "entrada padrão" (standard input, stdin). Eles se referem, na maioria das vezes, ao monitor e ao teclado, que são os meios básicos de interação com o usuário. No entanto, os sistemas operacionais permitem redirecionar a saída e a entrada de programas para outros dispositivos ou arquivos.

As funções de entrada e saída na linguagem C trabalham com fluxos (streams, em inglês) de dados, que são uma forma de abstração de dados de maneira sequencial. Assim, toda entrada e saída é feita da mesma maneira, com as mesmas funções, não importando o dispositivo com o qual estamos nos comunicando (teclado, terminal, arquivo, etc.). As mesmas funções que descrevem o acesso aos arquivos podem ser utilizadas para se acessar um terminal de vídeo.

Em C, as funções da biblioteca padrão para entrada e saída estão declaradas no cabeçalho stdio.h. Uma delas já foi introduzida em seções anteriores: printf(). A seguir daremos mais detalhes sobre essa função e introduziremos outras.

puts() e putchar()

editar

puts significa "put string" (colocar string), utilizado para "colocar" uma string na saída de dados. putchar significa "put char" (colocar caractere), utilizado para "colocar" um caractere na saída de dados.

São as funções mais simples do cabeçalho stdio.h. Ambas enviam (ou "imprimem") à saída padrão os caracteres fornecidos a elas; putchar() manda apenas um caractere, e puts() manda uma sequência de caracteres (ou string). Exemplo:

 puts ("Esta é uma demonstração da função puts.");
 putchar ('Z');

Note que junto com a função puts devemos usar literais de string (com aspas duplas), e com putchar devemos usar literais de caractere (com aspas simples). Se você tentasse compilar algo como putchar ("T"), o compilador daria uma mensagem de erro. Lembre-se que "T" é diferente de 'T'.

Podemos também colocar caracteres especiais, como a tabulação (\t) e a quebra de linha (\n):

 puts ("Primeira linha\nSegunda linha\te um grande espaço");
 putchar ('\n'); <i>// apenas envia uma quebra de linha</i>

Este código resultaria em algo parecido com:

Primeira linha
Segunda linha         e um grande espaço

Observe que a função puts() sempre coloca uma quebra de linha após imprimir a string. Já com as funções putchar() e printf() (vista a seguir), isso não ocorre. O código abaixo, por exemplo:

 putchar('c');
 putchar('h');
 putchar('\n');
 puts("String.");
 puts("Outra string.");

imprimiria algo parecido com isto:

ch
String.
Outra string.

Observe que os caracteres 'c' e 'h' são exibidos na mesma linha, pois não foi inserida uma quebra de linha entre eles. Já as strings "String." e "Outra string." são exibidas em linhas diferentes, pois a função puts() insere uma quebra de linha após cada string, mesmo que não haja um caractere '\n' nas literais de string do código.

Os outros caracteres especiais são introduzidos adiante.

Note que o argumento deve ser uma sequência de caracteres. Se você tentar, por exemplo, imprimir o número 42 desta maneira:

 puts(42);

Na verdade o que o compilador tentará fazer é imprimir a sequência de caracteres que começa na posição 42 da memória (provavelmente ele irá alertá-lo sobre isso se você tentar compilar esse código). Se você tentar executar esse código, provavelmente ocorrerá uma falha de segmentação (erro que ocorre quando um programa tenta acessar memória que não lhe pertence). A maneira correta de imprimir o número 42 seria colocá-lo entre aspas duplas:

 puts("42");

printf()

editar

printf vem de "print formatted" (imprimir formatado).

À primeira vista, a função printf() pode parecer idêntica à puts(). No entanto, ela é muito mais poderosa. Ela permite facilmente imprimir valores que não são sequências de caracteres, além de poder formatar os dados e juntar várias sequências de caracteres. Por isso, a função printf() é muito mais usada que a puts().

Ela pode ser usada exatamente como a função puts(), se fornecermos a ela apenas uma sequência de caracteres:

 printf("Este é um programa em C");

Ela também pode ser escrita da seguinte forma:

 printf("Ola"
        " mundo"  
        "!!!" );

Mas e se precisarmos imprimir o conteúdo de uma variável? A função printf também pode fazer isso! Você deve, obviamente, especificar onde o valor da variável deve ser impresso. Isso é feito através da especificação de formato %d, caso a variável seja do tipo int (sequências para outros tipos serão dadas adiante). Você também precisará, logicamente, especificar qual variável imprimir. Isso é feito dando-se mais um argumento à função printf(). O código deverá ficar assim:

 int teste;
 teste = 42;
 printf ("A variável 'teste' contém o número %d.", teste);

Tendo colocado isso no seu programa, você deverá ver na tela:

A variável 'teste' contém o número 42.

Vamos supor que você queira imprimir um número não inteiro. Você teria que trocar "%d" por %f. Exemplo:

 float pi;
 pi = 3.1415;
 printf ("O valor de pi é %f.", pi);

O código acima irá retornar:

O valor de pi é 3.1415.

Você pode imprimir quantos valores quiser, bastando para isso colocar mais argumentos e mais especificações de formato, lembrando de colocar na ordem certa. Alguns compiladores, como o gcc, mostram um aviso caso o número de argumentos seja diferente do número de especificações de formato, o que provavelmente causaria resultados indesejados. A sintaxe geral da função printf() é:

 printf ("string de formatação", arg1, arg2, ...);

Suponha que você tem um programa que soma dois valores. Para mostrar o resultado da conta, você poderia fazer isso:

 int a, b, c;
 ...          // leitura dos dados
 c = a + b;   // '''c''' é o resultado da soma
 printf ("%d + %d = %d", a, b, c);

O que resultaria em, para a = 5 e b = 9:

5 + 9 = 14

A seguir mostramos os especificadores de formato para vários tipos de dados.

Especificações de formato

editar

A documentação mais técnica os chama de "especificadores de conversão", pois o que ocorre na maioria das vezes é, de fato, a conversão de um valor numérico em uma sequência de caracteres que representa aquele valor. Mas o nome "formato" não deixa de estar correto, pois eles especificam em que formato (inteiro, real etc.) está o argumento correspondente.

Código Conversão/Formato do argumento
%d Número decimal inteiro (int). Também pode ser usado %i como equivalente a %d.
%u Número decimal natural (unsigned int), ou seja, sem sinal.
%o Número inteiro representado na base octal. Exemplo: 41367 (corresponde ao decimal 17143).
%x Número inteiro representado na base hexadecimal. Exemplo: 42f7 (corresponde ao decimal 17143). Se usarmos %X, as letras serão maiúsculas: 42F7.
%X Hexadecimal com letras maiúsculas
%f Número decimal de ponto flutuante. No caso da função printf, devido às conversões implícitas da linguagem C, serve tanto para float como para double. No caso da função scanf, %f serve para float e %lf serve para double.
%e Número em notação científica, por exemplo 5.97e-12. Podemos usar %E para exibir o E maiúsculo (5.97E-12).
%E Número em notação científica com o "e"maiúsculo
%g Escolhe automaticamente o mais apropriado entre %f e %e. Novamente, podemos usar %G para escolher entre %f e %E.
%p Ponteiro: exibe o endereço de memória do ponteiro em notação hexadecimal.
%c Caractere: imprime o caractere que tem o código ASCII correspondente ao valor dado.
%s Sequência de caracteres (string, em inglês).
%% Imprime um %
Observação Se você quiser imprimir um sinal de porcentagem, use %%. Exemplo:
 printf("O lucro para o último mês foi de 20%%.");

Numa sequência de controle, é possível também indicar a largura do campo, o número de casas decimais, o tamanho da variável e algumas opções adicionais. O formato geral é:

%[opções][largura do campo][.precisão][tamanho da variável]tipo de dado

A única parte obrigatória é o tipo de dado. Todas as outras podem ser omitidas.

Opções

editar

As opções são parâmetros opcionais que alteram a formatação. Você pode especificar zero ou mais delas, colocando-as logo após o sinal de porcentagem:

  • 0: o tamanho do campo deve ser preenchido com zeros à esquerda quando necessário, se o parâmetro correspondente for numérico.
  • - (hífen): o valor resultante deve ser alinhado à esquerda dentro do campo (o padrão é alinhar à direita).
  • (espaço): no caso de formatos que admitem sinal negativo e positivo, deixa um espaço em branco à esquerda de números positivos.
  • +: o sinal do número será sempre mostrado, mesmo que seja positivo.
  • ' (aspa simples/apóstrofo): números decimais devem ser exibidos com separador de milhares caso as configurações regionais o especifiquem. Essa opção normalmente só funciona nos sistemas Unix.

Largura do campo

editar

Como o próprio nome já diz, especifica qual a largura mínima do campo. Se o valor não ocupar toda a largura do campo, este será preenchido com espaços ou zeros. Por exemplo, podemos imprimir um código de até 5 dígitos preenchido com zeros, de maneira que os valores 1, 27, 409 e 55192 apareçam como 00001, 00027, 00409 e 55192.

A largura deve ser especificada logo após as opções, se presentes, e pode ser um número — que especifica a largura — ou um asterisco, que diz que a largura será especificada pelo próximo argumento (ou seja, o argumento anterior ao valor a ser impresso). Neste exemplo, o campo terá largura igual ao valor de num e o valor impresso será 300:

 printf ("%*d", num, 300);

O campo é impresso de acordo com as seguintes regras:

  • Se o valor for mais largo que o campo, este será expandido para poder conter o valor. O valor nunca será cortado.
  • Se o valor for menor que o campo, a largura do campo será preenchida com espaços ou zeros. Os zeros são especificados pela opção 0, que precede a largura.
  • O alinhamento padrão é à direita. Para se alinhar um número à esquerda usa-se a opção - (hífen ou sinal de menos) antes da largura do campo.

Por exemplo, compare as três maneiras de exibir o número 15:

 printf ("%5d", 15);   // exibe "   15"
 printf ("%05d", 15);  // exibe "00015"
 printf ("%-5d", 15);  // exibe "15   "

E alguns outros exemplos:

 printf ("%-10s", "José");  // exibe "José      "
 printf ("%10s", "José");   // exibe "      José"
 printf ("%4s", "José");    // exibe "José"

Precisão

editar

A precisão pode ter quatro significados diferentes:

  • Se a conversão solicitada for inteira (d, i, o, u, x, X): o número mínimo de dígitos a exibir (será preenchido com zeros se necessário).
  • Se a conversão for real (a, A, e, E, f, F): o número de casas decimais a exibir. O valor será arredondado se a precisão especificada no formato for menor que a do argumento.
  • Se a conversão for em notação científica (g, G): o número de algarismos significativos. O valor será arredondado se o número de algarismos significativos pedido for maior que o do argumento.
  • Se a conversão for de uma sequência de caracteres (s): o número máximo de caracteres a exibir.

Assim como a largura do campo, a precisão pode ser especificada diretamente por um número ou com um asterisco, mas deve ser precedida por um ponto.

Alguns exemplos:

 printf ("%.5d", 314);         // exibe "00314"
 printf ("%.5f", 2.4);         // exibe "2.40000"
 printf ("%.5g", 23456789012345);  // exibe "2.3457e+13"
 printf ("%.5s", "Bom dia");   // exibe "Bom d"

É claro que podemos combinar a largura com a precisão. Por exemplo, %10.4f indica um campo de número real de comprimento total dez e com 4 casas decimais. Note que, na largura do campo, o valor inteiro é levado em conta, inclusive o ponto decimal, e não apenas a parte inteira. Por exemplo, essa formatação aplicada ao número 3.45 irá resultar nisto:

"    3.4500"

Tamanho da variável

editar

É importante ressaltar que quando são usados modificadores de tamanho de tipos, a maneira como os dados são armazenados pode tornar-se diferente. Assim, devemos informar à função printf() precisamente qual o tipo da variável cujo valor desejamos exibir. A função printf() admite cinco principais modificadores de tamanho de variável:

  • hh: indica que a conversão inteira corresponde a uma variável char. Por exemplo, poderíamos usar o formato %hhd para exibir uma variável do tipo char na base decimal.
  • h: indica que a conversão inteira corresponde a uma variável short.
  • l: indica que a conversão inteira corresponde a uma variável long.
  • ll: indica que a conversão inteira corresponde a uma variável long long.
  • L: indica que a conversão de número real corresponde a uma variável long double.

Quando o tipo da variável não tem modificadores de tamanho (long ou short), não se usa nenhum modificador de tamanho da variável na função printf().

Sequências de escape

editar

Sequências de escape são combinações de caracteres que têm significado especial, e são sempre iniciadas por uma barra invertida (\). Você pode usá-las em qualquer literal de caractere ou string. Por exemplo, a string "linha 1\nlinha 2" equivale a:

linha 1
linha 2

pois a sequência \n indica uma quebra de linha. Como foi citado anteriormente, a função printf(), diferentemente de puts(), não imprime automaticamente uma quebra de linha no final da string. O código abaixo, por exemplo:

 printf("string 1");
 printf("string 2");

Imprimiria isto:

string 1string 2

Isso pode ser útil, pois às vezes é desejável permanecer na mesma linha.

A seguir apresentamos a tabela com as sequências de escape suportadas pela linguagem C:

Sequência Significado
\n Quebra de linha (line feed ou LF)
\t Tabulação horizontal
\b Retrocede o cursor em um caractere (backspace)
\r Retorno de carro (carriage return ou CR): volta o cursor para o começo da linha sem mudar de linha
\a Emite um sinal sonoro
\f Alimentação de formulário (form feed ou FF)
\v Tabulação vertical (em impressoras)
\" Aspa dupla
\' Aspa simples
\\ Barra invertida
\0 Caractere nulo (caractere de valor zero, usado como terminador de strings)
\N O caractere cuja representação octal é N (dígitos de 0 a 7)
\xN O caractere cuja representação hexadecimal é N (dígitos de 0 a 9 e de A a F)

Representação octal e hexadecimal

editar

Também é possível trocar uma sequência de escape pelo seu valor em octal ou hexadecimal. Você pode, por exemplo, trocar o caractere "\n" pelo valor octal "\12" ou hexadecimal "\x0A". Vejamos mais alguns exemplos.

Hexadecimal   Octal   Caracter
    \x00       \00        \0
    \x0A       \12        \n
    \x0D       \15        \r
    \x07       \07        \a
    \x08       \10        \b
    \x0B       \13        \v

scanf()

editar

A função scanf() lê dados da entrada padrão (teclado) e os guarda em variáveis do programa. Assim como para printf(), usamos uma string de formatação para especificar como serão lidos os dados. A sintaxe de scanf() é esta:

 scanf ("string de formatação", &amp;arg1, &amp;arg2, ...);

Como você pode ver, a sintaxe é quase igual à de printf(), com exceção do E comercial (&). Você entenderá melhor o seu uso nas seções seguintes, mas adiantamos que ele é um operador que retorna o endereço de uma variável. Isso é necessário pois a função scanf() deve modificar as variáveis, e quando não usamos o operador de endereço, passamos apenas o valor de uma variável para a função. Isso será explicado melhor no capítulo sobre ponteiros. O fato de scanf receber endereços de variáveis (em vez de seus valores) também explica por que ele precisa ser informado da diferença entre %f (float) e %lf (double) enquanto que o printf não precisa.

Um exemplo básico da utilização de scanf() é este:

 int a;
 scanf ("%d", &a);

O que este exemplo faz é declarar uma variável e aguardar o usuário digitar algo. Os dados só serão processados quando o usuário apertar Enter. Depois disso, os caracteres digitados pelo usuário serão convertidos para um valor inteiro e esse inteiro será guardado no endereço que corresponde à variável a. Se o valor digitado não puder ser convertido (porque o usuário não digitou nenhum algarismo válido), a variável não será modificada.

Assim como na função printf(), podemos receber quantos valores quisermos, bastando usar vários especificadores de conversão:

 int a;
 char b;
 float c;
 scanf ("%d %c %f", &a,&b,&c);

Dessa maneira, se o usuário digitar 120 z 17.63, teremos a igual a 120, b igual ao caractere 'z' e c igual ao número 17,63. Se o usuário tentar digitar mais de um espaço entre os dados ou simplesmente nenhum espaço, ainda assim o programa obterá os dados certos. Por exemplo, 120z17.63 também dará o mesmo resultado.

Agora uma questão um pouco mais difícil: vamos supor que especificamos um formato inteiro e o usuário digitou um número real, como por exemplo 12.5. O que deverá acontecer?

 #include <stdio.h>
 
 int main ()
 {
    int a;
    
    printf ("Digite um número: ");
    scanf ("%d", &a);
    printf ("\nO número digitado foi %d", a); 
    return (0);
 }

Se você testar com o valor 12.5, vai ver que o programa retornará o número 12, pois a função scanf() apenas interpreta os caracteres válidos para aquele formato.

Os especificadores de conversão são praticamente os mesmos que os da função printf(), com algumas mudanças. A maioria deles pula espaços em branco, exceto dois.

  • %i não é mais sinônimo de %d. O que %i faz é interpretar o valor digitado como hexadecimal, se iniciar-se por 0x ou 0X; como octal, se iniciar-se por 0; ou como decimal, caso nenhuma dessas condições seja verificada.
  • %a, %e/%E e %g são sinônimos de %f.
  • %lf deve ser usado para variáveis do tipo double.
  • %s lê uma sequência de caracteres não-brancos (qualquer caractere exceto espaço, tabulação, quebra de linha etc.), ou seja, uma palavra.
  • %c lê uma sequência de caracteres, sem ignorar espaços. O padrão é ler um caractere, se não for especificada a largura do campo.
  • %[...] lê uma sequência de caracteres, sem ignorar espaços, especificando entre colchetes quais caracteres devem ser aceitos, ou, se o primeiro caractere dentro dos colchetes for um acento circunflexo (^), quais não devem ser aceitos. Além disso, se colocarmos um traço entre dois caracteres, todos os caracteres entre os dois serão incluídos no padrão. Por exemplo, se quisermos incluir qualquer letra minúscula, poderiámos escrever %[a-z]; se quiséssemos também incluir as maiúsculas, colocaríamos %[a-zA-Z]. A leitura pára quando for encontrado um caractere que não coincide com o padrão especificado.

Já os modificadores funcionam de maneira bastante diferente:

  • O modificador * (asterisco) especifica que o valor atual deve ser lido da maneira especificada, mas não será guardado em nenhuma variável, e portanto não deve haver um ponteiro correspondente a esse valor. Por exemplo, poderiámos ter um programa que espera ler uma palavra e depois um número, mas não importa qual palavra é. Nesse caso usaríamos o modificador *: scanf ("%*s %d", &numero). O programa leria a palavra e guardaria o número na variável numero.
  • Como na função printf(), existe o especificador de largura do campo, que deve aparecer antes do especificador de conversão, mas em scanf() ele especifica a largura máxima. Se a largura máxima foi definida como n, scanf() pulará para o próximo campo se já tiver lido n caracteres. Por exemplo, scanf ("%4d", &num) lerá um número de até quatro algarismos. Se o usuário digitar mais, o excedente será não será lido por essa chamada, mas poderá ser lido por uma próxima chamada a scanf.

Mais detalhes sobre os especificadores de conversão e os modificadores podem ser encontrados na documentação da biblioteca padrão.

Valor de retorno

editar

A funcão scanf() retorna o número de conversões realizadas com sucesso. Isso é útil pois, se o valor contido numa variável após a chamada de scanf() for igual ao valor anterior, não é possível saber se o valor digitado foi o mesmo que já havia ou se não foi feita a conversão. Para obter esse número de conversões realizadas, você deve guardar o resultado numa variável do tipo int. Veja como proceder:

 int a, b;
 int num;
 num = scanf("%d%d", &a, &b);

Este exemplo lê dois números inteiros e os guarda nas variáveis a e b. O número de conversões realizadas é guardado na variável num. Se após o scanf, num for diferente de 2, é sinal de que o usuário digitou algo incompatível com o formato desejado.

Note que aqui introduzimos um conceito novo: o valor de retorno de uma função. Ele pode ser obtido simplesmente associando o valor de uma variável à chamada da função. Ele será detalhado na seção Funções, mas já é possível compreender um pouco sua utilização.

gets() e getchar()

editar

gets() e getchar(), assim como scanf(), lêem da entrada padrão. Assim como puts() e putchar(), não suportam formatação. Como o nome sugere, getchar() lê apenas um caractere, e gets() lê uma string até o final da linha ou até que não haja mais dados para ler, e adiciona o terminador de string "\0".

A sintaxe das funções é:

gets(ponteiro_para_string);
 char c;
 c = getchar();

No entanto, existe um problema com a função gets(). Veja o exemplo a seguir:

 #include <stdio.h>
 
 int main()
 {
   char buffer[10];
   printf("Entre com o seu nome: ");
   gets(buffer);
   printf("O nome é: %s", buffer);
   return 0;
 }

A notação char buffer[10], que ainda não foi introduzida (e será detalhada na seção Vetores (arrays)), pede que seja reservado um espaço para 10 caracteres para a string buffer. Portanto, se usuário digitar mais de 9 caracteres (pois o terminador de string é adicionado ao que o usuário digitou), os caracteres excedentes adicionais serão colocados na área de memória subsequente à ocupada pela variável, escrevendo uma região de memória que não está reservada à string. Este efeito é conhecido como "estouro de buffer" e pode causar problemas imprevisíveis. Por isso, não se deve usar a função gets(); mais tarde introduziremos a função fgets(), que não apresenta esse problema e que deve ser usada no lugar de gets().

sprintf() e sscanf()

editar

sprintf e sscanf são semelhantes a printf e scanf. Porém, ao invés de escreverem na saída padrão ou lerem da entrada padrão, escrevem ou lêem em uma string. A única mudança nos argumentos é a necessidade de especificar a string que deve ser lida ou atribuída no início. Veja os exemplos para entender melhor.

 #include <stdio.h>
 
 int main()
 {
   int i;
   char string1[30];
   printf("Entre um valor inteiro: ");
   scanf("%d", &i);
   sprintf(string1, "Valor de i = %d", i);
   puts(string1);
   return 0;
 }

Nesse exemplo, a mensagem que queríamos exibir na tela foi primeiramente salva em uma string, e depois essa string foi enviada para a tela. Se você olhar bem, se você tivesse alocado um valor menor para string1, também ocorreria um estouro de buffer. Para evitar esse problema, existe a função snprintf, que tem mais um argumento: o tamanho da string (deve ser colocado depois da string onde a mensagem será gravada).

 #include <stdio.h>
 
 int main()
 {
   int i, j, k;
   char string1[] = "10 20 30";
   sscanf(string1, "%d %d %d", &i, &j, &k);
   printf("Valores lidos: %d, %d, %d", i, j, k);
   return 0;
 }

Nesse exemplo, usamos a função sscanf para interpretar os valores contidos na string e guardá-los nas variáveis numéricas.