Contents

Capítulo 11 - Entrada e Saída


botões
botões Conteúdo Capítulo anterior Capítulo seguinte

Este capítulo foca algumas das formas de Entrada/Saída (I/O) da Linguagem C. Algumas dessas formas, já mencionadas em capítulos anteriores, são estudadas neste com maior detalhe.

Praticamente todas as formas de I/O do C têm os seus tipos e funções declaradas no ficheiro de inclusão da biblioteca standard, stdio.h.


Tópicos

setas
setas Up

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 efetuar 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 é suficiente conhecer acerca da estrutura. Basta declarar um apontador e usá-lo para efetuar operações de I/O (escrita e/ou leitura). Antes de se poder efetuar propriamente as operações de transferência de informação para os streams é necessário executar uma operação de abertura (open). Quando não houver necessidade de realizar 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 efetuadas as transferências de informação para os dispositivos que os streams representam. Na ilustração seguinte pode ver-se um esquema deste mecanismo.

Figura stream

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.

Redirecionamento

É 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 redirecionamento não faz parte da linguagem C mas sim do sistema operativo. Em geral é efetuado a partir da linha de comando (p. ex. no UNIX).

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

out > file1

O símbolo < utiliza-se para redirecionar 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 redirecionamento, 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 direta 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.

setas
setas Up

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

As funções mais básicas que permitem efetuar 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 caractere noutros streams que não o stdin e stdout:

setas
setas 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 da função em maior detalhe. Também vamos ver a função inversa, que lê valores do teclado diretamente 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:

Tabela especificador de formato
Especificador de formato
(a seguir a %)
Tipo Resultado
c char um único caractere
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 caractere %

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

Entre o caractere % e o caractere 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 exceçã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á foi dito a lista de argumentos, que irá 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 diretamente usado na lista de argumentos, uma vez que representa o endereço da 1ª posição do array.

Por exemplo:

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

setas
setas 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 é através de uma operação de abertura, que é efetuada através da 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 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, respetivamente.

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, se efetivamente foram abertos:

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) respetivamente. 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á mencionadas.

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 objetos 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 objetos 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 respetivo ficheiro;
int fclose(FILE *stream); - fecha o stream desassociando-se 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);

setas
setas Up

As funções sprintf e sscanf

Estas funções são em tudo idênticas a fprintf() e fscanf(), exceto no fato 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);
...

setas
setas 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, exceto se o argumento for incluído entre aspas; as aspas não fazem parte do argumento.

setas
setas 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 efetivamente 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 ecrã 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.

setas
setas Up