Previous Section Main Menu

CHAPTER 11

INPUT/OUTPUT
A MORE DETAILED LOOK

Section VII: Run Time Parameters - Passing Values to 'main' Section I: The C++ Perspective on Input and Output Section II: Basic Input Using Instances of the Class istream Section III: Basic Output Using Instances of the Class ostream
Section IV: File Handling Section V : The Design and Analysis for a Contract Program Using Files Section VI: Modifying the Code for a Contract Program Using Files

VII. Run Time Parameters - Passing Values to 'main'
Programmers often make a distinction between compile time and run time. Compile time is the period when a program is being compiled, while run time is the period when a program is running. The distinction is important, for example, in terms of the different types of errors that can occur. By this time you have seen many compile time error messages - error messages generated by syntactic errors in your code. In contrast, run time errors involve such things as trying to divide by 0. Such an error would not be caught at compile time because, at that point, the system does not know the values of the variables.

Dynamic vs. static memory allocation is also a compile time vs. a run time issue. As you know, when a program is finished compiling, all the instructions necessary to allocate memory for the program's variables and constants have been written. At this point, one could calculate all the static memory required of a program before running it and see if a given computer was capable of providing the required memory. Dynamic memory, on the other hand, is requested during run time and, in may cases, there is no way to know ahead of time how much memory will be required - although one can make estimates.

A. Run Time Parameters
Up to now we have written 'main' in all our programs so as not receive any parameters. It is possible, however, in certain run time environments to pass information to 'main' For example, the operating systems 'DOS' and 'Unix' allow one to call a program with parameters. This is so obvious that we tend to ignore it.

Anyone who knows 'DOS' knows that one can write the following command at the prompt:

copy file1.txt file2.txt

which, of course, means to copy 'file1.txt' into 'file2.txt'. What some may not realize is that 'copy' is a program and 'file1.txt' and 'file2.txt' are values being passed to this program. In C++, if we wish to use such run time parameters in a program, we need to declare 'main' with two parameters. The first parameter tells the system how many parameters there are and the second parameter is an array of strings where each array element is one of the parameters. When we run the program (by typing the program's name at the operating system prompt, the parameters are passed to 'main'.

For example, suppose we want to write our own version of 'copy'. We would create a file called 'copy' and place our 'main' in that file. The declaration for that 'main' would look as follows:

void main (int argc, char** argv)

The two parameter names 'argc' and 'argv' can be different but they are the standard ones used in C++. The first one holds the number of arguments in the run time call to the program. Interestingly enough, the name of the program is considered a parameter. Thus, if the following is typed at the operating system prompt:

copy file1.txt

the parameter 'argc' will hold the value 2.

B. Multi-Dimensional Arrays
The second argument represents the array of strings. We really have not explored such arrays up to this point. All we have seen are the so-called one-dimension arrays, for which one often pictures the values of the array as one long line. For example, a character array might look like:

FIGURE 11.4 DRAW an array with one name

However, an array of strings is two dimensional in the sense that a good picture for it would be:

Figure 11.5 DRAW a 'table' of names.

Of course, one can have two-dimensional arrays of integers or other types for that matter. For example, if you wanted to record the temperature for each day of each month you could use an array with 31 columns and 12 rows - see Figure 11.6.

The declaration for this would be:

int temperatures [12] [31];

As an another example, suppose we wanted an array of 20 strings, where each string represented a name. If the maximum size of a name was 40, this array could be declared as:

char names[20] [40];

To output the temperature on day 3 of the 10th month, one might write the code:

cout << "The temperature was: " << temperatures[9][2];

To output the 15th character of the fourth name one might write the code:

cout << "The name is: " << names[3][14];


Two points about this:

  1. Since the month index (the value 12) came before the day index (the value 31) in the declaration of the temperature array, we must use it first in the first example - to be consistent. Likewise, we must use the string index first in the second example.

  2. C++ arrays always start at zero for all dimensions. Therefore, the 10th month, the third day is written as [9][2] and the fourth name, the 15th character is written as [3][14].

When one wants to access a whole row of a two dimensional array at the same time, one only uses the first array index. This would not make sense with our temperature array but since strings can be handled as a unit, it makes sense, for example, to write:

cout << names[3];

to output the 4th name in the array called 'names'. (Of course, the names better have been stored with null terminating characters or we will have problems with this output.)

C. The Meaning of 'Char**'
The code, 'char**', is a complex way of representing an array of strings. We know that a string is an array and that arrays can also be seen as pointers. Thus, we have previously used code such as

char* name

to represent a string - called name. Since a string is an array, an array of strings is an array of arrays. Using the language of pointers, an array of arrays of some type can be spoken of as a pointer to a pointer to some type. In our case, a pointer to a pointer to a char or 'char**'.

Although this pointer based representation is somewhat ugly, it is the only way to represent the data coming into the program. Note that we have no idea how large the strings will be or how many there will be. In such a situation, the pointer-based approach must be used to allow the system to allocate the required amount of memory.

Here is the code for the whole 'copy' program:


// Demo of using arguments in main - a program to copy one file to another
// file: ch11copy.cpp
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>

typedef char string[80];

void main (int argc, char** argv)
// It is OK to use parameter names other than argc and argv but these are the standard

{       string inFileName;
	string outFileName;

	if (argc == 1)
	{
		cout << "Missing: Names of file to be copied and destination file\n";
		exit(1);
	}

	if (argc == 2)
	{	cout << "Missing: Name of destination file\n";
		exit(1);
	}

	if (argc > 3)
	{	cout << "Too many arguments\n";
		exit(1);
	}

	ifstream inFile (argv[1]);
	if (!inFile)
	{	cout << "Cannot open input file\n";
		exit(1);
	}

	ofstream outFile (argv[2]);
	if (!outFile)
	{	cout << "Cannot open output file\n";
		exit(1);
	}

// If both files have been successfully opened:
	char ch;
	while (outFile && inFile)
	{	inFile.get(ch);
		outFile << ch;
	}
}
Notice how the two parameters are used. The program uses 'argc' to make sure that the correct number of parameters have been entered. It then uses the string in array element 'argv[1]' as the name of the input file and the array element 'argv[2]' as the name of the output file. In other words, the strings in the parameter 'argv' are the parameters passed to the program at run time. (By the way, 'argv[0]' holds the string 'copy' - the name of the program.)

The rest of the code takes advantage of ideas we have already discussed. However, the test in the 'while' loop might be a bit puzzling. We have two files that are being worked with each time through the loop and we need to make sure both files are OK. In the case of the output file the loop will terminate only if a problem occurs such as if there is no more room on the disk. The input test is a bit more complex. The stream 'inFile' will have an 'error' condition either if some problem occurs or if the end-of-file marker is encountered. While 'end-of-file' is not really an error, it does set to TRUE the value of the 'fail' flag and thus we can use 'inFile' to check for the end of the file. In this way, the while loop will terminate when there are no more characters to read in the file to be copied.
 

Final Thoughts

This ends our discussion of C++ I/O. It also ends our "Story of C++ " - for the moment. There is much more to the story and we hope you hang around for the next installment. Until then

HAPPY CODING!

Top of Section Main Menu