TEMA 6 : Entrada y salida (E/S) de datos
6.0 Introducción
El objetivo de este tema es hacer un estudio completo en
todo lo referente a la entrada y, salida (E/S) en C, estudiando
también los dos sistemas de ficheros que existen en este lenguaje.
6.1 Entrada y salida (E/S)
Las operaciones de entrada y salida (abreviadamente E/S) no
forman parte del lenguaje C propiamente dicho, sino que están en una
biblioteca o librería: <stdio.h>. Todo programa que utilice
funciones de entrada y salida estándar deberá contener la línea:
#include <stdio.h>
6.1.1 E/S estándar
Por defecto, la entrada estándar es el teclado y la salida
estándar es la pantalla o monitor. Hay dos formas básicas de cambiar
la entrada y la salida estándar:
1. Con los símbolos de redirección (<, >, <<, >>) o de tubería (|)
del sistema operativo al ejecutar el programa desde la línea de
órdenes.
2. Con determinadas funciones y variables que se encuentran en la
librería <stdio.h> en el código fuente del programa.
6.2 Flujos y ficheros
Hay dos conceptos muy importantes en C relacionados con la
E/S: flujos (streams, en inglés) y ficheros. Los flujos son
sucesiones de caracteres a través de los cuales realizamos las
operaciones de E/S. Para el programador todos los flujos son
iguales. Para el C (en general para el sistema operativo) un fichero
es un concepto lógico que puede ser aplicado a cualquier cosa desde
ficheros de discos a terminales. A cada fichero se le asigna un
flujo al realizar la operación de apertura sobre él. Para el
programador un fichero es un dispositivo externo capaz de una E/S.
Todos los ficheros no son iguales pero todos los flujos sí. Esto
supone una gran simplificación para el usuario, ya que sólo tiene
que pensar en términos de flujo y no de dispositivos concretos. Por
ejemplo, si el usuario hace: printf ("mensaje"); sabe que ®mensaje¯
se escribe en el flujo estándar de salida, ya sea la pantalla, un
fichero de disco, una cinta, ...
6.3 Tipos de flujos: flujos de texto y flujos binarios
Cuando hemos dicho que todos los flujos son iguales, es
cierto que lo son en su utilización por parte del programador, pero
en realidad, podemos distinguir dos tipos:
- Flujos de texto: son una sucesión de caracteres originado en
líneas que finalizan con un carácter de nueva-línea. En estos
flujos puede no haber una relación de uno a uno entre los
caracteres que son escritos (leídos) y los del dispositivo
externo, por ejemplo, una nueva-línea puede transformarse en un
par de caracteres (un retorno de carro y un carácter de salto de
línea).
- Flujos binarios: son flujos de bytes que tienen una
correspondencia uno a uno con los que están almacenados en el
dispositivo externo. Esto es, no se presentan desplazamientos de
caracteres. Además el número de bytes escritos (leídos) es el
mismo que los almacenados en el dispositivo externo.
Esta diferencia de flujos es importante tenerla en cuenta al
leer ficheros de discos. Supongamos que tenemos un fichero de disco
con 7 caracteres donde el cuarto carácter es el carácter fin de
fichero (en sistema operativo DOS es el carácter con código ASCII
26). Si abrimos el fichero en modo texto, sólo podemos leer los 3
primeros caracteres, sin embargo, si lo abrimos en modo binario,
leeremos los 7 caracteres ya que el carácter con código ASCII 26 es
un carácter como cualquier otro.
6.4 Programas C con flujos
Al principio de la ejecución de un programa C se abren tres
flujos de tipo texto predefinidos:
stdin : dispositivo de entrada estándar
stdout: dispositivo de salida estándar
stderr: dispositivo de salidad de error estándar
Al finalizar el programa, bien volviendo de la función main
al sistema operativo o bien por una llamada a exit(), todos los
ficheros se cierran automáticamente. No se cerrarán si el programa
termina a través de una llamada a abort() o abortando el programa.
Estos tres ficheros no pueden abrirse ni cerrarse
explicítamente.
6.5 Resumen de lo anterior
Como todo lo que acabamos de decir puede resultar un poco
confuso a las personas que tienen poca experiencia en C, vamos a
hacer un pequeño resumen en términos generales:
1. En C, cualquier cosa externa de la que podemos leer o en la que
podemos escribir datos es un fichero.
2. El programador escribe (lee) datos en estos ficheros a través de
los flujos de cada fichero. De esta forma el programador escribe
(lee) los datos de la misma forma en todos los tipos de ficheros
independientemente del tipo de fichero que sea.
3. Aunque conceptualmente todos los flujos son iguales, en realidad
hay dos tipos: flujos de texto y flujos binarios.
4. Hay tres flujos de texto predefinidos que se abren
automáticamente al principio del programa: stdin, stdout y
stderr. Estos tres flujos se cierran automáticamente al final del
programa.
6.6 Pasos para operar con un fichero
Los pasos a realizar para realizar operaciones con un
fichero son los siguientes:
1) Crear un nombre interno de fichero. Esto se hace en C declarando
un puntero de fichero (o puntero a fichero). Un puntero de fichero
es una variable puntero que apunta a una estructura llamada FILE.
Esta estructura está definida en el fichero stdio.h y contiene toda
la información necesaria para poder trabajar con un fichero. El
contenido de esta estructura es dependiente de la implementación de
C y del sistema, y no es interesante saberlo para el programador.
Ejemplo:
FILE *pf; /* pf es un puntero de fichero */
2) Abrir el fichero. Esto se hace con la función fopen() cuyo
prototipo se encuentra en el fichero stdio.h y es:
FILE *fopen (char *nombre_fichero, char *modo);
Si el fichero con nombre nombre_fichero no se puede abrir
devuelve NULL.
El parámetro nombre_fichero puede contener la ruta completa
de fichero pero teniendo en cuenta que la barra invertida (\) hay
que repetirla en una cadena de caracteres.
Los valores válidos para el parámeto modo son:
+-------+-------------------------------------------------+
| Modo | Interpretación |
+-------+-------------------------------------------------+
| "r" | Abrir un fichero texto para lectura |
| "w" | Crear un fichero texto para escritura |
| "a" | Añadir a un fichero texto |
| "rb" | Abrir un fichero binario para lectura |
| "wb" | Crear un fichero binario para escritura |
| "ab" | Añadir a un fichero binario |
| "r+" | Abrir un fichero texto para lectura/escritura |
| "w+" | Crear un fichero texto para lectura/escritura |
| "a+" | Abrir un fichero texto para lectura/escritura |
| "rb+" | Abrir un fichero binario para lectura/escritura |
| "wb+" | Crear un fichero binario para lectura/escritura |
| "ab+" | Abrir un fichero binario para lectura/escritura |
+-------+-------------------------------------------------+
Si se utiliza fopen() para abrir un fichero para escritura,
entonces cualquier fichero que exista con ese nombre es borrado y se
comienza con un fichero nuevo. Si no existe un fichero con ese
nombre, entonces se crea uno. Si lo que se quiere es añadir al final
del fichero, se debe utilizar el modo "a". Si no existe el fichero,
devuelve error. Abrir un fichero para operaciones de lectura
necesita que el fichero exista. Si no existe devuelve error.
Finalmente, si se abre un fichero para operaciones de
lectura/escritura, no se borra en caso de existir. Sin embargo, si
no existe se crea.
Ejemplo:
FILE *pf;
pf = fopen ("c:\\autoexec.bat", "r");
if (pf == NULL) /* siempre se debe hacer esta comprobación*/
{
puts ("No se puede abrir fichero.");
exit (1);
}
3) Realizar las operaciones deseadas con el fichero como pueden ser
la escritura en él y la lectura de él. Las funciones que disponemos
para hacer esto las veremos un poco más adelante.
4) Cerrar el fichero. Aunque el C cierra automáticamente todos los
ficheros abiertos al terminar el programa, es muy aconsejable
cerrarlos explícitamente. Esto se hace con la función fclose() cuyo
prototipo es:
int fclose (FILE *pf);
La función fclose() cierra el fichero asociado con el flujo
pf y vuelca su buffer.
Si fclose() se ejecuta correctamente devuelve el valor 0. La
comproba ción del valor devuelto no se hace muchas veces porque no
suele fallar.
Ejemplo:
FILE *pf;
if ((pf = fopen ("prueba", "rb")) == NULL)
{
puts ("Error al intentar abrir el fichero.");
exit (1);
}
/* ... */
if (fclose (pf) != 0)
{
puts ("Error al intentar cerrar el fichero.");
exit (1);
}
Resumen de los 4 pasos para la manipulación de un fichero:
----------------------------------------------------------
1) Declarar un puntero de fichero.
FILE *pf;
2) Abrirlo el fichero.
if ((pf = fopen ("nombre_fichero", "modo_apertura")) == NULL)
error ();
else
/* ... */
3) Realizar las operaciones deseadas con el fichero.
/* En las siguientes ventanas veremos las
funciones que tenemos para ello. */
4) Cerrar el fichero.
if (fclose (pf) != 0)
error ();
else
/* ... */
6.7 Sistema de ficheros tipo UNIX
Debido a que el lenguaje C se desarrolló inicialmente bajo
el sistema operativo UNIX, se creó un segundo sistema de ficheros. A
este sistema se le llama E/S sin buffer, E/S de bajo nivel o E/S
tipo UNIX. Hoy en día, este sistema de ficheros está totalmente en
desuso y se considera obsoleto, además el nuevo estándar ANSI ha
decidido no estandarizar el sistema de E/S sin buffer tipo UNIX. Por
todo lo dicho no se puede recomendar este sistema a los nuevos
programadores C. Sin embargo, todavía existen programas que lo usan
y es soportado por la mayoría de los compiladores de C. Así que
incluimos una breve explicación al respecto.
6.7.1 Descriptores de ficheros
A diferencia del sistema de E/S de alto nivel, el sistema de
bajo nivel no utiliza punteros a ficheros de tipo FILE; el sistema
de bajo nivel utiliza descriptores de fichero de tipo int. A cada
fichero se le asocia un número (su descriptor de fichero).
Hay tres descriptores de fichero predefinidos:
0 entrada estándar
1 salida estándar
2 salida de errores estándar
Los cuatro pasos para la manipulación de ficheros con E/S de
alto nivel que vimos antes se transforman en la E/S de bajo nivel en
los siguientes pasos:
1) Declarar un descriptor de fichero para el fichero a abrir.
int fd;
2) Abrir el fichero.
if ((fd = open (nombre_fichero, modo_apertura)) = -1)
{
printf ("Error al intentar abrir el fichero.\n");
exit (1);
}
3) Manipular el fichero con las funciones que tenemos disponible
para ello.
4) Cerrar el fichero.
close (fd);