Iostream
examples
In
this section you’ll see some examples of what you can do with all the
information you’ve learned in this chapter. Although many tools exist to
manipulate bytes (stream editors like 
sed
and
awk
from
Unix are
perhaps the most well known, but a text editor also fits this category), they
generally have some limitations. 
sed
and 
awk
can be slow and can only handle lines in a forward sequence, and text editors
usually require human interaction, or at least learning a proprietary macro
language. The programs you write with iostreams have none of these limitations:
They’re fast, portable, and flexible. It’s a very useful tool to
have in your kit.
 
Code
generation
The
first examples concern the generation of programs that, coincidentally, fit the
format used in this book. This provides a little extra speed and consistency
when developing code. The first program creates a file to hold 
main( )
(assuming it takes no command-line arguments and uses the iostream library):
 
//: C18:Makemain.cpp
// Create a shell main() file
#include "../require.h"
#include <fstream>
#include <strstream>
#include <cstring>
#include <cctype>
using namespace std;
int main(int argc, char* argv[]) {
  requireArgs(argc, 1);
  ofstream mainfile(argv[1]);
  assure(mainfile, argv[1]);
  istrstream name(argv[1]);
  ostrstream CAPname;
  char c;
  while(name.get(c))
    CAPname << char(toupper(c));
  CAPname << ends;
  mainfile << "//" << ": " << CAPname.rdbuf()
    << " -- " << endl
    << "#include <iostream>" << endl
    << endl
    << "main() {" << endl << endl
    << "}" << endl;The
argument on the command line is used to create an 
istrstream,
so the characters can be extracted one at a time and converted to upper case
with the Standard C library macro 
toupper( ).
This returns an 
int
so it must be explicitly cast to a 
char.
This name is used in the headline, followed by the remainder of the generated
file.
 
Maintaining
class library source
The
second example performs a more complex and useful task. Generally, when you
create a class you think in library terms, and make a header file 
Name.h
for
the class declaration and a file where the member functions are implemented,
called 
Name.cpp.
T
hese
files have certain requirements: a particular coding standard (the program
shown here will use the coding format for this book), and in the header file
the declarations are generally surrounded by some preprocessor statements to
prevent multiple declarations of classes. (Multiple declarations confuse the
compiler – it doesn’t know which one you want to use. They could be
different, so it throws up its hands and gives an error message.)
 This
example allows you to create a new header-implementation pair of files, or to
modify an existing pair. If the files already exist, it checks and potentially
modifies the files, but if they don’t exist, it creates them using the
proper format.
 
//: C18:Cppcheck.cpp
// Configures .h & .cpp files
// To conform to style standard.
// Tests existing files for conformance
#include "../require.h"
#include <fstream>
#include <strstream>
#include <cstring>
#include <cctype>
using namespace std;
int main(int argc, char* argv[]) {
  const int sz = 40;  // Buffer sizes
  const int bsz = 100;
  requireArgs(argc, 1); // File set name
  enum bufs { base, header, implement,
    Hline1, guard1, guard2, guard3,
    CPPline1, include, bufnum };
  char b[bufnum][sz];
  ostrstream osarray[] = {
    ostrstream(b[base], sz),
    ostrstream(b[header], sz),
    ostrstream(b[implement], sz),
    ostrstream(b[Hline1], sz),
    ostrstream(b[guard1], sz),
    ostrstream(b[guard2], sz),
    ostrstream(b[guard3], sz),
    ostrstream(b[CPPline1], sz),
    ostrstream(b[include], sz),
  };
  osarray[base] << argv[1] << ends;
  // Find any '.' in the string using the
  // Standard C library function strchr():
  char* period = strchr(b[base], '.');
  if(period) *period = 0; // Strip extension
  // Force to upper case:
  for(int i = 0; b[base][i]; i++)
    b[base][i] = toupper(b[base][i]);
  // Create file names and internal lines:
  osarray[header] << b[base] << ".h" << ends;
  osarray[implement] << b[base] << ".cpp" << ends;
  osarray[Hline1] << "//" << ": " << b[header]
    << " -- " << ends;
  osarray[guard1] << "#ifndef " << b[base]
                  << "_H" << ends;
  osarray[guard2] << "#define " << b[base]
                  << "_H" << ends;
  osarray[guard3] << "#endif // " << b[base]
                  << "_H" << ends;
  osarray[CPPline1] << "//" << ": "
                    << b[implement]
                    << " -- " << ends;
  osarray[include] << "#include \""
                   << b[header] << "\"" <<ends;
  // First, try to open existing files:
  ifstream existh(b[header]),
           existcpp(b[implement]);
  if(!existh) { // Doesn't exist; create it
    ofstream newheader(b[header]);
    assure(newheader, b[header]);
    newheader << b[Hline1] << endl
      << b[guard1] << endl
      << b[guard2] << endl << endl
      << b[guard3] << endl;
  }
  if(!existcpp) { // Create cpp file
    ofstream newcpp(b[implement]);
    assure(newcpp, b[implement]);
    newcpp << b[CPPline1] << endl
      << b[include] << endl;
  }
  if(existh) { // Already exists; verify it
    strstream hfile; // Write & read
    ostrstream newheader; // Write
    hfile << existh.rdbuf() << ends;
    // Check that first line conforms:
    char buf[bsz];
    if(hfile.getline(buf, bsz)) {
      if(!strstr(buf, "//" ":") ||
         !strstr(buf, b[header]))
        newheader << b[Hline1] << endl;
    }
    // Ensure guard lines are in header:
    if(!strstr(hfile.str(), b[guard1]) ||
       !strstr(hfile.str(), b[guard2]) ||
       !strstr(hfile.str(), b[guard3])) {
       newheader << b[guard1] << endl
         << b[guard2] << endl
         << buf
         << hfile.rdbuf() << endl
         << b[guard3] << endl << ends;
    } else
      newheader << buf
        << hfile.rdbuf() << ends;
    // If there were changes, overwrite file:
    if(strcmp(hfile.str(),newheader.str())!=0){
      existh.close();
      ofstream newH(b[header]);
      assure(newH, b[header]);
      newH << "//@//" << endl // Change marker
        << newheader.rdbuf();
    }
    delete hfile.str();
    delete newheader.str();
  }
  if(existcpp) { // Already exists; verify it
    strstream cppfile;
    ostrstream newcpp;
    cppfile << existcpp.rdbuf() << ends;
    char buf[bsz];
    // Check that first line conforms:
    if(cppfile.getline(buf, bsz))
      if(!strstr(buf, "//" ":") ||
         !strstr(buf, b[implement]))
        newcpp << b[CPPline1] << endl;
    // Ensure header is included:
    if(!strstr(cppfile.str(), b[include]))
      newcpp << b[include] << endl;
    // Put in the rest of the file:
    newcpp << buf << endl; // First line read
    newcpp << cppfile.rdbuf() << ends;
    // If there were changes, overwrite file:
    if(strcmp(cppfile.str(),newcpp.str())!=0){
      existcpp.close();
      ofstream newCPP(b[implement]);
      assure(newCPP, b[implement]);
      newCPP << "//@//" << endl // Change marker
        << newcpp.rdbuf();
    }
    delete cppfile.str();
    delete newcpp.str();
  }This
example requires a lot of string formatting in many different buffers. Rather
than creating a lot of individually named buffers and 
ostrstream
objects,
a single set of names is created in the 
enum
bufs.
Then two arrays are created: an array of character buffers and an array of 
ostrstream
objects built from those character buffers. Note that in the definition for the
two-dimensional array of 
char
buffers 
b,
the number of 
char
arrays is determined by 
bufnum,
the last enumerator in 
bufs.
When you create an enumeration,
the compiler assigns integral values to all the 
enum
labels starting at zero, so the sole purpose of 
bufnum
is to be a counter for the number of enumerators in 
buf.
The length of each string in 
b
is 
sz. The
names in the enumeration are 
base,
the capitalized base file name without extension; 
header,
the header file name; 
implement,
the implementation file (
cpp)
name; 
Hline1,
the skeleton first line of the header file; 
guard1,
guard2,
and 
guard3,
the “guard” lines in the header file (to prevent multiple
inclusion); 
CPPline1,
the skeleton first line of the 
cpp
file; and 
include,
the line in the 
cpp
file that includes the header file.
 osarray
is an array of 
ostrstream
objects created using aggregate initialization and automatic counting. Of
course, this is the form of the 
ostrstream
constructor that takes two arguments (the buffer address and buffer size), so
the constructor calls must be formed accordingly inside the aggregate
initializer list. Using the 
bufs
enumerators, the appropriate array element of 
b
is tied to the corresponding 
osarray
object. Once the array is created, the objects in the array can be selected
using the enumerators, and the effect is to fill the corresponding 
b
element. You can see how each string is built in the lines following the 
ostrstream
array definition.
 Once
the strings have been created, the program attempts to open existing versions
of both the header and 
cpp
file as 
ifstreams.
If you test the object using the operator ‘
!’
and the file doesn’t exist, the test will fail. If the header or
implementation file doesn’t exist, it is created using the appropriate
lines of text built earlier.
 If
the files 
do
exist, then they are verified to ensure the proper format is followed. In both
cases, a 
strstream
is
created and the whole file is read in; then the first line is read and checked
to make sure it follows the format by seeing if it contains both a “
//:”
and the name of the file. This is accomplished with the Standard C library
function 
strstr( ).
If the first line doesn’t conform, the one created earlier is inserted
into an 
ostrstream
that has been created to hold the edited file.
 In
the header file, the whole file is searched (again using 
strstr( ))
to ensure it contains the three “guard” lines; if not, they are
inserted. The implementation file is checked for the existence of the line that
includes the header file (although the compiler effectively guarantees its
existence).
 In
both cases, the original file (in its 
strstream)
and the edited file (in the 
ostrstream)
are compared to see if there are any changes. If there are, the existing file
is closed, and a new 
ofstream
object is created to overwrite it. The 
ostrstream
is output to the file after a special change marker is added at the beginning,
so you can use a text search program to rapidly find any files that need
reviewing to make additional changes.
 
Detecting
compiler errors
All
the code in this book is designed to compile as shown without errors.
Any line of code that should generate a compile-time error is commented out
with the special comment sequence “//!”. The following program will
remove these special comments and append a numbered comment to the line, so
that when you run your compiler it should generate error messages and you
should see all the numbers appear when you compile all the files. It also
appends the modified line to a special file so you can easily locate any lines
that don’t generate errors:
 
//: C18:Showerr.cpp
// Un-comment error generators
#include "../require.h"
#include <iostream>
#include <fstream>
#include <strstream>
#include <cctype>
#include <cstring>
using namespace std;
char* marker = "//!";
char* usage =
"usage: showerr filename chapnum\n"
"where filename is a C++ source file\n"
"and chapnum is the chapter name it's in.\n"
"Finds lines commented with //! and removes\n"
"comment, appending //(#) where # is unique\n"
"across all files, so you can determine\n"
"if your compiler finds the error.\n"
"showerr /r\n"
"resets the unique counter.";
// File containing error number counter:
char* errnum = "../errnum.txt";
// File containing error lines:
char* errfile = "../errlines.txt";
ofstream errlines(errfile,ios::app);
int main(int argc, char* argv[]) {
  requireArgs(argc, 2, usage);
  if(argv[1][0] == '/' || argv[1][0] == '-') {
    // Allow for other switches:
    switch(argv[1][1]) {
      case 'r': case 'R':
        cout << "reset counter" << endl;
        remove(errnum); // Delete files
        remove(errfile);
        return 0;
      default:
        cerr << usage << endl;
        return 1;
    }
  }
  char* chapter = argv[2];
  strstream edited; // Edited file
  int counter = 0;
  {
    ifstream infile(argv[1]);
    assure(infile, argv[1]);
    ifstream count(errnum);
    assure(count, errnum);
    if(count) count >> counter;
    int linecount = 0;
    const int sz = 255;
    char buf[sz];
    while(infile.getline(buf, sz)) {
      linecount++;
      // Eat white space:
      int i = 0;
      while(isspace(buf[i]))
        i++;
      // Find marker at start of line:
      if(strstr(&buf[i], marker) == &buf[i]) {
        // Erase marker:
        memset(&buf[i], ' ', strlen(marker));
        // Append counter & error info:
        ostrstream out(buf, sz, ios::ate);
        out << "//(" << ++counter << ") "
            << "Chapter " << chapter
            << " File: " << argv[1]
            << " Line " << linecount << endl
            << ends;
          edited << buf;
        errlines << buf; // Append error file
      } else
        edited << buf << "\n"; // Just copy
    }
  } // Closes files
  ofstream outfile(argv[1]); // Overwrites
  assure(outfile, argv[1]);
  outfile << edited.rdbuf();
  ofstream count(errnum); // Overwrites
  assure(count, errnum);
  count << counter; // Save new counterThe
marker can be replaced with one of your choice.
 Each
file is read a line at a time, and each line is searched for the marker
appearing at the head of the line; the line is modified and put into the error
line list and into the 
strstream
edited.
When the whole file is processed, it is closed (by reaching the end of a
scope), reopened as an output file and 
edited
is poured into the file. Also notice the counter is saved in an external file,
so the next time this program is invoked it continues to sequence the counter.
 
A
simple datalogger
This
example shows an approach you might take to log data to disk and later retrieve
it for processing. The example is meant to produce a temperature-depth profile
of the ocean at various points. To hold the data, a class is used:
 
//: C18:DataLogger.h
// Datalogger record layout
#ifndef DATALOG_H
#define DATALOG_H
#include <ctime>
#include <iostream>
class DataPoint {
  std::tm time; // Time & day
  static const int bsz = 10;
  // Ascii degrees (*) minutes (') seconds ("):
  char latitude[bsz], longitude[bsz];
  double depth, temperature;
public:
  std::tm getTime();
  void setTime(std::tm t);
  const char* getLatitude();
  void setLatitude(const char* l);
  const char* getLongitude();
  void setLongitude(const char* l);
  double getDepth();
  void setDepth(double d);
  double getTemperature();
  void setTemperature(double t);
  void print(std::ostream& os);
};#endif
// DATALOG_H ///:~
 The
access functions provide controlled reading and writing to each of the data
members. The 
print( )
function formats the 
DataPoint
in a readable form onto an 
ostream
object (the argument to 
print( )).
Here’s the definition file:
 
//: C18:Datalog.cpp {O}
// Datapoint member functions
#include "DataLogger.h"
#include <iomanip>
#include <cstring>
using namespace std;
tm DataPoint::getTime() { return time; }
void DataPoint::setTime(tm t) { time = t; }
const char* DataPoint::getLatitude() {
  return latitude;
}
void DataPoint::setLatitude(const char* l) {
  latitude[bsz - 1] = 0;
  strncpy(latitude, l, bsz - 1);
}
const char* DataPoint::getLongitude() {
  return longitude;
}
void DataPoint::setLongitude(const char* l) {
  longitude[bsz - 1] = 0;
  strncpy(longitude, l, bsz - 1);
}
double DataPoint::getDepth() { return depth; }
void DataPoint::setDepth(double d) { depth = d; }
double DataPoint::getTemperature() {
  return temperature;
}
void DataPoint::setTemperature(double t) {
  temperature = t;
}
void DataPoint::print(ostream& os) {
  os.setf(ios::fixed, ios::floatfield);
  os.precision(4);
  os.fill('0'); // Pad on left with '0'
  os << setw(2) << getTime().tm_mon << '\\'
     << setw(2) << getTime().tm_mday << '\\'
     << setw(2) << getTime().tm_year << ' '
     << setw(2) << getTime().tm_hour << ':'
     << setw(2) << getTime().tm_min << ':'
     << setw(2) << getTime().tm_sec;
  os.fill(' '); // Pad on left with ' '
  os << " Lat:" << setw(9) << getLatitude()
     << ", Long:" << setw(9) << getLongitude()
     << ", depth:" << setw(9) << getDepth()
     << ", temp:" << setw(9) << getTemperature()
     << endl;In
print( ),
the call to 
setf( )
causes the floating-point output to be fixed-precision, and 
precision( )
sets the number of decimal places to four.
 The
default is to right-justify the data within the field. The time information
consists of two digits each for the hours, minutes and seconds, so the width is
set to two with 
setw( )
in each case. (Remember that any changes to the field width affect only the
next output operation, so 
setw( )
must be given for each output.) But first, to put a zero in the left position
if the value is less than 10, the fill character is
set to ‘0’. Afterwards, it is set back to a space.
 The
latitude and longitude are zero-terminated character fields, which hold the
information as degrees (here, ‘*’ denotes degrees), minutes
(‘), and seconds(“). You can certainly devise a more efficient
storage layout for latitude and longitude if you desire.
 
Generating
test data
Here’s
a program that creates a file of test data in binary form (using 
write( ))
and a second file in ASCII form using 
DataPoint::print( ).
You can also print it out to the screen but it’s easier to inspect in
file form.
 
//: C18:Datagen.cpp
//{L} Datalog
// Test data generator
#include "DataLogger.h"
#include "../require.h"
#include <fstream>
#include <cstdlib>
#include <cstring>
using namespace std;
int main() {
  ofstream data("data.txt");
  assure(data, "data.txt");
  ofstream bindata("data.bin", ios::binary);
  assure(bindata, "data.bin");
  time_t timer;
  // Seed random number generator:
  srand(time(&timer)); 
  for(int i = 0; i < 100; i++) {
    DataPoint d;
    // Convert date/time to a structure:
    d.setTime(*localtime(&timer));
    timer += 55; // Reading each 55 seconds
    d.setLatitude("45*20'31\"");
    d.setLongitude("22*34'18\"");
    // Zero to 199 meters:
    double newdepth  = rand() % 200;
    double fraction = rand() % 100 + 1;
    newdepth += double(1) / fraction;
    d.setDepth(newdepth);
    double newtemp = 150 + rand()%200; // Kelvin
    fraction = rand() % 100 + 1;
    newtemp += (double)1 / fraction;
    d.setTemperature(newtemp);
    d.print(data);
    bindata.write((unsigned char*)&d,
                  sizeof(d));
  }The
file DATA.TXT is created in the ordinary way as an ASCII file, but DATA.BIN has
the flag 
ios::binary
to tell the constructor to set it up as a binary file.
 The
Standard C library function 
time( ),
when called with a zero argument, returns the current time as a 
time_t
value, which is the number of seconds elapsed since 00:00:00 GMT, January 1
1970 (the dawning of the age of Aquarius?). The current time is the most
convenient way to seed the random number generator with the Standard C library
function 
srand( ),
as is done here.
 Sometimes
a more convenient way to store the time is as a 
tm
structure, which has all the elements of the time and date broken up into their
constituent parts as follows:
 
struct tm {
  int tm_sec; // 0-59 seconds
  int tm_min; // 0-59 minutes
  int tm_hour; // 0-23 hours
  int tm_mday; // Day of month
  int tm_mon; // 1-12 months
  int tm_year; // Calendar year
  int tm_wday; // Sunday == 0, etc.
  int tm_yday; // 0-365 day of year
  int tm_isdst; // Daylight savings?To
convert from the time in seconds to the local time in the 
tm
format, you use the Standard C library 
localtime( )
function, which takes the number of seconds and returns a pointer to the
resulting 
tm.
This 
tm,
however, is a 
static
structure inside the 
localtime( )
function, which is rewritten every time 
localtime( )
is called. To copy the contents into the 
tm
struct
inside 
DataPoint,
you might think you must copy each element individually. However, all you must
do is a structure assignment, and the compiler will take care of the rest. This
means the right-hand side must be a structure, not a pointer, so the result of 
localtime( )
is dereferenced. The desired result is achieved with
 d.setTime(*localtime(&timer)); After
this, the 
timer
is incremented by 55 seconds to give an interesting interval between readings.
 The
latitude and longitude used are fixed values to indicate a set of readings at a
single location. Both the depth and the temperature are generated with the
Standard C library 
rand( )
function,
which returns a pseudorandom number between zero and the constant RAND_MAX.
To put this in a desired range, use the modulus operator %
and the upper end of the range. These numbers are integral; to add a fractional
part, a second call to 
rand( )
is made, and the value is inverted after adding one (to prevent divide-by-zero
errors).
 In
effect, the DATA.BIN file is being used as a container for the data in the
program, even though the container exists on disk and not in RAM. To send the
data out to the disk in binary form, 
write( )
is used. The first argument is the starting address of the source block –
notice it must be cast to an 
unsigned
char*
because that’s what the function expects. The second argument is the
number of bytes to write, which is the size of the 
DataPoint
object. Because no pointers are contained in 
DataPoint,
there is no problem in writing the object to disk. If the object is more
sophisticated, you must implement a scheme for 
serialization
.
(Most vendor class libraries have some sort of serialization structure built
into them.)
 
Verifying
& viewing the data
To
check the validity of the data stored in binary format, it is read from the
disk and put in text form in DATA2.TXT, so that file can be compared to
DATA.TXT for verification. In the following program, you can see how simple
this data recovery is. After the test file is created, the records are read at
the command of the user.
 
//: C18:Datascan.cpp
//{L} Datalog
// Verify and view logged data
#include "DataLogger.h"
#include "../require.h"
#include <iostream>
#include <fstream>
#include <strstream>
#include <iomanip>
using namespace std;
int main() {
  ifstream bindata("data.bin", ios::binary);
  assure(bindata, "data.bin");
  // Create comparison file to verify data.txt:
  ofstream verify("data2.txt");
  assure(verify, "data2.txt");
  DataPoint d;
  while(bindata.read(
    (unsigned char*)&d, sizeof d))
    d.print(verify);
  bindata.clear(); // Reset state to "good"
  // Display user-selected records:
  int recnum = 0;
  // Left-align everything:
  cout.setf(ios::left, ios::adjustfield);
  // Fixed precision of 4 decimal places:
  cout.setf(ios::fixed, ios::floatfield);
  cout.precision(4);
  for(;;) {
    bindata.seekg(recnum* sizeof d, ios::beg);
    cout << "record " << recnum << endl;
    if(bindata.read(
      (unsigned char*)&d, sizeof d)) {
      cout << asctime(&(d.getTime()));
      cout << setw(11) << "Latitude"
           << setw(11) << "Longitude"
           << setw(10) << "Depth"
           << setw(12) << "Temperature"
           << endl;
      // Put a line after the description:
      cout << setfill('-') << setw(43) << '-'
           << setfill(' ') << endl;
      cout << setw(11) << d.getLatitude()
           << setw(11) << d.getLongitude()
           << setw(10) << d.getDepth()
           << setw(12) << d.getTemperature()
           << endl;
    } else {
      cout << "invalid record number" << endl;
      bindata.clear(); // Reset state to "good"
    }
    cout << endl
      << "enter record number, x to quit:";
    char buf[10];
    cin.getline(buf, 10);
    if(buf[0] == 'x') break;
    istrstream input(buf, 10);
    input >> recnum;
  }The
ifstream
bindata
is created from DATA.BIN as a binary file, with the 
ios::nocreate
flag on to cause the 
assert( )
to fail if the file doesn’t exist. The 
read( )
statement reads a single record and places it directly into the 
DataPoint
d
.
(Again, if 
DataPoint
contained pointers this would result in meaningless pointer values.) This 
read( )
action will set 
bindata’s
failbit
when
the end of the file is reached, which will cause the 
while
statement to fail. At this point, however, you can’t move the get pointer
back and read more records because the state of the stream won’t allow
further reads. So the 
clear( )
function is called to reset the 
failbit. Once
the record is read in from disk, you can do anything you want with it, such as
perform calculations or make graphs. Here, it is displayed to further exercise
your knowledge of iostream formatting.
 The
rest of the program displays a record number (represented by 
recnum)
selected by the user. As before, the precision is fixed at four decimal places,
but this time everything is left justified.
 The
formatting of this output looks different from before:
 
record 0
Tue Nov 16 18:15:49 1993
Latitude   Longitude  Depth     Temperature
-------------------------------------------
45*20'31"
 22*34'18"  186.0172  269.0167    
 To
make sure the labels and the data columns line up, the labels are put in the
same width fields as the columns, using 
setw( ).
The line in between is generated by setting the fill character to
‘-’, the width to the desired line width, and outputting a single
‘-’.
 If
the 
read( )
fails, you’ll end up in the 
else
part, which tells the user the record number was invalid. Then, because the 
failbit
was set, it must be reset with a call to 
clear( )
so the next 
read( )
is successful (assuming it’s in the right range).
 Of
course, you can also open the binary data file for writing as well as reading.
This way you can retrieve the records, modify them, and write them back to the
same location, thus creating a flat-file database management system. In my very
first programming job, I also had to create a flat-file DBMS – but using
BASIC on an Apple II. It took months, while this took minutes. Of course, it
might make more sense to use a packaged DBMS now, but with C++ and iostreams
you can still do all the low-level operations that are necessary in a lab.
 
Counting
editor
Often
you have some editing task where you must go through and sequentially number
something, but all the other text is duplicated. I encountered this problem
when pasting digital photos into a Web page – I got the formatting just
right, then duplicated it, then had the problem of incrementing the photo
number for each one. So I replaced the photo number with XXX, duplicated that,
and wrote the following program to find and replace the “XXX” with
an incremented count. Notice the formatting, so the value will be
“001,” “002,” etc.:
 
//: C18:NumberPhotos.cpp
// Find the marker "XXX" and replace it with an
// incrementing number whereever it appears. Used
// to help format a web page with photos in it
#include "../require.h"
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
using namespace std;
int main(int argc, char* argv[]) {
  requireArgs(argc, 2);
  ifstream in(argv[1]);
  assure(in, argv[1]);
  ofstream out(argv[2]);
  assure(out, argv[2]);
  string line;
  int counter = 1;
  while(getline(in, line)) {
    int xxx = line.find("XXX");
    if(xxx != string::npos) {
      ostringstream cntr;
      cntr << setfill('0') << setw(3) << counter++;
      line.replace(xxx, 3, cntr.str());
    }
    out << line << endl;
  }
Breaking
up big files
This
program was created to break up big files into smaller ones, in particular so
they could be more easily downloaded from an Internet server (since hangups
sometimes occur, this allows someone to download a file a piece at a time and
then re-assemble it at the client end). You’ll note that the program also
creates a reassembly batch file for DOS (where it is messier), whereas under
Linux/Unix you simply say something like “
cat
*piece* > destination.file
”. This
program reads the entire file into memory, which of course relies on having a
32-bit operating system with virtual memory for big files. It then pieces it
out in chunks to the smaller files, generating the names as it goes. Of course,
you can come up with a possibly more reasonable strategy that reads a chunk,
creates a file, reads another chunk, etc.
 Note
that this program can be run on the server, so you only have to download the
big file once and then break it up once it’s on the server.
 
//: C18:Breakup.cpp
// Breaks a file up into smaller files for 
// easier downloads
#include "../require.h"
#include <iostream>
#include <fstream>
#include <iomanip>
#include <strstream>
#include <string>
using namespace std;
int main(int argc, char* argv[]) {
  requireArgs(argc, 1);
  ifstream in(argv[1], ios::binary);
  assure(in, argv[1]);
  in.seekg(0, ios::end); // End of file
  long fileSize = in.tellg(); // Size of file
  cout << "file size = " << fileSize << endl;
  in.seekg(0, ios::beg); // Start of file
  char* fbuf = new char[fileSize];
  require(fbuf != 0);
  in.read(fbuf, fileSize);
  in.close();
  string infile(argv[1]);
  int dot = infile.find('.');
  while(dot != string::npos) {
    infile.replace(dot, 1, "-");
    dot = infile.find('.');
  }
  string batchName(
    "DOSAssemble" + infile + ".bat");
  ofstream batchFile(batchName.c_str());
  batchFile << "copy /b ";
  int filecount = 0;
  const int sbufsz = 128;
  char sbuf[sbufsz];
  const long pieceSize = 1000L * 100L;
  long byteCounter = 0;
  while(byteCounter < fileSize) {
    ostrstream name(sbuf, sbufsz);
    name << argv[1] << "-part" << setfill('0') 
      << setw(2) << filecount++ << ends;
    cout << "creating " << sbuf << endl;
    if(filecount > 1) 
      batchFile << "+";
    batchFile << sbuf;
    ofstream out(sbuf, ios::out | ios::binary);
    assure(out, sbuf);
    long byteq;
    if(byteCounter + pieceSize < fileSize)
      byteq = pieceSize;
    else
      byteq = fileSize - byteCounter;
    out.write(fbuf + byteCounter, byteq);
    cout << "wrote " << byteq << " bytes, ";
    byteCounter += byteq;
    out.close();
    cout << "ByteCounter = " << byteCounter 
      << ", fileSize = " << fileSize << endl;
  }
  batchFile << " " << argv[1] << endl;
  
	
		
		  Contact: webmaster@codeguru.com
		  
		  CodeGuru - the website for developers.