?? ch17.htm
字號:
Output: Enter a number (0 to quit): 1
You entered: 1. Square(1): 1. Cube(1): 1.
Enter a number (0 to quit): 2
You entered: 2. Square(2): 4. Cube(2): 8.
Enter a number (0 to quit): 3
You entered: 3. Square(3): 9. Cube(3): 27.
Enter a number (0 to quit): 4
You entered: 4. Square(4): 16. Cube(4): 64.
Enter a number (0 to quit): 5
You entered: 5. Square(5): 25. Cube(5): 125.
Enter a number (0 to quit): 6
You entered: 6. Square(6): 36. Cube(6): 216.
Enter a number (0 to quit): 0
</FONT></PRE>
<P><FONT COLOR="#000077"><B>Analysis:</B></FONT><B> </B>On lines 3 and 4, two inline
functions are declared: <TT>Square()</TT> and <TT>Cube()</TT>. Each is declared to
be inline, so like a macro function these will be expanded in place for each call,
and there will be no function call overhead.<BR>
As a reminder, expanded inline means that the content of the function will be placed
into the code wherever the function call is made (for example, on line 16). Because
the function call is never made, there is no overhead of putting the return address
and the parameters on the stack.</P>
<P>On line 16, the function <TT>Square</TT> is called, as is the function <TT>Cube</TT>.
Again, because these are inline functions, it is exactly as if this line had been
written like this:</P>
<PRE><FONT COLOR="#0066FF">16: cout << ". Square(" << x << "): " << x * x << ".
Cube(" << x << Â"): " << x * x * x <<
"." << endl;</FONT></PRE>
<P>
<PRE><FONT COLOR="#0066FF"></FONT></PRE>
<H3 ALIGN="CENTER"><A NAME="Heading24"></A><FONT COLOR="#000077">String Manipulation</FONT></H3>
<P>The preprocessor provides two special operators for manipulating strings in macros.
The stringizing operator (<TT>#</TT>) substitutes a quoted string for whatever follows
the stringizing operator. The concatenation operator bonds two strings together into
one.
<H4 ALIGN="CENTER"><A NAME="Heading25"></A><FONT COLOR="#000077">Stringizing</FONT></H4>
<P>The stringizing operator puts quotes around any characters following the operator,
up to the next white space. Thus, if you write</P>
<PRE><FONT COLOR="#0066FF">#define WRITESTRING(x) cout << #x
</FONT></PRE>
<P>and then call</P>
<PRE><FONT COLOR="#0066FF">WRITESTRING(This is a string);
</FONT></PRE>
<P>the precompiler will turn it into</P>
<PRE><FONT COLOR="#0066FF">cout << "This is a string";
</FONT></PRE>
<P>Note that the string <TT>This is a string</TT> is put into quotes, as required
by <TT>cout</TT>.
<H4 ALIGN="CENTER"><A NAME="Heading26"></A><FONT COLOR="#000077">Concatenation</FONT></H4>
<P>The concatenation operator allows you to bond together more than one term into
a new word. The new word is actually a token that can be used as a class name, a
variable name, an offset into an array, or anywhere else a series of letters might
appear.</P>
<P>Assume for a moment that you have five functions, named <TT>fOnePrint</TT>, <TT>fTwoPrint</TT>,
<TT>fThreePrint</TT>, <TT>fFourPrint</TT>, and <TT>fFivePrint</TT>. You can then
declare:</P>
<PRE><FONT COLOR="#0066FF">#define fPRINT(x) f ## x ## Print
</FONT></PRE>
<P>and then use it with <TT>fPRINT(Two)</TT> to generate <TT>fTwoPrint</TT> and with
<TT>fPRINT(Three)</TT> to generate <TT>fThreePrint.</TT></P>
<P>At the conclusion of Week 2, a <TT>PartsList</TT> class was developed. This list
could only handle objects of type <TT>List</TT>. Let's say that this list works well,
and you'd like to be able to make lists of animals, cars, computers, and so forth.</P>
<P>One approach would be to create <TT>AnimalList</TT>, <TT>CarList</TT>, <TT>ComputerList</TT>,
and so on, cutting and pasting the code in place. This will quickly become a nightmare,
as every change to one list must be written to all the others.</P>
<P>An alternative is to use macros and the concatenation operator. For example, you
could define</P>
<PRE><FONT COLOR="#0066FF">#define Listof(Type) class Type##List \
{ \
public: \
Type##List(){} \
private: \
int itsLength; \
};
</FONT></PRE>
<P>This example is overly sparse, but the idea would be to put in all the necessary
methods and data. When you were ready to create an <TT>AnimalList</TT>, you would
write</P>
<PRE><FONT COLOR="#0066FF">Listof(Animal)
</FONT></PRE>
<P>and this would be turned into the declaration of the <TT>AnimalList</TT> class.
There are some problems with this approach, all of which are discussed in detail
on Day 19, when templates are discussed.
<H3 ALIGN="CENTER"><A NAME="Heading27"></A><FONT COLOR="#000077">Predefined Macros</FONT></H3>
<P>Many compilers predefine a number of useful macros, including <TT>__DATE__</TT>,
<TT>__TIME__</TT>, <TT>__LINE__</TT>, and <TT>__FILE__</TT>. Each of these names
is surrounded by two underscore characters to reduce the likelihood that the names
will conflict with names you've used in your program.</P>
<P>When the precompiler sees one of these macros, it makes the appropriate substitutes.
For <TT>__DATE__</TT>, the current date is substituted. For <TT>__TIME__</TT>, the
current time is substituted. <TT>__LINE__</TT> and <TT>__FILE__</TT> are replaced
with the source code line number and filename, respectively. You should note that
this substitution is made when the source is precompiled, not when the program is
run. If you ask the program to print <TT>__DATE__</TT>, you will not get the current
date; instead, you will get the date the program was compiled. These defined macros
are very useful in debugging.
<H3 ALIGN="CENTER"><A NAME="Heading28"></A><FONT COLOR="#000077">assert()</FONT></H3>
<P>Many compilers offer an <TT>assert()</TT> macro. The <TT>assert()</TT> macro returns
<TT>TRUE</TT> if its parameter evaluates <TT>TRUE</TT> and takes some kind of action
if it evaluates <TT>FALSE</TT>. Many compilers will abort the program on an <TT>assert()</TT>
that fails; others will throw an exception (see Day 20, "Exceptions and Error
Handling").</P>
<P>One powerful feature of the <TT>assert()</TT> macro is that the preprocessor collapses
it into no code at all if <TT>DEBUG</TT> is not defined. It is a great help during
development, and when the final product ships there is no performance penalty nor
increase in the size of the executable version of the program.</P>
<P>Rather than depending on the compiler-provided <TT>assert()</TT>, you are free
to write your own <TT>assert()</TT> macro. Listing 17.5 provides a simple <TT>assert()</TT>
macro and shows its use.</P>
<P><A NAME="Heading29"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 17.5. A simple
assert() macro.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">1: // Listing 17.5 ASSERTS
2: #define DEBUG
3: #include <iostream.h>
4:
5: #ifndef DEBUG
6: #define ASSERT(x)
7: #else
8: #define ASSERT(x) \
9: if (! (x)) \
10: { \
11: cout << "ERROR!! Assert " << #x << " failed\n"; \
12: cout << " on line " << __LINE__ << "\n"; \
13: cout << " in file " << __FILE__ << "\n"; \
14: }
15: #endif
16:
17:
18: int main()
19: {
20: int x = 5;
21: cout << "First assert: \n";
22: ASSERT(x==5);
23: cout << "\nSecond assert: \n";
24: ASSERT(x != 5);
25: cout << "\nDone.\n";
26: return 0;
<TT>27: }</TT></FONT>
<FONT COLOR="#0066FF">
Output: First assert:
Second assert:
ERROR!! Assert x !=5 failed
on line 24
in file test1704.cpp
Done.
</FONT></PRE>
<P><FONT COLOR="#000077"><B>Analysis:</B></FONT><B> </B>On line 2, the term <TT>DEBUG</TT>
is defined. Typically, this would be done from the command line (or the IDE) at compile
time, so you can turn this on and off at will. On lines 8-14, the <TT>assert()</TT>
macro is defined. Typically, this would be done in a header file, and that header
(<TT>ASSERT.HPP</TT>) would be included in all your implementation files.<BR>
<BR>
On line 5, the term <TT>DEBUG</TT> is tested. If it is not defined, <TT>assert()</TT>
is defined to create no code at all. If <TT>DEBUG</TT> is defined, the functionality
defined on lines 8-14 is applied.</P>
<P>The <TT>assert()</TT> itself is one long statement, split across seven source
code lines, as far as the precompiler is concerned. On line 9, the value passed in
as a parameter is tested; if it evaluates <TT>FALSE</TT>, the statements on lines
11-13 are invoked, printing an error message. If the value passed in evaluates <TT>TRUE</TT>,
no action is taken.
<H4 ALIGN="CENTER"><A NAME="Heading31"></A><FONT COLOR="#000077">Debugging with assert()</FONT></H4>
<P>When writing your program, you will often know deep down in your soul that something
is true: a function has a certain value, a pointer is valid, and so forth. It is
the nature of bugs that what you know to be true might not be so under some conditions.
For example, you know that a pointer is valid, yet the program crashes. <TT>assert()</TT>
can help you find this type of bug, but only if you make it a regular practice to
use <TT>assert()</TT> liberally in your code. Every time you assign or are passed
a pointer as a parameter or function return value, be sure to assert that the pointer
is valid. Any time your code depends on a particular value being in a variable, <TT>assert()</TT>
that that is true.</P>
<P>There is no penalty for frequent use of <TT>assert()</TT>; it is removed from
the code when you undefine debugging. It also provides good internal documentation,
reminding the reader of what you believe is true at any given moment in the flow
of the code.
<H4 ALIGN="CENTER"><A NAME="Heading32"></A><FONT COLOR="#000077">assert() Versus
Exceptions</FONT></H4>
<P>On Day 20, you will learn how to work with exceptions to handle error conditions.
It is important to note that <TT>assert()</TT> is not intended to handle runtime
error conditions such as bad data, out-of-memory conditions, unable to open file,
and so forth. <TT>assert()</TT> is created to catch programming errors only. That
is, if an <TT>assert()</TT> "fires," you know you have a bug in your code.</P>
<P>This is critical, because when you ship your code to your customers, instances
of <TT>assert()</TT> will be removed. You can't depend on an <TT>assert()</TT> to
handle a runtime problem, because the <TT>assert()</TT> won't be there.</P>
<P>It is a common mistake to use <TT>assert()</TT> to test the return value from
a memory assignment:</P>
<PRE><FONT COLOR="#0066FF">Animal *pCat = new Cat;
Assert(pCat); // bad use of assert
pCat->SomeFunction();
</FONT></PRE>
<P>This is a classic programming error; every time the programmer runs the program,
there is enough memory and the <TT>assert()</TT> never fires. After all, the programmer
is running with lots of extra RAM to speed up the compiler, debugger, and so forth.
The programmer then ships the executable, and the poor user, who has less memory,
reaches this part of the program and the call to <TT>new</TT> fails and returns <TT>NULL</TT>.
The <TT>assert()</TT>, however, is no longer in the code and there is nothing to
indicate that the pointer points to <TT>NULL</TT>. As soon as the statement <TT>pCat->SomeFunction()</TT>
is reached, the program crashes.</P>
<P>Getting <TT>NULL</TT> back from a memory assignment is not a programming error,
although it is an exceptional situation. Your program must be able to recover from
this condition, if only by throwing an exception. Remember: The entire <TT>assert()</TT>
statement is gone when <TT>DEBUG</TT> is undefined. Exceptions are covered in detail
on Day 20.
<H4 ALIGN="CENTER"><A NAME="Heading33"></A><FONT COLOR="#000077">Side Effects</FONT></H4>
<P>It is not uncommon to find that a bug appears only after the instances of <TT>assert()</TT>
are removed. This is almost always due to the program unintentionally depending on
side effects of things done in <TT>assert()</TT> and other debug-only code. For example,
if you write</P>
<PRE><FONT COLOR="#0066FF">ASSERT (x = 5)
</FONT></PRE>
<P>when you mean to test whether <TT>x == 5</TT>, you will create a particularly
nasty bug.</P>
<P>Let's say that just prior to this <TT>assert()</TT> you called a function that
set <TT>x</TT> equal to 0. With this <TT>assert()</TT> you think you are testing
whether <TT>x</TT> is equal to 5; in fact, you are setting <TT>x</TT> equal to 5.
The test returns <TT>TRUE</TT>, because <TT>x = 5</TT> not only sets <TT>x</TT> to
5, but returns the value <TT>5</TT>, and because 5 is non-zero it evaluates as <TT>TRUE</TT>.</P>
<P>Once you pass the <TT>assert()</TT> statement, <TT>x</TT> really is equal to 5
(you just set it!). Your program runs just fine. You're ready to ship it, so you
turn off debugging. Now the <TT>assert()</TT> disappears, and you are no longer setting
<TT>x</TT> to 5. Because <TT>x</TT> was set to 0 just before this, it remains at
0 and your program breaks.</P>
<P>In frustration, you turn debugging back on, but hey! Presto! The bug is gone.
Once again, this is rather funny to watch, but not to live through, so be very careful
about side effects in debugging code. If you see a bug that only appears when debugging
is turned off, take a look at your debugging code with an eye out for nasty side
effects.
<H4 ALIGN="CENTER"><A NAME="Heading34"></A><FONT COLOR="#000077">Class Invariants</FONT></H4>
<P>Most classes have some conditions that should always be true whenever you are
finished with a class member function. These class invariants are the sine qua non
of your class. For example, it may be true that your <TT>CIRCLE</TT> object should
never have a radius of zero, or that your <TT>ANIMAL</TT> should always have an age
greater than zero and less than 100.</P>
<P>It can be very helpful to declare an <TT>Invariants()</TT> method that returns
<TT>TRUE</TT> only if each of these conditions is still true. You can then <TT>ASSERT(Invariants())</TT>
at the start and completion of every class method. The exception would be that your
<TT>Invariants()</TT> would not expect to return <TT>TRUE</TT> before your constructor
runs or after your destructor ends. Listing 17.6 demonstrates the use of the <TT>Invariants()</TT>
method in a trivial class.</P>
<P><A NAME="Heading35"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 17.6. Using
Invariants().</B></FONT></P>
<PRE><FONT COLOR="#0066FF">0: #define DEBUG
1: #define SHOW_INVARIANTS
2: #include <iostream.h>
3: #include <string.h>
4:
5: #ifndef DEBUG
6: #define ASSERT(x)
7: #else
8: #define ASSERT(x) \
9: if (! (x)) \
10: { \
11: cout << "ERROR!! Assert " << #x << " failed\n"; \
12: cout << " on line " << __LINE__ << "\n"; \
13: cout << " in file " << __FILE__ << "\n"; \
14: }
15: #endif
16:
17:
18: const int FALSE = 0;
19: const int TRUE = 1;
20: typedef int BOOL;
21:
22:
23: class String
24: {
25: public:
26: // constructors
27: String();
28: String(const char *const);
29: String(const String &);
30: ~String();
31:
32: char & operator[](int offset);
33: char operator[](int offset) const;
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -