The Essentials of C++, David hunter

Chapter 12. Miscellaneous C++ Topics

12.1 Command Line Arguments

  • Although none of the previous examples have done so, it is possible for C++ main functions to specify parameters that correspond to the command line arguments specified when the C++ program is executed. Two parameters are required to access command line arguments. The first should be an integer called argc which represents the number of command line arguments specified. The second is an array of character strings called argv. This array will be initialized by the operating system so that argv[0] is the name of the command invoked (i.e., the name of the program as specified on the command line) argv[1] is the first command line argument, arg[2] the second command line argument, and so forth.

  • For example, consider the program below.

include <iostream.h>
void main(int argc, char *argv[])
{
    cout << "Command invoked was:" 
     << argv[0];
     cout << "Command line parameters were: \n";
     for (int i = 1; i <= argc; ++i)
     cout << argv[i] << '\n';
}
  • Assuming this program were compiled into the executable file test, the command test one two three would result in the following output:
Command invoked was: test
Command line parameters were:
one
two
three

12.2 Funtions with a Variable Number of Arguments

  • C++ allows the specification of functions which take a variable number of arguments, provided that at least one fixed argument is provided. The notation
type name(type arg1, ...)
  • specifies a function that takes at least one argument. The ellipses notation specifies that a variable number of arguments may be specified following the previous arguments.

  • Some means of identifying the number of arguments set to a function must be provided by the programmer. Two means are traditionally adapted. Either the function is designed so that its first parameter is the number of arguments that will be specified, or the argument list is terminated by a special value indicating the end of the argument list. Both means are used in the examples to follow.

  • A set of macros defined in the file “stdarg.h” provides the means for accessing the arguments sent to such a function with a variable number of arguments. These macros are summarized in the following table.

Table 12.1
Macros
function description
va_list Used to define a pointer to the argument list provided to a function
va_start(va_list, lastfix) Used to initialize the argument list for processing. “lastfix” should be the name of the last fixed argument provided to the function.
va_arg(va, list, type) Used to get the next argument
va_end(va_list) Used to “clean up” the argument list after it has been processed
  • The technique used for accessing the argument list is the same in both functions. First, a variable representing the argument list is declared using the va_list macro. Next, the argument list is initialized for processing by calling the va_start macro. The first parameter to this macro should be the argument list pointer variable and the second should be the name of the last fixed argument provided to the function. After va_start has been executed, each succesive argument is accessed using va_arg. Each time this is called it returns the “next” argument. The function max1 stops calling va_arg when it has processed the required number of arguments, while max2 stops when it encounters an argument of zero.

  • The second parameter to va_arg should be the type of the current argument. This is so that it knows how far to advance the argument pointer so that it points to the next argument. In the examples provided all arguments are of type int. However, sould one wish to design a function in which the number as well as types of arguments may vary this introduces a considerable complication.

#include <iostream.h>
#include <stdarg.h>

int max1(int argcnt, ...)
// function to select maximum of set of integer arguments.
// argcnt specifies number of arguments
{
    // argments points to argument list
    va_list arguments;
    // setup for argument list processing
    va_start (arguments, argcnt);

    // current_max is first of the variable arguments

    int next_arg;
    int current_max = va_arg(arguments, int);

    // process remaining arguments
    for (int i = 1; i <= argcnt-1; ++i)
    {
        // get next argument
        next_arg = va_arg(arguments, int);
        if (next_arg > current_max)
        current_max = next_arg;
    };
    return current_max;
}

int max2(int arg1, ...)
// fucntion to select maximum of set of integer arguments terminated by 0
{
    va_list arguments;
    va_start (arguments, arg1);
    int next_arg = arg1;
    int current_max = arg1;

    // process remaining arguments
    while (next_arg != 0)
    {
        if (next_arg > current_max)
        current_max = next_arg;
        next_arg = va_arg(arguments, int);
    };
    return current_max;
}

void man()
{
    cout << max1(3, 4, 5, 2) << '\t' 
    << max1(2, 4, 3) << '\t'
    << max1(5, 1, 3, 4, 1, 2);
    // output will be: 544
    cout << max2(4, 5, 2, 0) << '\t'
    << max2(4, 3, 0) << '\t'
    << max2(1, 3, 4, 1, 2, 0);
    // output will be: 544
}
  • Program illustrating use of variable argument lists in C++

12.3 Function Pointers

  • The statement,
int (* int_fn) (int, int);
  • defines int_fn as a pointer to a function that takes two integer arguments and returns an integer value. Given some function fitting that description such as,
int max(int a, int b)
{
    if (a > b)
    return a;
    else
    return b;
}
  • int_fn can be made to point to that function be that statement int_fn = &max. Alternately, one may omit the address operator, so that the statement int_fn = max has the same effect. To execute the function pointed to by int_fn one writes a statement such as:
cout << (* int_fn)(5, 10).
  • In general, the notation return_type(* name)(type, type, …) defines name to be a pointer to a function of the specified return_type that takes parameters of the specified number and type. The function is called using the notation (* name)(arg, arg, …).

  • The typedef statement is often useful when using pointers to functions. Thus, rather than having to continually repeat int (* name) (int, int) every time one wished to define a pointer to this sort of function, one could create a type identifier called int_fn to represent “pointers to integer functions that take two integer parameters”:

typedef int (* int_fn) (int, int);
  • Or,
typedef float (* float_fn)(float)
  • to represent “pointers to float functions that take a single float parameter”.

  • Pointers to functions are used most often when a function needs the name of a function as a parameter This is illustrated below.

flat poly1(float x)
{
    return x*x + 2*x + 1;
}

float poly2(float x)
{
    return x*x*x + 3*x*x *4;
}

void chart(float_fn fn, float start, float end, float inc)
{
    for (float x = start; x <= end;
    x = x + inc)
    cout << x << fn(x) << '\n';
}

void main()
{
    chart(&poly1, 0.0, 1.0, 0.1);
    chart(&poly2, 0.0, 1.0, 0.1);
}
  • One can envision functions similar to chart that compute that area under the curve of a specified polynomial for a specified interval, or a function that plots a given polynomial on a graphics device.

12.4 Exception Hnadling

  • The keyword try defines a block of code in which erros can be intercepted and processed by programmer specified exception handling routines, specified by the keyword catch. The general form of a catch/try code segment is outlined below.
try {
    // ... do some stuff
}
catch (exception-object) {
    // ... handle this type of exception
}
catch (exceptin-object) {
    // ... handle this type of exception
}
// more catch blocks as needed
  • When an error is detected in the try block, an exception object of the appropariate class is “thrown” using the throw operator. Control is then transferred to the nearest catch block that is specified as catching that class of exception object. If no such catch block can be found the program terminates in the usual manner.

  • The nearest catch block is located by back-tracking through the sequence of function calls that led to the function in which the throw statement was executed. After the catch block is executed, control is tranferred to the next statement at the end of all related catch blocks, rather like executing a break statement within a switch construct. The technique is illustrated below.

class MathErr {
    public:
    MathErr(char *s) { strcpy (msg, s);};
    char msg[8];
};

class IOErr {
    public:
    MathErro(char *s) {strcpy(msg, s);};
    char msg[80];
};

void io_stuff()
{
    ofstream outfile("out.txt");
    if (outfile.fail())
    throw(IOErr("File open error");
    // ... do some stuff
    outfile.close();
}

void math_stuff()
{
    float x, y;
    cin >> x >> y;
    if (y == 0)
    throw(MathErr("Devide by zero"));
    cout << x/y;
    io_stuff();
}

void main()
{
    // any error outside of try block cause program termination in the default manner
    try {
        // MathErr or IOErr objects that are "thrown" will cause control to jump to the appropriate catch block below
        math_stuff();
    }
    catch (MathErr& merror) {
        cout << "Program aborted due to math error: "
        << ioerror.msg << '\n';
    }
    // Control is transferred here after any of the above catch blocks are executed
}
  • Exception handling in this way is a very recent addition to C++ and may not be supported on all installations.

12.5 String Streams

  • The file “strstream.h” provides facilities that allow programmers to treat character strings as input and output stream. It defines two classes: ostrstream and istrstream. The former is used to write to strings, the latter to read from strings. Their use is illustrated here.
#include <iostream.h>
#include <strstream.h>

void main()
{
    char obuf[32];
    int i = 42;
    ostrstream ostr(obuf, 32);
    ostr << "The value of i is " << i;
    // obuf is now the string "The value of i is 42;"
    cout << obuf;
    char ibuf* = "1234";
    istrstream istr(ibuf, 4);
    istr >> i; // i is now 1233
    cout << i;
}

12.6 void*

  • The type void* is a type designed to be compatible with any pointer type. Void pointers cannot be referenced without explicit type casting. Void pointers are usually used to specify function parameters that represent machine addresses. The example below uses the void* data type to implement a function that copies a block of memory.

  • Void pointers are considered archaic by most authors and should be avoided if possible. most problems for which one is tempted to use void pointers could be more reliably solved using templates.

Program illustrating use of void*
#include <iostream.h>
void copy(void *a, void *b, unsigend int size)
// Copy size number of bytes from address a to address b
{
    for (int = 1; <=size; ++i)
    {
        // void pointers must be typecast
        *(char*)b = *(char*)a;
        // characters are single bytes
        ++(char*)a;
        ++(char*)b;
    };
}

void main()
{
    int i=5; j=0;
    copy(&i, &j, sizeof(int));
    cout << j;
    double x=3.14, y=0;
    copy(&x, &y, sizeof(double));
    cout<<y;
}