READING FROM/WRITING TO BINARY FILES The sizeof() function When writing/reading data to/from a binary file, you need to know how many bytes to write/read. The sizeof() function will tell you how many bytes a specific variable or type has been allocated. Format: sizeof (variableName); OR sizeof (dataType); Result: Returns size of variable/type in bytes Example: // structure definition struct stockItem { int barCode; float price; int quantity; }; // variable declarations int numSize, charSize, floatSize, structSize, num; numSize = sizeof(num); charSize = sizeof(char); floatSize = sizeof(float); structSize = sizeof(stockItem); cout cout cout cout << << << << // // // // int variable size char dataType size float dataType size struct dataType size "char size is " << charSize << " byte" << endl; "float size is " << floatSize << " bytes" << endl; "Num size is " << numSize << " bytes" << endl; "stockItem size is " << structSize << " bytes" << endl; Resulting Output: char size is 1 byte float size is 4 bytes Num size is 4 bytes stockItem size is 12 bytes Since stockItem contains one float member (4 bytes) and 2 int members (4 bytes each), it makes sense that the structure would take up 12 bytes. NOTE: Sizes will vary from machine to machine. © 2008, Regis University last mod 3/20/08 Writing To a File The write function writes a given number of bytes to the given file stream, starting at the position of the "put" pointer: fileStream.write (char* dataPointer, int numBytes ); If the data being written to the file is NOT of char type, you must use the reinterpret_cast type converter to typecast the pointer into a char* type. If the put pointer is current at the end of the file, the file is extended. If the put pointer points into the middle of the file, characters in the file are overwritten with the new data. Write Example 1: const int SIZE = 10; char letters[SIZE]; Given the array of characters defined above, write the 10 characters in the array out to the binary file LETTERS.BIN, all at once: ofstream binFile; binFile.open("LETTERS.BIN", ios::binary | ios::out); binFile.write(letters, SIZE * sizeof(char)); binFile.close(); Write Example 2: const int MAX_NUMS = 100; int numArray[MAX_NUMS] = { 10, 20, 30, 40, 50, 60 }; int numCount = 6; In this case, we only want to write out the values in the array that have been initialized. So to write the six values, one value at a time, from the array to the binary file NUMS.BIN: ofstream numFile; numFile.open("NUMS.BIN", ios::binary | ios::out); for (idx = 0; idx < numCount; idx++) numFile.write( reinterpret_cast<char*>(&numArray[idx]), sizeof(int) ); numFile.close(); © 2008, Regis University last mod 3/20/08 Reading From a File The read function extracts a given number of bytes from the given stream, placing them into the memory pointed to by the first parameter. fileStream.read (char* dataPointer, int numBytes); If your pointer points to data that is NOT of char type, you must use the reinterpret_cast type converter to typecast the pointer into a char* type. It is the programmer's responsibility to create the variables where read will place its result and to ensure that the allocated memory is large enough to hold the number of bytes read. Read Example 1: const int SIZE = 10; char letters[SIZE] = {'A','B','C','D','E','F','G','H','I','J'}; Given the array of characters defined above, and the binary file LETTERS.BIN created in the previous section, read the 10 characters stored in the file into the letters array (all at once) and then display them: int idx; ifstream binFile; binFile.open("LETTERS.BIN", ios::binary | ios::in); if (!binFile) cout << "Error opening binary file" << endl; else { // opened file successfully // read data from file and store in array letters binFile.read(letters, SIZE * sizeof(char)); // close the file binFile.close(); // display the values now stored in the array for (idx = 0; idx < SIZE; idx++) cout << letters[idx] << endl; } // end else © 2008, Regis University last mod 3/20/08 Read Example 2: const int MAX_NUMS = 100; int numArray[MAX_NUMS]; int idx, numCount; Given the declarations above, and the binary file NUMS.BIN created in the previous (write) section, read the integers from the file and store them in the numArray array (note that in this case, we do not know in advance how many numbers are stored in the file): ifstream numFile; int numCount; numFile.open("NUMS.BIN", ios::binary | ios::in); if (!numFile) cout << "Error opening data file" << endl; else { // file opened successfully numCount = 0; // initialize count of numbers read // priming read of first data item numFile.read( reinterpret_cast<char*>(&numArray[numCount]), sizeof(int) ); while (numFile) { ++numCount; // number successfully read // try to read another number into next array cell numFile.read(reinterpret_cast<char*>(&numArray[numCount]), sizeof(int) ); } // end while numFile.close(); cout << numCount << " numbers read in are: " << endl; for (idx = 0; idx < numCount; idx++) cout << numArray[idx] << endl; } // end else The above code reads numbers from the file. When it hits the end-of-file, the file stream is placed in an error state (because the program tried to read past the end of file). When the file stream is in an error state, testing it returns FALSE (just like when you can't open the file). So each time the file stream is NOT in an error state, we know that another number was read from the file. On the other hand, when the file stream IS in an error state, we know that all the numbers in the file have been read. © 2008, Regis University last mod 3/20/08 Read Example 3: If an error occurs while reading a bunch of data at once (for example, if you try to read past the end of a file), the file input stream is placed in an error state. If that occurs, you can use the gcount function to find out the number of bytes that were actually read, and use the clear function to reset the file input stream to a usable state. Once a file input stream goes into an error state, all future read operations will fail, until the stream is reset using the clear function. Say we have the same declarations as in Read Example 2, and again we want to read the integers from the file and store them in the numArray array. But this time, instead of reading the numbers in one at a time, we will try to read 100 of them all at once (even though there are only actually 6 numbers in the file): ifstream numFile; numFile.open("NUMS.BIN", ios::binary | ios::in); if (!numFile) cout << "Error opening data file" << endl; else { // file opened successfully numFile.read( reinterpret_cast<char*>(numArray), MAX_NUMS * sizeof(int) ); if (numFile) // no error state, // so 100 numbers successfully read numCount = MAX_NUMS; else { // read error occurred numCount = (numFile.gcount() / sizeof(int)); numFile.clear(); // reset file stream } numFile.close(); cout << numCount << " numbers read in are: " << endl; for (idx = 0; idx < numCount; idx++) cout << numArray[idx] << endl; } // end else The above code tries to read 100 numbers from the file. But there were only 6 numbers in the file. Since there are less than 100 items in the file, the file stream is placed in an error state (because the program tried to read past the end of file). After the read, since the file stream IS in an error state, we use the gcount() function to get a count of the numbers actually read. © 2008, Regis University last mod 3/20/08 Mixing data types in a Binary File In the previous examples, the binary files we created contained only one type of data. But there is no restriction on what type of data can be stored in a binary file. Mixed Data Example1: Say that you have the following data in your program: int itemCount = 45; float itemPrice = 3.99; char itemColor = 'R'; // red You could write all three of these items out to a binary file as follows: ofstream dataFile; dataFile.open("DATA.BIN", ios::binary | ios::out); dataFile.write(reinterpret_cast<char*>(&itemCount), sizeof(int)); dataFile.write(reinterpret_cast<char*>(&itemPrice), sizeof(float)); dataFile.write(&itemColor, 1 ); dataFile.close(); And later read them in as follows: ifstream dataFile; dataFile.open("DATA.BIN", ios::binary | ios::in); dataFile.read(reinterpret_cast<char*>(&itemCount), sizeof(int)); dataFile.read(reinterpret_cast<char*>(&itemPrice), sizeof(float)); dataFile.read(&itemColor, 1 ); dataFile.close(); The key is that the program has to read the data back into memory in the exact same order that it was written out to the file, so that the bytes of data will be interpreted correctly. © 2008, Regis University last mod 3/20/08 You can also write out complex data to a binary file. Mixed Data Example 2: Say that you have the data from the previous example was stored in a structure: struct stockItem { int count; float price; char color; }; stockItem oneItem = {45, 3.99, 'R'}; You could write the oneItem record out to the END of a binary file as follows: ofstream dataFile; dataFile.open("DATA.BIN", ios::binary | ios::out | ios::app); dataFile.write(reinterpret_cast<char*>(&oneItem), sizeof(stockItem)); dataFile.close(); And later read it in as follows: stockItem xItem; ifstream dataFile; dataFile.open("DATA.BIN", ios::binary | ios::in); dataFile.read(reinterpret_cast<char*>(&xItem), sizeof(stockItem)); dataFile.close(); Mixed Data Example 3: To further expand the above example, you could have an array of stockItems: const int MAX_ITEMS = 100; stockItem inventory[MAX_ITEMS]; int invCount; Suppose the inventory array was partially filled, and invCount contains the number of items in the array. © 2008, Regis University last mod 3/20/08 You could write the records in the inventory array out to a NEW binary file as follows: ofstream dataFile; dataFile.open("DATA.BIN", ios::binary | ios::out | ios::trunc); for (idx = 0; idx < invCount; idx++) dataFile.write(reinterpret_cast<char*>(&inventory[idx]), sizeof(stockItem)); dataFile.close(); Later, you could read the records from the binary file back into the inventory array as follows (dataFile will evaluate to false when you try to read past the end of file): invCount = 0; ifstream dataFile; dataFile.open("DATA.BIN", ios::binary | ios::in); if (!dataFile) cout << "Error reading data file" << endl; else { do { dataFile.read(reinterpret_cast<char*>(&inventory[invCount]), sizeof(stockItem)); if (dataFile) // successful read invCount++; } } while (dataFile); // while more data dataFile.clear(); dataFile.close(); // reset stream after error // end else When we try to read past the end of the file, the file stream is placed in an error state. Therefore, we check the dataFile file stream after each read, to see if it is in an error state. When it IS in an error state, we know that there is no more data to read. WARNING: When a complex object with dynamic content, such as a string, is involved the issues of writing to a binary file become more complex. Simply writing the correct number of bytes, from the starting address of the string in memory, will NOT produce the desired result, and is beyond the scope of this class. In this class, you will ONLY be required to read/write simple data types to binary files. © 2008, Regis University last mod 3/20/08