web-dev-qa-db-fra.com

Comment lire / écrire une structure dans des fichiers binaires?

Je fais face à un petit problème. J'ai un struct, qui a un vecteur. Notez que le vecteur est dynamique à chaque itération. Maintenant, dans une itération particulière, comment puis-je stocker la structure qui contient un vecteur de taille n dans un fichier binaire?

Aussi, lors de la récupération, supposons que je connaisse la taille du vecteur, comment récupérer du fichier binaire, la variable struct contenant le vecteur de tous les éléments stockés?

Je suis capable de stocker quelque chose dans le fichier binaire (comme je peux voir la taille augmenter lors de l'écriture), mais lorsque j'essaie de récupérer les éléments, je reçois la taille du vecteur à zéro.

Malheureusement, je dois y parvenir en utilisant la STL standard et ne pas utiliser de bibliothèques tierces.

21
Shankar Raju

Vous devriez jeter un œil à Boost Serialization .

Si vous ne pouvez pas utiliser de bibliothèques tierces , vous devez savoir que C++ ne prend pas directement en charge la sérialisation. Cela signifie que vous devrez le faire vous-même.

Cet article montre une belle façon de sérialiser un objet personnalisé sur le disque et de le récupérer. Et ce tutoriel vous montre comment commencer dès maintenant avec fstream.

Voici ma tentative:

[~ # ~] éditez [~ # ~] : comme l'OP m'a demandé comment stocker/récupérer plus que d'enregistrer, j'ai décidé de mettre à jour le code original.

Alors, qu'est-ce qui a changé? Il y a maintenant un tableaustudent_t apprentice[3]; pour stocker les informations de 3 étudiants. L'ensemble du tableau est sérialisé sur le disque, puis tout est chargé dans le RAM où la lecture/recherche d'enregistrements spécifiques est possible. Notez qu'il s'agit d'un très très petit fichier (84 octets). Je ne suggère pas cette approche lorsque vous recherchez des enregistrements sur des fichiers volumineux.

#include <fstream>
#include <iostream>
#include <vector>
#include <string.h>

using namespace std;


typedef struct student
{
    char name[10];
    int age;
    vector<int> grades;
}student_t;

int main()
{
    student_t apprentice[3];  
    strcpy(apprentice[0].name, "john");
    apprentice[0].age = 21;
    apprentice[0].grades.Push_back(1);
    apprentice[0].grades.Push_back(3);
    apprentice[0].grades.Push_back(5);    

    strcpy(apprentice[1].name, "jerry");
    apprentice[1].age = 22;
    apprentice[1].grades.Push_back(2);
    apprentice[1].grades.Push_back(4);
    apprentice[1].grades.Push_back(6);

    strcpy(apprentice[2].name, "jimmy");
    apprentice[2].age = 23;
    apprentice[2].grades.Push_back(8);
    apprentice[2].grades.Push_back(9);
    apprentice[2].grades.Push_back(10);

    // Serializing struct to student.data
    ofstream output_file("students.data", ios::binary);
    output_file.write((char*)&apprentice, sizeof(apprentice));
    output_file.close();

    // Reading from it
    ifstream input_file("students.data", ios::binary);
    student_t master[3];
    input_file.read((char*)&master, sizeof(master));         

    for (size_t idx = 0; idx < 3; idx++)
    {
        // If you wanted to search for specific records, 
        // you should do it here! if (idx == 2) ...

        cout << "Record #" << idx << endl;
        cout << "Name: " << master[idx].name << endl;
        cout << "Age: " << master[idx].age << endl;
        cout << "Grades: " << endl;
        for (size_t i = 0; i < master[idx].grades.size(); i++)
           cout << master[idx].grades[i] << " ";
        cout << endl << endl;
    }

    return 0;
}

Sorties :

Record #0
Name: john
Age: 21
Grades: 
1 3 5 

Record #1
Name: jerry
Age: 22
Grades: 
2 4 6 

Record #2
Name: jimmy
Age: 23
Grades: 
8 9 10

Vidage du fichier binaire :

$ hexdump -c students.data 
0000000   j   o   h   n  \0 237   {  \0   �   �   {   � 025  \0  \0  \0
0000010   (   �   �  \b   4   �   �  \b   8   �   �  \b   j   e   r   r
0000020   y  \0   �  \0   �   �   |  \0 026  \0  \0  \0   @   �   �  \b
0000030   L   �   �  \b   P   �   �  \b   j   i   m   m   y  \0  \0  \0
0000040   �   6   �  \0 027  \0  \0  \0   X   �   �  \b   d   �   �  \b
0000050   h   �   �  \b                                                
0000054
28
karlphillip

Vous sérialisez généralement un vecteur en écrivant la longueur du vecteur, suivie de ce nombre d'éléments. Lorsque vous le relisez, avoir la longueur en premier vous permet de savoir combien d'éléments supplémentaires à lire dans le cadre de ce vecteur. En tant que première approximation simple, considérez quelque chose comme ceci:

template<class T>
std::ostream &operator<<(std::ostream &output, T const &input) {
    T::size_type size = input.size();

    output << size << "\n";
    std::copy(input.begin(), input.end(), 
         std::ostream_iterator<T::value_type>(output, "\n"));

    return output;
}

template<class T>
std::istream &operator>>(std::istream &input, T &output) {
    T::size_type size, i;

    input >> size;
    output.resize(size);
    std::copy_n(
        std::istream_iterator<t::value_type>(input),
        size,
        output.begin());

    return input;
}

Ceci est ouvert à de nombreux ajustements, améliorations et modifications simples - juste par exemple, pour le moment, j'ai passé le vecteur (ou autre - pourrait être un std :: deque, etc.) par référence plutôt que de passer itérateurs. Cela simplifie probablement la plupart des utilisations, mais ne convient pas aussi bien au reste de la bibliothèque.

Cela sérialise également au format texte, un numéro par ligne. Des discussions comparant du texte au binaire ont déjà eu lieu, donc je n'essaierai pas de répéter tous les arguments ici - je vais juste noter que la même idée de base peut être faite au format binaire aussi bien que du texte.

16
Jerry Coffin