The ostream
member function write
outputs to the specified stream a fixed number of bytes, beginning at a specific location in memory. When the stream is associated with a file, function write
writes the data at the location in the file specified by the put file-position pointer. The istream
member function read
inputs a fixed number of bytes from the specified stream to an area in memory beginning at a specified address. If the stream is associated with a file, function read
inputs bytes at the location in the file specified by the “get” file-position pointer.
ostream
Member Function write
Writing the integer number
to a file using the statement
outFile << number;
for a four-byte integer could output 1 to 11 bytes—up to 10 digits for a number in the range –2,147,483,647 to 2,147,483,647, plus a sign for a negative number. Instead, we can use the statement
outFile.write(
reinterpret_cast<const char*>(&number), sizeof(number));
which always writes in binary the four bytes used that represent the integer number
. Function write
treats its first argument as a group of bytes by viewing the object in memory as a const char*
, which is a pointer to a byte (recall that a char
is 1 byte). Starting from that location, function write
outputs the number of bytes specified by its second argument— an integer of type size_t
. As we’ll see, istream
function read
can subsequently be used to read the four bytes back into integer variable number
.
reinterpret_cast
OperatorUnfortunately, most pointers that we pass to function write
as the first argument are not of type const char*
. To output objects of other types, we must convert the pointers to those objects to type const char*
; otherwise, the compiler will not compile calls to function write
. C++ provides the reinterpret_cast
operator for cases like this to make it clear that you’re treating data as an unrelated type. Without a reinterpret_cast
, the write
statement that outputs the integer number
will not compile because the compiler does not allow a pointer of type int*
(the type returned by the expression &number
) to be passed to a function that expects an argument of type const char*
—as far as the compiler is concerned, these types are inconsistent (that is, not compatible).
A reinterpret_cast
is performed at compile time and does not change the value of the object to which its operand points. Instead, it requests that the compiler reinterpret the operand as the target type (specified in the angle brackets following the keyword reinterpret_cast
). In Fig. 14.10, we use reinterpret_cast
to convert a ClientData
pointer to a const char*
, which reinterprets a ClientData
object as its bytes that should be output to a file. Random-access file-processing programs rarely write a single field to a file. Typically, they write one object of a class at a time, as we show in the following examples.
Unfortunately, it’s easy to use reinterpret_cast
to perform dangerous manipulations that could lead to serious execution-time errors.
reinterpret_cast
is compiler dependent and can cause programs to behave differently on different platforms. Use this operator only if it’s absolutely necessary.
A program that reads unformatted data (written by write
) must be compiled and executed on a system compatible with the program that wrote the data, because different systems may represent internal data differently.
Consider the following problem statement:
Using random-access file-processing techniques, create a credit-processing program capable of storing at most 100 fixed-length records for a company that can have up to 100 customers. Each record should consist of an account number that acts as the record key, a last name, a first name and a balance. The program should be able to update an account, insert a new account, delete an account and insert all the account records into a formatted text file for printing.
The next few sections create this credit-processing program. Figure 14.10 illustrates opening a random-access file, defining the record format using an object of class ClientData
(Figs. 14.8–14.9) and writing data to the disk in binary format. This program initializes all 100 records of the file credit.dat
with empty objects, using function write
. Each empty object contains the account number 0
, empty last- and first-name strings and the balance 0.0
.
Objects of class string
do not have uniform size, rather they use dynamically allocated memory to accommodate strings of various lengths. We must maintain fixed-length records, so class ClientData
stores the client’s first and last name in fixed-length char
arrays (declared in Fig. 14.8, lines 31–32). Member functions setLastName
(Fig. 14.9, lines 27–33) and setFirstName
(Fig. 14.9, lines 39–45) each copy the characters of a string
object into the corresponding char
array. Consider function setLastName
. Line 29 invokes the string
member function size
to get the length of lastNameString
. Line 30 ensures that the length
is fewer than 15 characters, then line 31 copies length
characters from lastNameString
into the char
array lastName
using string
member function copy
. Line 32 inserts a null character into the char
array to terminate the pointer-based string in lastName
. Member function setFirstName
performs the same steps for the first name.
In Fig. 14.10, line 10 creates an ofstream
object for the file credit.dat
. The second argument to the constructor—ios::out
| ios::binary
—indicates that we are opening the file for output in binary mode, which is required if we are to write fixed-length records. Multiple file-open modes are combined by separating each open mode from the next with the |
operator, which is known as the bitwise inclusive OR operator. (Chapter 22 discusses this operator in detail.) Lines 22–23 cause the blankClient
(which was constructed with default arguments at line 18) to be written to the credit.dat
file associated with ofstream
object outCredit
. Remember that operator sizeof
returns the size in bytes of the object contained in parentheses (see Chapter 8). The first argument to function write
must be of type const char
*. However, the data type of &blankClient
is ClientData*
. To convert &blankClient
to const char*
, line 23 uses the reinterpret_cast
operator, so the call to write
compiles without issuing a compilation error.