NOTE: Ce n'est pas une réponse aux erreurs de compilation que vous obtenez, mais plutôt une vision plus large du problème de la persistance que vous gérez.
La sérialisation et la désérialisation ne sont pas le problème le plus simple sur lequel vous pouvez travailler. Mon conseil serait d'investir dans l'apprentissage des bibliothèques (boost :: sérialisation) et de les utiliser. Ils ont déjà résolu de nombreux problèmes auxquels vous serez confrontés à un moment ou à un autre. De plus, ils ont déjà différents formats de sortie (binaire, xml, json ...)
La première chose que vous devez décider, c'est que si vous décidez d'aller de l'avant et de mettre en œuvre votre propre, quel sera le format de fichier et si cela convient à tous vos besoins. Sera-t-il toujours utilisé dans le même environnement? La plateforme va-t-elle changer (32/64bits)? Vous pouvez décider de le rendre binaire car c'est le plus simple, ou le rendre lisible pour un être humain. Si vous décidez de XML, JSON ou d'autres formats plus complexes, il suffit de l'oublier et d'utiliser une bibliothèque.
La solution la plus simple est de travailler sur un fichier binaire et c'est aussi la solution qui vous donnera le plus petit fichier.D'un autre côté, il est très sensible aux changements d'architecture (disons que vous migrez d'une architecture/OS de 32 à 64 bits)
Après avoir décidé du format, vous devrez travailler sur les informations supplémentaires qui ne font pas partie de votre objets maintenant mais doit être inséré dans le fichier pour une récupération ultérieure. Puis commencez à travailler (et à tester) des plus petites parties à des éléments plus complexes.
Un autre conseil serait de commencer à travailler avec la partie la plus simple et la plus définie à partir de là. Commencez par éviter autant que possible les modèles, et une fois que vous l'avez clairement défini et que vous travaillez pour un type de données donné, travaillez sur la manière de le généraliser pour tout autre type.
Disclaimer: J'ai écrit le code directement sur le navigateur, donc il pourrait y avoir des erreurs, des fautes de frappe ou à peu près tout :)
Texte
La première approche simple est juste un texte ÉCRITURE représentation du texte. L'avantage est qu'il est portable et plus court en code (sinon plus simple) que l'approche binaire. Les fichiers résultants seront plus grands mais lisibles par l'utilisateur.
À ce stade, vous devez savoir comment lire du texte avec iostreams. En particulier, chaque fois que vous essayez de lire une chaîne, le système lira les caractères jusqu'à ce qu'il atteigne un séparateur. Cela signifie que le code suivant:
std::string str;
std::cin >> str;
lit uniquement le premier espace, la première tabulation ou la fin de la ligne. Lors de la lecture des nombres (ints par exemple), le système lira tous les chiffres valides jusqu'au premier chiffre non valide. C'est-à-dire:
int i;
std::cin >> i;
avec l'entrée 12345a consommera tous les caractères jusqu'à 'a'. Vous devez le savoir car cela influera sur la façon dont vous persistez à récupérer des données pour une récupération ultérieure.
// input: "This is a long Description"
std::string str;
std::cin >> str; // Will read 'This' but ignore the rest
int a = 1;
int b = 2;
std::cout << a << b; // will produce '12'
// input: 12
int read;
std::cint >> read; // will read 12, not 1
Vous avez donc besoin de séparateurs à insérer dans la sortie et d'analyser l'entrée. À titre d'exemple, je vais sélectionner le '|' personnage. Ce doit être un caractère qui n'apparaît pas dans les champs de texte.
Ce sera également une bonne idée non seulement de séparer les éléments mais aussi d'ajouter des informations supplémentaires (taille du vecteur). Pour les éléments du vecteur, vous pouvez décider d'utiliser un séparateur différent. Si vous voulez être en mesure de lire le fichier manuellement, vous pouvez utiliser « \ n » de sorte que chaque article se trouve dans sa propre ligne
namespace textual {
std::ostream & operator<<(std::ostream& o, InventoryItem const & data)
{
return o << data.Item << "|" << data.Description << "|" << data.Quantity
<< "|" << data. ...;
}
std::ostream & operator<<(std::ostream & o, std::vector<InventoryItem> const & v)
{
o << v.size() << std::endl;
for (int i = 0; i < v.size(); ++i) {
o << v[i] << std::endl; // will call the above defined operator<<
}
}
}
Pour la lecture, vous devez diviser l'entrée par « \ n » à obtenir chaque élément, puis avec '|' pour analyser le InventoryItem:
namespace textual {
template <typename T>
void parse(std::string const & str, T & data)
{
std::istringstream st(str); // Create a stream with the string
st >> data; // use operator>>(std::istream
}
std::istream & operator>>(std::istream & i, InventoryItem & data)
{
getline(i, data.Item, '|');
getline(i, data.Description, '|');
std::string tmp;
getline(i, tmp, '|'); // Quantity in text
parse(tmp, data.Quantity);
getline(i, tmp, '|'); // wholesaleCost in text
parse(tmp, data. wholesaleCost);
// ...
return i;
}
std::istream & operator>>(std::istream & i, std::vector<InventoryItem> & data)
{
int size;
std::string tmp;
getline(i, tmp); // size line, without last parameter getline splits by lines
parse(tmp, size); // obtain size as string
for (int i = 0; i < size; ++i)
{
InventoryItem data;
getline(i, tmp); // read an inventory line
parse(tmp, data);
}
return i;
}
}
Dans la fonction de lecture vectorielle J'ai utilisé getline + analyser pour lire l'entier. Cela permet de garantir que le prochain getline() lira réellement le premier InventoryItem et non le \ n 'final après la taille.
Le morceau de code le plus important est le modèle 'parse' qui est capable de convertir d'une chaîne à n'importe quel type qui a l'opérateur d'insertion défini. Il peut être utilisé pour lire les types primitifs, les types de bibliothèques (chaîne, par exemple) et les types d'utilisateurs ayant l'opérateur défini. Nous l'utilisons pour simplifier un peu le reste du code.
binaire
Pour un format binaire, (abstraction faite architecture, ce sera une douleur dans le cul si vous migrez) la façon la plus simple que je peux penser est écrit le nombre de elemements dans le vecteur comme size_t (quelle que soit la taille de votre implémentation), suivi de tous les éléments. Chaque élément imprimera la représentation binaire de chacun de ses membres. Pour les types de base comme int, il sortira simplement le format binaire de l'int. Pour les chaînes, nous aurons recours à l'écriture d'un nombre size_t avec le nombre de caractères dans la chaîne suivi du contenu de la chaîne.
namespace binary
{
void write(std::ofstream & o, std::string const & str)
{
int size = str.size();
o.write(&size, sizeof(int)); // write the size
o.write(str.c_str(), size); // write the contents
}
template <typename T>
void write_pod(std::ofstream & o, T data) // will work only with POD data and not arrays
{
o.write(&data, sizeof(data));
}
void write(std::ofstream & o, InventoryItem const & data)
{
write(o, data.Item);
write(o, data.Description);
write_pod(o, data.Quantity);
write_pod(o, data. ...
}
void write(std::ofstream & o, std::vector<InventoryItem> const & v)
{
int size = v.size();
o.write(&size, sizeof(size)); // could use the template: write_pod(o, size)
for (int i = 0; i < v.size(); ++i) {
write(o, v[ i ]);
}
}
}
J'ai choisi un nom différent pour le modèle qui écrit que les types de base des fonctions qui écrivent des chaînes ou InventoryItems. La raison en est que nous ne voulons plus utiliser par erreur le modèle pour écrire un type complexe (c'est-à-dire des chaînes contenant UserInfo) qui stockera une représentation erronée sur le disque.
récupération à partir du disque devrait être assez similaire:
namespace binary {
template <typename T>
void read_pod(std::istream & i, T& data)
{
i.read(&data, sizeof(data));
}
void read(std::istream & i, std::string & str)
{
int size;
read_pod(i, size);
char* buffer = new char[size+1]; // create a temporary buffer and read into it
i.read(buffer, size);
buffer[size] = 0;
str = buffer;
delete [] buffer;
}
void read(std::istream & i, InventoryItem & data)
{
read(i, data.Item);
read(i, data.Description);
read(i, data.Quantity);
read(i, ...
}
void read(std::istream & i, std::vector<InventoryItem> & v)
{
v.clear(); // clear the vector in case it is not empty
int size;
read_pod(i, size);
for (int i = 0; i < size; ++i)
{
InventoryItem item;
read(i, item);
v.push_back(item);
}
}
}
Pour utiliser cette approche, le std :: istream et std :: ostream doit être ouvert en mode binaire.
int main()
{
std::ifstream persisted("file.bin", ios:in|ios::binary);
std::vector<InventoryItem> v;
binary::read(persisted, v);
// work on data
std::ofstream persist("output.bin", ios::out|ios::binary);
binary::write(persist, v);
}
tous les contrôles d'erreur est laissé comme un exercice pour le lecteur :)
Si vous avez une question sur une partie du code, il suffit de demander.
Je pense qu'il vaudrait mieux que les modifications au code original soient affichées en tant que réponses/suivis plutôt que de modifier la question. À la fin, les réponses originales seront complètement décalées et seront déroutantes pour les autres lecteurs. –