Homework Assignment - Using array of structs with binary files

After two days of working on this, I have to raise the white flag. The problem says to create an array of structures (three of them), and write them via binary output to a file. Then, open that file, capture all of the data, and ask the user which of the three books he/she wants information about. I am to use the seekg function to align the starting read point based on the user input.

I get the file to write, and I can also retrieve the first and third record - the second record's title is not displaying correctly, and all three crash after the display. Any help would be very much appreciated!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  #include<iostream>
#include<fstream>
#include<string>

using namespace std;
struct Book
   {
      string title;
      int numPages;
   };
const int numBooks = 3; //constant int for 3 books 

int main()
{
   fstream bookFile;
   bookFile.open("books.txt", ios::out | ios::binary); // declare file object and use binary output
   if (bookFile)
   {
      Book bookArray[numBooks] = { { "C++ Programming", 1000 }, // book array with 3 books
                                   { "Java Programming", 1250},
                                   { "Head First C", 750     } };
      cout << "Writing book array to file...." ;
      bookFile.write(reinterpret_cast<char *>(bookArray), sizeof(bookArray));
      cout << "Successful. " << endl << endl;
   }
   else
      cout << "Error opening output file. Program aborting. " << endl;
   bookFile.close();


   fstream inputBookFile; //declare new book file for input use
   inputBookFile.open("books.txt", ios::in | ios:: binary); // open inputBookFile books.txt in binary input mode
   Book outputBook; // book type to hold read data 
   long fileByteNumber; // variable to hold the byte number of file  
   int userInt; // variable to hold user choice of which book # info to display

   if (inputBookFile) //if file opened successfully, continue
   {
      cout << "Please enter a number from 1 - 3." << endl;
      cout << "The corresponding book information will be shown. ";
      cin >> userInt; // capture user choice
      cout << endl;

      cout << "Here is the information for book #" << userInt << ": " << endl;
      userInt--; //decrement user's # to start at 0
      fileByteNumber = (sizeof(Book) * userInt); 
      inputBookFile.seekg(fileByteNumber, ios::beg); //uses byteNumber to locate correct record from the beginning of the file
      inputBookFile.read(reinterpret_cast<char *>(&outputBook), sizeof(outputBook));
      cout << "Title: " << outputBook.title << endl;
      cout << "Pages: " << outputBook.numPages << endl << endl;
   }

   else //if error opening books file, abort
      cout << "Error opening books file. Program aborting. " << endl;

   inputBookFile.close(); // close output file
   return 0;
}
Last edited on
1
2
3
4
5
6
7
struct Book
   {
      string title;
      int numPages;
   };
//...
bookFile.write(reinterpret_cast<char *>(bookArray), sizeof(bookArray));
You can only save trivial types using write. string is not trivial. Actually it is mandatory that string contains at least one pointer, so you are saving a pointer. And all pointer values are different each launch. Only reason that some data was read succesfully, it that memory belonging to originals was not overwritten yet.
Thanks MiiNiiPaa. Would I be correct in assuming that I need to create a char* for the title field? Can you assist me with the syntax? The fact that it is an element of an array of structures is throwing me off.

for (int i = 0; i < 3; i++)
{
const char *p = bookArray[i].title.c_str();
}

I attempted to change the 3 titles using the above for loop, but with the same results. Thanks
Would I be correct in assuming that I need to create a char* for the title field?
You would still saving pointer and not value.

Here are some solutions:
1) Use a character array. It is contained inside structure itself and safe to copy. Generally you can safely read/write only types which are TriviallyCopiable: http://en.cppreference.com/w/cpp/types/is_trivially_copyable
Objects of trivially-copyable types are the only C++ objects that may be safely copied with std::memcpy or serialized to/from binary files with std::ofstream::write()/std::ifstream::read().


2) Serialize stuff yourself instead of relying on builtin read/write. Even better: save stuff in text format, not binary one.
Thanks. The problem is that I must use binary format as well as a string in the actual structure. So unless I can find some way to convert that string to something that can use the .write function, I'm in trouble!
A character array is your best bet. Something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct serialize_helper
{
    char title[64];
    int numPages;
}

std::ostream& serialize(const Book& b, std::ostream& out)
{
    if(b.title.size() > 63) 
        std::exit(EXIT_FAILURE); //make sure our title fit into array
    serialize_helper temp;
    strncpy(temp.title, b.title.c_str(), 64);
    temp.numPages = b.numPages;
    return out.write(temp, sizeof(temp));
}


Book deserialize(std::istream& in)
{
    serialize_helper temp;
    in.read(temp, sizeof(temp));
    Book b; //Everything down here is 1 line in C++11
    b.title = temp.title;
    b.numPages = temp.numPages;
    return b;
}
Thanks so much MiiNiPaa. I appreciate your help...got it working.
Topic archived. No new replies allowed.