Problema con manejo de archivos en C++

Iniciado por placa4, 12 Marzo 2013, 07:39 AM

0 Miembros y 1 Visitante están viendo este tema.

placa4

Hola, resulta que estaba creando un programa que lea de un archivo de texto con el siguiente formato:

<pregunta> esta es la pregunta </pregunta> <respuesta> esta es la respuesta a la pregunta </respuesta>


Así pudiera guardar la pregunta en una cadena, y luego la respuesta en otra. De esta manera el programa iría haciendo las preguntas aleatoriamente y luego mostrando las respuestas, pero tengo un problema. La clase ifstream no la sé usar y siempre me trata una entrada más de la que quiero, lo que me hace tener que usar de forma rara el número de preguntas (restándole uno porque si no usa una entrada vacía de los arrays), ¿alguien me puede decir a qué se debe? Pongo el code completo:

main.cpp
Código (cpp) [Seleccionar]
#include <iostream>
#include <fstream>
#include "pregunta.h"

using namespace std;

int main()
{
   Pregunta p;
   p.comenzar();

   return 0;
}

pregunta.h
Código (cpp) [Seleccionar]
#include <iostream>
#include <fstream>

#define MAX_LETRAS 5000
#define MAX_PREGUNTAS 200

class Pregunta{
   private:
       char pregunta[MAX_PREGUNTAS][MAX_LETRAS];
       char respuesta[MAX_PREGUNTAS][MAX_LETRAS];
       int nPreguntas;
   public:
       Pregunta();
       void comenzar();
       void auxMuestra();
};

pregunta.cpp
Código (cpp) [Seleccionar]
#include "pregunta.h"
#include <stdlib.h> //Pseudorandomizado y limpiar la pantalla
#include <time.h> //Para la semilla de randomizacion

using namespace std;

//Constructor
Pregunta::Pregunta(){
   char aux[MAX_LETRAS];
   nPreguntas = 0;
   ifstream archivo;
   archivo.open("preguntas.txt",ifstream::in);

   while(!archivo.eof()){
       //PREGUNTA
       //Comida de etiqueta <pregunta>
       archivo.getline(aux, MAX_LETRAS, '>');
       //Adquirir el texto de la pregunta
       archivo.getline(pregunta[nPreguntas], MAX_LETRAS, '<');
       //Comida de etiqueta </pregunta>
       archivo.getline(aux, MAX_LETRAS, '>');
       //RESPUESTA
       //Comida de etiqueta <respuesta>
       archivo.getline(aux, MAX_LETRAS, '>');
       //Adquirir el texto de la respuesta
       archivo.getline(respuesta[nPreguntas], MAX_LETRAS, '<');
       //Comida de etiqueta </respuesta>
       archivo.getline(aux, MAX_LETRAS, '>');
       nPreguntas++;
   }

   archivo.close();
}

void Pregunta::comenzar(){
   srand (time(NULL));
   while(true){
       int aleatorio = rand() % (nPreguntas-1);
       cout << "PREGUNTA: " << endl;
       cout << pregunta[aleatorio];
       cout << endl;
       cin.get();
       cout << "RESPUESTA: " << endl;
       cout << respuesta[aleatorio];
       cin.get();
       system("cls");
   }
}

void Pregunta::auxMuestra(){
   for(int i=0; i < nPreguntas; i++){
       cout << "PREGUNTA" << endl << pregunta[i];
       cout << endl << "RESPUESTA" << endl << respuesta[i] << endl;
   }
}


El problema está en el constructor de la clase Pregunta, donde se inicializa el array con preguntas y respuestas. Antes hice una traza de la variable nPreguntas y siempre coge una más de la que debe, lo que me cabrea bastante y es la razón por la que pregunto esto. Es decir, si el texto tiene 2 preguntas, nPreguntas vale 3,y así, en comenzar() fijaros que tengo que hacer módulo de nPreguntas-1 y no de nPreguntas, para que funcione correctamente.

Muchas gracias de antemano y un saludo.

amchacon

#1
Oh, es un pequeño fallo de concepto que tiene iostream.

Veras, la bandera eof se activa cuando se accede fuera del archivo. Si tu tienes 4 preguntas, leera las 4 preguntas y hará otra vuelta más porque aún no has sobrepasado el final de archivo

Hay dos soluciones:

- La chapucera, consiste en disminuir en 1 el numero de preguntas.
- Obtener el tamaño del archivo.txt y leer dicha cantidad de bytes.

La segunda se podría implementar de la siguiente forma:

Código (cpp) [Seleccionar]
const unsigned int MAXIMO_BUFFER = 1048576; // Tamanyo máximo que podemos cargar en la RAM, he puesto aproximadamente 1 MB pero podríamos poner más

ifstream archivo;
archivo.open("preguntas.txt",ifstream::in); // Desconozco si funciona en este modo, supongo que sí
archivo.seekg(0,ios::end); // Nos vamos al final del archivo
unsigned int Tamanyo = Lectura.tellg(); // Lee la posicion actual
Lectura.seekg(0,ios::beg); // Volvemos al principio

if (Tamanyo <= MAXIMO_BUFFER)
{
    char* Buffer = new char[Tamanyo];
    archivo.read(Buffer,Tamanyo); // Leemos el archivo del tiron

    // Tenemos el archivo ya entero cargado, solo tendremos que acceder a la cadena Buffer y sacar las preguntas.

}
else // El tamanyo es excesivo, lo leeremos poco a poco
     {
        while(archivo.tellg() < tamanyo) // Mientras la posicion del archivo no llegue al tamanyo maximo
         {
     
       archivo.getline(aux, MAX_LETRAS, '>');
       archivo.getline(pregunta[nPreguntas], MAX_LETRAS, '<');
       archivo.getline(aux, MAX_LETRAS, '>');
       archivo.getline(aux, MAX_LETRAS, '>');
       archivo.getline(respuesta[nPreguntas], MAX_LETRAS, '<');
       archivo.getline(aux, MAX_LETRAS, '>');
       nPreguntas++;
        }
      }


El if es opcional, solo sería para que los archivos pequeños cargasen más rápido. Se podría dejar solo con el else.

Por cierto si usas la clase string combinada con la clase vector, podrás leer cualquier numero de preguntas con cualquier numero de letras:

Código (cpp) [Seleccionar]
#include <iostream>
#include <fstream>
#include <vector>

using namespace std;

class Pregunta{
   private:
       vector<string> preguntas;
       vector<string> respuestas;
       int nPreguntas;
   public:
       Pregunta();
       void comenzar();
       void auxMuestra();
};


Código (cpp) [Seleccionar]

        while(archivo.tellg() < tamanyo) // Mientras la posicion del archivo no llegue al tamanyo maximo
         {
      preguntas.push_back(0); // Creamos un nuevo elemento vacio en el vector
      respuestas.push_back(0);
       archivo.getline(aux, MAX_LETRAS, '>');
       archivo.getline(pregunta[nPreguntas], MAX_LETRAS, '<');
       archivo.getline(aux, MAX_LETRAS, '>');
       archivo.getline(aux, MAX_LETRAS, '>');
       archivo.getline(respuesta[nPreguntas], MAX_LETRAS, '<');
       archivo.getline(aux, MAX_LETRAS, '>');
       nPreguntas++;
        }
     


Para más información sobre las clases string y vector:
http://www.cplusplus.com/reference/string/string/
http://www.cplusplus.com/reference/vector/vector/
Por favor, no me manden MP con dudas. Usen el foro, gracias.

¡Visita mi programa estrella!

Rar File Missing: Esteganografía en un Rar

rir3760

Ya que piensa usar etiquetas en lugar de lineas para separar las preguntas y respuestas me parece mas sencillo utilizar solo un objeto de la clase string para almacenar todo el contenido del archivo. Mas o menos así:
Código (cpp) [Seleccionar]
ifstream in("Entrada.txt", ifstream::in);
string texto;
string linea;

while (getline(in, linea))
   texto += linea + '\n';

cout << texto;
in.close();


Y para encontrar las etiquetas de apertura y cierre puede utilizar la función miembro std::string::find.

Un saludo
C retains the basic philosophy that programmers know what they are doing; it only requires that they state their intentions explicitly.
--
Kernighan & Ritchie, The C programming language

placa4

Aaah bonitas ideas amchacon. Claro usando vector es cierto podría hacerlo sin límite de antemano, no lo había pensado.

Y muchas gracias por aclarármelo ^_^, claro vigilando a mano el tamaño de los bytes es algo análogo a si eof (si es que funcionara como a mí me gustaría ¬¬) devolviera true al llegar al final del archivo y no al sobrepasarlo, perfecto =)

Y en cuanto a rir3760 muchas gracias, es también una buena idea, pero no uso etiquetas por capricho, verás yo lo hice con la idea de que las preguntas y respuestas pudieran tener por dentro saltos de línea, párrafos etc. no fueran simples preguntas de un renglón, solo fue por eso, pero muchas gracias =)

Un saludo a los dos.