📄 ch17.htm
字号:
although it is customary to use the filename in all uppercase with the dot (<TT>.</TT>)
changed to an underscore. This is purely convention, however.
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>It never hurts to use inclusion
guards. Often they will save you hours of debugging time.
<HR>
</BLOCKQUOTE>
<H4 ALIGN="CENTER"><A NAME="Heading11"></A><FONT COLOR="#000077">Defining on the
Command Line</FONT></H4>
<P>Almost all C++ compilers will let you <TT>#define</TT> values either from the
command line or from the integrated development environment (and usually both). Thus
you can leave out lines 1 and 2 from Listing 17.1, and define <TT>DemoVersion</TT>
and <TT>BetaTestVersion</TT> from the command line for some compilations, and not
for others.</P>
<P>It is common to put in special debugging code surrounded by <TT>#ifdef DEBUG</TT>
and <TT>#endif</TT>. This allows all the debugging code to be easily removed from
the source code when you compile the final version; just don't define the term <TT>DEBUG</TT>.
<H4 ALIGN="CENTER"><A NAME="Heading12"></A><FONT COLOR="#000077">Undefining</FONT></H4>
<P>If you have a name defined and you'd like to turn it off from within your code,
you can use <TT>#undef</TT>. This works as the antidote to <TT>#define</TT>. Listing
17.2 provides an illustration of its use.</P>
<P><A NAME="Heading13"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 17.2. Using
#undef.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">1: #define DemoVersion
2: #define DOS_VERSION 5
3: #include <iostream.h>
4:
5:
6: int main()
7: {
8:
9: cout << "Checking on the definitions of DemoVersion, DOS_VERSION Â _and WINDOWS_VERSION...\n";
10:
11: #ifdef DemoVersion
12: cout << "DemoVersion defined.\n";
13: #else
14: cout << "DemoVersion not defined.\n";
15: #endif
16:
17: #ifndef DOS_VERSION
18: cout << "DOS_VERSION not defined!\n";
19: #else
20: cout << "DOS_VERSION defined as: " << DOS_VERSION << endl;
21: #endif
22:
23: #ifdef WINDOWS_VERSION
24: cout << "WINDOWS_VERSION defined!\n";
25: #else
26: cout << "WINDOWS_VERSION was not defined.\n";
27: #endif
28:
29: #undef DOS_VERSION
30:
31: #ifdef DemoVersion
32: cout << "DemoVersion defined.\n";
33: #else
34: cout << "DemoVersion not defined.\n";
35: #endif
36:
37: #ifndef DOS_VERSION
38: cout << "DOS_VERSION not defined!\n";
39: #else
40: cout << "DOS_VERSION defined as: " << DOS_VERSION << endl;
41: #endif
42:
43: #if_Tz'WINDOWS_VERSION
44: cout << "WINDOWS_VERSION defined!\n";
45: #else
46: cout << "WINDOWS_VERSION was not defined.\n";
47: #endif
48:
49: cout << "Done.\n";
50: return 0;
<TT>51: }</TT></FONT>
<FONT COLOR="#0066FF">
Output: Checking on the definitions of DemoVersion, DOS_VERSION
 _and WINDOWS_VERSION...\n";
DemoVersion defined.
DOS_VERSION defined as: 5
WINDOWS_VERSION was not defined.
DemoVersion defined.
DOS_VERSION not defined!
WINDOWS_VERSION was not defined.
Done.</FONT></PRE>
<DL>
<DD><FONT COLOR="#0066FF"></FONT>
</DL>
<P><FONT COLOR="#000077"><B>Analysis:</B></FONT><B> </B>Listing 17.2 is the same
as Listing 17.1 until line 29, when <TT>#undef DOS_VERSION</TT> is called. This removes
the definition of the term <TT>DOS_VERSION</TT> without changing the other defined
terms (in this case, <TT>DemoVersion</TT>). The rest of the listing just repeats
the printouts. The tests for <TT>DemoVersion</TT> and <TT>WINDOWS_VERSION</TT> act
as they did the first time, but the test for <TT>DOS_VERSION</TT> now evaluates <TT>TRUE</TT>.
In this second case <TT>DOS_VERSION</TT> does not exist as a defined term.
<H4 ALIGN="CENTER"><A NAME="Heading15"></A><FONT COLOR="#000077">Conditional Compilation</FONT></H4>
<P>By combining <TT>#define</TT> or command-line definitions with <TT>#ifdef</TT>,
<TT>#else</TT>, and <TT>#ifndef</TT>, you can write one program that compiles different
code, depending on what is already <TT>#define</TT>d. This can be used to create
one set of source code to compile on two different platforms, such as DOS and Windows.</P>
<P>Another common use of this technique is to conditionally compile in some code
based on whether <TT>debug</TT> has been defined, as you'll see in a few moments.
<BLOCKQUOTE>
<P>
<HR>
<B>DO</B> use conditional compilation when you need to create more than one version
of your code at the same time. <B>DON'T</B> let your conditions get too complex to
manage. <B>DO</B> use <TT>#undef</TT> as often as possible to avoid leaving stray
definitions in your code. <B>DO</B> use inclusion guards!
<HR>
</BLOCKQUOTE>
<H3 ALIGN="CENTER"><A NAME="Heading16"></A><FONT COLOR="#000077">Macro Functions</FONT></H3>
<P>The <TT>#define</TT> directive can also be used to create macro functions. A macro
function is a symbol created using <TT>#define</TT> and that takes an argument, much
like a function does. The preprocessor will substitute the substitution string for
whatever argument it is given. For example, you can define the macro <TT>TWICE</TT>
as</P>
<PRE><FONT COLOR="#0066FF">#define TWICE(x) ( (x) * 2 )
</FONT></PRE>
<P>and then in your code you write</P>
<PRE><FONT COLOR="#0066FF">TWICE(4)
</FONT></PRE>
<P>The entire string <TT>TWICE(4)</TT> will be removed, and the value <TT>8</TT>
will be substituted! When the precompiler sees the <TT>4</TT>, it will substitute
<TT>( (4) * 2 )</TT>, which will then evaluate to <TT>4 * 2</TT> or <TT>8</TT>.</P>
<P>A macro can have more than one parameter, and each parameter can be used repeatedly
in the replacement text. Two common macros are <TT>MAX</TT> and <TT>MIN</TT>:</P>
<PRE><FONT COLOR="#0066FF">#define MAX(x,y) ( (x) > (y) ? (x) : (y) )
#define MIN(x,y) ( (x) < (y) ? (x) : (y) )
</FONT></PRE>
<P>Note that in a macro function definition, the opening parenthesis for the parameter
list must immediately follow the macro name, with no spaces. The preprocessor is
not as forgiving of white space as is the compiler.</P>
<P>If you were to write</P>
<PRE><FONT COLOR="#0066FF">#define MAX (x,y) ( (x) > (y) ? (x) : (y) )
</FONT></PRE>
<P>and then tried to use <TT>MAX</TT> like this,</P>
<PRE><FONT COLOR="#0066FF">
int x = 5, y = 7, z;
z = MAX(x,y);
</FONT></PRE>
<P>the intermediate code would be</P>
<PRE><FONT COLOR="#0066FF">int x = 5, y = 7, z;
z = (x,y) ( (x) > (y) ? (x) : (y) ) (x,y)
</FONT></PRE>
<P>A simple text substitution would be done, rather than invoking the macro function.
Thus the token <TT>MAX</TT> would have substituted for it <TT>(x,y) ( (x) > (y)
? (x) : (y) )</TT>, and then that would be followed by the <TT>(x,y)</TT> which followed
<TT>Max</TT>.</P>
<P>By removing the space between <TT>MAX</TT> and <TT>(x,y)</TT>, however, the intermediate
code becomes:</P>
<PRE><FONT COLOR="#0066FF">int x = 5, y = 7, z;
z =7;
</FONT></PRE>
<H4 ALIGN="CENTER"><A NAME="Heading17"></A><FONT COLOR="#000077">Why All the Parentheses?</FONT></H4>
<P>You may be wondering why there are so many parentheses in many of the macros presented
so far. The preprocessor does not demand that parentheses be placed around the arguments
in the substitution string, but the parentheses help you to avoid unwanted side effects
when you pass complicated values to a macro. For example, if you define <TT>MAX</TT>
as</P>
<PRE><FONT COLOR="#0066FF">#define MAX(x,y) x > y ? x : y
</FONT></PRE>
<P>and pass in the values <TT>5</TT> and <TT>7</TT>, the macro works as intended.
But if you pass in a more complicated expression, you'll get unintended results,
as shown in Listing 17.3.</P>
<P><A NAME="Heading18"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 17.3. Using
parentheses in macros.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">1: // Listing 17.3 Macro Expansion
2: #include <iostream.h>
3:
4: #define CUBE(a) ( (a) * (a) * (a) )
5: #define THREE(a) a * a * a
6:
7: int main()
8: {
9: long x = 5;
10: long y = CUBE(x);
11: long z = THREE(x);
12:
13: cout << "y: " << y << endl;
14: cout << "z: " << z << endl;
15:
16: long a = 5, b = 7;
17: y = CUBE(a+b);
18: z = THREE(a+b);
19:
20: cout << "y: " << y << endl;
21: cout << "z: " << z << endl;
22: return 0;
<TT>23: }</TT></FONT>
<FONT COLOR="#0066FF">
Output: y: 125
z: 125
y: 1728
z: 82
</FONT></PRE>
<P><FONT COLOR="#000077"><B>Analysis:</B></FONT><B> </B>On line 4, the macro <TT>CUBE</TT>
is defined, with the argument <TT>x</TT> put into parentheses each time it is used.
On line 5, the macro <TT>THREE</TT> is defined, without the parentheses.<BR>
In the first use of these macros, the value <TT>5</TT> is given as the parameter,
and both macros work fine. <TT>CUBE(5)</TT> expands to <TT>( (5) * (5) * (5) )</TT>,
which evaluates to <TT>125</TT>, and <TT>THREE(5)</TT> expands to <TT>5 * 5 * 5</TT>,
which also evaluates to <TT>125</TT>.</P>
<P>In the second use, on lines 16-18, the parameter is <TT>5 + 7</TT>. In this case,
<TT>CUBE(5+7)</TT> evaluates to</P>
<PRE><FONT COLOR="#0066FF">( (5+7) * (5+7) * (5+7) )</FONT></PRE>
<P>which evaluates to</P>
<PRE><FONT COLOR="#0066FF">( (12) * (12) * (12) )</FONT></PRE>
<P>which in turn evaluates to <TT>1728</TT>. <TT>THREE(5+7)</TT>, however, evaluates
to</P>
<PRE><FONT COLOR="#0066FF">5 + 7 * 5 + 7 * 5 + 7</FONT></PRE>
<P>Because multiplication has a higher precedence than addition, this becomes</P>
<PRE><FONT COLOR="#0066FF">5 + (7 * 5) + (7 * 5) + 7</FONT></PRE>
<P>which evaluates to</P>
<PRE><FONT COLOR="#0066FF">5 + (35) + (35) + 7</FONT></PRE>
<P>which finally evaluates to <TT>82</TT>.
<H4 ALIGN="CENTER"><A NAME="Heading20"></A><FONT COLOR="#000077">Macros Versus Functions
and Templates</FONT></H4>
<P>Macros suffer from four problems in C++. The first is that they can be confusing
if they get large, because all macros must be defined on one line. You can extend
that line by using the backslash character (<TT>\</TT>), but large macros quickly
become difficult to manage.</P>
<P>The second problem is that macros are expanded inline each time they are used.
This means that if a macro is used a dozen times, the substitution will appear 12
times in your program, rather than appear once as a function call will. On the other
hand, they are usually quicker than a function call because the overhead of a function
call is avoided.</P>
<P>The fact that they are expanded inline leads to the third problem, which is that
the macro does not appear in the intermediate source code used by the compiler, and
therefore is unavailable in most debuggers. This makes debugging macros tricky.</P>
<P>The final problem, however, is the biggest: macros are not type-safe. While it
is convenient that absolutely any argument may be used with a macro, this completely
undermines the strong typing of C++ and so is anathema to C++ programmers. However,
there is a way to overcome this problem, as you'll see on Day 19, "Templates."
<H3 ALIGN="CENTER"><A NAME="Heading21"></A><FONT COLOR="#000077">Inline Functions</FONT></H3>
<P>It is often possible to declare an inline function rather than a macro. For example,
Listing 17.4 creates a <TT>CUBE</TT> function, which accomplishes the same thing
as the <TT>CUBE</TT> macro in Listing 17.3, but does so in a type-safe way.</P>
<P><A NAME="Heading22"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 17.4. Using
inline rather than a macro.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">1: #include <iostream.h>
2:
3: inline unsigned long Square(unsigned long a) { return a * a; }
4: inline unsigned long Cube(unsigned long a)
5: { return a * a * a; }
6: int main()
7: {
8: unsigned long x=1 ;
9: for (;;)
10: {
11: cout << "Enter a number (0 to quit): ";
12: cin >> x;
13: if (x == 0)
14: break;
15: cout << "You entered: " << x;
16: cout << ". Square(" << x << "): ";
17: cout << Square(x);
18: cout<< ". Cube(" _<< x << "): ";
19: cout << Cube(x) << "." << endl;
20: }
21: return 0;
<TT>22: }</TT></FONT>
<FONT COLOR="#0066FF">
Output: Enter a number (0 to quit): 1
You entered: 1. Square(1): 1. Cube(1): 1.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -