Writing To a File

advertisement
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
Download