Click Here!
home account info subscribe login search FAQ/help site map contact us


 
Brief Full
 Advanced
      Search
 Search Tips
[an error occurred while processing this directive]
Previous Table of Contents Next


Steps

The sample code is shared between this How-To and How-To 15.5. Note that these techniques cannot just be applied to an existing program. The choice of how to store data is so fundamental to how a program operates that this decision must be made very early in the design process.

1.  Take stock of the stream options available, and open the file stream in binary mode:
inline
PhoneDatabase::PhoneDatabase( const char* filename )
    : Datafile( filename, ios::in | ios::out | ios::binary |
		      ⇒ ios::app),
   CurrentRecord(0), current(-1)
{
}

The preceding options state to open the file for reading and writing and not to erase the contents. Each operation after the file is opened will seek to the position it needs, so starting at the end of the file is acceptable. It is possible to add a Datafile.seekg(0) into the constructor, but the downside is that the code will have to seek to the end again to add new records.
2.  For most applications, it is best to isolate complicated I/O operations to a few functions per class. This keeps the code in one place, where it is easier to maintain and troubleshoot. In the sample code, isolate the operations AddRecord and GetRecord. The following is The implementation of the AddRecord, GetRecord, and UpdateRecord functions in phone.cpp:
void PhoneDatabase::GetRecord( PhoneRecord& record, int recnum )
{
   if( recnum != current )
    {
       Datafile.seekg( (recnum * sizeof(PhoneRecord)) ) ;
	CurrentRecord = ++recnum ;
   }
   else
       CurrentRecord++ ;

   Datafile.read( (char*) &record, sizeof(PhoneRecord)) ;
}

void PhoneDatabase::UpdateRecord( const PhoneRecord& record,
				             ⇒int recnum )
{
   if( recnum != current )
   {
	Datafile.seekp( (recnum * sizeof(PhoneRecord)) ) ;
	 CurrentRecord = recnum++ ;
   }
   else
     CurrentRecord++ ;

   Datafile.write( (char*) &record, sizeof(PhoneRecord)) ;
}

void PhoneDatabase::AddRecord( const PhoneRecord& record )
{
    Datafile.seekp( 0, ios::end ) ;
   CurrentRecord = Datafile.tellp() / sizeof(PhoneRecord) + 1 ;
   Datafile.write( (char*) &record, sizeof(PhoneRecord) ) ;
}

In the preceding code, the class keeps track of the record to which the database is currently pointing. This will be used to facilitate sequential processing (using the overloaded >> and << operators).
3.  Build surrounding I/O functions as you see fit. In the case of the example, the class managing the database was given stream-like operators for linear processing of records. The following code shows higher-level I/O operations for the data in phone.cpp:
// Overloaded operators for sequential reading

 PhoneDatabase& operator<<( PhoneDatabase& strm,
			  ⇒const PhoneRecord& data )
 {
     strm.AddRecord( data ) ;
   return strm ;
 }

 PhoneDatabase& operator>>( PhoneDatabase& strm,
			    Ⅾ PhoneRecord& data )
 {
       strm.GetRecord( data, strm.current );
       return strm ;
 }

These functions are designed to behave identically to the functions used in regular stream processing from the user’s point of view. Other functions were implemented to facilitate importing data from a text file and printing data to the screen. Because these are based on true streams, they deal with the text mode istream and ostream objects. These functions are there to be used in importing and exporting data from text. Note in the following code the extra processing required to work with the same data in text mode.
// overloaded operators for the record structure

inline
ostream& operator<< (ostream& strm, const PhoneRecord& data )
{
    strm         << data.FirstName << “ “
	       << data.LastName << “ “
	    << data.AreaCode << “ “
	    << data.PhoneNumber << “ “;
   if( !strlen( data.Extension ) )
       strm  << “0 “;
   else
      strm  << data.Extension << “ “ ;

   return strm;
}

inline
istream& operator>> (istream& strm, PhoneRecord& data )
{
    strm         >> data.FirstName
	    >> data.LastName
	    >> data.AreaCode
	    >> data.PhoneNumber
	    >> data.Extension ;
   if( !strcmp( “0”, data.Extension ) )
	   data.Extension[0] = ‘\0’ ;

   return strm ;
}

These functions do more processing than their binary counterparts, and are slower to execute.
4.  Implement error checking. There should be some way for the program using your class to see whether there has been a problem, and to deal with it. Functions to overload the typecasting of the managing class to a boolean type can make possible syntax such as while( db >> records ) for use in processing data. Alternately, your class can internalize error handling completely. This can be difficult because the class created could be used in ways that were not initially accounted for in the design. The following code shows the error reporting and handling functions for the PhoneDatabase class in phone.cpp:
bool operator!() ;
   bool bad() { return Datafile.bad() ; }
   bool good() { return Datafile.good() ; }
   bool fail() { return Datafile.fail() ; }
   void clear() { Datafile.clear() ; }

   // overloaded typecasts to allow while( dbname >> record )
   // style code
   operator bool() { return Datafile.good(); }
...
inline
bool PhoneDatabase::operator!()
{
    if( !Datafile ) return false ;
   return true ;
}

Most of the functions query the fstream object that the object contains. The idea was to emulate the error functions of a true stream class. The overloaded typecast operator is very useful for loops (as indicated by the comments).


Previous Table of Contents Next


Products |  Contact Us |  About Us |  Privacy  |  Ad Info  |  Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-1999 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permision of EarthWeb is prohibited.