Capítulo 11 - Entrada e Saída


Conteúdo Capítulo anterior Capítulo seguinte

Este capítulo focará algumas das muitas formas de Entrada/Saída (I/O) da Linguagem C. Algumas dessas formas foram já brevemente mencionadas em capítulos anteriores, mas tornaremos a estudá-las, com muito mais detalhe aqui. Praticamente todas as formas de I/O do C têm os seus tipos e funções declarados no ficheiro de inclusão da biblioteca standard stdio.h.


Tópicos



Streams

Os streams constituem uma forma portável de ler e escrever informação nos programas escritos em C. São também uma forma bastante eficiente e flexível de efectuar essa transferência de informação.

Um stream é um ficheiro ou outro dispositivo de entrada/saída (por exemplo: o terminal, o teclado, a impressora, etc) que é manipulado através de um apontador para o stream. Na biblioteca standard os streams são representados por uma estrutura definida em stdio.h e com o nome de FILE. Os programas que manipulam streams usam então um apontador para esta estrutura (FILE *).

Do ponto de vista do programa em C não é necessário conhecer mais nada acerca desta estrutura. Basta declarar um apontador para ela e usá-lo para efectuar operações de I/O (escrita e/ou leitura). Antes de se poderem efectuar propriamente as operações de transferência de informação para os streams é necessário executar uma operação de abertura (open). Quando já não houver mais necessidade de novas operações de transferência de informação podemos (e devemos) fechar o stream com uma operação de fecho (close).

As operações de I/O em streams são bufferizadas, isto é, existe sempre uma área de memória intermédia (o buffer) para e de onde são efectuadas as transferências de informação para os dispositivos que os streams representam. Na ilustração seguinte pode ver-se um esquema deste mecanismo.

A existência de um buffer leva a uma maior eficiência nas operações de I/O, no entanto os dados presentes no buffer não são imediatamente transferidos para o stream. Qualquer terminação anormal de um programa pode conduzir a perda de informação se esta ainda não tiver sido transferida.

Streams pré-definidos

No UNIX, e na maior parte doutros Sistemas Operativos definem-se à partida pelo menos 3 streams, já abertos e prontos a utilizar:

Todos eles usam texto como modo de I/O.

Redireccionamento

É um processo de associar os streams pré-definidos stdin e stdout com ficheiros ou outros dispositivos de I/O que não o terminal e o teclado. O redireccionamento não faz parte da linguagem C mas sim do sistema operativo. Em geral é efectuado a partir da linha de comando (p. ex. no UNIX).

O símbolo > é utilizado para redireccionar o stdout para um ficheiro. Assim se tivermos um programa out que escreve normalmente no écran do terminal, podemos dirigir a sua saída directamente para um ficheiro invocando o comando:

out > file1

O símbolo < utiliza-se para redireccionar um ficheiro especificado para o stream pré-definido stdin de um programa. Assim, se tivermos um programa denominado in que espera dados provenientes do teclado, estes, através do redireccionamento, podem provir de um ficheiro, como se mostra a seguir:

in < file2

Pode utilizar-se ainda o símbolo | (pipe), que faz uma ligação directa entre o stdout de um programa e o stdin de um outro programa:

prog1 | prog2

A saída de prog1, que normalmente seria escrita no terminal, funciona como entrada de prog2, que normalmente a esperaria vinda do teclado.

Up

Operações básicas de entrada/saída

As funções mais básicas que permitem efectuar operações de entrada/saída nos streams pré-definidos stdin e stdout são:

Estas funções, bem como todas as outras que dizem respeito a operações de entrada/saída suportadas pela biblioteca standard do C, estão declaradas no ficheiro de inclusão stdio.h.

Exemplo:

#include <stdio.h>
...
int ch;
...
ch = getchar();
putchar((char) ch);
...

Existem funções semelhantes para leitura e escrita de um carácter noutros streams que não o stdin e stdout:

Up

Entrada/saída formatadas

Já temos visto, em exemplos anteriores, a escrita de texto e valores, com um formato especificado, no terminal utilizando a função printf(). Vamos ver a utilização desta função em maior detalhe a seguir. Veremos também a função inversa, que lê valores do teclado directamente para variáveis - a função scanf().

Printf

A função, também declarada em stdio.h, é definida como se mostra a seguir:

int printf(char *format, ...); - Escreve em stdout a sua lista de argumentos (especificada em ...) de acordo com um formato especificado na string format; retorna o número de caracteres escrito.

A string format contém 2 tipos de especificações:

Especificador de formato
(a seguir a %)
Tipo Resultado
c char um único carácter
i ou d int número inteiro
o int número em octal
x ou X int número em hexadecimal
(com letras minúsculas ou maiúsculas)
u unsigned int número inteiro sem sinal
s char * escreve o string terminado com \0
f double ou float número real com parte decimal
e ou E double ou float  número real escrito em notação científica 
g ou G double ou float equivalente a e ou f
 (é escolhido o que ocupar menos espaço) 
hi ou hd short número inteiro curto
li ou ld long número inteiro longo
Lf long double número real com parte decimal
% - escreve o carácter %

Em geral os prefixos h e l representam os modificadores short e long, respectivamente.

Entre o carácter % e o carácter especificador de formato podemos colocar ainda os seguintes indicadores:

Alguns exemplos:

printf("%-2.3f\n", 17.23478);

produz no terminal a saída:    17.234

printf("VAT = 17.5%%\n");

que escreve:   VAT = 17.5%

Scanf

A função scanf() é definida em stdio.h como se segue:

int scanf(char *format, ...); - lê caracteres de stdin e coloca os valores lidos e convertidos nas variáveis cujos endereços são passados na lista de argumentos a seguir à indicação do formato; retorna o número de caracteres lidos e convertidos.

A indicação do formato é muito semelhante à especificada para printf(). A única excepção diz respeito aos especificadores f, e ou g, que aqui se referem exclusivamente a valores do tipo float ( os valores lidos e convertidos deverão ser passados a apontadores para variáveis do tipo float). Para especificar valores do tipo double deverão ser usados os especificadores lf, le ou lg.

Como já se disse a lista de argumentos, que irão receber os valores lidos, deverá conter apenas apontadores ou endereços de variáveis. Por exemplo:

scanf("%d", &i);

para ler um valor inteiro do teclado e colocá-lo na variável de tipo int i.

No caso de arrays (strings, por exemplo) o próprio nome pode ser directamente usado na lista de argumentos, uma vez que representa o endereço da 1ª posição do array. Veja-se o exemplo:

char str[80];
...
scanf("%s", str);

Up

Ficheiros

Os streams mais comuns são os que ficam associados a ficheiros armazenados em disco. A primeira operação necessária para trabalhar com esse tipo de streams é uma operação de abertura, efectuada com a função fopen():

FILE *fopen(char *name, char *mode);

A função fopen() retorna um apontador para uma estrutura FILE. O parâmetro name é o nome do ficheiro armazenado no disco que se pretende aceder. O parâmetro mode controla o tipo de acesso. Se o ficheiro especificado não puder ser acedido, por qualquer razão, a função retornará um apontador nulo (NULL).

Os modos principais incluem:

É possível acrescentar aos designadores de modo as letras 't' ou 'b', que especificam o tipo de informação do ficheiro: textual ou binária, respectivamente.

O apontador retornado pela função deve ser guardado, uma vez que é necessário como parâmetro para todas as funções de acesso ao stream assim aberto.

Exemplo de abertura de um ficheiro chamado myfile.dat para leitura apenas:

#include <stdio.h>
...
FILE *stream;
...
stream = fopen("myfile.dat", "rb");

É sempre boa política verificar se os ficheiros que se pretendem abrir, o foram efectivamente:

if ( (stream = fopen("myfile.dat", "rb")) == NULL) {
   printf("Can't open %s\n", "myfile.dat");
   exit(1);
}

Leitura e escrita de ficheiros

As funções fprintf() e fscanf() são utilizadas para aceder a streams associados a ficheiros de um modo idêntico às funções printf() e scanf(), que estão associadas à saída standard (stdout) e entrada standard (stdin) respectivamente. Incluem apenas mais um parâmetro que é o apontador para FILE obtido com a função fopen():

int fprintf(FILE *stream, char *format, ...);
int fscanf(FILE *stream, char *format, ...);

Exemplo:

#include <stdio.h>
...
char string[80];
FILE *stream;
...
if ( (stream = fopen(...)) != NULL)
   fscanf(stream, "%s", string);
...

Outras funções que trabalham com streams associados a ficheiros:

int fgetc(FILE *stream);
int fputc(char ch, FILE *stream);

As duas funções anteriores são idênticas a getchar() e putchar(), já descritas.

size_t fread(void *ptr, size_t size, size_t nobj, FILE *stream); - lê do stream para o array apontado por ptr um número de bytes igual a size*nobj; retorna o número de objectos lidos, que pode ser menor do que nobj;
size_t fwrite(void *ptr, size_t size, size_t nobj, FILE *stream);
- escreve no stream provenientes do array apontado por ptr um número de bytes igual a size*nobj; retorna o número de objectos escritos, que pode ser menor do que nobj;
int fflush(FILE *stream);
- transfere qualquer informação que porventura ainda se encontre no buffer associado ao stream para o respectivo ficheiro;
int fclose(FILE *stream); - fecha o stream desassociando-o do ficheiro;

Os streams pré-definidos e abertos também podem ser acedidos com as funções próprias dos ficheiros:

fprintf(stderr, "Cannot compute!!\n");
fscanf(stdin, "%s", string);

Up

As funções sprintf e sscanf

Estas funções são em tudo idênticas a fprintf() e fscanf(), excepto no facto de escreverem ou lerem para ou de strings em vez de ficheiros:

int sprintf(char *string, char *format, ...);
int sscanf(char *string, char *format, ...);

Por exemplo:

#include <stdio.h>
...
float full_tank = 47.0;
float miles = 300;
char miles_per_litre[80];
...
sprintf(miles_per_litre, "Miles per litre = %2.3f", miles / full_tank);
...

Up

Passagem de parâmetros para programas

A linguagem C permite a passagem de argumentos, especificados na linha de comando que invoca um programa, para a função que começa a execução do programa. Estes argumentos aparecem na linha de comando logo após o nome do programa e são separados por um espaço. São passados à função main() como strings, através de um mecanismo especial.

Para receber estes argumentos a função main() tem de ser declarada como:

main(int argc, char **argv)

Os parâmetros argc e argv têm o seguinte significado:

Um programa simples que utiliza este mecanismo é:

#include <stdio.h>

void main(int argc, char **argv)
{
   /* programa que imprime os seus argumentos */

   int i;

   printf("argc = %d\n\n", argc);
   for (i=0; i<argc; ++i)
      printf("argv[%d]: %s\n", i, argv[i]);
}

Se este programa for compilado como args.exe e se for invocado como:

args f1 f2 "f3 a" 4 stop!

a saída será:

argc = 6

argv[0]: args
argv[1]: f1
argv[2]: f2
argv[3]: f3 a
argv[4]: 4
argv[5]: stop!

Notas: argv[0] é o nome do programa; argc conta também o nome do programa; os argumentos são delimitados por espaço, excepto se o argumento for incluído entre aspas; as aspas não fazem parte do argumento.

Up

Exercícios

1. Escreva um programa que copie um ficheiro para outro. Os nomes dos ficheiros são passados como argumentos da invocação do programa. O ficheiro deve ser copiado utilizando blocos de 512 bytes. Deverá verificar que foram efectivamente passados 2 argumentos ao programa, que o primeiro argumento pode ser aberto para leitura e ainda que o segundo argumento pode ser aberto para escrita.

2. Escreva um programa, denominado last, que escreve no écran as últimas n linhas de um ficheiro de texto. Por defeito o valor de n é 5, podendo ser modificado através de um argumento passado na linha de comando (-n). O nome do ficheiro de texto é também passado na linha de comando. A sintaxe para invocar este programa é então:    last [-n] file_name

3. Escreva um programa que compare 2 ficheiros de texto, passados na linha de comando. Sempre que 2 linhas diferirem, estas deverão ser escritas no écran com a indicação do ficheiro a que pertencem.

Up

2006

Antônio Paulo Neto
a.paulo2007@hotmail.com