Wednesday, October 28, 2009

17.8. Creating a Random-Access File



17.8. 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 the 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 four-byte 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. 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.


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.10–17.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.












Fig. 17.10. ClientData class header
file.


 

 1   // Fig. 17.10: ClientData.h
2 // Class ClientData definition used in Fig. 17.12–Fig. 17.15.
3 #ifndef CLIENTDATA_H
4 #define CLIENTDATA_H
5
6 #include <string>
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













Fig. 17.11. ClientData class represents a
customer's credit information.


 

 1   // Fig. 17.11: ClientData.cpp
2 // Class ClientData stores customer's credit information.
3 #include <string>
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 ] = '\0'; // 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 ] = '\0'; // 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




Objects of class string
do not have uniform size, rather they use dynamically allocated memory to
accommodate strings of various lengths. This program must maintain fixed-length
records, so class ClientData stores the client's first and last name in
fixed-length char arrays. Member functions setLastName (Fig. 17.11, lines
37–45) and setFirstName (Fig. 17.11, lines 54–62) 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 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. Lines 31–32
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 at 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.











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


 

 1   // Fig. 17.12: Fig17_12.cpp
2 // Creating a randomly accessed file.
3 #include <iostream>
4 using std::cerr;
5 using std::endl;
6 using std::ios;
7
8 #include <fstream>
9 using std::ofstream;
10
11 #include <cstdlib>
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::out | 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






 


No comments:

Post a Comment