Creating a Random-Access File
The ostream member function write outputs a fixed number of bytes, beginning at a specific location in memory, to the specified stream. 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.
Writing Bytes with ostream Member Function write
When writing an integer number to a file, instead of using the statement
outFile << number;
which for a four-byte integer could print as few digits as one or as many as 11 (10 digits plus a sign, each requiring a single byte of storage), we can use the statement
outFile.write( reinterpret_cast< const char * >( &number ), sizeof( number ) );
which always writes the binary version of the integer's four bytes (on a machine with fourbyte integers). 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 (remember that a char is one byte). Starting from that location, function write outputs the number of bytes specified by its second argumentan integer of type size_t. As we will see, istream function read can subsequently be used to read the four bytes back into integer variable number.
Converting Between Pointer Types with the reinterpret_cast Operator
Unfortunately, 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 in which a pointer of one type must be cast to an unrelated pointer type. You can also use this cast operator to convert between pointer and integer types, and vice versa. 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 incompatible.
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. 17.12, we use reinterpret_cast to convert a ClientData pointer to a const char *, which reinterprets a ClientData object as bytes to be output to a file. Random-access file-processing programs rarely write a single field to a file. Normally, they write one object of a class at a time, as we show in the following examples.
Error-Prevention Tip 17.1
It is easy to use reinterpret_cast to perform dangerous manipulations that could lead to serious execution-time errors. |
Portability Tip 17.1
Using reinterpret_cast is compiler-dependent and can cause programs to behave differently on different platforms. The reinterpret_cast operator should not be used unless absolute necessary. |
Portability Tip 17.2
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. |
Credit Processing Program
Consider the following problem statement:
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 several sections introduce the techniques for creating this credit-processing program. Figure 17.12 illustrates opening a random-access file, defining the record format using an object of class ClientData (Figs. 17.1017.11) 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 0 for the account number, the null string (represented by empty quotation marks) for the last and first name and 0.0 for the balance. Each record is initialized with the amount of empty space in which the account data will be stored.
Figure 17.10. ClientData class header file.
1 // Fig. 17.10: ClientData.h 2 // Class ClientData definition used in Fig. 17.12Fig. 17.15. 3 #ifndef CLIENTDATA_H 4 #define CLIENTDATA_H 5 6 #include 7 using std::string; 8 9 class ClientData 10 { 11 public: 12 // default ClientData constructor 13 ClientData( int = 0, string = "", string = "", double = 0.0 ); 14 15 // accessor functions for accountNumber 16 void setAccountNumber( int ); 17 int getAccountNumber() const; 18 19 // accessor functions for lastName 20 void setLastName( string ); 21 string getLastName() const; 22 23 // accessor functions for firstName 24 void setFirstName( string ); 25 string getFirstName() const; 26 27 // accessor functions for balance 28 void setBalance( double ); 29 double getBalance() const; 30 private: 31 int accountNumber; 32 char lastName[ 15 ]; 33 char firstName[ 10 ]; 34 double balance; 35 }; // end class ClientData 36 37 #endif |
Figure 17.11. ClientData class represents a customer's credit information.
(This item is displayed on pages 860 - 861 in the print version)
1 // Fig. 17.11: ClientData.cpp 2 // Class ClientData stores customer's credit information. 3 #include 4 using std::string; 5 6 #include "ClientData.h" 7 8 // default ClientData constructor 9 ClientData::ClientData( int accountNumberValue, 10 string lastNameValue, string firstNameValue, double balanceValue ) 11 { 12 setAccountNumber( accountNumberValue ); 13 setLastName( lastNameValue ); 14 setFirstName( firstNameValue ); 15 setBalance( balanceValue ); 16 } // end ClientData constructor 17 18 // get account-number value 19 int ClientData::getAccountNumber() const 20 { 21 return accountNumber; 22 } // end function getAccountNumber 23 24 // set account-number value 25 void ClientData::setAccountNumber( int accountNumberValue ) 26 { 27 accountNumber = accountNumberValue; // should validate 28 } // end function setAccountNumber 29 30 // get last-name value 31 string ClientData::getLastName() const 32 { 33 return lastName; 34 } // end function getLastName 35 36 // set last-name value 37 void ClientData::setLastName( string lastNameString ) 38 { 39 // copy at most 15 characters from string to lastName 40 const char *lastNameValue = lastNameString.data(); 41 int length = lastNameString.size(); 42 length = ( length < 15 ? length : 14 ); 43 strncpy( lastName, lastNameValue, length ); 44 lastName[ length ] = ' |