This Web page contains an extensive list of corrections to, and comments on, the book "C++ for Java Programmers," by Timothy Budd. These comments and corrections represent the opinion of the course instructor, and are in no way approved or authorized by Mr. Budd.
Index:
max
includes a blank before
the left parenthesis, but the call to that function omits
the blank. This is a bad habit; don't emulate it. It is
your choice whether to place a blank after the function
name, but you should be consistent throughout your
program, in both declarations and invocations. Little
stylistic details like this may seem unimportant, but they
will make a big difference in the readability of your
programs.
scanf
function should not be used in C++
programs. Use cin >> *p
or cin
>> i
instead.
comp
routine is implemented
incorrectly. It should return -1, 0, or 1, depending on
whether a
is respectively less than, equal
to, or greater than b
. A correct
implementation is:
int comp (void * a, void * b) { double * d1 = (double *) a; double * d2 = (double *) b; if (*d1 < *d2) return -1; else if (*d1 > *d2) return 1; else return 0; }Note that it is not sufficient to simply cast
d1-d2
to type int
and return
that value; why not?
set_new_handler
routine.
NULL
(which is defined in
iostream.h
, among other places) as
being more readable and giving a clearer
indication of the programmer's intentions.
In C++, either convention is acceptable, but you
should be consistent in any given program.
inline
keyword) to achieve the same effect without
exposing the implementation to the reader. This
is a personal preference, however, and since many
programmers disagree, you will not be penalized if
you place inline code in your class declarations.
p
and
np
) are rarely, if ever, defensible. They
are a symptom of lazy typists. You will lose points on
your assignments if you choose such short, non-mnemonic
names. (Note also that 1 + strlen(...)
should
be strlen(...) + 1
.)
g
will be destroyed just before the
program exits. When the program terminates, the reference
count for the value "abc"
will reach zero,
and the value will be deleted.
In addition, you should note that global values are almost never necessary and are considered very bad style. You should avoid them like the plague, particularly in this class.
virtual
keyword is removed. The exact size depends on the
hardware being used, and could be as small as 4 (on an
Intel 286 running Windows 3.1) or as large as 16 (on a DEC
Alpha). The only thing that is certain is that the size
will be larger with the virtual
keyword than
without.
On Turing, the size will be either 8 (without
virtual
) or 12 (with virtual
).
printf
routine is C-style I/O, not
C++-style. It should be avoided in C++ programs.
Also, the variable name c
is poor style, and
the string arguments to printf
should have
embedded newlines ("\n
") at the end.
->*
, new[]
, and
delete[]
may be overloaded. In addition,
although they are not exactly operators, typecast
coversions may be overloaded (see Section 7.15).
value
) public. This technique
violates the design principles of encapsulation and data
hiding, and should be scrupulously avoided. Don't do it,
even temporarily!
const
keyword for value
returns is necessary only for class
types.
If you write (a + b) = b;
using integers, the
compiler will reject the assignment without need for the
const
keyword.
const
keyword is necessary
only for the postfix version of the ++
operator. If the prefix version returns a reference to
the object, it may legally return a non-const
object. For example, if a
is an integer, you
can write
++ ++a;but not
a++ ++;(The blanks between the plus signs are not required by the language, but may enhance readability for some people.)
if (this == &right)The code will not compile as given.
box
class could
assign freely. Since the assignment operator is
implemented as a no-op (i.e., it does nothing), this would
cause great confusion while debugging.
A better approach is to write the declaration with a semicolon instead of curly braces:
private: void operator = (box & right);By doing it this way, even member functions are protected against accidental use of the disabled operator (although the error will be detected later, at link time, instead of at compile time).
emptyBox * operator & () { return this; }The code given in the book will not compile.
void
from an assignment operator
is lazy and bad practice. It is simple and standard to
return a reference to this
.
Proper const
declarations are missing from
the example in a number of places: the arguments to the
copy constructor, assignment operator, and addition operator,
as well as the int
type-casting operator.
This lack not only prevents these functions from being
applied to const
objects, but also hides an
ambiguity problem involving the addition operator.
box
as it is
created and deleted, while also tracking the nesting of
various functions. The output
can be difficult to read, but careful study will prove to
be instructive. Note that Budd's original example will
not work exactly as described in the book; see the
comments in my version for more
details.
'\0123'
is an illegal constant. To create a
character with a value of octal 123, you must write
'\123'
. Hexadecimal constants can be written
using the letter "x": '\x53'
is exactly the
same as '\123'
.
wchar_t
). The
notation L'a'
means the letter "a" in a
wide-character type; the notation L'1234'
is
illegal. To generate a wide constant by its octal or
hexadecimal value, the backslash notation must be used:
L'\123456'
or L'\xA72E'
. In
either case, the maximum width of a wchar_t
,
and thus the maximum number of hex or octal digits in a
character constant, is implementation-dependent.
L
modifier can be applied to
strings as well as characters. For example, L"A
Literal Text"
is legal and generates the given text as
a wchar_t
.
strcpy
and strcat
should be the variable name text
, not the
word literal
.
randomInteger
class suffers from two
serious problems. First, there are three different
library functions commonly available for generating
pseudorandom numbers. Of these, rand()
is by
far the poorest, and it should always be avoided.
Much better pseudorandom numbers are generated by the
random()
function, which returns a 31-bit
pseudorandom integer. The best commonly available
generator, however, is drand48()
, which also
has the advantage of returning double-precision values
between zero (inclusive) and 1 (noninclusive). You should
always start with drand48()
as your preferred
generator, and fall back to random()
only if
drand48()
is unavailable.
The other problem with the example code is the use of the
modulo (%
) operator to reduce the pseudorandom
numbers to a small range. NEVER USE MODULAR
ARITHMETIC WITH PSEUDORANDOM NUMBERS!
Most pseudorandom numbers are less random in the least
significant bits. For example, if you use a
max
of 16 in Budd's sample class, you
may find that your "random" number repeats in a cycle
of length 16. The most significant
bits of the number are much more "random."
For this reason, the output of a pseudorandom-number generator should be scaled by division. The easiest way to do it is to convert the result to a double-precision number in the range 0 to 1, and then multiply by the desired range. For example:
#include <stdlib.h> ... int i = ((double)random() / RAND_MAX) * max; int j = max * drand48();Either of these sequences will generate a pseudorandom integer in the range of 0 to
max - 1
,
inclusive.
Finally, astute students will note that I always refer to pseudorandom, not random, numbers. The values returned by the functions under discussion are not truly random, though they can be used as such for many purposes. A complete discussion is beyond the scope of this course, however.
stdio
library is not appropriate for use
in C++ programs. It is a C thing, and Budd should not
have given it space. On many systems, mixing C-style and
C++-style I/O in the same program will cause things to break.
(I have nevertheless included corrections to this section,
since some people might use these pages as a reference for
C programming someday.)
fopen
is
incorrect because the message "file cannot be opened" is
issued to the standard output, stdout
,
rather than standard error, stderr
. Error
messages should never be written to
stdout
. See the following example (after
correcting it, below) for how to one method of writing to
stderr
.
fputs
is incorrect because
the message (msg
) does not include a
newline. There is an unfortunate inconsistency between
puts
and fputs
:
puts
appends a newline, whereas with
fputs
you must provide the newline yourself.
In addition, there is no reason to create a variable to hold the message. It is better to simply include the string literal directly in the function call. Finally, the comments should be aligned for readability.
Thus, the corrected code should read:
fputc('z', fp); // write character to fp int c = fgetc(fp); // read character from fp fputs("unrecoverable program error\n", stderr); // write message to standard error
Finally, note that the sample code doesn't do anything
useful, and in fact it wouldn't work with the file pointer
fp
from earlier on the page, because the file
"mydata.dat" was opened for reading (the "r" argument to
fopen
), and thus fputc
would be
rejected.
fopen
, fputc
, and
fputc
are C-style I/O. Don't use them in C++
programs.
setjmp/longjmp
facility was a horrible
(though sometimes necessary) feature in C. There is
utterly no reason to use it in C++, and there are
many reasons to avoid it. Budd should never have
mentioned it, and you should never use it.
const
" using
new-style typecasts is incorrect. There is a special
cast, const_cast
, that is designed for this
purpose. Thus, the typecast statement should read:
char * p = const_cast<char *>(name); // cast away the const part(However, even with this change, the code will not work as given on all machines. The reason is that the memory used to store the string "Fred" is protected by the hardware. If you insert these three lines into a program and try to run it on Turing, you will get a segmentation fault.)
bbox.test(2.3)
"
call on page 202 will not produce the results claimed. A
correct example of these two lines would be:
void test (int v, double d = 2.3) { ... }; ... bBox.test(3);
draw
by specifying a
replacement to be used within a GraphicalDeck
.
rational
class can be much
improved by using default arguments for the constructors.
Rather than giving three different constructors with
increasing numbers of arguments, only one constructor is
needed, as follows:
rational(int t = 0, int b = 1) : top(t), bottom(b) { normalize(); }Also, the formatting of these declarations is execrable. Function arguments should be placed immediately next to the function names, not separated by large amounts of whitespace. The formatting in the example makes it appear that
rational
is a return type, not a
function name.
+=
operator, you should also define
-=
, /=
, *=
, etc.
Similarly, if you are going to define ++
, you
should also define --
. Users of a class
should not have to experiment or examine the header file
to discover which operations are supported and which are
not.
operator++
is wrong. The return
statement must return *this
to match the
return type of the function.
putback
function:
stream.putback(character);
stream.clear(stream.rdstate() & ~ios::failbit);If you forget to do this, your program will fail to see its last input item.
find
operation will return this information. For example:
cityInfo::iterator whichCity = travelCosts.find(newCity); if (whichCity == travelCosts.end()) // have not seen it yetUsing
find
is more direct than using
count
, and is more efficient when working
with multimaps instead of maps.
main
routine
is known as the all-pairs shortest-path or APSP problem.
The approach given here, repeatedly applying Dijkstra's
single-source shortest-path algorithm, is not a particularly
efficient
solution to APSP. Efficient APSP algorithms are not an
appropriate topic for this course, but you should be aware
that this program is not a good example if you need to
solve a moderatly large APSP problem.
A far better way to write this code would be to define two constants that control where the display appears, such as TABLEAU_X and TABLEAU_Y, and others such as PILE_SPACING and CARD_SPACING that control appearance within the display. Other values should then be calculated from these numbers. Changing TABLEAU_X would shift everything on the screen without a need for further changes. A major advantage of this approach is that the computer does the calculations, so there is no room for human error.
randomInteger
class should extract the
high-order bits from the return value of
rand()
. In addition, there is no need for
the temporary variable. One line is sufficient:
return (double)rand() * max / RAND_MAX;
Furthermore, there is no need to create a special class, and use the horribly misconceived () operator. A far better (complete) implementation would be:
unsigned int randomInteger(unsigned int max) { return (double)rand() * max / RAND_MAX; }This obviates the unnecessary requirement for declaring a dummy object (such as
swapper
on page 262)
just to make the function accessible. It is also simpler and
much easier to read.
This page maintained by Geoff Kuenning.