    <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:admin="http://webns.net/mvcb/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:content="http://purl.org/rss/1.0/modules/content/">
     <channel>
        <title>ACCU  :: C++ Streams (Part 2)</title>
        <link>https://members.accu.org/index.php/articles/1352</link>
        <description>Professionalism in Programming</description>
        <dc:language>en-us</dc:language> 
        <dc:creator>Administrator</dc:creator> 
        <admin:generatorAgent rdf:resource="http://www.xaraya.org" /> 
        <admin:errorReportsTo rdf:resource="mailto:webeditor@accu.org" />
       <sy:updatePeriod>hourly</sy:updatePeriod>
       <sy:updateFrequency>1</sy:updateFrequency>
       <docs>http://backend.userland.com/rss</docs>




<div class="xar-mod-head"><span class="xar-mod-title">Programming Topics + Overload Journal #2 - Jun 1993</span></div>

<table border="0" cellpadding="1" cellspacing="0">
    <tbody>
    <tr>
        <td valign="top">
            Browse in :
       </td>
       <td valign="top">

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c13/">Topics</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c65/">Programming</a>
<br />

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c76/">Journals</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c78/">Overload</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c222/">02</a>
<br />

                                            <a href="https://members.accu.org/index.php/articles/c65-222/">Any of these categories</a>

                    -                        <a href="https://members.accu.org/index.php/articles/c65+222/">All of these categories</a>
<br />
</td>
   </tr>
   </tbody>
</table>




<div class="xar-error">
   <p>
 <strong>Note:</strong> when you create a new publication type,
the articles module will automatically use the templates
<em>user-display-[publicationtype].xt</em>
and <em>user-summary-[publicationtype].xt</em>.
If those templates do not exist when you try to preview or display a new article,
you'll get this warning :-)  Please place your own templates in themes/<em>yourtheme</em>/modules/articles . The templates will get the extension .xt there. </p>
</div>
<div class="xar-norm xar-standard-box-padding">
   <h1><strong>Title:</strong>&nbsp;C++ Streams (Part 2)</h1>
<p><strong>Author:</strong>&nbsp;</p>
<p>
<strong>Date:</strong> 30 June 1993 11:59:00 +01:00 or Wed, 30 June 1993 11:59:00 +01:00</p>
<p><strong>Summary:</strong>&nbsp;</p>
<p><strong>Body:</strong>&nbsp;<p>Welcome to the second part of Streams. In this part I am going to
demonstrate some of the various methods of accessing files using
streams. Where possible I will try and use an OO approach, but often a
C-like approach has to be used.</p>
<p>The basic classes for file access are ifstream and ofstream (Input
File STREAM and Output File STREAM). For the purposes of this article,
I will be splitting file access into two categories, text and binary.</p>
<h2>TEXT FILES</h2>
<p>Text files are the simplest way to hold information on disk. A text
file (at its simplest) consists of a series of ASCII characters,
separated into lines by the end-of-line (EOL) (also called new-line)
character.</p>
<h2>READING A TEXT FILE LINE-BY-LINE</h2>
<p>In this first example, I will demonstrate how to read a simple text
file and display its contents on the screen.</p>
<pre class="programlisting">//<br>//&nbsp; LISTERl.CPP<br>//<br>//&nbsp; Run as LISTER1 &lt;FileToBeListed&gt;<br>//<br>#include &lt;iostream.h&gt;<br>#include &lt;fstream.h&gt;<br><br>void main(int argc, char *argv[])<br>{ <br>  char read_buffer[ 132];<br><br>  if (argc != 2)<br>  { <br>    cerr &lt;&lt; &quot;ERROR&gt; One filename must be supplied&quot;<br>         &lt;&lt; endl; <br>    cerr &lt;&lt; &quot;E.G. lister myfile.txt&quot;<br>         &lt;&lt; endl; return;<br>  }<br><br>  ifstream text_file(argv[l]); <br>  if(!tcxt_file)<br>    cout &lt;&lt; &quot;Problem opening text file &quot;<br>         &lt;&lt; argv[l] &lt;&lt; endl; <br>  else<br>  {<br>    while (text_file)<br>    {<br>      text_file.getline(read_buffer,sizeof(read buffer)); <br>      cout &lt;&lt; read_buffer &lt;&lt; endl; <br>    } <br>  } <br>}</pre>
<p>The ifstream object text_file opens the file entered as the first
parameter on the command line during its construction (and closes it
automatically during destruction). As the constructor is not permitted
to return a value of any kind, the status of the open must be tested by
another means.</p>
<h2>TESTING A STREAM FOR ERRORS</h2>
<p>This can be achieved in two ways. The first is to use one of the
functions that have a Boolean return value, these are good(), bad() and
eof() used as follows:</p>
<pre class="programlisting">if (text_file.good())  // i/o operation was successful <br>if (text_file.bad())&nbsp;&nbsp; // i/o operation failed <br>if (text_file.eof())&nbsp;&nbsp; // end-of-file encountered</pre>
<p>The other is to use the overloaded ! and void* operators in the
following manner:</p>
<pre class="programlisting">if (text_file )&nbsp;&nbsp;&nbsp;&nbsp; // I/O operation was successful (void*) <br>if ( !text_file )&nbsp;  // I/O operation failed</pre>
<h2>OBTAINING MORE DETAIL ABOUT A STREAM ERROR</h2>
<p>The cause of the error can be investigated by a call to a member
function of ios called rdstate(), which will return an error value.
These error flags take the form of bits set in the return value and can
be tested by bitwise anding with one of the three ios flags as follows:</p>
<pre class="programlisting">if (text_file.rdstate() &amp; ios::eofbit)<br>    // end of file occurred <br>if (text_file.rdstate() &amp; ios::failbit)<br>    // last operation failed. <br>if (text_file.rdstate() &amp; ios::badbit)<br>    // invalid operation.</pre>
<p>Borland's stream library has one extra flag that is non standard:</p>
<pre class="programlisting">if (text_file.rdstate() &amp; ios::hardfail) <br>    // unrecoverable error</pre>
<h2>MORE TEXT READING</h2>
<p>In the last example, the reading of the file is performed by the
getline() member function. This extracts the characters up to and
including the EOL delimiter, and places them, (excluding the EOL) into
the read buffer. This is not the only way to read in a text file, there
are a whole family of 'get' member functions that can be used to
extract characters, words, strings or streambufs from a file stream.
Also the EOL delimiter can be changed when required to use a different
character to mark the end of line.</p>
<p>If a file contains a series of character strings terminated with the
$ symbol it could be safely read by means of the line:</p>
<pre class="programlisting">text_file.getline(read_buffer, sizeof(buffer), '$');</pre>
<p>The dollar symbol will not appear in the read_buffer.</p>
<h2>READING TEXT FILES VIA EXTRACTOR OPERATOR</h2>
<p>Using the getline() method of reading a file is no better than
reading it in C. An ifstream object is an input stream in the same
manner as cin, and can be used to read in formatted information, as in
the following program.</p>
<pre class="programlisting">//<br>//&nbsp;&nbsp; NAMELIST.CPP<br>//<br>//&nbsp;&nbsp; Run as NAMELIST names.lis<br>//<br>#include &lt;iostream.h&gt;<br>#include &lt;fstream.h&gt;<br><br>void main(int argc, char *argv[]) <br>{<br>  char surname[15]; <br>  char first_name[15]; <br>  char middle_initial; <br>  int age; <br>  if (argc != 2)<br>  {<br>    cerr &lt;&lt; &quot;ERROR&gt; One filename must be supplied&quot; <br>         &lt;&lt; endl;<br>    cerr &lt;&lt; &quot;E.G. lister myfile.txt&quot; &lt;&lt; endl;<br>    return; <br>  }<br><br>  ifstream text_file(argv[l]); <br>  if(!text_file)<br>    cout &lt;&lt; &quot;Problem opening text file &quot;<br>         &lt;&lt; argv[l] &lt;&lt; endl; <br>  else<br>  { <br>    while (text_file)<br>    { <br>      text_file &gt;&gt; surname &gt;&gt; first_name<br>                &gt;&gt; middle_initial &gt;&gt; age; <br>      if(text_file) <br>      {<br>        cout &lt;&lt; first_name &lt;&lt; &quot; &quot; <br>             &lt;&lt; middle_initial &lt;&lt; &quot; &quot; <br>             &lt;&lt; surname<br>             &lt;&lt; &quot; aged &quot; &lt;&lt; age &lt;&lt; &quot; years old&quot; <br>             &lt;&lt; endl; <br>      } <br>    } <br>  } <br>}</pre>
<p>There are several things to note in this program, the major one
being that you must test the result of the stream before using it
(something I often forget to do!). Also note that the names,
space-separated in the original file, appear as individual
null-terminated strings when read into data items. If one of the names
does not have a middle initial the program will not work correctly.</p>
<p>This leads to the problem of how best to read in string items, as
each space-separated group of characters will be broken into individual
strings. One method is to have a class String which will read a quoted
string from a stream, only terminating on the second quote character.
Another would to use null terminated strings in files.</p>
<p>Another way to tackle this problem (untidy, but it works) is that
until the next item after the char* is accepted, the value will get
appended to the current char*. This method is not capable of
distinguishing two strings from one another and is not a good
general-purpose solution. </p>
<pre class="programlisting">//<br>//&nbsp;&nbsp; NAMELIS2.CPP (EXTRACT OF)<br>//<br>//&nbsp;&nbsp;&nbsp; Run as NAMELIS2 names.lis<br>//<br>char name[40];<br>char temp[40];<br>int age;<br><br>text_file &gt;&gt; name; <br>if (text_file) <br>  do<br>  {<br>    text_file &gt;&gt; age; <br>    if (text_file.rdstate() &amp; ios::failbit)<br>    {<br>      // clear the ios::failbit not changing other flags <br>      text_file.clear(text_file.rdstate() &amp; ~ios::failbit); <br>      text_file &gt;&gt; temp; <br>      strcat (name, &quot;&quot;); <br>      strcat (name, temp);<br>    } <br>    else<br>      break; <br>  } while (1);<br></pre>
<p>The other solution, relying on quoted strings, is achieved as follows:</p>
<pre class="programlisting">class read_string<br>{<br>protected: <br>  char* ps;<br>public:<br>  read_string(char* pps) { ps = pps; *ps = '\0'; }<br>  friend istream&amp; operator &gt;&gt; (istream&amp;, read_string&amp;);<br>};<br><br>istream&amp; operator &gt;&gt; (istream&amp; is, read_string&amp; r)<br>{<br>  char charread; <br>  if ((charread=is.peek()) == '\n')<br>    charread = is.get(); // discard any surplus linefeed <br>  if ((charread=is.peek()) != '\&quot;') // reject if not quote<br>  {<br>    // set failbit not able to read string <br>    is.clear(ios::failbit);<br>  }<br>  else<br>  {<br>    charread = is.get(); // discard first quote character<br>    charread = is.get();<br>    while (charread != '\&quot;' &amp;&amp; is)<br>    {<br>      *r.ps++ = charread; <br>      charread = is.get();<br>    } <br>    *r.ps=0;<br>  }<br>  return is; <br>}</pre>
<p>The code is now a lot cleaner as the quoted string can be read
directly into name via name_string object:</p>
<pre class="programlisting">char name[40]; <br>while (text_file)<br>{<br>  read_string name_string(name); <br>  text_file &gt;&gt; name_string; <br>  if(text_file)<br>    text_file &gt;&gt; age; <br>  if(text_file)<br>  { <br>    cout &lt;&lt; name<br>         &lt;&lt;&nbsp; &quot; aged &quot; &lt;&lt; age <br>         &lt;&lt; &quot; years old&quot; &lt;&lt; endl; <br>  } <br>}<br></pre>
<p>The final solution, relying on null terminated strings is achieved
as follows:</p>
<pre class="programlisting">class read_string<br>{<br>protected: <br>  char* ps;<br>public:<br>  read_string(char* pps) { ps = pps; *ps = '\0'; }<br>  friend istream&amp; operator &gt;&gt; (istream&amp;, read_string&amp;);<br>};<br><br>istream&amp; operator &gt;&gt; (istream&amp; is, read_string&amp; r)<br>{<br>  char charread;<br>  if ((charread=is.peek()) == '\n')<br>    charread = is.get(); // discard any surplus linefeed <br>  charread = is.get(); <br>  while (charread &amp;&amp; is)<br>  {<br>    *r.ps++ = charread; <br>    charread = is.get();<br>  }<br>  *r.ps=0; <br>  return is;<br>}</pre>
<p>The usage of this type of string will be the same as for the quoted
string.</p>
<h2>WRITING TO TEXT FILES</h2>
<p>Writing to text files is as simple as reading from them. One
diminutive complication is that the output file must be opened in the
correct manner. The default opening method of a file for output is such
that it truncates any existing file of the same name.</p>
<pre class="programlisting">ofstream output_file(&quot;writer.dat&quot;);</pre>
<p>which is the equivalent of:</p>
<pre class="programlisting">ofstream output_file(&quot;writer.dat&quot;, ios::trunc);</pre>
<p>In order to append to an existing file, an additional parameter must
be passed:</p>
<pre class="programlisting">ofstream append_file(&quot;exists.dat&quot;, ios::app);</pre>
<p>It is also possible to prevent automatic creation of a file that
does not already exist:</p>
<pre class="programlisting">ofstream old_file(&quot;old.dat&quot;, ios::nocreate);</pre>
<p>and to ensure the use of a file only if it already exists:</p>
<pre class="programlisting">ofstream new_file(&quot;new.dat&quot;, ios::noreplace);</pre>
<p>The writing of the data is easy, as the following program to copy a
series of files from the command line into a target file demonstrates.
The syntax of the command is:</p>
<pre class="programlisting">insert &lt;fl&gt; [&lt;f2&gt; ...] into &lt;f3&gt;</pre>
<p>The full program can be found on the companion disk (called
insert.cpp). The essence of the writing operation is the same as for
cout where the line:</p>
<pre class="programlisting">target_file &lt;&lt; buffer &lt;&lt; endl;</pre>
<p>writes each line in turn to the file indicated by &lt;f3&gt;.</p>
<h2>FORMATTED WRITING TO TEXT FILES</h2>
<p>As with reading files in, writing out can be performed on a series
of data types strung together as in the following example:</p>
<pre class="programlisting">int a = 5; <br>float b = 7.5F; <br>char c[] = &quot;Hello World&quot;; <br>ofstream data_file(&quot;data.dat&quot;); <br>int i = 5; <br>while (i--) <br>  datafile &lt;&lt; a++ &lt;&lt; b++ &lt;&lt; c &lt;&lt; endl;</pre>
<p>will produce the output in data.dat of:</p>
<pre class="programlisting">57.5Hello World 68.5Hello World etc.</pre>
<p>If we intend to read the strings back in, it would be useful to
space-separate them:</p>
<pre class="programlisting">char ds = ' '; // data separator<br>data_file a++ &lt;&lt; ds &lt;&lt; b++ &lt;&lt; ds &lt;&lt; c &lt;&lt; endl;</pre>
<h2>WRITING STRUCTS TO TEXT FILES</h2>
<p>When dealing with structs, it is possible to define friend overloads
of the extractor and insertor operators such that the struct is written
and read back in correctly. (Yes this can be done from within a struct
and not a class as in program TXTSTRUC.CPP on the companion disk). The
strings again pose a problem, but this can be negated to a small extent
by writing the string bounded by quotes as in the above example or by
using a custom delimiter when the string is written, thus enabling that
delimiter to be used to read the string back in. This can cause
problems if the overloaded inserter is subsequently used to output the
struct to the screen or printer.</p>
<p>A better way of writing and reading structs is to use a binary
format. This will be dealt with later.</p>
<h2>MOVING THE FILE POINTER IN A STREAM</h2>
<p>Before moving off text based I/O, I must mention that it is possible
to move around a text stream in a non&shy;sequential manner, though
this is more often of use in a binary file. In the same way that a C
program would use fseek to reposition a file pointer within a file,
there are a pair of member functions available to derivatives of
istream both called seekg (for seek get). These can be used in the
following manner:</p>
<pre class="programlisting">Type 1:<br>  // go 200 bytes into the file <br>  stream text_file.seekg(200); <br>  // goto start of memory string <br>  memstring.seekg(0);<br><br>Type 2:<br>  // goto 2nd record<br>  text_file.seekg(sizeof(rec), ios::beg); <br>  // goto next record<br>  text_file.seekg(sizeof(rec), ios::cur); <br>  // goto prev record <br>  text_file.seekg(-sizeof(rec),ios::cur);<br>  // goto last record <br>  text_file.seekg(-sizeof(rec),ios::end);</pre>
<p>The type 1 uses the beginning of the stream as its start point, and
the long parameter supplied is an offset from the beginning of the
stream. The type 2 uses the second parameter to determine the start
point (beginning, current or end of file), and the first parameter to
determine the offset from that position. Note that this time the first
parameter can be negative to indicate that the jump is prior to the
position indicated.</p>
<p>The member function tellg(void) can be used to determine the current
position in an input stream (in bytes from the start of the stream)&quot;.</p>
<p>There are equivalent member functions for streams derived from
ostream, and they are called seekp (for seek put) and tellp().</p>
<h2>PRINTER OUTPUT</h2>
<p>As hinted at previously, output to a printer is the same as for any
other stream, by use of the command:</p>
<pre class="programlisting">ofstream printer(&quot;lpt1:&quot;); if (printer)<br>{ <br>  printer &lt;&lt; &quot;Hello printer&quot; &lt;&lt; endl;<br>}</pre>
<h2>BINARY FILES</h2>
<p>As mentioned earlier, binary files are better for storing and
retrieving structs. In this example only the essential information is
highlighted, as the majority of the processing is the same as previous
examples. The complete program BINSTRUC.CPP can be found on the
companion disk.</p>
<p>Files opened in binary mode require an additional parameter:</p>
<pre class="programlisting">ifstream infile (&quot;fred.bin&quot;, ios::binary); 
ofstream outfile (&quot;fred.bin&quot;, ios::binary);</pre>
<p>Data is written by means of the write member function</p>
<pre class="programlisting">outfile.write((char*) &amp;mystruct, sizeof(mystruct));</pre>
<p>and read back in by means of the read member function</p>
<pre class="programlisting">infile.read((char*) &amp;mystruct, sizeof(mystruct));</pre>
<p>Tacky isn't it? This method of reading and writing is no better than
using C. Where a file is required for both input and output, the stream
object can be of type fstream, which is the equivalent of a
hypothetical iofstream.</p>
<h2>SIMPLE PERSISTENCE</h2>
<p>In order to make it appear more acceptable we really need to make
our objects (or structs) persistent. In the case of non-derived objects
this is straightforward to accomplish. To save hierarchical objects is
not difficult, but reading them back in can present a few problems, so
I will save this subject for another day.</p>
<p>Again the means to accomplish the task of adding simple persistence
to objects is by means of overloading the inserter and extractor
operators.</p>
<p>The following program is a simple editor for a list of aircraft
specifications , which utilise the overloading techniques. It is not a
bullet-proof implementation, but merely demonstrates how simple
persistence can be achieved.</p>
<pre class="programlisting">//<br>//&nbsp;&nbsp; SPERSIST.CPP<br>//<br>//&nbsp;&nbsp; Run as SPERSIST<br>//<br>#include &lt;iostream.h&gt;<br>#include &lt;fstream.h&gt;<br>#include &lt;string.h&gt;<br><br>class aircraft<br>{ <br>protected:<br>  char maker[20];<br>  char name[20];<br>  int max_speed;<br>  long max_altitude;<br>  int max_range; <br>public:<br>  aircraft(void);<br>  aircraft(char* m, char* n, int s, long a, int r);<br>  void display_data(void);<br>  void get_data(void);<br>  size_t size(void)<br>  {<br>   &nbsp;return sizeof(maker)<br>                 +sizeof(name) <br>                 +sizeof(max_speed)<br>                &nbsp;+sizeof(max_altitude) <br>                 +sizeof(max_range);<br>  }<br>  friend fstream&amp; operator &lt;&lt; (fstream&amp; , aircraft&amp;);<br>  friend fstream&amp; operator &gt;&gt; (fstream&amp; , aircraft&amp;);<br>};<br><br>aircraft::aircraft(void)<br>{<br>  memset(maker, 0, sizeof(maker));<br>  memset(name, 0, sizeof(name));<br>  max_speed = 0;<br>  max_altitude = 0L;<br>  max_range = 0; <br>}<br><br>aircraft::aircraft(char* m, char* n, int s, long a, int r)<br>{<br>  memset(maker, 0, sizeof(maker));<br>  memset(name, 0, sizeof(name));<br>  strcpy( maker, m);<br>  strcpy(name, n);<br>  max_speed = s;<br>  max_altitude = a;<br>  max_range = r; <br>}<br><br>void aircraft::display_data(void)<br>{ <br>  cout &lt;&lt; endl<br>       &lt;&lt; &quot;NAME: &quot; &lt;&lt;&nbsp; maker &lt;&lt; &quot; &quot;<br>       &lt;&lt; name &lt;&lt; endl<br>       &lt;&lt; &quot;SPEED:&quot; &lt;&lt; max_speed &lt;&lt; endl<br>       &lt;&lt; &quot;ALTITUDE: &quot; &lt;&lt; max_altitude &lt;&lt; endl<br>       &lt;&lt; &quot;RANGE: &quot; &lt;&lt; max_range &lt;&lt; endl;<br>}<br><br>void aircraft::get_data(void)<br>{ <br>  cout &lt;&lt; endl<br>       &lt;&lt; &quot;CURRENT NAME:&quot; &lt;&lt; maker<br>       &lt;&lt; &quot; &quot; &lt;&lt; name &lt;&lt; endl<br>       &lt;&lt; &quot;Enter new Maker Name : &quot;; <br>  memset(maker, 0, sizeof(maker)); <br>  cin &gt;&gt; maker,<br>  cout &lt;&lt; &quot;Enter new Model Name : &quot;; <br>  memset(name, 0, sizeof(name)); <br>  cin &gt;&gt; name; <br>  cout &lt;&lt; &quot;Current maximum SPEED: &quot;<br>       &lt;&lt; max_speed &lt;&lt; endl<br>       &lt;&lt; &quot;Enter new maximum SPEED : &quot;; <br>  cin &gt;&gt; max_speed; <br>  cout &lt;&lt; &quot;Current maximum ALTITUDE:&quot;<br>       &lt;&lt; max_altitude &lt;&lt; endl<br>       &lt;&lt; &quot;Enter new maximum ALTITUDE : &quot;; <br>  cin &gt;&gt; max_altitude; <br>  cout &lt;&lt; &quot;Current maximum RANGE: &quot;<br>       &lt;&lt; max_range &lt;&lt; endl<br>       &lt;&lt; &quot;Enter new maximum RANGE : &quot;; <br>  cin &gt;&gt; max_range; <br>}<br><br>fstream&amp; operator&lt;&lt;(fstream&amp; ofs, aircraft&amp; a)<br>{<br>  ofs.write(a.maker, sizeof(a.maker)); <br>  ofs.write(a.name, sizeof(a.name)); <br>  ofs.write((unsigned char*) &amp;a.max_speed,<br>            sizeof(a.max_speed)); <br>  ofs.write((unsigned char*) &amp;a.max_altitude,<br>            sizeof(a.max_altitude)); <br>  ofs.write((unsigned char*) &amp;a.max_range,<br>            sizeof(a.max_range)); <br>  return ofs; <br>}<br><br>fstream&amp; operator&gt;&gt;(fstream&amp; ifs, aircraft&amp; a)<br>{<br>  ifs.read(a.maker, sizeof(a.maker)); <br>  ifs.read(a.name, sizeof(a.name)); <br>  ifs.read((unsigned char*) &amp;a.max_speed,<br>           sizeof(a.max_speed)); <br>  ifs.read((unsigned char*) &amp;a.max_altitude,<br>           sizeof(a.max_altitude)); <br>  ifs.read((unsigned char*) &amp;a.max_range,<br>           sizeof(a.max_range)); <br>  return ifs; <br>}<br><br>void main(void)<br>{ <br>  fstream plane_file(&quot;aircraft.bin&quot;,<br>                     ios::binary | ios::in | ios::out); <br>  aircraft plane; <br>  char choice = '\0'; <br>  do<br>  {<br>    cout &lt;&lt; endl<br>         &lt;&lt; &quot;A - Add new aircraft&quot;&nbsp;&nbsp;&nbsp;&nbsp; &lt;&lt; endl <br>         &lt;&lt; &quot;M - Modify Current&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;&lt; endl <br>         &lt;&lt; &quot;N - Goto Next Record&quot;&nbsp;&nbsp; &nbsp; &lt;&lt; endl <br>         &lt;&lt; &quot;P - Goto Previous Record&quot; &lt;&lt; endl <br>         &lt;&lt; &quot;X - To terminate program&quot; &lt;&lt; endl;<br><br>    do<br>    { <br>      cout &lt;&lt; endl &lt;&lt; &quot;Enter Choice :&quot;;<br>      cin &gt;&gt; choice;<br>      choice &amp;= 0xdf; // force to uppercase <br>    } while (! strchr(&quot;AMNPX&quot;, choice)); <br>    // until choice is one of list <br>    long prev_offset = -2L * (long)plane.size(); <br>    switch (choice)<br>    {<br>    case 'A':<br>      aircraft newplane; // create a temp aircraft<br>      newplane.get_data(); // and load data from user<br>      plane_file.seekp(0L, ios::end); // goto EOF<br>      plane_file &lt;&lt; newplane &lt;&lt; flush; // append<br>      break; <br>    case 'M':<br>      plane.get_data(); // reload current aircraft<br>      // set file pointer to start of the record<br>      plane_file.seekp(plane_file.tellg()-(long)plane.size());<br>      plane_file &lt;&lt; plane &lt;&lt; flush; // and write it<br>      break;<br>   &nbsp;case'P':<br>      //jump to previous record<br>      plane_file.seekg(prev_offset, ios::cur); <br>    case 'N':<br>      plane_file &gt;&gt; plane; // read record<br>      if(plane_file)<br>        plane.display_data();<br>      else<br>      {<br>        // on bounds error, set read pointer <br>        cout &lt;&lt; Unable to read record&quot; &lt;&lt; endl; <br>        plane_file.clear(); <br>        // to start of file <br>        plane_file.seekg(0L);<br>      }<br>      break; <br>    case 'X':<br>      cout &lt;&lt; &quot;Thank you for entering data&quot; &lt;&lt; endl; <br>      break;<br>    } <br>  } while (choice != 'X');<br>}</pre>
<p>In this program, the overloaded extractor and inserter operators are
used to read from and write to the disk. I have copped out and used
get_data() and display_data() member functions rather than confuse
things by further overloading of inserter and extractor operators. You
can see that positioning the file pointers is messy, as is the error
handling (almost non-existent), and with the exception of writing and
reading in the correct format, there is little gained over C in this
area.</p>
<p>A BETTER WAY?</p>
<p>Provided that memory is not a scarce resource, a simple technique
that can be used is to generate a template class which controls the
loading and storing of a file in a list container; this template class
can be used to hold a variety of different classes. Again, it is only
put together as an example and you should spend some effort to improve
it before serious use commences. Also, I have chosen to use a sorted
list, and I have therefore been forced to add a less than operator as
well as an equality operator to the list of member functions. In this
case I am using the BIDS templates, but any template container class
will have a usable list of some sort.</p>
<pre class="programlisting">//<br>//&nbsp;&nbsp; TEMPPERS.CPP (EXTRACT FROM)<br>//<br>#include &lt;conio.h&gt;<br>#include &lt;listimp.h&gt;<br><br>class aircraft<br>{ <br>protected:<br>  ... as before <br>public:<br>  ... as before plus<br>  aircraft(aircraft&amp;);<br>  int operator == (aircraft &amp;);<br>  int operator &lt; (aircraft &amp;);<br>};<br><br>aircraft::aircraft(aircraft&amp; a)<br>{<br>  memset(maker, 0, sizeof(maker));<br>  memset(name, 0 , sizeof(name));<br>  strcpy(maker, a.maker);<br>  strcpy(name, a.name);<br>  max_speed = a.max_speed;<br>  max_altitude = a.max_altitude;<br>  max_range = a.max_range; <br>}<br><br>int aircraft::operator == (aircraft&amp; a)<br>{ <br>  if (!strcmp(maker,a.maker) &amp;&amp; <br>      !strcmp(name,a.name) &amp;&amp; <br>      max_speed == a.max_speed &amp;&amp; <br>      max_altitude == a.max_altitude &amp;&amp; <br>      max_range == a.max_range )<br>    return 1; <br>  else<br>    return 0; <br>}<br><br>int aircraft::operator &lt; (aircraft&amp; a)<br>{<br>  int result;<br>  // test maker for &lt; == &gt;<br>  if ((result = strcmp(maker,a.maker)) &lt; 0 ) return 1; <br>  else if (result) return 0; <br>  // test name for &lt; == &gt;<br>  if ( (result = strcmp(name,a.name)) &lt; 0 ) return 1; <br>  else return 0;<br>  // no sorting takes place on attributes, only on keys <br>}<br><br>template&lt;class T&gt; class persist<br>{ <br>protected:<br>  char filename[60]; <br>public:<br>  persist(char* fname);<br>  ~persist(void);<br>  BI_SListImp&lt;T&gt; list;<br>};<br><br>template&lt;class T&gt; persist&lt;T&gt;::persist(char* fname)<br>{<br>  strcpy(filename, fname);<br>  fstream thisfile( filename , ios::binary | ios::in);<br>  T readval,<br>  // add each record in file to container<br>  while (thisfile)<br>  {<br>    thisfile &gt;&gt; readval;<br>    if (thisfile) list.add(readval); <br>  } <br>}<br><br>template&lt;class T&gt; persist&lt;T&gt;::~persist(void)<br>{<br>  fstream thisfile(filename,<br>                   ios::trunc | ios::binary | ios::out); <br>  BI_ListIteratorImp&lt;T&gt; next(list); <br>  // write each item in the container back to the tile <br>  while (next &amp;&amp; thisfile)<br>  { <br>    thisfile &lt;&lt; next++;<br>  }<br>  delete filename;<br>}<br><br>void main(void)<br>{<br>  // create an aircraft list linked to the file aircraft.bin<br>  // and automatically load them in<br>  persist&lt;aircraft&gt; airfile(&quot;aircraft.bin&quot;);<br>  // create another aircraft<br>  aircraft sw(&quot;Supermarine&quot;, &quot;Walrus&quot;,<br>              180, 24000L, 2300); <br>  // and add it to the list <br>  airfile.list.add(sw);<br><br>  BI_ListIteratorImp&lt;aircraft&gt; next(airfile.list);<br><br>  // Display the contents of the list<br>  cout &lt;&lt; &quot;The contents of the aircraft file are : &quot;<br>       &lt;&lt; endl; <br>  while (next)<br>  {<br>    (next++).display_data(); <br>    cout &lt;&lt; &quot;Press a key &quot; &lt;&lt; endl; <br>    getch();<br>  }<br>  // and automatically save the list as airfile object 
// goes out of scope <br>}</pre>
<p>The main() function demonstrates how easy it is to set up a list
loaded with the file specified. Any alterations to the contents of this
list are saved automatically when the airfile object goes out of scope.</p>
<p>The problem with such techniques is &quot;What happens when I have a
class hierarchy to consider?&quot;. The answer in short is &quot;You are in
trouble&quot;. Although it is straightforward to overload the inserter
operators to format the outgoing record in the correct format, reading
them back in presents more of a problem. You cannot rely on the
compiler sorting out which class type the record belongs to. It is
always possible to precede the record with a marker to indicate what
kind of object is coming back in, and this I will do in the next
instalment, but a serious problem will then occur if any attempt is
made to go backwards or non-sequentially through the file. There is no
guarantee that all the records will be of the same length, and so
positioning at the nth record is impossible with a simple structure.</p></p>
<p><strong>Notes:</strong>&nbsp;</p>
<p><em>More fields may be available via dynamicdata ..</em></p>
</div>
</channel>
</rss>
