How to Read/Write a Struct in Binary Files

How to read / write a struct in Binary Files?

You should have a look at Boost Serialization.

If you can't use 3rd party libraries, you must know that C++ doesn't support serialization directly. This means you'll have to do it yourself.

This article shows a nice way of serializing a custom object to the disk and retrieving it back. And this tutorial shows you how to get started right now with fstream.

This is my attempt:

EDIT: since the OP asked how to store/retrieve more than record I decided to updated the original code.

So, what changed? Now there's an array student_t apprentice[3]; to store information of 3 students. The entire array is serialized to the disk and then it's all loaded back to the RAM where reading/searching for specific records is possible. Note that this is a very very small file (84 bytes). I do not suggest this approach when searching records on huge files.

#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;
}

Outputs:

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

Dump of the binary file:

$ 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

Writing and Reading a structure from a binary file C

The problem is with your structure:

struct contactStruct 
{
int phoneNumber;
char * firstName; // <-- you have pointers, not data
char * lastName;
char * companyName;
};

When you save your contactStruct to file, using fwrite( & contact, sizeof(struct contactStruct), 1, filePointer); you save the values of the pointers to the file, and not the contents of the strings they point to. This means the actual data never gets saved, and when you read the file, you have pointer that point nowhere.

If what you seek to do is simply have fixed-length records in your file - I suggest you do, for now, You should change your struture to hold data. The length. You must decide how long the strings should be depending on what you need.

#define CS_PHONE_LEN  11     // remember that your strings will need a zero termination.
#define CS_FIRSTNAME_LEN 24 // I've chosen arbitrary lengths for the demonstration.
#define CS_LASTNAME_LEN 24
#define CS_COMPANY_LEN 24

struct contactStruct
{
char phoneNumber[CS_PHONE_LEN]; // phone numbers are usually stored as text.
char firstName[CS_FIRSTNAME_LEN];
char lastName[CS_LASTNAME_LEN];
char companyName[CS_COMPANY_LEN];
};

You will have to take care in your program not to store strings longer than the sizes defined in your structure, or the data will become corrupt. You'll probably want to use strncpy_s() to store strings in the data members, here's the doc for the function http://en.cppreference.com/w/c/string/byte/strncpy.

strncpy_s(cs.phoneNumber, CS_PHONE_LEN, userEntryString, CS_PHONE_LEN - 1);

If you use the older strncpy, beware that it will not null terminate your destination string if the source string is too long.

strncpy(cs.phoneNumber, userEntryString, CS_PHONE_LEN - 1);
cs.phoneNumber[CS_PHONE_LEN - 1] = 0; // make sure it's null-terminated.

A safer, alternate way to write this (above is the more oft-used, 'classic' notation.

strncpy_s(cs.phoneNumber, sizeof(cs.phoneNumber), userEntryString, sizeof(cs.phoneNumber) - 1);

// or...

strncpy(cs.phoneNumber, userEntryString, sizeof(cs.phoneNumber) - 1);
cs.phoneNumber[sizeof(cs.phoneNumber) - 1] = 0; // make sure it's null-terminated.

This way, your writes are independent of the name of the constants, while the lengths are always right.

Reading/Writing a structure into a binary file

I'm assuming your struct looks like this:

struct Medicazos
{
char Nombre[60];
char Clave_Acceso[20];
char Especialidad[40];
int Id_Doctor;
int Estado;
}

You can read/write/copy this guy around as a single unit. There's no need to do piecemeal access until you're ready to actually use the values.

struct Medicazos m = {"Bob", "Password", "Feet", 123, 456};

FILE* f = fopen(...);
fwrite(&m, sizeof(struct Medicazos), 1, f);

And the same (but backward) for fread.

(By the way, your capitalized variable names are killing me.)

Read/write a vector of Structs into a binary file and reading a vector of structs from a file in C++

The std::vector object itself probably does not contain any actual data. Instead, it probably only contains bookkeeping information, for example

  • the number of valid elements,
  • the maximum number of elements for which memory has been allocated, and
  • a pointer to the start of the actual data.

Therefore, simply dumping the contents of the std::vector object to file will not be useful.

If you want to write the actual data of a std::vector to file, you must first decide how it should be stored in the file. One simple way of storing it would be to first write the number of elements as an int to the file, and then to write all of the individual elements to the file one after another. That way, when reading the file later, the reading function can easily find out how many elements it should read from the file, simply by reading the first value.

You may want to change your function create to the following:

void create(std::vector<info>& test)
{
std::ofstream file("info.dat", std::ios::binary );

for ( const info& inf : test )
{
//write "name" member to file
file.write( reinterpret_cast<const char*>(&inf.name), sizeof inf.name );

//write "age" member to file
file.write( reinterpret_cast<const char*>(&inf.age), sizeof inf.age );

//write "bbl" member to file
file.write( reinterpret_cast<const char*>(&inf.bbl), sizeof inf.bbl );

//write "my" member to file, giving it special treatment,
//because it is a std::vector
{
int num_elements = inf.my.size();

//write number of elements to file
file.write( reinterpret_cast<const char*>(&num_elements), sizeof num_elements );

//write the individual elements to file
for ( int i = 0; i < num_elements; i++ )
{
file.write( reinterpret_cast<const char*>(&inf.my[i]), sizeof inf.my[i] );
}
}
}

//verify that stream is still in a good state
if ( !file )
throw std::runtime_error( "output error" );
}

Note that I removed std::ios::app in the code above, because it did not seem appropriate for what you are doing.

For reading the file contents, you can now use the following function:

void read(std::vector<info>& test)
{
std::ifstream file("info.dat", std::ios::binary );

info inf;

//try reading new entries from file until end-of-file or error occurs
for (;;)
{
//read "name" member from file
file.read( reinterpret_cast<char*>(&inf.name), sizeof inf.name );

//read "age" member from file
file.read( reinterpret_cast<char*>(&inf.age), sizeof inf.age );

//read "bbl" member from file
file.read( reinterpret_cast<char*>(&inf.bbl), sizeof inf.bbl );

//read "my" member from file, giving it special treatment,
//because it is a std::vector
{
int num_elements;

//read number of elements from file
file.read( reinterpret_cast<char*>(&num_elements), sizeof num_elements );

//don't start loop if loop counter is invalid
if ( !file )
break;

//read the individual elements from file
for ( int i = 0; i < num_elements; i++ )
{
MyStruct my;

file.read( reinterpret_cast<char*>(&my), sizeof my );
if ( file )
inf.my.push_back( my );
else
break;
}

//stop main loop if data was not successfully read
if ( !file )
break;

test.push_back( inf );
}
}
}

Your entire program will now look like this:

#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>

struct MyStruct
{
int a;
};

struct info
{
char name[30];
int age;
char bbl[20];
std::vector<MyStruct> my;
};

void create(std::vector<info>& test)
{
std::ofstream file("info.dat", std::ios::binary );

for ( const info& inf : test )
{
//write "name" member to file
file.write( reinterpret_cast<const char*>(&inf.name), sizeof inf.name );

//write "age" member to file
file.write( reinterpret_cast<const char*>(&inf.age), sizeof inf.age );

//write "bbl" member to file
file.write( reinterpret_cast<const char*>(&inf.bbl), sizeof inf.bbl );

//write "my" member to file, giving it special treatment,
//because it is a std::vector
{
int num_elements = inf.my.size();

//write number of elements to file
file.write( reinterpret_cast<const char*>(&num_elements), sizeof num_elements );

//write the individual elements to file
for ( int i = 0; i < num_elements; i++ )
{
file.write( reinterpret_cast<const char*>(&inf.my[i]), sizeof inf.my[i] );
}
}
}

//verify that stream is still in a good state
if ( !file )
throw std::runtime_error( "output error" );
}

void read(std::vector<info>& test)
{
std::ifstream file("info.dat", std::ios::binary );

info inf;

//try reading new entries from file until end-of-file or error occurs
for (;;)
{
//read "name" member from file
file.read( reinterpret_cast<char*>(&inf.name), sizeof inf.name );

//read "age" member from file
file.read( reinterpret_cast<char*>(&inf.age), sizeof inf.age );

//read "bbl" member from file
file.read( reinterpret_cast<char*>(&inf.bbl), sizeof inf.bbl );

//read "my" member from file, giving it special treatment,
//because it is a std::vector
{
int num_elements;

//read number of elements from file
file.read( reinterpret_cast<char*>(&num_elements), sizeof num_elements );

//don't start loop if loop counter is invalid
if ( !file )
break;

//read the individual elements from file
for ( int i = 0; i < num_elements; i++ )
{
MyStruct my;

file.read( reinterpret_cast<char*>(&my), sizeof my );
if ( file )
inf.my.push_back( my );
else
break;
}

//stop main loop if data was not successfully read
if ( !file )
break;

test.push_back( inf );
}
}
}

int main()
{
info info1;
// create a test vector
std::vector<info> test;
info1.age = 3443;
std::cin >> info1.name;
std::cin >> info1.bbl;
MyStruct my;
my.a = 4;
info1.my.push_back(my);

test.push_back(info1);

std::cout << '\n';
create(test);

test.clear(); // clear the vector

read( test );

// print out the contents of test
for (int i = 0; i < test.size(); i++)
std::cout << "{ " << test[i].name << ", " << test[i].age << ", " << test[i].bbl << " } ";
std::cout << '\n';
}

This program has the following output:

TestName
TestBBL

{ TestName, 3443, TestBBL }

As you can see, the written data was read back properly.

How can I write a struct from a binary file to a nested struct in another binary file?

In inputUser you write doing

fwrite(&travelers, 1, sizeof(user), input);

it seems more logical to do

fwrite(&travelers, sizeof(user), 1, input);

because fwrite is

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

rather than

size_t fwrite(const void *ptr, size_t nmemb, size_t size,
FILE *stream);

Of course it is the same elsewhere like in checkIn, and for the fread where you also exchange nmemb and size


Note in checkin you do two times flights = fopen("flights.dat", "rb") :

    if( (flights = fopen("flights.dat", "rb") ) == NULL) {
printf("Error.");
}
else {
printf("Available flights:\n\n");

flights = fopen("flights.dat", "rb");

because you use the same variable flights only the second FILE can be closed. After a time you will not be able to open again a file because the number of open file at the same time is limited.


Warning

 while( !(feof(flights)))

generally does not work, use the result of fread to stop to read


You can remove all your fflush(stdin);, they do nothing :

For input streams associated with seekable files (e.g., disk files,
but not pipes or terminals
), fflush() discards any buffered data ...


I encourage you to check the result of your scanf to be sure a valid input was enter, so to check it returns 1 when you read a value and 2 the few cases you read 2 values

Also in

   scanf("%d", &user_number);
fseek(users, (user_number-1)*sizeof(user), SEEK_SET);
fread(&travelers, 1, sizeof(user), users);

and

       scanf("%d", &choice1);

fseek(flights, (choice1-1)*sizeof(flight), SEEK_SET);
fread(&list, 1, sizeof(flight), flights);

there is no check at all

So you do not know what you read, you suppose a valid int was enter for user_number and choice1, you suppose their value is compatible with the size of the file, then you suppose you can read a user/fligh

You really need to add checks


I guess the main problem is in the function checkin() inside booking.h

Yes, in checkin your way to modify the flight part of a user is not the right, you do

 fseek(users, (user_number-1)*sizeof(user), SEEK_SET);

fwrite(&travelers.b.list, 1, sizeof(user), users);

the fseek set the position at the beginning of the user in the file, but rather than to fwrite all the user you fwrite the sub part travelers.b.list so you replace name/surname/... by the flight (and the memory after it because you write the size of the user rather than on the the size of a flight) which furthermore is not initialized because previously you fread in the variable list rather than in travelers.b.list.

A way to correct can be to update travelers.b.list then to write all the user, so to replace

       fread(&list, 1, sizeof(flight), flights);

printf("\n--------YOU CHOSE FLIGHT %d-------- \n\n", choice1);
printf("Flight code: %s\nCompany name: %s\nDEPARTURE FROM: %s\nARRIVAL AT: %s\nPlane code: %s\nDay of flight: %hu\nMonth of flight: %hu\n"
"Year of flight: %hu\nHour of departure: %hu:%hu\nHour of arrival: %hu:%hu\nFlight time: %hu min.\n\n",
list.flight_code, list.companyname, list.departure,
list.arrival, list.plane_code, list.date_of_flight.day,
list.date_of_flight.month, list.date_of_flight.year, list.hour_departure,
list.minute_departure,list.hour_arrival,list.hour_departure, list.flight_time);

users = fopen("users.dat", "rb+");
fseek(users, (user_number-1)*sizeof(user), SEEK_SET);

fwrite(&travelers.b.list, 1, sizeof(user), users);

by

        fread(&travelers.b.list, sizeof(flight), 1, flights);

printf("\n--------YOU CHOSE FLIGHT %d-------- \n\n", choice1);
printf("Flight code: %s\nCompany name: %s\nDEPARTURE FROM: %s\nARRIVAL AT: %s\nPlane code: %s\nDay of flight: %hu\nMonth of flight: %hu\n"
"Year of flight: %hu\nHour of departure: %hu:%hu\nHour of arrival: %hu:%hu\nFlight time: %hu min.\n\n",
travelers.b.list.flight_code, travelers.b.list.companyname, travelers.b.list.departure,
travelers.b.list.arrival, travelers.b.list.plane_code, travelers.b.list.date_of_flight.day,
travelers.b.list.date_of_flight.month, travelers.b.list.date_of_flight.year, travelers.b.list.hour_departure,
travelers.b.list.minute_departure,travelers.b.list.hour_arrival,travelers.b.list.hour_departure, travelers.b.list.flight_time);

users = fopen("users.dat", "rb+");
fseek(users, (user_number-1)*sizeof(user), SEEK_SET);

fwrite(&travelers, sizeof(user), 1, users);

An other way closer to your code is to fseek at the right position and to write the right flight with the right size, so to replace

 fseek(users, (user_number-1)*sizeof(user), SEEK_SET);

fwrite(&travelers.b.list, 1, sizeof(user), users);

by

 fseek(users, (user_number-1)*sizeof(user) + (((char *) &travelers.b.list) - ((char *) &travelers)), SEEK_SET);

fwrite(&list, sizeof(list), 1, users);


Related Topics



Leave a reply



Submit