Reading from a Random-Access File Sequentially
In the previous sections, we created a random-access file and wrote data to that file. In this section, we develop a program that reads the file sequentially and prints only those records that contain data. These programs produce an additional benefit. See if you can determine what it is; we will reveal it at the end of this section.
The istream function read inputs a specified number of bytes from the current position in the specified stream into an object. For example, lines 5758 from Fig. 17.14 read the number of bytes specified by sizeof( ClientData ) from the file associated with ifstream object inCredit and store the data in the client record. Note that function read requires a first argument of type char *. Since &client is of type ClientData *, &client must be cast to char * using the cast operator reinterpret_cast. Note that line 24 includes the header file clientData.h defined in Fig. 17.10, so the program can use ClientData objects.
Figure 17.14. Reading a random-access file sequentially.
(This item is displayed on pages 865 - 866 in the print version)
1 // Fig. 17.14: Fig17_14.cpp 2 // Reading a random access file sequentially. 3 #include 4 using std::cerr; 5 using std::cout; 6 using std::endl; 7 using std::fixed; 8 using std::ios; 9 using std::left; 10 using std::right; 11 using std::showpoint; 12 13 #include 14 using std::setprecision; 15 using std::setw; 16 17 #include 18 using std::ifstream; 19 using std::ostream; 20 21 #include 22 using std::exit; // exit function prototype 23 24 #include "ClientData.h" // ClientData class definition 25 26 void outputLine( ostream&, const ClientData & ); // prototype 27 28 int main() 29 { 30 ifstream inCredit( "credit.dat", ios::in ); 31 32 // exit program if ifstream cannot open file 33 if ( !inCredit ) 34 { 35 cerr << "File could not be opened." << endl; 36 exit( 1 ); 37 } // end if 38 39 cout << left << setw( 10 ) << "Account" << setw( 16 ) 40 << "Last Name" << setw( 11 ) << "First Name" << left 41 << setw( 10 ) << right << "Balance" << endl; 42 43 ClientData client; // create record 44 45 // read first record from file 46 inCredit.read( reinterpret_cast< char * >( &client ), 47 sizeof( ClientData ) ); 48 49 // read all records from file 50 while ( inCredit && !inCredit.eof() ) 51 { 52 // display record 53 if ( client.getAccountNumber() != 0 ) 54 outputLine( cout, client ); 55 56 // read next from file 57 inCredit.read( reinterpret_cast< char * >( &client ), 58 sizeof( ClientData ) ); 59 } // end while 60 61 return 0; 62 } // end main 63 64 // display single record 65 void outputLine( ostream &output, const ClientData &record ) 66 { 67 output << left << setw( 10 ) << record.getAccountNumber() 68 << setw( 16 ) << record.getLastName() 69 << setw( 11 ) << record.getFirstName() 70 << setw( 10 ) << setprecision( 2 ) << right << fixed 71 << showpoint << record.getBalance() << endl; 72 } // end function outputLine
|
Figure 17.14 reads every record in the credit.dat file sequentially, checks each record to determine whether it contains data, and displays formatted outputs for records containing data. The condition in line 50 uses the ios member function eof to determine when the end of file is reached and causes execution of the while statement to terminate. Also, if an error occurs when reading from the file, the loop terminates, because inCredit evaluates to false. The data input from the file is output by function outputLine (lines 6572), which takes two argumentsan ostream object and a clientData structure to be output. The ostream parameter type is interesting, because any ostream object (such as cout) or any object of a derived class of ostream (such as an object of type ofstream) can be supplied as the argument. This means that the same function can be used, for example, to perform output to the standard-output stream and to a file stream without writing separate functions.
What about that additional benefit we promised? If you examine the output window, you will notice that the records are listed in sorted order (by account number). This is a consequence of how we stored these records in the file, using direct-access techniques. Compared to the insertion sort we used in Chapter 7, sorting using direct-access techniques is relatively fast. The speed is achieved by making the file large enough to hold every possible record that might be created. This, of course, means that the file could be occupied sparsely most of the time, resulting in a waste of storage. This is another example of the space-time trade-off: By using large amounts of space, we are able to develop a much faster sorting algorithm. Fortunately, the continuous reduction in price of storage units has made this less of an issue.