Homework Assignment - Using array of structs with binary files

Jul 10, 2015 at 3:40am
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 Jul 10, 2015 at 3:41am
Jul 10, 2015 at 7:08am
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.
Jul 10, 2015 at 12:01pm
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
Jul 10, 2015 at 12:11pm
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.
Jul 10, 2015 at 1:18pm
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!
Jul 10, 2015 at 1:50pm
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;
}
Jul 10, 2015 at 2:40pm
Thanks so much MiiNiPaa. I appreciate your help...got it working.
Topic archived. No new replies allowed.