What is the #define directive in C++?

The #define directive

#define is a Preprocessor Directivecommand that is executed by the compiler before compiling the program that has the following structure:

#define identifier replacement-text

Before compilation, the compiler replaces any occurrences of identifier that don’t occur as part of a string literaltext enclosed in double quotes in the source code or a commenttext in the source code that is ignored by the compiler with the replacement-text.

This allows #define to create Symbolic Constants and Macros.

Using #define to create Symbolic Constants

Symbolic Constants can store any text as the replacement-text:

#define PI 3.142

The replacement-text here is 3.142, which is a numeric literal.

Another example:

#define PI = 3.142

The replacement-text here is = 3.142, which is not a numeric literal.

The following code provides an example of how to use #define to create Symbolic Constants:

#include <iostream>
#define PI_1 3.142
#define PI_2 = 3.142
using namespace std;
int main() {
double pi_1 = PI_1;
double pi_2 PI_2;
cout << "PI_1: " << pi_1 << endl;
cout << "PI_2: " << pi_2 << endl;
return 0;
}

Lines 7 & 8 in the snippet above show how both Symbolic Constants examples can be used. PI_1 is supplying the value 3.142 to pi_1, whereas PI_2 is supplying = 3.142 to pi_2, which can be identified by the missing = in line 8.

On the contrary, C/C++ Constants declared with the const keyword can only declare type-specific constants:

const double PI = 3.142

The value of the PI constant is 3.142, not = 3.142, as was the case in the second Symbolic Constant example.

The examples above also imply that Symbolic Constants do not apply type checkingprocess of verifying and enforcing the constraints of types.

Using #define to create Macros

Macros also follow the same structure as Symbolic Constants; however, Macros allow arguments to be included in the identifier:

#define SQUARE_AREA(l) ((l) * (l))

Unlike in functions, the argument here is enclosed in parenthesis in the identifier and does not have a type associated with it.

Before compilation, the compiler will replace every instance of SQUARE_AREA(l) by ((l) * (l)), where l can be any expression. An example is shown below:

#include <iostream>
#define SQUARE_AREA(l) ((l) * (l))
using namespace std;
int main() {
int len = 2;
int area = SQUARE_AREA(len);
cout << "Area of Sqaure of length 2 is: " << area << endl;
return 0;
}

Here, the len variable (with value 2) is substituted as the value for l in SQUARE_AREA(l); therefore, the expression ((len) * (len)) evaluates to 4.

Additionally, it is also possible to have multiple arguments in Macros, you simply need to add extra variable names separated by commas:

#define RECTANGLE_AREA(l, w) ((l) * (w))

Lastly, a few traps to be aware of:

In the following snippet, the l argument is not enclosed in a parenthesis in the replacement-text:

#define SQAURE_AREA(l) (l * l)

In this case, if an expression is passed as an argument, it would lead to unexpected behavior, as shown below:

#include <iostream>
#define SQUARE_AREA(l) (l * l)
using namespace std;
int main() {
int area = SQUARE_AREA(2 + 2);
cout << "Area of Sqaure of length 4 is: " << area << endl;
return 0;
}

The area of a square, with a length of 4, is 16. However the result of the above code shows 8. This is due to operator precedence: when l is substituted by 2 + 2, the expression (l * l) becomes (2 + 2 * 2 + 2). Since * has a higher precedence than +, the expression simplifies to 2 + 4 + 2, which eventually results in 8. Therefore, it’s better to enclose every argument in a parentheses.

Another trap that programmers usually fall prey to is using expressions as arguments that modify the behavior of the variable. For example:

#include <iostream>
#define SQUARE_AREA(l) ((l) * (l))
using namespace std;
int main() {
int len = 2;
int area = SQUARE_AREA(len++);
cout << "Area of Sqaure of length 2 is: " << area << endl;
cout << "Len: " << len << endl;
return 0;
}

The area of a square with a length of 2 is being computed. The result should be 4; however, the result of the above code is 6. Here is an explanation as to why this occurs:

If SQAURE_AREA(l) was a function, the len++ argument would pass the value 2 to the function and then increase the value of len to 3. But, in our case, the value of len comes out to be 4. This happens because the compiler first replaces SQUARE_AREA(len++) with ((len++) * (len++)). Then, the compiler evaluates the first len++ and the expression simplifies to (2 * (len++)). At this point, the value of len is 3. Now ,the compiler evaluates the second len++, the expression becomes (2 * 3), and the value of len increses to 4. As a result, the area is calculated to be 6 rather than 4.

Copyright ©2024 Educative, Inc. All rights reserved