📄 chapter2.html
字号:
helps greatly when you have a complicated logical expression where one of the terms is another logical expression. To not a term that is itself a logical expression, simply reapply the rule. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> To not a logical expression, not both terms and change the logical operator. Apply this rule recursively as needed. </tr></td></table></blockquote><a name="cpreprocessor"><br></a><big><b>2.2 C Preprocessor</b></big> <br><br><b>2.2.1 Writing Macros</b> <br><br>How do you write macros? Are you aware of the problems that macros can have? Let's take the following SQUARE() macro. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>SQUARE() macro, has problems</b>#define SQUARE(x) x*x</pre></tr></td></table> <br>What is SQUARE(7)? It is 49. What is SQUARE(2+3)? Is it 25? No, it is 11. Expanding the macro gives 2+3*2+3. The multiplication is done first, so now you can see why the result is 11. You may now be inclined to rewrite the SQUARE() macro as follows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>SQUARE() macro, has problems</b>#define SQUARE(x) (x)*(x)</pre></tr></td></table> <br>This macro may still have problems with the unary operators. Consider sizeof SQUARE(10), which is really sizeof (10)*(10), which is not sizeof((10)*(10))! The correct way to write SQUARE() is as follows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>SQUARE() macro, final form</b>#define SQUARE(x) ((x)*(x))</pre></tr></td></table> <br>Notice the extra set of parentheses. This too has problems in some special circumstances. Namely, how does SQUARE(++x) behave? It is actually undefined because it is up to the compiler vendor to decide exactly when multiple post- / pre- increment/decrement operations take place in relation to each other in the entire statement. Let's say x contains three; what is ((++x)*(++x))? Is it 16, 20 or 25? Or what about SQUARE(x++), which is ((x++)*(x++)). Is it 9? Is it 12? The answer is compiler specific. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Macros that reference an argument more than once have problems with arguments that have side effects. </tr></td></table></blockquote>A possible solution to the problem of side effects is to use inline functions, a feature of C++. This is OK, but in doing so, you lose the polymorphic behavior of working on multiple data types automatically. This is because inline functions are true functions and you must declare the data types of the arguments. So, you must write a new inline function for every data type that you want the inline function to work with. This admittedly is a little bit of a pain, but it may be worth it in some cases. <br><br>The best solution, available only in some C++ environments, is the use of templates. Templates are used to describe how to do something, while not specifying the data type to use. Templates are still new and not included in all C++ development environments. They are not included in Microsoft C8. <br><br><b>2.2.2 Using Macros That Contain Scope</b> <br><br>Have you ever written a macro that needed its own scope? If so, how did you go about writing it? Consider the following example. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>A macro that needs a scope, has problems</b>#define POW3(x,y) int i; for(i=0; i<y; ++i) {x*=3;}</pre></tr></td></table><br>The problem with this macro is that because it uses temporary variable i, the body of the macro must be contained within its own scope in C and should be contained in its own scope in C++. Consider the following. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>A macro with scope, has problems</b>#define POW3(x,y) {int i; for(i=0; i<y; ++i) {x*=3;}}</pre></tr></td></table><br>Even this latest fix may have problems as this next example shows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Using POW3(), has problems</b>if (expression) POW3(lNum, nPow);else (statement)</pre></tr></td></table> <br>The problem is the semicolon after POW3. The POW3 macro without the semicolon is a valid statement by itself due to the begin/end braces creating a scope. Adding the semicolon only creates a new statement, which is the problem since the else can then no longer be paired with the if. <br><br>So how can a macro, no matter how many statements it contains, be enclosed in its own scope, be treated like one statement and require that it be terminated by a semicolon? The solution to this problem is subtle but very elegant. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>A macro with scope, final form</b>#define POW3(x,y) do {int i; for(i=0; i<y; ++i) {x*=3;}} while(0)</pre></tr></td></table> <br>By using a do/while loop that never loops, the macro body gets its own scope and requires a semicolon after it. Most optimizing compilers will optimize away the loop that never loops. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Using a do/while loop that never loops is a great way to hide the body of a macro within its own scope. </tr></td></table></blockquote>However, some C compilers (Microsoft C8 included) may complain about the constant zero in while(0) when the highest warning level of the compiler is used. If this occurs, you can use a #pragma to disable the warning. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Pragma to disable warning number 4127 in Microsoft C8</b>#pragma warning(disable:4127)</pre></tr></td></table> <br><b>2.2.3 If Statements in Macros</b> <br><br>If you write a macro that contains if statements, you must be careful not to have the <a href="#danglingelse">dangling else §2.1.13</a> problem previously discussed. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Macro containing if/else, has problems</b>#define ODS(s) if (bDebugging) OutputDebugString(#s)</pre></tr></td></table> <br>One solution would be to enclose the body of the macro in a do/while loop that never loops, which would have the added benefit of requiring the macro to be terminated with a semicolon. This version of ODS() has the dangling else problem. However, through careful usage of the if/else statement, you can eliminate the problem. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Macro containing if/else, problem solved</b>#define ODS(s) if (!bDebugging) {} else OutputDebugString(#s)</pre></tr></td></table> <br>This version of ODS() no longer has the dangling else problem. The solution is to always rework the macro so that it contains both an if clause and an else clause. Again, a semicolon is required after a macro that uses this new form. <br><br>Be careful when writing macros that contain if/else statements not to end the macro with an ending brace, or the macro terminated by a semicolon will actually be treated like two statements. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Never conclude a macro with an ending brace. </tr></td></table></blockquote><b>2.2.4 Ending a Macro with a Semicolon or a Block of Code</b> <br><br>It is possible to use the subtleties of an if/else statement in a macro to your advantage. For example, how would you write a macro that must either be terminated with a semicolon or a block of code? <br><br>The <a href="chapter3.html#winassert">WinAssert() §3.3</a> macro uses this technique to allow a block of code to be conditionally executed. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>WinAssert() syntax</b>/*--- Ended with a semicolon ---*/WinAssert(expression);/*--- Or ended with a block of code ---*/WinAssert(expression) { (block of code) }</pre></tr></td></table> <br>Notice that the WinAssert() syntax allows either a semicolon or a block of code to follow it. The WinAssert() macro looks like the following. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>WinAssert() macro</b>#define WinAssert(exp) if (!(exp)) {AssertError;} else</pre></tr></td></table><br>The key to this WinAssert() macro is that whatever follows the macro is what follows the else statement. A semicolon or a block of code are both valid in this context.<br><br><a name="loopmacros"></a><b>2.2.5 LOOP(), LLOOP() and ENDLOOP Macros</b> <br><br>The vast majority of the loops that I have written start at zero, have a less than comparison with some upper limit and increment the looping variable by one every iteration. Since this is the case, why not abstract this out of the code into a macro? In addition, since the looping variable is used to control the loop, it should not be visible (accessible) outside the loop. The solution involves three macros. The first two, LOOP() and LLOOP(), set up the for loop for int's and long's. The third macro, ENDLOOP, finishes what LOOP() and LLOOP() started. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>The LOOP(), LLOOP() and ENDLOOP macros</b>#define LOOP(nArg) { int _nMax=nArg; int loop; \ for (loop=0; loop<_nMax; ++loop)#define LLOOP(lArg) { long _lMax=lArg; long lLoop; \ for (lLoop=0; lLoop<_lMax; ++lLoop)#define ENDLOOP }</pre></tr></td></table> <br>Notice how the extra begin/end brace pair limit the scope of the loop and lLoop variables and that the loop limit is evaluated only once. This allows costly expressions such as strlen(). to be used as the loop limit because the evaluation takes place once, not on every loop iteration. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Sample code that uses LOOP(), LLOOP() and ENDLOOP</b>LOOP(10) { printf( "%d\n", loop ); } ENDLOOPLLOOP(10) { printf( "%ld\n", lLoop ); } ENDLOOP</pre></tr></td></table> <br>You may be wondering why the C++ method of declaring the loop variable inside the for statement isn't used instead. This would allow you to get rid of the begin brace and ENDLOOP macro. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>C++ loop code, with variable declaration problems</b>for (int loop=0; loop<10; ++loop) { ... }...for (int loop=0; loop<10; ++loop) { ... }</pre></tr></td></table> <br>Unfortunately, the C++ method defines the loop variable from the definition point until the end of the current scope (but this is changing in C++). In other words, the loop variable would now be known outside the scope of the loop and using two LOOP()'s in the same scope would end up declaring the looping variable twice, which cannot be done in the same scope. <br><br><b>2.2.6 NUMSTATICELS() Macro</b> <br><br>Provided you have an array in which the number of elements in the array is known to the compiler, write a macro NUMSTATICELS() (number of static elements) that given only the array name returns the number of elements in the array. Consider the following example. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>NUMSTATICELS() desired behavior example</b>#include <stdio.h>#define NUMSTATICELS(pArray) (determine pArray array size)int main(void){ long alNums[100]; printf( "array size=%d\n", NUMSTATICELS(alNums) ); return 0;} /* main */<br><b>Desired output</b>array size=100</pre></tr></td></table> <br><br>How would you write NUMSTATICELS() so that the above example works? The solution is to write NUMSTATICELS() as follows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>NUMSTATICELS() macro</b>#define NUMSTATICELS(pArray) (sizeof(pArray)/sizeof(*pArray))</pre></tr></td></table> <br>This macro works because sizeof(pArray) is the size in bytes of the entire array and sizeof(*pArray) is the size in bytes of one element in the array. Dividing gives the maximum number of elements possible in the array. NUMSTATICELS() does not need to work on a pointer to a dynamically allocated array. <br><br>So, in the above example, if we assume that sizeof(long) is 4, sizeof(alNums) is 400 and sizeof(*alNums) is 4. Dividing gives the
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -