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


How It Works

In the sample code, the error handling decisions are nicely isolated from the rest of the program. The function receives the ostream object after the extraction operation has completed (or been attempted, at least), and can then query the object for its status, handle any predicted errors, and decide whether the loop should continue based on its findings.

In general, file streams differ from other streams in that they have an underlying buffer class that holds file-related things such as the file descriptor. This class is called filebuf, and is returned by the file stream’s member function rdbuf(). This is important for getting further error information from the operating system.

Some systems, such as Win32, might also have some information available though a function that stores the last error encountered. In the case of Win32 specifically, the GetLastError() function can be useful.

Comments

The C++ streams classes only perform minimal error handling for the programs that use them. This is simply because handling specific errors in a portable and operating system–independent way is difficult to do well, and the programmer (you) looking for the error knows best the types of error to expect.

To know more about ways to handle specific errors, consult your operating system or compiler documentation.

15.4 Read and write binary files?

Problem

The human representations of data in a computer are essentially text. However, this file format is not the most efficient. An eight-digit number will take 8 bytes, plus delimiting spaces, when stored as text, whereas the binary representation is only 4 bytes, with no delimiter required.

Accuracy is also a concern. Floating-point numbers run the risk of losing accuracy every time they are converted to text and back. The default precision of streams output for floating-point numbers is 6, but double and long double precision floating-point numbers have a much higher degree of accuracy. Again, space is also a concern (standard floats in binary form take 4 bytes, doubles take 8, and long doubles take 10), but it is accuracy that’s the bigger concern.

The final concern is performance. Converting text to and from formats that a computer can use for calculations is time-consuming, and this can significantly affect program performance when dealing with large amounts of data. Binary access bypasses all of this, so that in-memory structures can be filled from disk files with a minimum of processing.

Technique

To open a file for writing, open an ofstream like this:

ofstream myoutfile( “output.dat”, ios::out | ios::binary ) ;

The key here is the ios::binary open mode. Similarly, to read a binary file, an ifstream will work with the correct modifier:

ifstream myinfile( “input.dat”, ios::in | ios::binary ) ;

Opening the files, however, is the easiest part. At issue is the core design philosophy of the IOStreams library. Its insertion and extraction operators are entirely designed to convert data to and from human representations to something that can be used in the program. Suppose you have the ofstream object open, and your code does the following:

int myInt = 234, myOtherInt = 556 ;

myoutfile << myInt << myOtherInt ;

Both numbers would still be written to the file as text, and it would look like the following if you looked at the contents with the DOS type command or the UNIX cat command (depending on your system).

C:\TEMP> type output.dat
234556
C:\TEMP>

If you were to attempt to read the numbers back, you would get only one number, and that would be 234556. The way to deal with this is to cast the data you want to write to the file to the most fundamental of all data types, the char. A char is essentially a byte on the machine, and that is what to write to the file.

The read and write member functions (which the insertion and extraction operators ultimately call) are the easiest way to write simple data types in a binary format. Overloading the insertion and extraction operators is a technique better suited to classes.

The prototypes of the read and write methods of the streams classes are as follows:

istream::read( const char* data, int size ) ;

ostream::write( const char* data, int size ) ;

where data is a pointer to the data you want to write, and size is the size of the data. The sizeof() operator would be used to get the size.

The revised example of writing to the binary file (assuming the stream is open, and so on) is

int myInt = 234, myOtherInt = 556 ;

myoutfile.write( (char*) &myInt, sizeof(int) ) ;
myoutfile.write( (char*) &myOtherInt, sizeof(int) ) ;

To read the data back (again, assuming the stream is open, and so on):

int myInt, myOtherInt ;

myinfile.read( (char*) &myInt, sizeof(int) ) ;
myinfile.read( (char*) &myOtherInt, sizeof(int) ) ;

The key things here are that the address of the variable is cast to a char*, and that the size of the data type is the second argument. Some programmers prefer to use the actual variable name rather than the type. Beware of dealing with strings (that is, null-terminated character arrays) or other arrays in this manner. sizeof( myArray ) would be 4 on most systems, regardless of the actual size of the array because myArray is just a pointer.

Binary I/O of classes, structs, and other types work exactly the same way.


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.