Foro de elhacker.net

Programación => Programación C/C++ => Mensaje iniciado por: placa4 en 12 Marzo 2013, 07:39 am



Título: Problema con manejo de archivos en C++
Publicado por: placa4 en 12 Marzo 2013, 07:39 am
Hola, resulta que estaba creando un programa que lea de un archivo de texto con el siguiente formato:
Código:
<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
  1. #include <iostream>
  2. #include <fstream>
  3. #include "pregunta.h"
  4.  
  5. using namespace std;
  6.  
  7. int main()
  8. {
  9.    Pregunta p;
  10.    p.comenzar();
  11.  
  12.    return 0;
  13. }
  14.  
pregunta.h
Código
  1. #include <iostream>
  2. #include <fstream>
  3.  
  4. #define MAX_LETRAS 5000
  5. #define MAX_PREGUNTAS 200
  6.  
  7. class Pregunta{
  8.    private:
  9.        char pregunta[MAX_PREGUNTAS][MAX_LETRAS];
  10.        char respuesta[MAX_PREGUNTAS][MAX_LETRAS];
  11.        int nPreguntas;
  12.    public:
  13.        Pregunta();
  14.        void comenzar();
  15.        void auxMuestra();
  16. };
  17.  
pregunta.cpp
Código
  1. #include "pregunta.h"
  2. #include <stdlib.h> //Pseudorandomizado y limpiar la pantalla
  3. #include <time.h> //Para la semilla de randomizacion
  4.  
  5. using namespace std;
  6.  
  7. //Constructor
  8. Pregunta::Pregunta(){
  9.    char aux[MAX_LETRAS];
  10.    nPreguntas = 0;
  11.    ifstream archivo;
  12.    archivo.open("preguntas.txt",ifstream::in);
  13.  
  14.    while(!archivo.eof()){
  15.        //PREGUNTA
  16.        //Comida de etiqueta <pregunta>
  17.        archivo.getline(aux, MAX_LETRAS, '>');
  18.        //Adquirir el texto de la pregunta
  19.        archivo.getline(pregunta[nPreguntas], MAX_LETRAS, '<');
  20.        //Comida de etiqueta </pregunta>
  21.        archivo.getline(aux, MAX_LETRAS, '>');
  22.        //RESPUESTA
  23.        //Comida de etiqueta <respuesta>
  24.        archivo.getline(aux, MAX_LETRAS, '>');
  25.        //Adquirir el texto de la respuesta
  26.        archivo.getline(respuesta[nPreguntas], MAX_LETRAS, '<');
  27.        //Comida de etiqueta </respuesta>
  28.        archivo.getline(aux, MAX_LETRAS, '>');
  29.        nPreguntas++;
  30.    }
  31.  
  32.    archivo.close();
  33. }
  34.  
  35. void Pregunta::comenzar(){
  36.    srand (time(NULL));
  37.    while(true){
  38.        int aleatorio = rand() % (nPreguntas-1);
  39.        cout << "PREGUNTA: " << endl;
  40.        cout << pregunta[aleatorio];
  41.        cout << endl;
  42.        cin.get();
  43.        cout << "RESPUESTA: " << endl;
  44.        cout << respuesta[aleatorio];
  45.        cin.get();
  46.        system("cls");
  47.    }
  48. }
  49.  
  50. void Pregunta::auxMuestra(){
  51.    for(int i=0; i < nPreguntas; i++){
  52.        cout << "PREGUNTA" << endl << pregunta[i];
  53.        cout << endl << "RESPUESTA" << endl << respuesta[i] << endl;
  54.    }
  55. }
  56.  

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.


Título: Re: Problema con manejo de archivos en C++
Publicado por: amchacon en 12 Marzo 2013, 15:57 pm
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
  1. 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
  2.  
  3. ifstream archivo;
  4. archivo.open("preguntas.txt",ifstream::in); // Desconozco si funciona en este modo, supongo que sí
  5. archivo.seekg(0,ios::end); // Nos vamos al final del archivo
  6. unsigned int Tamanyo = Lectura.tellg(); // Lee la posicion actual
  7. Lectura.seekg(0,ios::beg); // Volvemos al principio
  8.  
  9. if (Tamanyo <= MAXIMO_BUFFER)
  10. {
  11.     char* Buffer = new char[Tamanyo];
  12.     archivo.read(Buffer,Tamanyo); // Leemos el archivo del tiron
  13.  
  14.     // Tenemos el archivo ya entero cargado, solo tendremos que acceder a la cadena Buffer y sacar las preguntas.
  15.  
  16. }
  17. else // El tamanyo es excesivo, lo leeremos poco a poco
  18.      {
  19.         while(archivo.tellg() < tamanyo) // Mientras la posicion del archivo no llegue al tamanyo maximo
  20.          {
  21.  
  22.        archivo.getline(aux, MAX_LETRAS, '>');
  23.        archivo.getline(pregunta[nPreguntas], MAX_LETRAS, '<');
  24.        archivo.getline(aux, MAX_LETRAS, '>');
  25.        archivo.getline(aux, MAX_LETRAS, '>');
  26.        archivo.getline(respuesta[nPreguntas], MAX_LETRAS, '<');
  27.        archivo.getline(aux, MAX_LETRAS, '>');
  28.        nPreguntas++;
  29.         }
  30.       }
  31.  

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
  1. #include <iostream>
  2. #include <fstream>
  3. #include <vector>
  4.  
  5. using namespace std;
  6.  
  7. class Pregunta{
  8.    private:
  9.        vector<string> preguntas;
  10.        vector<string> respuestas;
  11.        int nPreguntas;
  12.    public:
  13.        Pregunta();
  14.        void comenzar();
  15.        void auxMuestra();
  16. };

Código
  1.         while(archivo.tellg() < tamanyo) // Mientras la posicion del archivo no llegue al tamanyo maximo
  2.          {
  3.       preguntas.push_back(0); // Creamos un nuevo elemento vacio en el vector
  4.       respuestas.push_back(0);
  5.        archivo.getline(aux, MAX_LETRAS, '>');
  6.        archivo.getline(pregunta[nPreguntas], MAX_LETRAS, '<');
  7.        archivo.getline(aux, MAX_LETRAS, '>');
  8.        archivo.getline(aux, MAX_LETRAS, '>');
  9.        archivo.getline(respuesta[nPreguntas], MAX_LETRAS, '<');
  10.        archivo.getline(aux, MAX_LETRAS, '>');
  11.        nPreguntas++;
  12.         }
  13.  
  14.  

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/


Título: Re: Problema con manejo de archivos en C++
Publicado por: rir3760 en 12 Marzo 2013, 16:33 pm
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
  1. ifstream in("Entrada.txt", ifstream::in);
  2. string texto;
  3. string linea;
  4.  
  5. while (getline(in, linea))
  6.   texto += linea + '\n';
  7.  
  8. cout << texto;
  9. in.close();

Y para encontrar las etiquetas de apertura y cierre puede utilizar la función miembro std::string::find (http://www.cplusplus.com/reference/string/string/find/).

Un saludo


Título: Re: Problema con manejo de archivos en C++
Publicado por: placa4 en 13 Marzo 2013, 07:08 am
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.