En este capítulo se verán varias formas de entrada y salida (E/S). Se han mencionado brevemente algunas formas y ahora se revisarán con un poco más de detalle.
Los programas que hagan uso de las funciones de la biblioteca de E/S deben incluir la cabecera, esto es:
#include <stdio.h>
En muchas ocasiones es útil reportar los errores en un programa de C. La
función de la biblioteca estándar perror
es la indicada para
hacerlo. Es usada conjuntamente con la variable errno
y frecuentemente cuando se encuentra un error se desea terminar el programa antes. Además se revisa la función exit()
y errno
, que en un sentido estricto no son parte de la biblioteca stdio.h, para ver como trabajan con perror
.
El prototipo de la funcion perror
es:
void perror(const char *s);
La función perror()
produce un mensaje que va a la salida estándar
de errores, describiendo el último error encontrado durante una llamada al
sistema o a ciertas funciones de biblioteca. La cadena de caracteres s
que se pasa como argumento, se muestra primero, luego un signo de dos puntos y
un espacio en blanco; por último, el mensaje y un salto de línea. Para
ser de más utilidad, la cadena de caracteres pasada como argumento
debería incluir el nombre de la función que incurrió en el error. El
código del error se toma de la variable externa errno
, que toma un
valor cuando ocurre un error, pero no es puesta a cero en una llamada no
errónea.
A la variable especial del sistema errno
algunas llamadas al
sistema (y algunas funciones de biblioteca) le dan un valor entero, para
indicar que ha habido un error. Esta variable esta definida en la cabecera
#include <errno.h>
y para ser usada dentro de un programa debe ser
declarada de la siguiente forma:
extern int errno;
El valor sólo es significativo cuando la llamada devolvió un error
(usualmente -1), algunas veces una función también puede devolver -1 como
valor válido, por lo que se debe poner errno
a cero antes de la
llamada, para poder detectar posibles errores.
La función exit
tiene el siguiente prototipo de acuerdo a la
cabecera #include <stdlib.h>
:
void exit(int status);
La función produce la terminacón normal del programa y la devolución de status al proceso padre o al sistema operativo. El valor de status es usado para indicar como ha terminado el programa:
EXIT_SUCCESS
en una terminación
EXIT_FAILURE
en una terminación
Por lo tanto cuando se encuentre un error se llamará a la función como
exit(EXIT_FAILURE)
para terminar un programa con errores.
Los flujos son una forma flexible y eficiente para leer y escribir datos.
Existe una estructura interna de C, FILE
, la cual representa a todas
los flujos y esta definida en stdio.h
. Por lo tanto simplemente se
necesita referirse a la estructura para realizar entrada y salida de datos.
Para usar los flujos entonces se debe declarar una variable o apuntador de este tipo en el programa. No se requiere conocer más detalles acerca de la definición. Se debe abrir un flujo antes de realizar cualquier E/S, después se puede accesar y entonces se cierra.
El flujo de E/S usa un BUFFER, es decir, un pedazo fijo de área temporal de la memoria (el buffer) es leído o escrito a un archivo. Lo siguiente se muestra en la figura 17.1. Observar que el apuntador del archivo actualmente apunta a éste buffer.
Esto conduce a un uso eficiente de E/S pero se debe tener cuidado: los datos
escritos a un buffer no aparecen en un archivo (o dispositivo) hasta que el
buffer es escrito (con \n
se puede hacer). Cualquier salida anormal del
código puede causar problemas.
En UNIX se tienen predefinidos 3 flujos (en stdio.h): stdin, stdout y stderr.
Todas ellas usan texto como método de E/S.
Los flujos stdin y stdout pueden ser usadas con archivos, programas, dispositivos de E/S como el teclado, la consola, etc. El flujo stderr siempre va a la consola o la pantalla.
La consola es el dispositivo predefinido para stdout y stderr. El teclado lo es para stdin.
Los flujos predefinidos son automáticamente abiertas.
Lo siguiente no es parte de C, pero depende del sistema operativo. Se hace redireccionamiento desde la línea de comandos con:
>
que redirecciona stdout a un archivo.
Por lo tanto, si se tiene un programa llamado salida, que usualmente muestra en pantalla algo, entonces:
salida > archivo_sal
mandará la salida al archivo archivo_sal
<
redirecciona stdin desde un archivo a un programa.
Por lo tanto, si se espera entrada desde el teclado para un programa llamado entrada, se puede leer en forma similar la entrada desde un archivo.
entrada < archivo_ent
Con |
entubamiento o pipe se coloca stdout de un programa
en stdin de otro,
prog1 | prog2
Por ejemplo, mandar la salida (usualmente a consola) de un programa directamente a la impresora
out | lpr
Hay un par de funciones que dan las facilidades básicas de E/S. Quizás las
más comunes son: getchar()
y putchar()
. Están definidas y
son usadas como sigue:
int getchar(void)
-- lee un caracter de stdin
int putchar(char ch)
-- escribe un caracter a stdout y regresa
el caracter escrito.
main() { int ch; ch = getchar(); (void) putchar((char) ch); }
Otras funciones relacionadas son:
int getc(FILE *flujo); int putc(char ch, FILE *flujo);
Se han visto ya algunos ejemplos de como C usa la E/S formateada. En esta sección se revisarán con más detalle.
El prototipo de la función esta definido como:
int printf( const char *formato, lista arg ...);
que muestra en stdout la lista de argumentos de acuerdo al formato especificado. La función devuelve el número de caracteres impresos.
La cadena de formateo tiene dos tipos de objetos:
%
y listados en la tabla 16.1.
|
Entre el % y el caracter de formato se puede poner:
Por lo tanto:
printf("%-2.3f\n",17.23478);
la salida en pantalla será:
17.235
y
printf("VAT=17.5%%\n");
genera la siguiente salida:
VAT=17.5%
La función esta definida como sigue:
int scanf( const char *formato, lista arg ...);
Lee de la entrada estándar (stdin) y coloca la entrada en la dirección de las variables indicadas en lista args. Regresa el número de caracteres leídos.
La cadena de control de formateo es similar a la de printf
.
Importante: se requiere la dirección de la variable o un apuntador
con scanf
.
Por ejemplo:
scanf("%d", &i);
Para el caso de un arreglo o cadena sólo se requiere el nombre del mismo para poder
usar scanf
ya que corresponde al inicio de la dirección de la cadena.
char cadena[80]; scanf("%s",cadena);
Los archivos son la forma más común de los flujos.
Lo primero que se debe hacer es abrir el archivo. La función
fopen()
hace lo siguiente:
FILE *fopen(const char *nomb, const char *modo);
fopen
regresa un apuntador a un FILE
. En la cadena nomb
se pone el nombre y la trayectoria del archivo que se desea accesar. La cadena
modo
controla el tipo de acceso. Si un archivo no puede ser accesado
por alguna razón un apuntador NULL
es devuelto.
Los modos son:
Para abrir un archivo se debe tener un flujo (apuntador tipo archivo) que
apunte a la estructura FILE
.
Por lo tanto, para abrir un archivo denominado miarch.dat para lectura haremos algo como lo siguiente;
FILE *flujo; /* Se declara un flujo */ flujo = fopen("miarch.dat","r");
es una buena práctica revisar si un archivo se pudo abrir correctamente
if ( (flujo = fopen("miarch.dat","r")) == NULL ) { printf("No se pudo abrir %s\n","miarch.dat"); exit(1); } .....
Las funciones fprintf
y fscanf
son comúnmente empleadas para accesar archivos.
int fprintf(FILE *flujo, const char *formato, args ... ); int fscanf(FILE *flujo, const char *formato, args ... );
Las funciones son similares a printf
y scanf
excepto que los datos son leídos desde el flujo, el cual deberá ser abierto con fopen()
.
El apuntador al flujo es automáticamente incrementado con todas las funciones de lectura y escritura. Por lo tanto, no se debe preocupar en hacer lo anterior.
char *cadena[80]; FILE *flujo; if ( (flujo = fopen( ... )) != NULL) fscanf(flujo,"%s",cadena);
Otras funciones para archivos son:
int getc(FILE *flujo) int fgetc(FILE *flujo) int putc(char ch, FILE *s) int fputc(char ch, FILE *s)
Estas son parecidas a getchar
y putchar
.
getc
esta definida como una macro del preprocesador en stdio.h
. fgetc
es una función de la biblioteca de C. Con ambas se consigue el mismo resultado.
Para el volcado de los datos de los flujos a disco, o bien, para disasociar un flujo a un archivo, haciendo previamente un volcado, usar:
int fflush(FILE *flujo); int fclose(FILE *flujo);
También se puede tener acceso a los flujos predeterminados con fprintf
, etc. Por ejemplo:
fprintf(stderr,"¡¡No se puede calcular!!\n"); fscanf(stdin,"%s",string);
Son parecidas a fprintf
y fscanf
excepto que escriben/leen una cadena.
int sprintf(char *cadena, char *formato, args ... ) int sscanf(char *cadena, cahr *formato, args ... )
Por ejemplo:
float tanque_lleno = 47.0; /* litros */ float kilometros = 400; char km_por_litro[80]; sprintf( km_por_litro, "Kilometros por litro = %2.3f", kilometros/tanque_lleno);
Existen unas cuantas funciones útiles para conocer el estado de algún flujo y que tienen los prototipos siguientes:
int feof(FILE *flujo); int ferror(FILE *flujo); void clearerr(FILE *flujo); int fileno(FILE *flujo);
fp
, línea a línea se podría hacer algo como:
while ( !feof(fp) ) fscanf(fp,"%s",linea);
Esta forma de E/S es sin buffer -cada requerimiento de lectura/escritura genera un acceso al disco (o dispositivo) directamente para traer/poner un determinado número de bytes.
No hay facilidades de formateo -ya que se están manipulando bytes de información.
Lo anterior significa que se estan usando archivos binarios (y no de texto).
En vez de manejar apuntadores de archivos, se emplea un manejador de archivo de bajo nivel o descriptor de archivo, el cual da un entero único para identificar cada archivo.
Para abrir un archivo usar:
int open(char* nomb, int flag);
que regresa un descriptor de archivo ó -1 si falla.
flag
controla el acceso al archivo y tiene los siguientes macros definidas en fcntl.h
:
para ver otras opciones usar man
.
La función:
int creat(char* nomb, int perms);
puede también ser usada para crear un archivo.
Otras funciones son:
int close(int fd); int read(int fd, char *buffer, unsigned longitud); int write(int fd, char *buffer, unsigned longitud);
que pueden ser usadas para cerrar un archivo y leer/escribir un
determinado número de bytes de la memoria/hacia un archivo en la
localidad de memoria indicada por buffer
.
La función sizeof()
es comúnmente usada para indicar la longitud
.
Las funciones read
y write
regresan el número de bytes leídos/escritos o -1 si fallan.
Se tiene a continuación dos aplicaciones que usan algunas de las funciones indicadas:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> main(int argc, char **argv) { float buffer[]={23.34,2.34,1112.33}; int df; int bytes_esc; int num_flot; /* Primeramente se crea el archivo */ if ( (df = open(argv[1], O_CREAT, S_IRUSR | S_IWUSR) ) == -1) { /* Error, archivo no abierto */ perror("Archivo datos, apertura"); exit(1); } else printf("Descriptor de archivo %d\n",df); /* Despues se abre para solamente escribir */ if ( (df = open(argv[1], O_WRONLY) ) == -1) { /* Error, archivo no abierto */ perror("Archivo datos, apertura"); exit(1); } else printf("Descriptor de archivo %d\n",df); /* En primer lugar se escribe el n´umero de flotantes que seran escritos */ num_flot = 3; if ( (bytes_esc = write(df, &num_flot, sizeof(int)) ) == -1) { /* Error en la escritura */ perror("Archivo datos, escritura"); exit(1); } else printf("Escritos %d bytes\n",bytes_esc); /* Se escribe el arreglo de flotantes */ if ( (bytes_esc = write(df, buffer, num_flot*sizeof(float)) ) == -1) { /* Error en la escritura */ perror("Archivo datos, escritura"); exit(1); } else printf("Escritos %d bytes\n",bytes_esc); close(df); }
Ejemplo de lectura del ejemplo anterior:
/* Este programa lee una lista de flotantes de un archivo binario. */ /* El primer byte del archivo es un entero indicando cuantos */ /* flotantes hay en el archivo. Los flotantes estan despues del */ /* entero, el nombre del archivo se da en la linea de comandos. */ #include <stdio.h> #include <fcntl.h> main(int argc, char **argv) { float buffer[1000]; int fd; int bytes_leidos; int num_flot; if ( (fd = open(argv[1], O_RDONLY)) == -1) { /* Error, archivo no abierto */ perror("Archivo datos"); exit(1); } if ( (bytes_leidos = read(fd, &num_flot, sizeof(int))) == -1) { /* Error en la lectura */ exit(1); } if ( num_flot > 999 ) { /* arch muy grande */ exit(1); } if ( (bytes_leidos = read(fd, buffer, num_flot*sizeof(float))) == -1) { /* Error en la lectura */ exit(1); } }
Copiar bloques de 512 bytes cada vez.
Revisar que el programa: - tenga dos argumentos o mostrar "El programa requiere dos argumentos"; - el primer nombre de archivo sea de lectura o mostrar "No se puede abrir archivo ... para lectura"; - que el segundo nombre del archivo sea de escritura o mostrar "No se puede abrir archivo ... para escritura".