![]() |
![]() |
![]() |
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.
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.
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.
É 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.
![]() |
As funções mais básicas que permitem efectuar operações de entrada/saída nos streams pré-definidos stdin e stdout são:
int getchar(void);
- lê apenas um carácter
de stdin (codificado como inteiro)int putchar(char ch);
- coloca o carácter ch
em stdoutEstas 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:
int getc(FILE *stream);
int putc(char ch, FILE *stream);
![]() |
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()
.
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 stringformat
; 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%
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);
![]() |
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);
}
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;- 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;
size_t fwrite(void *ptr, size_t size, size_t nobj, FILE *stream);- transfere qualquer informação que porventura ainda se encontre no buffer associado ao stream para o respectivo ficheiro;
int fflush(FILE *stream);
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);
![]() |
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);
...
![]() |
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.
![]() |
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.
![]() |
2006
Antônio Paulo Neto