Arrays of Pointers, Conditional Compilation

Arrays of Pointers

We can declare a pointer to an array or an array of pointers:

void main(void)
{
  int values[3] = {10, 20, 30};

  int* p_values;         // pointer to an int (or int array)
  int* p_vals[3];        // array of pointers to ints

  p_values = values;     // points to an array of ints

  p_vals[0] = &values[0];  // points to an int
  p_vals[1] = &values[1];  // points to an int
  p_vals[2] = &values[2];  // points to an int

  cout << values[1] << endl;         // output is 20
  cout << p_values[1] << endl;       // output is 20
  cout << *(p_values + 1) << endl;   // output is 20
  cout << *p_vals[1] << endl;        // output is 20
  cout << *(*(p_vals + 1)) << endl;  // output is 20

  char* days[7];
  days[0] = "Sunday";
  days[1] = "Monday";
  days[2] = "Tuesday";
  days[3] = "Wednesday";
  days[4] = "Thursday";
  days[5] = "Friday";
  days[6] = "Saturday";

  cout << days[3] << endl;   // Wednesday
}
We could have initialized days like this:
char* days[] = {"Sunday", "Monday", "Tuesday",
                "Wednesday", "Thursday", "Friday",
                "Saturday"};
Arrays of Character Strings

Usually, we want to allocate memory for pointers dynamically. It is easy to allocate memory for each pointer in the array using a loop:

void someFunction(void)
{
  char* days[7]; // an array of pointers to chars

    // allocate space for each array
  for (int i = 0; i < 7; i++)
    days[i] = new char[10];

    // fill in the allocated space
  strcpy(days[0], "Sunday");
  strcpy(days[1], "Monday");
  strcpy(days[2], "Tuesday");
  strcpy(days[3], "Wednesday");
  strcpy(days[4], "Thursday");
  strcpy(days[5], "Friday");
  strcpy(days[6], "Saturday");

    // **** use days[] here ****

    // free the memory allocated
  for (i = 0; i < 7; i++)
    delete [] days[i];
}
Note that this leaves the days[] array as dangling pointers. It is not a problem since the function is returning and we cannot possibly use days[] anymore.
  // free the memory allocated
for (i = 0; i < 7; i++)
{
  delete [] days[i];
  days[i] = NULL;
}
Passing Parameters to a Program

We have seen to declarations for main():

void main(void);    // no parameters, no return
int main(void);     // no parameters, returns an int
main() can also be declared to accept 2 or 3 parameters:
void main(int argc, char* argv[]); 
When you run a program, you type the name of the program followed by 0 or more parameters:
program param1 param2 param3 param4 . . .
These parameters are passed along to the program as an array of NULL terminated strings. We have been passing parameters to programs since the first day:
      command-line                          argc
pico program6.cpp                            2
vi program6.cpp                              2
xlC -o lab6 program6.cpp                     4
mail -s "Subject" user@computer.domain       4
ftp tsx-11.mit.edu                           2 
telnet pccaix.sycrci.pcc.edu                 2  
Accessing Parameters in main

In this example, the number of arguments is 4:

xlC -o Lab6 program6.cpp

argv[0] is "xlC"
argv[1] is "-o"
argv[2] is "Lab6"
argv[3] is "program6.cpp"
Assume we have a C++ file named program6.cpp and we compile it under Turbo C++. The executable file will be called program.exe.
#include <iostream.h>

void main(int paramCount, char *paramList[])
{
  for (int i = 0; i < paramCount; i++)
  {
    cout << "Parameter #" << i << " is ";
    cout << paramList[i] << endl;
  }
}
Typing this at the DOS prompt:
program6 lexicon.txt input.txt output.txt 100
Causes this output from the program:
Parameter #0 is D:\DATA\PCC\CS161\PROGRAM6.EXE
Parameter #1 is lexicon.txt
Parameter #2 is input.txt
Parameter #3 is output.txt
Parameter #4 is 100

It is important to remember that each argument is represented as a string. The number 100 is not the integer 100, but it is the string "100".

Converting Parameters

Since all parameters passed to the program are strings, we need a way to convert them to numbers. There are several conversion functions in the standard C++ library that you can use by including <stdlib.h>. Here's a sample of some of the function declarations and example uses:

atoi(const char string[]);   // string to int
atol(const char string[]);   // string to long
atof(const char string[]);   // string to double

int i = atoi("123");         // i is 123
int j = atoi("32.78");       // j is 32
int k = atoi("a");           // k is 0
double d = atof("32.78");    // d is 32.78
This example is add.cpp:
#include <iostream.h>   // cout
#include <stdlib.h>     // atoi()

void main(int paramCount, char* paramList[])
{
  if (paramCount != 3)
  {
    cout << "Usage: add integer integer" << endl;
    return;
  }
  int operand1 = atoi(paramList[1]);
  int operand2 = atoi(paramList[2]);
  cout << operand1 + operand2 << endl;
}

Example runs:
add 3 5
8

add 3 
Usage: add integer integer

Array Indexing vs. Pointer Arithmetic

Given the declarations: int a[10] and int* p = a, then these all access the same element:
  
a[5]   p[5]   *(a + 5)   *(p + 5)  

void main(void)
{
  int array = {10, 20, 30};
  int* p = array;      // ASSUME: address of array is 1000
                       //   and that an int is 2 bytes

  cout << p << endl;   // contents of p (1000)
  cout << *p << endl;  // contents at address 1000 (10)

  p++;                 // increment p's contents (1002)
  cout << p << endl;   // contents of p (1002)
  cout << *p << endl;  // contents at address 1002 (20)

  (*p)++;              // increment contents at 1002 (21)
  cout << p << endl;   // content of p (1002)
  cout << *p << endl;  // content at address 1002 (21)
  
}

1000
10
1002
20
1002
21
Conditional Compilation
#define DEBUGGING   // remove to disable debug output

void main(void)
{
  . . .
  InitializeProgram();

  ReadArrayFromFile(filename, array, length);

  #ifdef DEBUGGING
    cout << "Read the file:" << endl;
    for (int i = 0; i < length; i++)
      cout << array[i] << endl;
  #endif

  ProcessArray(array, length);

  #ifdef DEBUGGING
    DEBUG_VerifyArray(array, length);
    cout << "Verified array:" << endl;
    for (int i = 0; i < length; i++)
      cout << array[i] << endl;
  #endif
}
Simple Example

This small example is in a source file name debug.cpp:

#include <iostream.h>
 
void main(void)
{
  #ifdef FOO
    cout << "FOO is defined" << endl;
  #else
    cout << "FOO is not defined" << endl;
  #endif
}
Compile the program without using the -D switch and the program is equivalent to this:
void main(void)
{
  cout << "FOO is not defined" << endl;
}

% xlC debug.cpp
% a.out
FOO is not defined
Compile the program with the -D switch and it's the same as this:
void main(void)
{
  cout << "FOO is defined" << endl;
}

% xlC -DFOO debug.cpp
% a.out
FOO is defined
Real World Example

This example shows how one function can work in 3 different situations:

int exists(char *filepath)
{
  int found;

    // Borland's way (DOS)
  #ifdef BORLAND

    struct ffblk finfo;
    found = findfirst(filepath, &finfo, 0);

      // Microsoft's way (Windows)
  #else 

      #ifdef WIN32   // 32-bit Windows

        struct _finddata_t finfo;
        long handle = _findfirst(filepath, &finfo);
        if (handle == -1)
          found = 1;
        else
          found = 0;

      #else  // 16-bit Windows

        struct _find_t finfo;
        found = _dos_findfirst(filepath, 0, &finfo);

      #endif   // 32-bit or 16-bit

  #endif   // DOS or Windows

  if (found == 0)   // it was found here
    return -1;
  else
    return 0;
}
So, for example, if BORLAND is defined then the above code is equivalent to this:
 
int exists(char* filepath)
{
  int found;

  struct ffblk finfo;
  found = findfirst(filepath, &finfo, 0);

  if (found == 0)   // it was found here
    return -1;
  else
    return 0;
}
Back to Outline