Chapter – 7

Symbolic Constants and Macros

This chapter introduces you to the definition of symbolic constants and macros illustrating their significance and use. In addition, standard macros for character handling are introduced.

 ■ MACROS

Sample program

Screen output

****** Table for the Sine Function ******    .

x	sin(x)
-------------------------
0.000000	0.000000
0.392699	0.382683
0.785398	0.707107
   .	           .
   .	           .
   .	           .

C++ has a simple mechanism for naming constants or sequences of commands, that is for defining macros. You simply use the preprocessor’s #define directive.

Syntax: #define name substitutetext

This defines a macro called name. The preprocessor replaces name with substitute- text throughout the subsequent program. For example, in the program on the opposite page, the name PI is replaced by the number 3.1415926536 throughout the program in the first phase of compilation.
There is one exception to this general rule: substitution does not take place within strings. For example, the statement

cout << ” PI “;

outputs only PI and not the numerical value of PI.

□   Symbolic Constants

Macros that are replaced by constants, such as the PI macro, are also known as symbolic constants. You should note that neither an equals sign nor a semicolon is used, as these would become part of the substitute text.

You can use any macros you have previously defined in subsequent #define direc- tives. The program opposite uses the symbolic constant PI to define other constants.

□   More about Working with Macros

Any preprocessor directive, and this includes the #define directive, must be placed in a line of its own. If the substitute text is longer than one line, you can terminate the line with a backslash \ and continue the substitute text in the next line, as is illustrated by the macro HEADER on the opposite page.

The rules that apply to naming variables also apply to naming macros. However, it is standard practice to capitalize symbolic constants to distinguish them from the names of variables in a program.

Using macros makes a C++ program more transparent and flexible. There are two main advantages:

  1. Good readability: You can name a macro to indicate the use of the macro
  2. Easy to modify: If you need to change the value of a constant throughout a pro- gram, you simply change the value of the symbolic constant in the #define directive.

 ■ MACROS WITH PARAMETERS

Sample program

It is possible to call macros with arguments. To do so, you must supply the appropriate parameters when defining the macro. The parameters are replaced by valid arguments at run time.

Example:  #define SQUARE(a)  ((a) * (a))

This defines a macro called SQUARE() with a parameter a. The name of the macro must be followed immediately by a left bracket. When the macro is called, for example

Example: z = SQUARE(x+1);

the preprocessor inserts the substitute text with the current arguments, which will be expanded as follows, in this case

z = ((x+1) * (x+1));

This example also shows that you must be careful when using brackets to indicate param- eters for macros. Omitting the brackets in the previous example, SQUARE, would cause the expression to be expanded as follows z = x + 1 * x + 1.

The outer brackets in the definition ensure that even when the macro is used in a complex expression, the square is calculated before the result can be used for any further calculations.

□   Macros for Screen Control

The program opposite uses macros to change the appearance of the screen. Peripheral devices, such as the screen or printers, can be controlled by special character sequences that normally begin with the ESC character (decimal 27, octal 033) and are thus known as escape sequences. A number of ANSI standard escape sequences exists for screen con- trol.1 See the appendix on Escape Sequences for Screen Control for an overview of the most important sequences.

CLS is a macro without any parameters that uses the escape sequence \033[2J to clear the screen. LOCATE is just one example of a macro with two parameters. LOCATE uses the escape sequence \033[z;sH to place the cursor at the position of the next screen output. The values z for the line and s for the column require decimal input with z= 1, s = 1 representing the top left corner of the screen or window.

The ball is “thrown in” at the coordinates x = 2, y = 3 and bounces off the “floor” and the “walls.” In direction x (horizontally) the ball has a constant speed of dx = 1 or -1. In direction y (vertically) the ball is subject to a constant acceleration of 1, expressed as speed += 1.

1 These escape sequences are valid for all standard UNIX terminals. The driver ansi.sys must be loaded for DOS or a DOS box in Win95 or Win98. For Win NT and Win 2000, corresponding functions based on system calls are offered for download.

 ■ WORKING WITH THE #define DIRECTIVE

Using macros in different source files

You can place the #define directive in any line of a program as long as it is placed prior to using the macro. However, it is recommended to place all definitions at the beginning of the source file for ease of location and modification.
If you need to use the same macros in different source files, it makes sense to create a header file. You can then include the header file in your source files. This method also lends itself to large-scale software projects. The programmers working on the project then have access to the same set of macro definitions and other declarations. This con- cept is illustrated opposite using the header file proj.h as an example.
Macros with parameters can be called just like functions. You should note the following important differences, however:

Macros: A macro definition must be visible to the compiler. The substitute text is inserted and re-compiled each time the macro is called. For this reason, a macro should contain only a few statements to avoid inflating the object file each time the macro is called. The speed of program execution will, however, improve since the program does not need to branch to sub-routines in contrast to normal func- tion calls. This can become apparent when macros are used within loops, for example.
Side effects of macros are possible if the substitute text contains multiple instances of a parameter. The statement SQUARE( ++x ) expands to ((++x).

  • (++x)), for example. The variable x is incremented twice and the product
    does not represent the square of the incremented number.

Functions: Functions are compiled independently. The linker then links them into the executable file. The program branches to the function whenever it is called, and this can reduce the speed of execution in comparison to a macro. However, the executable file will be shorter as it contains only one instance of the function code.
The compiler checks the argument types, and the side effects seen with macros cannot occur.
Inline functions, which are introduced in the chapter on functions, are an alterna- tive to macros.

■ CONDITIONAL INCLUSION

Multiple inclusions of header files:

□   Redefining Macros

A macro cannot simply be redefined. A macro definition is valid until it is removed by using an #undef directive. However, you do not need to supply the parameter list of a macro with parameters.

Example: 

 #define MIN(a,b) ((a)<(b)? (a) : (b))
 . . .  	// Here MIN can be called 
 #undef MIN

The macro MIN cannot be used after this point, but it can be defined again, possibly with a different meaning, using the #define directive.

□   Conditional Inclusion

You can use the preprocessor directives #ifdef and #ifndef to allow the compiler to check whether a macro has been defined.

Syntax:    #ifdef name
           . . .	// Block, which will be compiled
                        // if name is defined.
           #endif

In the case of the #ifndef directive, the code block is compiled up to the next #endif

only if the macro name has not been previously defined.

On conditional inclusion else branching and nesting is also permissible. See Pre- processor Directives in the appendix for further information.

A macro definition does not need to include a substitute text to be valid.

Example: #define MYHEADER

A symbol without a substitute text is often used to identify header files and avoid multi- ple inclusion.

If you have a header file named “article.h”, you can identify the header by defin- ing a symbol, such as _ARTICLE_, within that file.

Example:

#ifndef _ARTICLE_
#define _ARTICLE_
. . .          // Content of the header file 
#endif

If you have already included the header, _ARTICLE_ will already be defined, and the contents of the header file need not be compiled. This technique is also employed by standard header files.

 ■ STANDARD MACROS FOR CHARACTER MANIPULATION

Sample program

NOTE:
The program reads characters from a file until end-of-file. When reading keyboard input, end-of-file is simulated by Ctrl+Z (DOS) or Ctrl+D (UNIX).

Macros for character classification:

The following section introduces macros that classify or convert single characters. The macros are defined in the header files ctype.h and cctype.

□   Case Conversion

You can use the macro toupper to convert lowercase letters to uppercase. If c1 and c2 are variables of type char or int where c1 contains the code for a lowercase letter, you can use the following statement

Example: c2 = toupper(c1);

to assign the corresponding uppercase letter to the variable c2. However if c1 is not a lowercase letter, toupper(c1) returns the character “as is.”

The sample program on the opposite page reads standard input, converts any letters from lower- to uppercase, and displays the letters. As toupper only converts the letters of the English alphabet by default, any national characters, such as accentuated charac- ters in other languages, must be dealt with individually. A program of this type is known as a filter and can be applied to files. Refer to the next section for details.

The macro tolower is available for converting uppercase letters to lowercase.

□   Testing Characters

A number of macros, all of which begin with is…, are available for classifying charac- ters. For example, the macro islower(c) checks whether c contains a lowercase letter returning the value true, in this case, and false in all other cases.

Example:

char c; cin >> c;	// Reads and
                        // classifies 
if( !isdigit(c) )	// a character.
cout << "The character is no digit \n";

The following usage of islower() shows a possible definition of the toupper() macro:

Example:

#define toupper(c) \
        (islower(c) ? ((c)-'a'+'A') : (c))

This example makes use of the fact that the codes of lower- and uppercase letters differ by a constant, as is the case for all commonly used character sets such as ASCII and EBCDIC.

The opposite page contains an overview of macros commonly used to classify characters.

■ REDIRECTING STANDARD INPUT AND OUTPUT

Sample program:

How to call the program

  1. Redirecting the standard input:

lines < text.dat | more

This outputs the text file text.dat with line numbers. In addition, the data stream is sent to the standard filter more, which pauses screen output if the page is full.

2. Redirecting the standard output:

lines > new.dat

Here the program reads from the keyboard and adds the output to the new file
new.dat. Please note, if the file already exists, it will be overwritten!
You can use:

lines >> text.dat

to append program output to the file text.dat. If the file text.dat does not already exist, it will be created.
Type Ctrl+Z (DOS) or Ctrl+D (UNIX) to terminate keyboard input.

□   Filter Programs

The previous program, toupper.cpp, reads characters from standard input, processes them, and sends them to standard output. Programs of this type are known as filters.

In the program toupper.cpp, the loop

while( cin.get ( c )) { . . . }

is repeated while the test expression cin.get(c) yields the value true, that is, as long as a valid character can be read for the variable c. The loop is terminated by end-of-file or if an error occurs since the test expression cin.get(c) will then be false.

The program on the opposite page, lines.cpp, is also a filter that reads a text and outputs the same text with line numbers. But in this case standard input is read line by line.

while ( getline (cin , line) ) { . . . }

The test expression getline(cin,line) is true while a line can be read.

□   Using Filter Programs

Filter programs are extremely useful since various operating systems, such as DOS, Win**, WinNT, and UNIX are capable of redirecting standard input and output. This allows easy data manipulation.

For example, if you need to output text.dat with line numbers on screen, you can execute the program lines by typing the following command:

Example: lines < text.dat

This syntax causes the program to read data from a file instead of from the keyboard. In other words, the standard input is redirected.

The opposite page contains additional examples. You can redirect input and output simultaneously:

Example: lines < text.dat > new.dat

In this example the contents of text.dat and the line numbers are stored in new.dat. The program does not generate any screen output.

NOTE:
These examples assume that the compiled program lines.exe is either in the current directory or in a directory defined in your system’s PATH variable.
EXERCISES

Hints for Exercise 2
You can use the function kbhit() to test whether the user has pressed a key. If so, the function getch() can be used to read the character.This avoids interrupting the program when reading from the keyboard.
These functions have not been standardized by ANSI but are available on almost every system. Both functions use operating system routines and are declared in the header file conio.h.

The function kbhit()
Prototype: int kbhit ();
Returns: 0, if no key was pressed, otherwise != 0.
When a key has been pressed, the corresponding character can be read by getch().

The function getch()
Prototype: int getch ();
Returns: The character code.There is no special return value on reaching end-of-file or if an error occurs.
In contrast to cin.get(), getch() does not use an input buffer when reading characters, that is, when a character is entered, it is passed directly to the program and not printed on screen. Additionally, control characters, such as return ( = 13), Ctrl+Z ( = 26), and Esc ( = 27), are passed to the program “as is.”

Example:

int c;
if( kbhit() != 0) // Key was pressed?
{
   c = getch();	        // Yes -> Get character 
   if( c == 27 )	// character == Esc?
   // . . .
}
NOTE: When a function key, such as F1, F2, ..., Ins, Del, etc. was pressed, the function
getch() initially returns 0. A second call yields the key number.
Exercise 1

Please write

  1. the macro ABS, which returns the absolute value of a number,
  2. the macro MAX, which determines the greater of two numbers.

In both cases use the conditional operator ?: .

Add these macros and other macros from this chapter to the header file myMacros.h and then test the macros.

If your system supports screen control macros, also add some screen control macros to the header. For example, you could write a macro named COLOR(f,b) to define the foreground and background colors for the following output.

Exercise 2

Modify the program ball1.cpp to

  1. display a white ball on a blue background,
  2. terminate the program when the Esc key is pressed,
  3. increase the speed of the ball with the + key and decrease the speed with the – key.

You will need the functions kbhit() and getch() (shown opposite) to solve parts b and c of this problem.

Exercise 3

Write a filter program to display the text contained in any given file.The program should filter any control characters out of the input with the exception of the characters \n (end-of-line) and \t (tabulator), which are to be treated as normal characters for the purpose of this exercise. Control characters are defined by codes 0 to 31.

A sequence of control characters is to be represented by a single space character.

A single character, that is, a character appearing between two control characters, is not to be output!

NOTE: Since the program must not immediately output a single character following a control character, you will need to store the predecessor of this character. You may want to use two counters to count the number of characters and control characters in the current string.

SOLUTIONS

Exercise 1

//

// myMacros.h

// Header file contains the Macros

// ABS, MIN, MAX, CLS, LOCATE, COLOR, NORMAL, INVERS

// and symbolic constants for colors.

//

#ifndef _MYMACROS_

#define _MYMACROS_

#include <iostream>

using namespace std;

//

// Macro ABS

// Call: ABS( val)

// Returns the absolute value of val

#define ABS ( a ) ( ( a ) >= 0 ? ( a ) : – ( a ) )

//

// Macro MIN

// Call: MIN(x,y)

// Returns the minimum of x and y

#define MIN ( a, b) ( ( a ) <= ( b ) ? ( a ) : ( b ) )

//

// Macro MAX

// Call: MAX(x,y)

// Returns the maximum of x and y

#define MAX (a, b ) ( ( a ) >= ( b ) ? ( a ) : ( b ) )

//

// Macros for controlling the screen

//

// Macro CLS

// Call: CLS;

// Clears the screen

#define CLS   ( cout << ” \033[2J “)

//

// Macro LOCATE

// Call: LOCATE(row, column);

// Positions the cursor to (row,column).

// (1,1) is the upper left corner.

#define LOCATE ( r, c ) (cout <<” \033 [ ” << ( r ) << ‘; ‘<<( c )<< ‘ H ‘ )

//

// Macro COLOR

// Call: COLOR(foreground, background);

// Sets the foreground and background color

// for the following output.

#define COLOR( f, b ) (cout << ” \033 [ 1;3″<< ( f ) \

<<“;4″<< (b) <<‘m’ << flush)

// 1: light foreground

// 3x: foreground x

// 4x: background x

// Color values for the macro COLOR

// To call ex.: COLOR( WHITE,BLUE);

#define BLACK 0

#define RED      1

#define GREEN    2

#define YELLOW   3

#define BLUE     4

#define MAGENTA 5

#define CYAN     6

#define WHITE    7

//

// Macro INVERS

// Call: INVERS;

// The following output is inverted.

#define INVERS (cout << “\033[7m”)

//

// Macro NORMAL

// Call: NORMAL;

// Sets the screen attributes on default values.

#define NORMAL (cout << “\033[0m”)

#endif     // _MYMACROS_

Exercise 2

//———————————–
// ball2.cpp
// Simulates a bouncing ball
//———————————–

#include <iostream>
#include <string> 
using namespace std;
#include <conio.h>	  // For kbhit() and getch() 
#include "myMacros.h"
 
#define ESC	27	  // ESC terminates the program 
unsigned long delay = 5000000;	    // Delay for output

int main()
{
   int x = 2, y = 2, dx = 1, speed = 0; 
   bool end = false;
   string floor(80, '-'),
   header	= "**** BOUNCING BALL ****", 
   commands = "[Esc] = Terminate	"
              "[+] = Speed up   	[-] = Slow down";

COLOR(WHITE,BLUE);	CLS;
LOCATE(1,25); cout << header; 
LOCATE(24,1); cout << floor; 
LOCATE(25,10); cout << commands;

while( !end)	        // As long as the flag is not set
{
   LOCATE(y,x); cout << 'o';	     // Show the ball 
   for( long wait = 0; wait < delay; ++wait)
      ;
   if(x == 1 || x == 79) dx = -dx; // Bounce off a wall? 
   if( y == 23 )	// On the floor?
{
    speed = - speed;
    if( speed == 0 ) speed = -7;	// Kick
}
    speed += 1;	                        // Speed up = 1

    LOCATE(y,x); cout << ' ';	        // Clear screen 
    y += speed; x += dx;	        // New position

    if( kbhit() != 0 )	                // Key pressed?
{
    switch(getch())	                // Yes
{
     case '+': delay -= delay/5;	        // Speed up 
               break;
     case '-': delay += delay/5;	        // Slow down 
               break;
     case ESC: end = true;	                // Terminate
    }
  }
}
 NORMAL;    CLS;
 return  0;
}

Exercise 3

//———————————————–
// NoCtrl.cpp
// Filter to ignore control characters
// To call e.g.: NoCtrl < file
//———————————————–

#include <iostream> 
using namespace std;

#define isCtrl(c)	( c >= 0 && c <= 31 \
                                 && c != '\n' && c != '\t')

int main()
{
   char c, prec = 0;	         // Character and predecessor 
   long nCtrl = 0, nChar = 0;    // Number of the following
                                 // control characters or
                                 // other characters
   while( cin.get(c))
    {
       if( isCtrl(c))	         // Control characters
    {
       ++nCtrl; 
       nChar = 0;
    }
      else	                 // Normal character
    {
      if( nCtrl > 0)
    {
         cout.put(' ');
         nCtrl = 0;
    }
    switch( ++nChar)
    {
         case 1:	break;
         case 2:	cout.put(prec);	    // Predecessor and 
         default:       cout.put(c);	    // current character
     }
     prec = c;
   }
 }
 return 0;
}