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 ] = ''; // append null character to lastName 45 } // end function setLastName 46 47 // get first-name value 48 string ClientData::getFirstName() const 49 { 50 return firstName; 51 } // end function getFirstName 52 53 // set first-name value 54 void ClientData::setFirstName( string firstNameString ) 55 { 56 // copy at most 10 characters from string to firstName 57 const char *firstNameValue = firstNameString.data(); 58 int length = firstNameString.size(); 59 length = ( length < 10 ? length : 9 ); 60 strncpy( firstName, firstNameValue, length ); 61 firstName[ length ] = ''; // append null character to firstName 62 } // end function setFirstName 63 64 // get balance value 65 double ClientData::getBalance() const 66 { 67 return balance; 68 } // end function getBalance 69 70 // set balance value 71 void ClientData::setBalance( double balanceValue ) 72 { 73 balance = balanceValue; 74 } // end function setBalance

Figure 17.12. Creating a random-access file with 100 blank records sequentially.

(This item is displayed on pages 861 - 862 in the print version)

1 // Fig. 17.12: Fig17_12.cpp 2 // Creating a randomly accessed file. 3 #include 4 using std::cerr; 5 using std::endl; 6 using std::ios; 7 8 #include 9 using std::ofstream; 10 11 #include 12 using std::exit; // exit function prototype 13 14 #include "ClientData.h" // ClientData class definition 15 16 int main() 17 { 18 ofstream outCredit( "credit.dat", ios::binary ); 19 20 // exit program if ofstream could not open file 21 if ( !outCredit ) 22 { 23 cerr << "File could not be opened." << endl; 24 exit( 1 ); 25 } // end if 26 27 ClientData blankClient; // constructor zeros out each data member 28 29 // output 100 blank records to file 30 for ( int i = 0; i < 100; i++ ) 31 outCredit.write( reinterpret_cast< const char * >( &blankClient ), 32 sizeof( ClientData ) ); 33 34 return 0; 35 } // end main

Objects of class string do not have uniform size because they use dynamically allocated memory to accommodate strings of various lengths. This program must maintain fixedlength records, so class ClientData stores the client's first and last name in fixed-length char arrays. Member functions setLastName (Fig. 17.11, lines 3745) and setFirstName (Fig. 17.11, lines 5462) each copy the characters of a string object into the corresponding char array. Consider function setLastName. Line 40 initializes the const char * lastNameValue with the result of a call to string member function data, which returns an array containing the characters of the string. [Note: This array is not guaranteed to be null terminated.] Line 41 invokes string member function size to get the length of lastNameString. Line 42 ensures that length is fewer than 15 characters, then line 43 copies length characters from lastNameValue into the char array lastName. Member function setFirstName performs the same steps for the first name.


In Fig. 17.12, line 18 creates an ofstream object for the file credit.dat. The second argument to the constructorios::binaryindicates that we are opening the file for output in binary mode, which is required if we are to write fixed-length records. Lines 3132 cause the blankClient 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 on line 31 must be of type const char *. However, the data type of &blankClient is ClientData *. To convert &blankClient to const char *, line 31 uses the cast operator reinterpret_cast, so the call to write compiles without issuing a compilation error.

Категории