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 arquivo de inclusão da biblioteca standard stdio.h.
Streams pré-definidos
Redireccionamento
Printf
Scanf
Leitura e escrita de arquivos
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 arquivo 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 efetuar operações de I/O (escrita e/ou leitura). Antes de se poderem efetuar 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 fechamento (close).
As operações de I/O em streams são bufferizadas, isto é, existe sempre uma área de memória intermediaria (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.
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 de outros Sistemas Operacionais temos por default pelo menos 3 streams, já abertos e prontos a serem usados:
stdin (geralmente associado ao teclado)
stdout (geralmente associado ao terminal)
stderr (também geralmente associado ao terminal)
Todos eles usam texto como modo de I/O.
É um processo de associar os streams pré-definidos stdin e stdout com arquivos 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 operacional. Em geral é efetuado a partir da linha de comando (p. ex. no UNIX).
O símbolo > é utilizado para redireccionar o stdout para um arquivo. Assim se tivermos um programa out que escreve normalmente no vídeo do terminal, podemos dirigir a sua saída diretamente para um arquivo invocando o comando:
out > file1
O símbolo < utiliza-se para redirecionar um arquivo 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 arquivo, 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.
As funções mais básicas que permitem efetuar 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 stdout
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 arquivo 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 em outros 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 diretamente 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 string format; retorna o número de caracteres escrito.
A string format contém 2 tipos de especificações:
caracteres ordinários - que são simplesmente copiados para o stream stdout;
especificações de conversão - formadas pelo carácter %, seguido de um ou mais caracteres de especificação do formato, de acordo com a tabela seguinte:
Especificador de formato |
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 |
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 |
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:
- (sinal menos) : faz uma justificação à esquerda;
+ (sinal mais) : o valor a escrever contém sempre um sinal (- ou +);
valor inteiro : tamanho em caracteres do valor a escrever (se o valor para ser escrito ocupar mais caracteres do que o especificado, não é truncado);
indicador na forma num1.num2 : num1-tamanho total em caracteres do valor a escrever; num2-precisão (número de algarismos decimais).
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 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á 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 diretamente 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 arquivos armazenados em disco. A primeira operação necessária para trabalhar com esse tipo de streams é uma operação de abertura, efetuada 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 arquivo armazenado no disco que se pretende acessar. O parâmetro mode controla o tipo de acesso. Se o arquivo especificado não puder ser acessado, por qualquer razão, a função retornará um apontador nulo (NULL).
Os modos principais incluem:
"r" - apenas leitura;
"w" - apenas escrita, cria sempre um novo arquivo mesmo que já exista;
"a" - permite acrescentar nova informação a arquivos já existentes.
É possível acrescentar aos designadores de modo as letras 't' ou 'b', que especificam o tipo de informação do arquivo: texto 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 arquivo chamado myfile.dat para leitura apenas:
#include <stdio.h>
...
FILE *stream;
...
stream = fopen("myfile.dat", "rb");
É sempre boa política verificar se os arquivos que se pretendem abrir, o foram efetivamente:
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 acessar a streams associados a arquivos 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 arquivos:
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 arquivo;
int fclose(FILE *stream); - fecha o stream desassociando-o do arquivo;
Os streams pré-definidos e abertos também podem ser acessados com as funções próprias dos arquivos:
fprintf(stderr, "Cannot compute!!\n");
fscanf(stdin, "%s", string);
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 arquivos:
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 chama 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:
argc - contém o número de argumentos passados, incluindo o próprio nome do programa;
argv - é um array de strings onde em cada posição é passado um argumento; na primeira posição (índice 0) é sempre passado o nome do programa.
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 chamado 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.
[REV 5/2000]