简体   繁体   中英

How to read/write string type member of a struct using binary file in/out in c++?

I have 2 c++ code: one is for write data into a binary file, another is for read that file.

write.cpp code is as below:

#include <iostream>
#include <fstream>
using namespace std;
const int NAME_SIZE = 51;
struct Data
{
    char name[NAME_SIZE];
    int age;
};

int main()
{
    Data person;
    char again;

    fstream people("people.db", ios::out | ios::binary);

    do
    {
        cout << "Enter the following data about a "<< "person:\n";
        cout << "Name: ";
        cin.getline(person.name, NAME_SIZE);
        cout << "Age: ";
        cin >> person.age;
        cin.ignore();

        people.write(reinterpret_cast<char *>(&person),sizeof(person));

        cout << "Do you want to enter another record? ";
        cin >> again;
        cin.ignore();
    } while (again == 'Y' || again == 'y');
    people.close();



    return 0;
}

read.cpp code is as below:

#include <iostream>
#include <fstream>
using namespace std;
const int NAME_SIZE = 51;
struct Data
{
    char name[NAME_SIZE];
    int age;
};

int main()
{
    Data person;
    char again;
    fstream people;

    people.open("people.db", ios::in | ios::binary);

    if (!people)
    {
        cout << "Error opening file. Program aborting.\n";
        return 0;
    }

    cout << "Here are the people in the file:\n\n";
    people.read(reinterpret_cast<char *>(&person),sizeof(person));

    while (!people.eof())
    {
        cout << "Name: ";
        cout << person.name << endl;
        cout << "Age: ";
        cout << person.age << endl;

        cout << "\nPress the Enter key to see the next record.\n";
        cin.get(again);
        people.read(reinterpret_cast<char *>(&person),sizeof(person));
    }
    cout << "That's all the data in the file!\n";
    people.close();
    return 0;
}

Above mentioned codes work fine. The problem arises when I use string type members in the structure:

new write.cpp :

#include <iostream>
#include <fstream>
using namespace std;

struct Data
{
    string name;
    int age;
};

int main()
{
    Data person;
    char again;

    fstream people("people.db", ios::out | ios::binary);

    do
    {
        cout << "Enter the following data about a "<< "person:\n";
        cout << "Name: ";
        cin>>person.name;
        cout << "Age: ";
        cin >> person.age;
        cin.ignore();

        people.write(reinterpret_cast<char *>(&person),sizeof(person));

        cout << "Do you want to enter another record? ";
        cin >> again;
        cin.ignore();
    } while (again == 'Y' || again == 'y');
    people.close();



    return 0;
}

new read.cpp :

#include <iostream>
#include <fstream>
using namespace std;

struct Data
{
    string name;
    int age;
};

int main()
{
    Data person;
    char again;
    fstream people;

    people.open("people.db", ios::in | ios::binary);

    if (!people)
    {
        cout << "Error opening file. Program aborting.\n";
        return 0;
    }

    cout << "Here are the people in the file:\n\n";
    people.read(reinterpret_cast<char *>(&person),sizeof(person));

    while (!people.eof())
    {
        cout << "Name: ";
        cout << person.name << endl;
        cout << "Age: ";
        cout << person.age << endl;

        cout << "\nPress the Enter key to see the next record.\n";
        cin.get(again);
        people.read(reinterpret_cast<char *>(&person),sizeof(person));
     }
     cout << "That's all the data in the file!\n";
     people.close();
    return 0;
}

Now when I run read.cpp the program can't read string and the program crashes. I must use string as a member of the structure. How to solve this problem?

The only way that comes to mind is to write the following data separately:

  1. Length of the string.
  2. The array of characters of the string.
  3. The age.

and read them separately.

Create functions to write/read an instance of Data such that they are aware of each other's implementation strategy.

std::ostream& write(std::ostream& out, Data const& data)
{
   size_t len = data.name.size();
   out.write(reinterpret_cast<char const*>(&len), sizeof(len));
   out.write(data.name.c_str(), len);
   out.write(reinterpret_cast<char const*>(&data.age));
   return out;
}

std::istream& read(std::istream& in, Data& data)
{
   size_t len;
   in.read(reinterpret_cast<char*>(&len), sizeof(len));

   char* name = new char[len+1];
   in.read(name, len);
   name[len] = '\0';
   data.name = name;
   delete [] name;

   in.read(reinterpret_cast<char*>(&data.age));
   return in;
}

and use them similarly to your first approach.

Instead of using

people.write(reinterpret_cast<char *>(&person),sizeof(person));

use

write(people, person);

Instead of using

people.read(reinterpret_cast<char *>(&person),sizeof(person));

use

read(people, person);

One problem is that sizeof(person.Name) does not give what you think it does. It always gives the same size (28 bytes in my case) not matter what characters you assign to your person.Name string. This is because of std::string contains at least:

  • a pointer to the actual string
  • other data structure to hold the available size and the size used

Therefore, you cannot call people.write(reinterpret_cast<char *>(&person),sizeof(person)); . The content of your string is not located at &person (its located wherever the pointer in std::string is pointing to)

So, what happens when you do cout << person.name << endl; after reading it from your file? You've actually read the address (not the content) where person.name 's string pointer was pointing to, when you wrote person to people.db. This is of course not a valid memory location, after reading it from your file, again.

The following code snippet could be helpful in your case. Instead of writing the length of the string, it is possible to use a delimiter and a pre-defined string length.

constexpr char delimiter = '\0';
constexpr uint32_t maxStringSize = 1024;

struct Data
{
    string name;
    int age;
};

When writing the file, place a delimiter after the string.
Let's say we have a Data structure {"John", 42} then we would write as follows:

std::ofstream outStream(filename, std::ios::binary);
outStream << structure.name << delimiter << structure.age;
outStream.close();

Reading the file is not the mirror of the write (unfortunately).
We'll use the std::ifstream::getline to read the string without knowing the size of it. (Error checking omitted)

std::ifstream istrm(filename, std::ios::binary);
Data dataRead;

// string input - use a buffer and look for the next delimiter
char* buf = new char[maxStringSize];
istrm.getline(buf, maxStringSize, delimiter);
dataRead.name = std::string(buf);

// the number input
istrm >> dataRead.age;

For inspiration how to read/write a vector of this struct you can check my repository .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM