[?][C++] Vectores, strings y archivos con clase - C++

Iniciado por marlboreano, 6 Marzo 2015, 15:38 PM

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

marlboreano

Hola a todos; me encuentro con un grave problema (que raramente no vi mientras hacía mi proyecto). En fin, tengo dos clases (en el proyecto, tengo unas cuaaantas más, pero a modo explicativo y para resumir, solo 2 por acá): "Madre" e "Hija", que se relacionan por composición (Una madre tiene un vector (stl) de hijas). El tema es que también en su composición tienen strings (para almacenar los nombres en este caso) y tengo un vector que uso desde el main que es de tipo Madre.
La idea es poder guardar esos datos en un archivo para poder consultarlo luego más tarde (en el vector voy guardando Madres [con sus respectivas hijas]), pero me encuentro con que si quiero guardar un vector en un fichero, solo guardo 'basura' o 'posibles direcciones', nada de lo que me interese.
Les dejo el código de ejemplo que hice, para ver si me pueden dar una mano y/o una guía para revisar, ya que, si bien en este caso las clases son bastante simples (y hasta podrían heredarse), en mi proyecto original son bastante más complicadas y no deseo re-diseñar todas las mismas, ya que me llevaría mucho más tiempo que encontrarle una solución a esto.

Madre.h
Código (cpp) [Seleccionar]
#ifndef MADRE_H
#define MADRE_H
#include "Hija.h"
#include <vector>
#include <string>
using namespace std;

class Madre {
private:
string nombre;
int edad;
vector<Hija> susHijas;
protected:
public:
//Constructor
Madre();
//Consultores
int MostrarEdad(void);
string MostrarNombre(void);
Hija MostrarHija(const int&);
int CantidadHijas(void);
//Cargadores
bool CargarEdad(const int&_edad);
bool CargarNombre(const string&_nombre);
bool CargarHija(const Hija &_hija);
bool EliminarHija(const int &_indice);
bool CargarDatos(const string &_nombre, const int &_edad);
//Destructor
~Madre();
};

#endif


Madre.cpp
Código (cpp) [Seleccionar]
#include "Madre.h"
#include <sstream>
using namespace std;

Madre::Madre(void) : nombre("N/N"), edad(-1) {
susHijas.clear();
}

Madre::~Madre(void) {

}

int Madre::MostrarEdad(void) {
return edad;
}

string Madre::MostrarNombre(void) {
return nombre;
}

bool Madre::CargarEdad(const int & _edad) {
edad = _edad;
return (edad==_edad)?true:false;
}

bool Madre::CargarNombre(const string & _nombre) {
nombre = _nombre;
return (nombre==_nombre)?true:false;
}

Hija  Madre::MostrarHija(const int &_indice) {
if (unsigned(_indice)>susHijas.size() || susHijas.empty()) {
Hija aux;
return aux;
}
return susHijas[_indice];
}

bool Madre::CargarHija(const Hija &_hija) {
susHijas.push_back(_hija);
return true;
}

bool Madre::EliminarHija(const int &_indice) {
if (unsigned(_indice)>susHijas.size()) {
return false;
}
susHijas.erase(susHijas.begin()+_indice);
return true;
}

int Madre::CantidadHijas(void) {
return int(susHijas.size());
}

bool Madre::CargarDatos(const string &_nombre, const int &_edad) {
nombre = _nombre;
edad = _edad;
return true;
}


Hija.h
Código (cpp) [Seleccionar]
#ifndef HIJA_H
#define HIJA_H
#include <string>
using namespace std;

class Hija {
private:
string nombre;
int edad;
protected:
public:
//Constructor
Hija();
//Consultores
int MostrarEdad(void);
string MostrarNombre(void);
//Cargadores
bool CargarEdad(const int&_edad);
bool CargarNombre(const string&_nombre);
bool CargarDatos(const string &_nombre, const int &_edad);
//Destructor
~Hija();
};

#endif


Hija.cpp
Código (cpp) [Seleccionar]
#include "Hija.h"

Hija::Hija() : nombre("N/N"), edad(-1) {

}

Hija::~Hija() {

}


int Hija::MostrarEdad(void) {
return edad;
}

string Hija::MostrarNombre(void) {
return nombre;
}

bool Hija::CargarEdad(const int & _edad) {
edad = _edad;
return (edad==_edad)?true:false;
}

bool Hija::CargarNombre(const string & _nombre) {
nombre = _nombre;
return (nombre==_nombre)?true:false;
}

bool Hija::CargarDatos(const string &_nombre, const int &_edad) {
nombre = _nombre;
edad = _edad;
return true;
}


Main.cpp
Código (cpp) [Seleccionar]
#include<iostream>
#include <string>
#include <fstream>
#include "Madre.h"
#include <sstream>
#include "Hija.h"
using namespace std;

ostream& operator<<(ostream &os, Madre &_madre) {
stringstream aux;
if (!_madre.CantidadHijas()==0) {
aux<<" y tiene "<<_madre.CantidadHijas()<<" hijas";
} else {
aux<<" y no tiene hijas";
}
return os<<_madre.MostrarNombre()<<" - "<<_madre.MostrarEdad()<<aux.str();
}

ostream& operator<<(ostream &os, Hija &_hija) {
return os<<_hija.MostrarNombre()<<" - "<<_hija.MostrarEdad();
}

int main (int argc, char *argv[]) {
//Creo y cargo los datos de una madre
Madre m1;
m1.CargarDatos("Laura",40);
//Creo y cargo los datos de una hija
Hija h1;
h1.CargarDatos("Litoral",15);
//Inserto la hija en el vector de la Madre
m1.CargarHija(h1);
//Muestro los datos de la madre
cout<<m1<<endl;
//Muestro los datos de la Hija
cout<<h1<<endl;

//Creo un vector de madres para trabajar con ellas
vector<Madre> vMadres;
//Cargo una madre al mismo
vMadres.push_back(m1);
//¿Guardar?
return 0;
}


Desde ya, muchas gracias por su comprensión, enseñanza, queja o cualquier aporte.

Nota: El último comentario y su raro "&#191;" es porque soy hispanohablante orgulloso, y utilizo mis signos "¿" cuando y cómo yo quiero, si ASCII no lo toma, problema suyo.
Nota 2: Si, se que sería raro ponerle de nombre "Litoral" a una hija, pero es así cuando uno tiene la cabeza nublada y no se le ocurre nada  :xD.

ivancea96

#1
Para guardar un vector en un archivo, serializalo. Conviertelo a una cadena de caracteres, con el formato que prefieras.
Luego, claro está, tendrás que hacer un método para deserializarlo, transformando la cadena, en un vector.

EDITO:
Si solo quieres guardar los datos en el archivo sin más, haz un ciclo por cada dato del vector, y lo pasas al archivo.

marlboreano

Gracias por tu tan pronta respuesta, ivancea96, el tema es que según veo en la documentación sería "ilegal" intentar guardarme los string de cada objeto.

Mejor un ejemplo porque explicando soy un queso:
Código (cpp) [Seleccionar]

ofstream salida("datos.dat",ios::binary|ios::trunc);
if (!salida.is_open()) {
cerr << "No se pudo guardar" <<endl;
}
int ind = 0;
while(salida.write(reinterpret_cast<char*>(&vMadres[ind]),sizeof(Madre))) {
ind++;
}
salida.close();


Esto, en teoría, no me guardaría los nombres de cada clase (Madres e Hijas). Ese es mi principal problema.
Además, tampoco me podría guardar el vector entero, que sería de lo más sencillo :/.

Desde ya, muchas gracias.

ivancea96

Con serializarlo, me refiero a serializarlo tú:

Código (cpp) [Seleccionar]
ofstream out = /*...*/;
vector<string> v = /*...*/;
size_t tam = v.size();
out.write((char*)&tam, sizeof(tam));
for(string& s:v){
    tam = s.size();
    out.write((char*)&tam, sizeof(tam));
    out.write(s.c_str(), tam);
}


Vector:
<NUM_STRINGS><string1><string2><...>

Y luego cada string:
<NUM_CHARS><chars_array>

marlboreano

Disculpame, ivancea96, pero no logro entender la sintaxis (estoy aprendiendo según el standard C++99 aún). Se que las dos primeras líneas son a modo explicativo no más, ya que no es legal ese tipo de declaración, como también que el casteo es ambiguo (me recomendaron en la facultad siempre utlizar el "reinterpretcast<char*>" y no el casteo que utilizaba (char*), pero me pierdo en las líneas 4, 7 y 8.

Línea 4:
Código (cpp) [Seleccionar]
out.write((char*)&tam, sizeof(tam));

Esto le pasa al flujo un size_t interpretado como char* con su tamaño respectivo en bytes.
¿Para qué?
Otra: Si un size_t es un entero sin signo que interpreta bytes, ¿para qué hacerle un sizeof? ¿O lo estarás utilizando como un unsigned int? (lo digo desde mi más completa ignorancia, nada es para ofender sino para aprender)

Línea 7:
Código (cpp) [Seleccionar]
out.write((char*)&tam, sizeof(tam));

Lo mismo que antes (aunque a esta altura del código, tam ahora equivale al tamaño del string 's' y no del vector 'v').

Línea 8:
Código (cpp) [Seleccionar]
out.write(s.c_str(), tam);

Acá si entiendo "algo". Le pasas al flujo el string 's' convertido a char, con su tamaño.

Por el tipo de bucle for, me di cuenta que tu código ejemplo se rige bajo el standard C++11, que tiene varias ventajas, pero como los compiladores viejos interpretan según el C++99, todavía sigo con este pateando.

Otra cosa, si me podrías facilitar algún enlace, título de material o demás sobre serialización (Español o Inglés) de C++, te estaría agradecido eternamente, ya que lo que encuentro es para Java o Python (creo) y, si bien la sintaxis no es muy difícil de comprender, a veces uno se termina perdiendo.

Desde ya, te pido disculpas. Cuando estaba leyendo tu respuesta se cayó mi servicio de internet, tuve un par de problemitas y ayer a la noche pude volver a conectarme, pero no tuve tiempo de responder. Muchas gracias por tus aclaraciones :).
Saludos.

ivancea96

Código (cpp) [Seleccionar]
out.write((char*)&tam, sizeof(tam));

Veamos. size_t es un unsigned int, 4 bytes. Al hacer &tam, saco la dirección (size_t*), y con (char*), lo paso a un arreglo de char, eso está claro.
Si hago eso, es porque quiero escribir en 'out', los datos binarios del size_t.

Suponiendo que tam=5. en hexadecimal sería: 0x00000005. Si hago "out << tam", va a escribir un '5' en el archivo. Pero eso no me interesa al serializar, ya que en decimal, el número ocupa más memoria. Así que haciendo el casting a char*, convierto el número 0x00000005 a un array de char {0x00, 0x00, 0x00, 0x05}. Así es como lo quiero poner en el archivo: 4 caracteres.

El sizeof(tam), equivalente a sizeof(size_t), es para saber el tamaño en bytes, ya que se lo tengo que pasar a la función write. En este caso, el tamaño es 4.


Si en la facultad te dicen de usar reinterpret_cast, puedes usarlo. Funcionará igual.


Acerca de lo de serializar en Java, cuidado, porque Java suele tener métodos propios para serializar clases estandar (pese a que puedas hacerlos también por ti mismo.

No se de tutoriales para serializar, lo único importante es el concepto: convertir una clase a un arreglo de bytes. (Y poder revertirlo luego)

marlboreano

Muchas gracias ivancea96 por la explicación y la predisposición. Voy a seguir buscando en Google todo lo que me sirva para poder serializar mis clases (estoy en medio de un proyecto y lo único que me falta es guardar [y cargar, obviamente] los datos que manejo desde un programa cliente).

Saludos :).