2 Tipos, funciones y clases usados frecuentemente con ficheros

Funciones y tipos C estándar:

Tipo FILE

C define la estructura de datos FILE en el fichero de cabecera stdio.h para el manejo de ficheros. Nosotros siempre usaremos punteros a estas estructuras.

La definición de ésta estructura depende del compilador, pero en general mantienen un campo con la posición actual de lectura/escritura, un buffer para mejorar las prestaciones de acceso al fichero y algunos campos para uso interno.

Función fopen

Sintaxis:

FILE *fopen(char *nombre, char *modo);

Esta función sirve para abrir y crear ficheros en disco. El valor de retorno es un puntero a una estructura FILE. Los parámetros de entrada son:

  1. nombre: una cadena que contiene un nombre de fichero válido, esto depende del sistema operativo que estemos usando. El nombre puede incluir el camino completo.
  2. modo: especifica en tipo de fichero que se abrirá o se creará y el tipo de datos que puede contener, de texto o binarios:
    • r: sólo lectura. El fichero debe existir.
    • w: se abre para escritura, se crea un fichero nuevo o se sobreescribe si ya existe.
    • a: añadir, se abre para escritura, el cursor se situa al final del fichero. Si el fichero no existe, se crea.
    • r+: lectura y escritura. El fichero debe existir.
    • w+: lectura y escritura, se crea un fichero nuevo o se sobreescribe si ya existe.
    • a+: añadir, lectura y escritura, el cursor se situa al final del fichero. Si el fichero no existe, se crea.
    • t: tipo texto, si no se especifica "t" ni "b", se asume por defecto que es "t"
    • b: tipo binario.

Función fclose

Sintaxis:

int fclose(FILE *fichero);

Es importante cerrar los ficheros abiertos antes de abandonar la aplicación. Esta función sirve para eso. Cerrar un fichero almacena los datos que aún están en el buffer de memoria, y actualiza algunos datos de la cabecera del fichero que mantiene el sistema operativo. Además permite que otros programas puedan abrir el fichero para su uso. Muy a menudo, los ficheros no pueden ser compartidos por varios programas.

Un valor de retorno cero indica que el fichero ha sido correctamente cerrado, si ha habido algún error, el valor de retorno es la constante EOF. El parámetro es un puntero a la estructura FILE del fichero que queremos cerrar.

Función fgetc

Sintaxis:

int fgetc(FILE *fichero);

Esta función lee un carácter desde un fichero.

El valor de retorno es el carácter leído como un unsigned char convertido a int. Si no hay ningún carácter disponible, el valor de retorno es EOF. El parámetro es un puntero a una estructura FILE del fichero del que se hará la lectura.

Función fputc

Sintaxis:

int fputc(int caracter, FILE *fichero);

Esta función escribe un carácter a un fichero.

El valor de retorno es el carácter escrito, si la operación fue completada con éxito, en caso contrario será EOF. Los parámetros de entrada son el carácter a escribir, convertido a int y un puntero a una estructura FILE del fichero en el que se hará la escritura.

Función feof

Sintaxis:

int feof(FILE *fichero);

Esta función sirve para comprobar si se ha alcanzado el final del fichero. Muy frecuentemente deberemos trabajar con todos los valores almacenados en un archivo de forma secuencial, la forma que suelen tener los bucles para leer todos los datos de un archivo es permanecer leyendo mientras no se detecte el fin de fichero. Esta función suele usarse como prueba para verificar si se ha alcanzado o no ese punto.

El valor de retorno es distinto de cero sólo si no se ha alcanzado el fin de fichero. El parámetro es un puntero a la estructura FILE del fichero que queremos verificar.

Función rewind

Sintaxis:

void rewind(FILE *fichero)

Es una función heredada de los tiempos de las cintas magnéticas. Literalmente significa "rebobinar", y hace referencia a que para volver al principio de un archivo almacenado en cinta, había que rebobinarla. Eso es lo que hace ésta función, sitúa el cursor de lectura/escritura al principio del archivo.

El parámetro es un puntero a la estructura FILE del fichero que queremos rebobinar.

Ejemplos:

// ejemplo1.c: Muestra un fichero dos veces.
#include <stdio.h>

int main()
{
   FILE *fichero;

   fichero = fopen("ejemplo1.c", "r");
   while(!feof(fichero)) fputc(fgetc(fichero), stdout);
   rewind(fichero);
   while(!feof(fichero)) fputc(fgetc(fichero), stdout);
   fclose(fichero);
   getchar();
   return 0;
}

Función fgets

Sintaxis:

char *fgets(char *cadena, int n, FILE *fichero);

Esta función está diseñada para leer cadenas de caracteres. Leerá hasta n-1 caracteres o hasta que lea un retorno de línea. En este último caso, el carácter de retorno de línea también es leído.

El parámetro n nos permite limitar la lectura para evitar derbordar el espacio disponible en la cadena.

El valor de retorno es un puntero a la cadena leída, si se leyó con éxito, y es NULL si se detecta el final del fichero o si hay un error. Los parámetros son: la cadena a leer, el número de caracteres máximo a leer y un puntero a una estructura FILE del fichero del que se leerá.

Función fputs

Sintaxis:

int fputs(const char *cadena, FILE *stream);

La función fputs escribe una cadena en un fichero. No se añade el carácter de retorno de línea ni el carácter nulo final.

El valor de retorno es un número no negativo o EOF en caso de error. Los parámetros de entrada son la cadena a escribir y un puntero a la estructura FILE del fichero donde se realizará la escritura.

Función fread

Sintaxis:

size_t fread(void *puntero, size_t tamaño, size_t nregistros, FILE *fichero);

Esta función está pensada para trabajar con registros de longitud constante. Es capaz de leer desde un fichero uno o varios registros de la misma longitud y a partir de una dirección de memoria determinada. El usuario es responsable de asegurarse de que hay espacio suficiente para contener la información leída.

El valor de retorno es el número de registros leídos, no el número de bytes. Los parámetros son: un puntero a la zona de memoria donde se almacenarán los datos leídos, el tamaño de cada registro, el número de registros a leer y un puntero a la estructura FILE del fichero del que se hará la lectura.

Función fwrite

Sintaxis:

size_t fwrite(void *puntero, size_t tamaño, size_t nregistros, FILE *fichero);

Esta función también está pensada para trabajar con registros de longitud constante y forma pareja con fread. Es capaz de escribir hacia un fichero uno o varios registros de la misma longitud almacenados a partir de una dirección de memoria determinada.

El valor de retorno es el número de registros escritos, no el número de bytes. Los parámetros son: un puntero a la zona de memoria donde se almacenarán los datos leídos, el tamaño de cada registro, el número de registros a leer y un puntero a la estructura FILE del fichero del que se hará la lectura.

Ejemplo:

// copia.c: Copia de ficheros
// Uso: copia <fichero_origen> <fichero_destino>

#include <stdio.h>

int main(int argc, char **argv) {
    FILE *fe, *fs;
    unsigned char buffer[2048]; // Buffer de 2 Kbytes
    int bytesLeidos;

    if(argc != 3) {
       printf("Usar: copia <fichero_origen> <fichero_destino>\n");
       return 1;
    }

    // Abrir el fichero de entrada en lectura y binario
    fe = fopen(argv[1], "rb");
    if(!fe) {
       printf("El fichero %s no existe o no puede ser abierto.\n", argv[1]);
       return 1;
    }
    // Crear o sobreescribir el fichero de salida en binario
    fs = fopen(argv[2], "wb");
    if(!fs) {
       printf("El fichero %s no puede ser creado.\n", argv[2]);
       fclose(fe);
       return 1;
    }
    // Bucle de copia:
    while((bytesLeidos = fread(buffer, 1, 2048, fe)))
       fwrite(buffer, 1, bytesLeidos, fs);
    // Cerrar ficheros:
    fclose(fe);
    fclose(fs);
    return 0;
}

Función fprintf

Sintaxis:

int fprintf(FILE *fichero, const char *formato, ...);

La función fprintf funciona igual que printf en cuanto a parámetros, pero la salida se dirige a un fichero en lugar de a la pantalla.

Función fscanf

Sintaxis:

int fscanf(FILE *fichero, const char *formato, ...);

La función fscanf funciona igual que scanf en cuanto a parámetros, pero la entrada se toma de un fichero en lugar del teclado.

Función fflush

Sintaxis:

int fflush(FILE *fichero);

Esta función fuerza la salida de los datos acumulados en el buffer de salida del fichero. Para mejorar las prestaciones del manejo de ficheros se utilizan buffers, almacenes temporales de datos en memoria, las operaciones de salida se hacen a través del buffer, y sólo cuando el buffer se llena se realiza la escritura en el disco y se vacía el buffer. En ocasiones nos hace falta vaciar ese buffer de un modo manual, para eso sirve ésta función.

El valor de retorno es cero si la función se ejecutó con éxito, y EOF si hubo algún error. El parámetro de entrada es un puntero a la estructura FILE del fichero del que se quiere vaciar el buffer. Si es NULL se hará el vaciado de todos los ficheros abiertos.

Funciones C específicas para ficheros de acceso aleatorio

Función fseek

Sintaxis:

int fseek(FILE *fichero, long int desplazamiento, int origen);

Esta función sirve para situar el cursor del fichero para leer o escribir en el lugar deseado.

El valor de retorno es cero si la función tuvo éxito, y un valor distinto de cero si hubo algún error.

Los parámetros de entrada son: un puntero a una estructura FILE del fichero en el que queremos cambiar el cursor de lectura/escritura, el valor del desplazamiento y el punto de origen desde el que se calculará el desplazamiento.

El parámetro origen puede tener tres posibles valores:

  1. SEEK_SET el desplazamiento se cuenta desde el principio del fichero. El primer byte del fichero tiene un desplazamiento cero.
  2. SEEK_CUR el desplazamiento se cuenta desde la posición actual del cursor.
  3. SEEK_END el desplazamiento se cuenta desde el final del fichero.

Función ftell

Sintaxis:

long int ftell(FILE *fichero);

La función ftell sirve para averiguar la posición actual del cursor de lectura/excritura de un fichero.

El valor de retorno será esa posición, o -1 si hay algún error.

El parámetro de entrada es un puntero a una estructura FILE del fichero del que queremos leer la posición del cursor de lectura/escritura.

Clases para manejar ficheros en C++

Existen tres clases para manejar ficheros: ifstream, ofstream y fstream. La primera está orientada a ficheros de entrada, la segunda a ficheros de salida, y la tercera puede manejar cualquiera de los dos tipos o ficheros de entrada y salida.

Clase ifstream

El constructor está sobrecargado para poder crear streams de varias maneras:

ifstream();
ifstream(const char *name, int mode = ios::in,
   int = filebuf::openprot);

El primero sólo crea un stream de entrada pero no lo asocia a ningún fichero. El segundo lo crea, lo asocia al fichero con el nombre "name" y lo abre.

Los parámetros son: el nombre del fichero, el modo, que para ifstream es ios::in por defecto. El tercer parámetro se refiere al buffer, y no nos preocupa de momento.

Clase ofstream

Lo mismo pasa con ofstream, salvo que los valores por defecto de los parámetros son diferentes:

ofstream();
ofstream(const char *name, int mode = ios::out,
   int = filebuf::openprot);

Clase fstream

fstream();
fstream(const char *name, int mode = ios::in,
   int = filebuf::openprot);

Método open

Todas estas clases disponen además del método "open", para abrir el fichero a lo largo de la ejecución del programa.

void open(const char *name, int mode,
   int prot=filebuf::openprot);

"name" es el nombre del fichero, mode es el modo en que se abrirá, puede ser uno o una combinación del tipo enumerado open_mode, de la clase "ios":

enum open_mode { in, out, ate, app, trunc, nocreate,
   noreplace, binary };

Cada uno de los valores se pueden combinar usando el operador de bits OR (|), y significan lo siguiente:

  • in: modo de entrada.
  • out: modo de salida.
  • ate: abre el fichero y sitúa el cursor al final.
  • app: modo append, parecido al anterior, pero las operaciones de escritura siempre se hacen al final del fichero.
  • trunc: si se aplica a ficheros de salida, se creará el fichero si no existe previamente, o se truncará con un tamaño de 0 bytes, si existe.
  • nocreate: impide crear un fichero si no existe, en ese caso, la función falla.
  • noreplace: lo ignoro.
  • binary: abre el fichero en modo binario.

Los tres últimos modos probablemente no son estándar, y es posible que no existan en muchos compiladores.

Método close

void close();

Sencillamente, cierra el fichero asociado a un stream.

Operador >>:

Igual que sucede con el stream estándar cout, el operador de flujo de salida >> se puede usar con streams de salida cuando trabajemos con texto.

Operador <<:

Del mismo modo, al igual que sucede con el stream estándar cin, el operador de flujo de entrada << se puede usar con streams de entrada cuando trabajemos con texto.

Método de salida put:

ostream& put(char ch);

Sirve para cualquier stream de salida, e inserta un carácter en el stream.

Método de entrada get

int get();
istream& get(char*, int len, char = '\n');
istream& get(char&);
istream& get(streambuf&, char = '\n');

La primera forma no se recomienda y se considera obsoleta, lee un carácter desde el stream de entrada.

La segunda lee caracteres y los almacena en el buffer indicado en el primer parámetro hasta que se leen "len" caracteres o hasta que se encuentra el carácter indicado en el tercer parámetro, que por defecto es el retorno de línea.

La tercera forma extrae un único carácter en la referencia a char proporcionada.

La cuarta no nos interesa de momento.

Método de entrada getline

istream& getline(char*, int, char = '\n');

Extrae caracteres hasta que se encuentra el delimitador y los coloca en el buffer, elimina el delimitador del stream de entrada y no lo añade al buffer.

Método eof

int eof();

Verifica si se ha alcanzado el final del fichero, devuelve un valor nulo si no es así.

Método clear

void clear(iostate state=0);

Cada vez que se produzca una condición de error en un stream es necesario eliminarla, ya que en caso contrario ninguna operación que se realice sobre él tendrá éxisto. Por ejemplo, si llegamos hasta el final de fichero, el stream quedará en estado "eof" hasta que se elimine explícitamente ese estado. Eso se hace mediante el método "clear", sin parámetros dejará el estado en 0, es decir, sin errores.

Los estados posibles se definen en un enumerado:

enum io_state { goodbit, eofbit, failbit, badbit };
  • goodbit: indica que el estado es correcto.
  • eofbit: indica que se ha detectado fin de fichero.
  • failbit: indica que una operación sobre el stream ha fallado.
  • badbit: se activa si falla una operación de escritura de buffers.

Método bad

int bad();

Devuelve el estado del bit "badbit".

Método fail:

int fail();

Devuelve el estado del bit "failbit".

Método good

int good();

Devuelve el estado del bit "goodbit".

Ejemplo:

Veamos el ejemplo anterior de mostrar dos veces un fichero, pero esta vez escrito para C++ usando streams:

// ejemplo1.cpp: Muestra un fichero dos veces.
#include <iostream>
#include <fstream>
using namespace std;

int main() {
   ifstream fichero("ejemplo1.cpp");
   char c;

   while(fichero.get(c)) cout.put(c);
   fichero.clear(); // (1)
   fichero.seekg(0);
   while(fichero.get(c)) cout.put(c);
   fichero.close();
   cin.get();
   return 0;
}

Como vemos en (1), es necesario eliminar el bit de eof, que se ha activado al leer hasta el final del fichero, cuando el último intento de llamar a "get" ha fallado, porque se ha terminado el fichero.

Método is_open

int is_open();

Devuelve un valor no nulo si el fichero está abierto.

Método flush

ostream& flush();

Realiza las operaciones de escritura pendientes que aún se han realizado sólo en el buffer.

Métodos relacionados con acceso aleatorio

Disponemos de otro tipo enumerado en ios para indicar movimientos relativos dentro de un stream de acceso aleatorio:

enum seek_dir { beg, cur, end};
  • beg: relativo al principio del fichero.
  • cur: relativo a la posición actual del cursor dentro del fichero.
  • end: relativo al final del fichero.

Método seekg

Cambia la posición del cursor en streams de entrada.

istream& seekg(streampos pos);
istream& seekg(streamoff offset, seek_dir dir);

La primera forma es para cambiar la posición de modo absoluto. La segunda para cambios relativos, en la que se indica el salto en el primer parámetro y el punto de partida en el segundo, que puede ser cualquiera de los indicados anteriormente: ios::beg, ios::cur o ios::end.

Método seekp

Cambia la posición del cursor en streams de salida.

ostream& seekp(streampos pos);
ostream& seekp(streamoff offset, seek_dir);

Lo mismo que seekg, pero aplicado a estream de salida.

Método tellg

streampos tellg();

Devuelve la posición actual del cursor dentro de un stream de entrada.

Método tellp

streampos tellp();

Devuelve la posición actual del cursor dentro de un stream de salida.

Método read

istream& read(char*, int);

Lee el número de caracteres indicado en el segundo parámetro dendro del buffer suministrado por el primero.

Método gcount

int gcount();

Devuelve el número de caracteres sin formato de la última lectura. Las lecturas sin formato son las realizadas mediante las funciones get, getline y read.

Método write

ostream& write(const char*, int);

Escribe el número de caracteres indicado en el segundo parámetro desde el buffer suministrado por el primero.

Ejemplo:

De nuevo haremos el ejemplo de copiar ficheros, pero esta vez usando streams.

// copia.cpp: Copia de ficheros
// Uso: copia <fichero_origen> <fichero_destino>

#include <iostream>
#include <fstream>
using namespace std;

int main(int argc, char **argv) {
    ifstream entrada;
    ofstream salida;

    char buffer[2048]; // Buffer de 2 Kbytes
    int bytesLeidos;

    if(argc != 3) {
       printf("Usar: copia <fichero_origen> <fichero_destino>\n");
       return 1;
    }

    // Abrir el fichero de entrada en lectura y binario
    entrada.open(argv[1]);
    if(!entrada.good()) {
       printf("El fichero %s no existe o no puede ser abierto.\n", argv[1]);
       return 1;
    }
    // Crear o sobreescribir el fichero de salida en binario
    salida.open(argv[2]);
    if(!salida.good()) {
       printf("El fichero %s no puede ser creado.\n", argv[2]);
       entrada.close();
       return 1;
    }
    // Bucle de copia:
    do {
       entrada.read(buffer, 2048);
       bytesLeidos = entrada.gcount();
       salida.write(buffer, bytesLeidos);
    } while(bytesLeidos > 0);
    // Cerrar ficheros:
    entrada.close();
    salida.close();
    return 0;
}