14.8 Creating a Random-Access File

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.

14.8.1 Writing Bytes with 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.

14.8.2 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 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.

Error-Prevention Tip 14.3

Unfortunately, it’s easy to use reinterpret_cast to perform dangerous manipulations that could lead to serious execution-time errors.

 

Portability Tip 14.1

reinterpret_cast is compiler dependent and can cause programs to behave differently on different platforms. Use this operator only if it’s absolutely necessary.

 

Portability Tip 14.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.

14.8.3 Credit-Processing Program

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.814.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.

Fig. 14.8 Class ClientData definition used in Fig. 14.10Fig. 14.13.

Alternate View

 1   // Fig. 14.8: ClientData.h
 2   // Class ClientData definition used in Fig. 14.10–Fig. 14.13.
 3   #ifndef CLIENTDATA_H
 4   #define CLIENTDATA_H
 5
 6   #include <string>
 7
 8   class ClientData {
 9   public:
10      // default ClientData constructor
11      ClientData(int = 0, const std::string& = "",
12         const std::string& = "", double = 0.0);
13
14      // accessor functions for accountNumber
15      void setAccountNumber(int);
16      int getAccountNumber() const;
17
18      // accessor functions for lastName
19      void setLastName(const std::string&);
20      std::string getLastName() const;
21
22      // accessor functions for firstName
23      void setFirstName(const std::string&);
24      std::string getFirstName() const;
25
26      // accessor functions for balance
27      void setBalance(double);
28      double getBalance() const;
29   private:
30      int accountNumber;
31      char lastName[15];
32      char firstName[10];
33      double balance;
34   };
35
36   #endif

Fig. 14.9 ClientData class stores a customer’s credit information.

Alternate View

 1   // Fig. 14.9: ClientData.cpp
 2   // Class ClientData stores customer's credit information.
 3   #include <string>
 4   #include "ClientData.h"
 5   using namespace std;
 6
 7   // default ClientData constructor
 8   ClientData::ClientData(int accountNumberValue, const string& lastName,
 9      const string& firstName, double balanceValue)
10      : accountNumber(accountNumberValue), balance(balanceValue) {
11      setLastName(lastName);
12      setFirstName(firstName);
13   }
14
15   // get account-number value
16   int ClientData::getAccountNumber() const {return accountNumber;}
17
18   // set account-number value
19   void ClientData::setAccountNumber(int accountNumberValue) {
20      accountNumber = accountNumberValue; // should validate
21   }
22
23   // get last-name value
24   string ClientData::getLastName() const {return lastName;}
25
26   // set last-name value
27   void ClientData::setLastName(const string& lastNameString) {
28      // copy at most 15 characters from string to lastName
29      size_t length{lastNameString.size()};
30      length = (length < 15 ? length : 14);
31      lastNameString.copy(lastName, length);
32      lastName[length] = ''; // append null character to lastName
33   }
34
35   // get first-name value
36   string ClientData::getFirstName() const {return firstName;}
37
38   // set first-name value
39   void ClientData::setFirstName(const string& firstNameString) {
40      // copy at most 10 characters from string to firstName
41      size_t length{firstNameString.size()};
42      length = (length < 10 ? length : 9);
43      firstNameString.copy(firstName, length);
44      firstName[length] = ''; // append null character to firstName
45   }
46
47   // get balance value
48   double ClientData::getBalance() const {return balance;}
49
50   // set balance value
51   void ClientData::setBalance(double balanceValue) {balance = balanceValue;}

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.

14.8.4 Opening a File for Output in Binary Mode

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.

Fig. 14.10 Creating a random-access file with 100 blank records sequentially.

Alternate View

 1   // Fig. 14.10: Fig14_10.cpp
 2   // Creating a randomly accessed file.
 3   #include <iostream>
 4   #include <fstream>
 5   #include <cstdlib>
 6   #include "ClientData.h" // ClientData class definition
 7   using namespace std;
 8
 9   int main() {
10      ofstream outCredit{"credit.dat", ios::out | ios::binary};
11
12      // exit program if ofstream could not open file
13      if (!outCredit) {
14         cerr << "File could not be opened." << endl;
15         exit(EXIT_FAILURE);
16      }
17
18      ClientData blankClient; // constructor zeros out each data member
19
20      // output 100 blank records to file
21      for (int i{0}; i < 100; ++i) {
22         outCredit.write(                                                    
23            reinterpret_cast<const char*>(&blankClient), sizeof(ClientData));
24      }
25   }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset