Contents  1-5  6-11  12-16  17-21  22-27   28-33  34 - 38  39-46  Projects   MFC

   Contact
   C
   C++
   Visual Basic
   Java
   JavaScript
   DHTML
   Style Sheets
   About
   Normalization
   Active X
   TDC Binding
   PHP
   Perl and CGI
   Flash
   XML
   SQL
   Chat
   MCSE
   Linux
   Cabling   
 

   
 
    
    

In C++, the preprocessor processes code after the # sign at compile time.  When the compiler sees the preprocessor
sign, it performs those operations first before compiling the code.  This can be useful for creating a program that will
compile into different versions based upon altering the code that is preprocessed.  It looks like it's time for a few more
keywords:

"You must be the change you wish to see in the world." - Mahatma Gandhi

#define - defines a string substitution Example: #define BIG 512 will substitute string 512 everywhere it sees string BIG.
token - string of characters than can be used wherever a string, constant or other set of letters might be used. BIG is a token.

#define BIG 512      //BIG becomes a token for the literal constant, 512.
int MyArray[BIG];
    //is the same as writing int MyArray[512];

Using #define for constants substitutes for them, but this is not always a good idea, since #define makes only a string substitution and never does any type checking.  Let's look at some other keywords:

#ifdef - tests whether a string has been defined.
#ifndef - tests whether a string has NOT been defined.
#endif - used to end an "ifdef" or "ifndef" statement.
#else - used with define, operates like if/else statement.

#define DEBUG
#ifdef DEBUG
       cout << "Debug defined.";
#endif

This would add the cout << "Debug defined." to the source code at compile time since it was defined.  If we commented it out, it would not be added.  Another example:

#define DemoVersion
#define DOS_VERSION 6
#include <iostream.h>

int main()
{
    cout << "Checking on the definitions of DemoVersion, DOS_VERSION";
    cout << "and WINDOWS_VERSION...\n";

    #ifdef DemoVersion
           cout << "DemoVersion defined.\n";
    #else
           cout << "DemoVersion not defined.\n";
    #endif

    #ifndef DOS_VERSION
            cout << "DOS_VERSION not defined!\n";
    #else
            cout << "DOS_VERSION defined as: " << DOS_VERSION << endl;
    #endif

    #ifdef WINDOWS_VERSION
           cout << "WINDOWS_VERSION defined!\n";
    #else
           cout << "WINDOWS_VERSION was not defined.\n";
    #endif
           cout << "Done.\n";

    return 0;
}

Output:
Checking on the definitions of DemoVersion, DOS_VERSION and WINDOWS_VERSION...
DemoVersion defined...
DOS_VERSION defined as: 5
WINDOWS_VERSION was not defined.
Done.


In derivation and polymorphism, there is the danger of including a declaration of the same class more than once.  Improper inclusion will generate a compiler error.  This becomes a serious problem when your project consists of many separate source and header files.  If the Animal class is declared in "ANIMAL.HPP" then the Dog class, which derives from Animal, must include it in "DOG.HPP".  The Cat class also includes "ANIMAL.HPP" for the same reason.  If you create a method that uses Dog and Cat, you might include Animal twice.  We can solve this problem with inclusion guards.  The use of inclusion guards may save you hours of debugging.  Cause compiler to test a condition before including a file. If the Animal has not been included statement evaluates to be true, the Animal code is added.  If the Animal has been included statement evaluates to be false, the Animal code is not included a 2nd erroneous time.  At top of the base class, in "ANIMAL.HPP" in this case, we would write:

#ifndef ANIMAL_HPP
#define ANIMAL_HPP
        ***REST OF ENTIRE FILE GOES HERE***
#endif

Defining on the command line involves placing special debugging code between "#ifdef DEBUG" and "#endif".  This allows special code to be turned on or off for debugging by whether or not "DEBUG" is defined.  Using #define commands,  you may compile for different operating systems using the same source code.  This is called CONDITIONAL compilation.  Let's look at one more keyword:

#undef - This is the opposite of and antidote for define. It undefines a previously defined token.

Macro functions - A symbol created using #define and taking an argument, like a function.  The preprocessor will substitute the substitution string for whatever argument it is given.  Common macro functions that return the greater or lesser of 2 values are:

#define MAX(x,y) ( (x) > (y) ? (x) : (y) ) 
#define MIN(x,y) ( (x) < (y) ? (x) : (y) )

Below is an examples that will double what is passed in: 

#define TWICE(x) ( (x) * 2)
TWICE(4) 

In the final intermediate code, the entire string "TWICE(4)" will be removed and the value "8" will be substituted in its place.  The parameters must come DIRECTLY after the substitution string, the preprocessor is not as forgiving of white space.  Example:

#define MAX (x,y) ( (x) > (y) ? (x) : (y) )  //wrong; white space between MAX and (x,y)
int x = 5, y = 7, z;
z = MAX(x.y);

The expression below creates intermediate code that is a simple text substitution rather than invoking the macro:

int x = 5, y = 7, z;
z = (x,y) ( (x) > (y) ? (x) : (y) ) (x,y)

When white space is removed, the intermediate code becomes:

int x = 5, y = 7, z;
z = 7;

Parentheses help avoid unwanted side effects when passing complicated values to a macro.  Example:

//Macro Expansion
#include <iostream.h>
#define CUBE(a) ( (a) * (a) * (a) )
#define THREE(a) a * a * a
int main()
{
    long x = 5;
    long y = CUBE(x);
    long z = THREE(x);
    cout << "y: " << y << endl;
    cout << "z: " << z << endl;
    long a = 5, b = 7;
    y = CUBE(a+b);
    z = THREE(a+b);
    cout << "y: " << y << endl;
    cout << "z: " << z << endl;
    return 0;
}

Output:
y: 125
z: 125
y: 1728
z: 82

The reason for the different outcomes to what should essentially be the same answer lies in whether or not we use parentheses.  The macro, CUBE, is defined using parentheses.  The macro, THREE, is defined without using parentheses.  In first instance, the value 5 is given to both macros as a parameter, and they both work out to 125.

However, in the next instance, the parameter becomes "5 + 7".  In the case of the macro, CUBE, it evaluates to ( (5+7) * (5+7) * (5+7) ), which evaluates to ( (12) * (12) * (12) ) which evaluates to 1,728.  In the case of the macro, THREE, however, there are no parentheses.  Since multiplication has a higher precedence (binds tighter) than addition, the expression evaluates to 5 + 7 * 5 + 7 * 5 + 7 which evaluates to 5 + (7*5) + (7*5) + 7 which evaluates to 5 + (35) + (35) + 7 which evaluates to 82

There are 4 general problems with macros:

1 - Macros are confusing if large, they have to be defined on one line.  You can extend the line using "\", but they can still be confusing.
2 - Macros are expanded inline each time they are used.  Though this runs quicker, it increases program size.
3 - Macros do not appear in the intermediate source code, and so are unavailable to most debuggers.
4 - Macros are not type-safe.  Any argument can be used, and this undermines the strong typing of C++. Templates overcome this.

Stringizing - The stringizing operator is "#".  It puts quotes around any characters following the operator, until the next white space.  For example, if you write
#define WRITESTRING(x) cout << #x and then call WRITESTRING(This is a string); the precompiler will turn it into cout << "This is a string";.  The string, "This is a string", is put into quotes as required by cout. 

Concatenation - Concatenation allows you to bond together more than one term into a new word.  A new word is usually a token that can be used as a class name, a variable name, an offset to an array, or anywhere else a series of letters might appear.  To do this we'd write ## between the items we want to concatenate.  What if we write:

#define fPRINT(x) f ## x ## Print
fPRINT(Two)
fPRINT(Three)

In this case,the precompiler would generate "fTwoPrint" and "fThreePrint".   If we wrote:

#define Listof(Type) class Type##List \
{ \
    public: \
    Type##List() {} \

    private: \
    int intsLength; \
};

When we were ready to create the class AnimalList, we'd write "Listof(Animal)", and this would be turned into the declaration of the class AnimalList at compile time.  There are some problems with this method that are solved by using Templates.

Predefined macros - Many compilers predefine macros such as _LINE_ , _DATE_ , _TIME_ , and _FILE_ .  Each are surrounded by two underscore characters to make sure they don't conflict with other file names.  Remember the substitution is made when the source is compiled, not when the program is run. Therefore,  _DATE_  will render the date the program was compiled on, not the current date. 

assert() - The assert() function is offered by many compilers, or we can create our own.  This function returns true if its parameter evaluates to be true, and takes an action if it evaluates to be false.  The precompiler collapses it into no code at all if DEBUG is not defined.  Compilers abort the program or throw exceptions when assert() fails.  Remember that white space really matters with #, the preprocessor, defines and macros.  If you are going to go to the next line, you must use the " \ " character and go to the line immediately after it, with NO lines in between:

//ASSERTS - Remember to use the " \ " when skipping to the next line wit no lines in between.
#define DEBUG

#include <iostream>
using namespace std;

#ifndef DEBUG
        #define ASSERT(x)
#else
        #define ASSERT(x) \
        if(! (x)) \
        {   \
          cout << "ERROR!! The Assert() argument " << #x << " failed\n"; \
          cout << " on line " << __LINE__ << "\n"; \
          cout << " in file " << __FILE__ << "\n"; \
        }
#endif


int main()
{
    int x = 5;

    cout << "First assert: \n";

   
//Evaluates true - since x has the value of 5.
    ASSERT(x==5);

    cout << "\nSecond assert: \n";

   
//Evaluates false - since x does have the value of 5.
    ASSERT(x != 5);

    cout << "\nDone.\n";

    return 0;
}

Output:
First assert:
Second assert:
ERROR!! Assert x!=5 failed
on line 24
in file test2103.cpp

In the example above, DEBUG is defined and the ASSERT() macro is defined.  This is typically done in a header file and included in main().  As we proceed, the term DEBUG is tested.  If it is not defined, ASSERT() is defined to create no code and do nothing at all.  If DEBUG is defined, and ASSERT() returns false, an error message will be displayed showing the line number and file name.  In this example, ASSERT() is 1 long statement split across 7 lines using " \ ".  The value passed in to ASSERT() as a parameter is tested.  If it evaluates to be false, the error message is invoked, printing a message.  If the value passed in is true, no action is taken.  Any time code depends on a particular value being in a particular variable, you may want to use ASSERT() to evaluate that it is true. 

Are there side effects?  Yes, ASSERT() can have side effects.  If you write ASSERT(x = 5) using the assignment operator, when you really mean to test whether or not x==5 using the evaluates to operator, you will create a nasty bug.  If x was previously 0 and you want to check to see if it is equal to 5, you may not realize that you are actually setting x to be equal to 5, and then returning the value 5.  The test will always return true, since x is 5 (true) and not 0 (false).  When you turn debugging off, x no longer gets set to 5, and so the program breaks.  When debugging is turned back on, it works again because x is set to 5, and not just tested.  Keep an eye out for side effects that show up when debugging is turned off and on.  These bugs can be difficult to find and detect.

Class invariants are conditions that should always be true whenever you are finished with a class member function.  For example, Circles should NEVER have a radius of 0, or Animals should ALWAYS have an age > 0 but < 200.  To test for the conditions, you would declare in
Invariant() method that only returns true if each of these conditions are still true.  Then insert Assert(Invariants()) at the start and completion of each class method.  The exception is that your Invariants() is not expected to return true before your constructor runs, or after your destructor ends.  Example:

#define DEBUG
#define SHOW_INVARIANTS

#include <iostream.h>
#include <string.h>

#ifndef DEBUG
        #define ASSERT(x)
#else
        #define ASSERT(x) \
                if(! (x)) \
                { \
                  cout << "ERROR!! Assert " << #x << " failed\n"; \
                  cout << " on line " << __LINE__ << "\n"; \
                  cout << " in file " << __FILE__ << "\n"; \
                }
#endif

//-------------------------------------------------------------------------------------

class String
{
      public:
     
//Overloaded constructors
      String();
      String(const char * const);

     
//Copy constructor
      String(const String &);
      ~String();

      char & operator[](int offset);
      char operator[](int offset) const;

      String & operator= (const String &);

      int GetLen() const { return itsLen; }
      const char * GetString() const { return itsString; }

      bool Invariants() const;

      private:
      String (int);     
//private constructor
      char * itsString;
      unsigned short itsLen;
};


//The default constructor creates string of 0 bytes
String::String()
{
        itsString = new char[1];
        itsString[0] = '\0';
        itsLen=0;
        ASSERT(Invariants());
}

//A private (helper) constructor, used only by class methods for creating a new string of
//the required size. It is null filled.

String::String(int len)
{
        itsString = new char[len+1];
        for(int i = 0; i<=len; i++)
        itsString[i] = '\0';
        itsLen=len;

        ASSERT(Invariants());
}

// Converts a character array to a String
String::String(const char * const cString)
{
        itsLen = strlen(cString);
        itsString = new char[itsLen+1];
        for(int i = 0; i<itsLen; i++)
             itsString[i] = cString[i];
        itsString[itsLen]='\0';

        ASSERT(Invariants());
}

//The copy constructor
String::String (const String & rhs)
{
        itsLen=rhs.GetLen();
        itsString = new char[itsLen+1];
        for(int i = 0; i<itsLen;i++)
            itsString[i] = rhs[i];
        itsString[itsLen] = '\0';

        ASSERT(Invariants());
}

//The destructor, frees allocated memory
String::~String ()
{
        ASSERT(Invariants());
        delete [] itsString;
        itsLen = 0;
}

//Overloaded operator equals, frees existing memory then copies the string and size
String& String::operator=(const String & rhs)
{
        ASSERT(Invariants());
        if(this == &rhs)
            return *this;

        delete [] itsString;

        itsLen=rhs.GetLen();
        itsString = new char[itsLen+1];
  
        for(int i = 0; i<itsLen;i++)
            itsString[i] = rhs[i];

        itsString[itsLen] = '\0';

        ASSERT(Invariants());

        return *this;
}

//Non-constant offset operator, returns a reference to a character so it can be changed.
char & String::operator[](int offset)
{
       ASSERT(Invariants());
      
       if(offset > itsLen)
           return itsString[itsLen-1];
       else
           return itsString[offset];

       ASSERT(Invariants());
}

//Constant offset operator for use on const objects (see copy constructor)
char String::operator[](int offset) const
{
     ASSERT(Invariants());

     if(offset > itsLen)
         return itsString[itsLen-1];
     else
         return
itsString[offset];

     ASSERT(Invariants());
}


bool String::Invariants() const
{
             #ifdef SHOW_INVARIANTS
                    cout << " String OK. ";
             #endif

             return ( (itsLen && itsString) || (!itsLen && !itsString) );
}

//-------------------------------------------------------------------------------------

class Animal
{
      public:
      Animal():itsAge(1),itsName("Such an animal!")
      {ASSERT(Invariants());}
      Animal(int, const String &);
      ~Animal(){}

      int GetAge() { ASSERT(Invariants()); return itsAge;}
      void SetAge(int Age) 
      { 
           ASSERT(Invariants()); 
           itsAge = Age; 
           ASSERT(Invariants()); 
      }

      String & GetName() { ASSERT(Invariants()); return itsName; }
      void SetName(const String & name)
      { 
           ASSERT(Invariants()); 
           itsName = name; 
           ASSERT(Invariants());
      }

      bool Invariants();

      private:
      int itsAge;
      String itsName;
};


Animal::Animal(int age, const String& name):
itsAge(age),
itsName(name)
{  ASSERT(Invariants());  }


bool
Animal::Invariants()
{
     #ifdef SHOW_INVARIANTS
            cout << " Animal OK. ";
     #endif

     return (itsAge > 0 && itsName.GetLen());
}

//-------------------------------------------------------------------------------------

int main()
{
    Animal Rufus(2,"Rufus");

    cout << "\n" << Rufus.GetName().GetString() << " is ";
    cout << Rufus.GetAge() << " years old.";

    Rufus.SetAge(5);

    cout << "\n" << Rufus.GetName().GetString() << " is ";
    cout << Rufus.GetAge() << " years old.";
 
    return 0;
}
 

Output:
String OK. String OK. String OK. String OK. String OK. String OK. String
OK. Animal OK. String OK. Animal OK.
Rufus is Animal OK. 2 years old. Animal OK. Animal OK. Animal OK.
Rufus is Animal OK. 5 years old. String OK.


In this example, the ASSERT() macro is defined.  If DEBUG is defined, it writes out an error message when the ASSERT() macro evaluates to be false.  Later, the String class member function, "
Invariants()", is declared.  After the object is fully constructed, the Invariants() method is called to confirm proper construction.  This process is repeated for all the other constructors as they are called, one by one.  The destructor then calls Invariants() before it sets out to destroy each object.  The remaining class functions call the Invariant() method first before taking any action, and then again before returning any values.  This validates a fundamental C++ principal:  member functions other than constructors and destructors should work on valid objects, and they should leave them in a valid state.  As the program progresses, the Animal class declares and defines its own Invariants() method. 

Printing interim values - As another way of assisting the debugging process, you can print the current values of pointers, variables and strings for checking assumptions and locating off-by-one loops.  Example:

//Printing values in DEBUG mode
#include <iostream.h>

#define DEBUG

#ifndef DEBUG
        #define PRINT(x)
#else
        #define PRINT(x) \
                cout << #x << ":\t" << x << endl;
#endif


enum BOOL { FALSE, TRUE } ;


int main()
{
    int x = 5;
    long y = 2153978;

    PRINT(x);
    for(int i = 0; i < x; i++)
    {
         PRINT(i);
    }

    PRINT(y);
    PRINT("Bye-bye!");

    int *px = &x;

    PRINT(px);
    PRINT(*px);

    return 0;
}
Output:
x: 3
i: 0
i: 1
i: 2
i: 3
i: 4
y: 2153978
"Bye-bye!": Bye-bye!
px: 0x2100
*px: 5

In this example, the macro prints the current value of the supplied parameter.  The first thing fed to cout is the stringized version of the parameter.  If we pass in x, cout receives "x".  Next, cout receives a quoted string ":t" which prints a colon and then a TAB.  Finally, cout receives the value of a parameter, (x), and then adds "endl" which writes a new line and flushes out the buffer.

Debugging levels - Setting different levels of debugging offers more control than simply turning DEBUG on and off.  To define a debugging level, follow the
#define DEBUG statement with a number.  You can have any number of levels, though it is common to have four: high, medium, low and none.  Below is an example of setting up different levels of debugging on the String and Animal classes.

#include <iostream.h>
#include <string.h>

//So, int value of  0      1        2        3

enum LEVEL { NONE, LOW, MEDIUM, HIGH };
const int FALSE = 0;
const int TRUE = 1;
typedef int BOOL;

//-------------------------------------------------------------------------------------

#define DEBUGLEVEL HIGH


#if DEBUGLEVEL < LOW  
//No debugging. Must be low, medium or high.
    #define ASSERT(x)
#else
    #define ASSERT(x) \
    if(! (x)) \
    { \
      cout << "ERROR!! Assert " << #x << " failed\n"; \
      cout << " on line " << __LINE__ << "\n"; \
      cout << " in file " << __FILE__ << "\n"; \
    }
#endif


#if DEBUGLEVEL < MEDIUM
    #define EVAL(x)
#else
    #define EVAL(x) \
      cout << #x << ":\t" << x << endl;
#endif


#if DEBUGLEVEL < HIGH
    #define PRINT(x)
#else
    #define PRINT(x) \
     cout << x << endl;
#endif

//-------------------------------------------------------------------------------------

class String
{
      public:
     
//Constructors
      String();
      String(const char * const);

      String(const String &);
      ~String();

      char & operator[](int offset);
      char operator[](int offset) const;

      String & operator= (const String &);

      int GetLen()const { return itsLen; }
 
      const char * GetString() const 
      { return itsString; }

      BOOL Invariants() const;

      private:
      String (int); 
//Private constructor
      char * itsString;
      unsigned short itsLen;
};


//Default constructor creates string of 0 bytes
String::String()
{
        itsString = new char[1];
        itsString[0] = '\0';
        itsLen=0;

        ASSERT(Invariants());
}

//Private (helper) constructor, used only by class methods for creating a
//new string of required size. Null filled.

String::String(int len)
{
        itsString = new char[len+1];

        for(int i = 0; i<=len; i++)
            itsString[i] = '\0';

        itsLen=len;

        ASSERT(Invariants());
}

//Converts a character array to a String
String::String(const char * const cString)
{
        itsLen = strlen(cString);
        itsString = new char[itsLen+1];

        for(int i = 0; i<itsLen; i++)
            itsString[i] = cString[i];

        itsString[itsLen]='\0';

        ASSERT(Invariants());
}

//Copy constructor
String::String (const String & rhs)
{
        itsLen = rhs.GetLen();
        itsString = new char[itsLen+1];

        for(int i = 0; i<itsLen;i++)
            itsString[i] = rhs[i];

        itsString[itsLen] = '\0';

        ASSERT(Invariants());
}

//Destructor, frees allocated memory
String::~String ()
{
        ASSERT(Invariants());

        delete [] itsString;
        itsLen = 0;
}

//Overloaded operator equals, frees existing memory then copies string and size.
String & String::operator=(const String & rhs)
{
       ASSERT(Invariants());

       if(this == &rhs)
          return *this;

       delete [] itsString;
       itsLen = rhs.GetLen();
       itsString = new char[itsLen+1];

       for(int i = 0; i<itsLen;i++)
           itsString[i] = rhs[i];

       itsString[itsLen] = '\0';

       ASSERT(Invariants());

       return *this;
}

//Non-constant offset operator, returns reference to character so it can be changed.
char & String::operator[](int offset)
{
     ASSERT(Invariants());

     if(offset > itsLen)
         return itsString[itsLen-1];
     else
         return
itsString[offset];

     ASSERT(Invariants());
}

//Constant offset operator for use on const objects (see copy constructor)
char String::operator[](int offset) const
{
     ASSERT(Invariants());

     if(offset > itsLen)
         return itsString[itsLen-1];
     else
         return
itsString[offset];

     ASSERT(Invariants());
}


BOOL String::Invariants() const
{
     PRINT("(String Invariants Checked)");
     return ( (BOOL) (itsLen && itsString) || (!itsLen && !itsString) );
}

//-------------------------------------------------------------------------------------

class Animal
{
      public:
      Animal():itsAge(1),itsName("John Q. Animal")
      { ASSERT(Invariants()); }
      Animal(int, const String &);
      ~Animal(){}

      int GetAge() 
      { 
          ASSERT(Invariants()); 
          return itsAge;
      }

      void SetAge(int Age) 
      { 
           ASSERT(Invariants()); 
           itsAge = Age; 
           ASSERT(Invariants()); 
      }

      String & GetName() 
      { 
              ASSERT(Invariants()); 
              return itsName; 
      }

      void SetName(const String & name)
      { 
           ASSERT(Invariants()); 
           itsName = name; 
           ASSERT(Invariants());
      }

      BOOL Invariants();

      private:
      int itsAge;
      String itsName;
};


Animal::Animal(int age, const String& name):
itsAge(age),
itsName(name)
{  ASSERT(Invariants());  }


BOOL Animal::Invariants()
{
     PRINT("(Animal Invariants Checked)");
     return (itsAge > 0 && itsName.GetLen());
}

//-------------------------------------------------------------------------------------

int main()
{
    const int AGE = 3;
    EVAL(AGE);

    Animal Ruffus(AGE,"Ruffus");

    cout << "\n" << Ruffus.GetName().GetString();
    cout << " is ";
    cout << Ruffus.GetAge() << " years old.";

    Ruffus.SetAge(7);

    cout << "\n" << Ruffus.GetName().GetString();
    cout << " is ";
    cout << Ruffus.GetAge() << " years old.";

    return 0;
}

 
Output 1 (DEBUG set to HIGH):
AGE: 3
(String Invariants Checked)
(String Invariants Checked)
(String Invariants Checked)
(String Invariants Checked)
(String Invariants Checked)
(String Invariants Checked)
(String Invariants Checked)
(String Invariants Checked)
(Animal Invariants Checked)
(String Invariants Checked)
(Animal Invariants Checked)

Ruffus is (Animal Invariants Checked)
3 years old.(Animal Invariants Checked)
(Animal Invariants Checked)
(Animal Invariants Checked)

Ruffus is (Animal Invariants Checked)
7 years old.(String Invariants Checked)

In this example, the ASSERT() macro is defined to be stripped out if the DEBUG level is less than LOW (that is, NONE).  If it is any other level, DEBUG is enabled and ASSERT() works accordingly.  EVAL() is declared to be stripped out if DEBUG is less than MEDIUM or if DEBUG is LOW or NONE.  The PRINT() macro is declared to be stripped out if DEBUGLEVEL is less than HIGH.  It is used only when the debug level is set to its maximum.  You can set level to to MEDIUM to disable this macro, yet still use EVAL() and ASSERT(). 

PRINT() is used within the Invariants() methods to print an informative message.  EVAL() is used to evaluate the current value of the constant integer AGE.  When using macros and preprocessor commands, remember:

  • Use uppercase letters for macro names.
  • Don't allow macros to have side effects.
  • Surround all arguments with parentheses. 

©2004 C. Germany