I'm not aware of great recent introductory Java programming books,
except for advanced topics: you appreciate Bloch and Goetz when you've
already learned Java. That Herb Schildt wrote a book about Java bothers
me: his books on C and C++ were awful. But judging by the publishing
press he is an authority on C, C++, C# and Java. I'll be less harsh
about the Deitels: they are merely mediocre.
All the C and C++ experts agree that Schildt's books are awful.
But in most cases that just means that they are awful at teaching
beginners how to program.
A few quotes from Feather:
<quote>
A proper understanding of the terms "implementation-defined",
"undefined", and "unspecified", and of the differences between them, is
essential to understanding the limits that the standard puts on the
programmer and the implementor. Unfortunately, the differences are not
explained at all, and the book leaves me wondering why the different
terms are used at all.
</quote>
<quote>
## An unsigned integer expression cannot overflow. This is because
## there is no way to represent such an overflow as an unsigned
## quantity.
More nonsense. An implementation either does or doesn't have a way to
represent overflow - usually integers don't, while floating point may or
may not (some systems have INFINITY values that effectively indicate
overflow). However, an unsigned integer expression cannot overflow
because the standard says so - the choice was made that unsigned integer
arithmetic is done modulo some base (UINT_MAX+1 for unsigned int,
ULONG_MAX+1 for unsigned long). There is no magic about this; it was an
arbitrary decision by the authors of the standard.
</quote>
A few quote from Seebs:
<quote>
As bits are shifted off one end, zeroes are brought in the other
end. (In the case of a signed, negative integer, a right shift will
cause a 1 to be brought in so that the sign bit is preserved.)
Clear, lucid, but not actually true. What happens when you right-shift a
signed, negative number, is "implementation-defined" (C99, 6.5.7,
paragraph 5); that is to say, the implementation has to document what
happens. While the behavior Schildt describes is one of the common
choices, other systems have been known to shift in zeroes. There may be
other behaviors out there. This ties in somewhat with Schildt's comments
about the use of two's complement representations, where the behavior he
describes is common but not actually required.
</quote>
<quote>
If stream is associated with a file opened for writing, a call to
fflush() causes the contents of the output buffer to be physically
written to the file. The file remains open.
There's a couple of questionable bits here, although they're arcane
enough that you might reasonably call them nitpicks.
Streams in C can be opened for reading, writing, or "update"—both
reading and writing. You can safely use fflush() on an update stream
only if the most recent operation on it was not a read (writes or seeks,
for instance, are allowed). Schildt misses that distinction, although I
simply can't tell whether he fails to point out that you can sometimes
flush an update stream, or fails to point out that you sometimes can't
flush an update stream.
More perniciously, it's worth pointing out that fflush() does not
cause the contents of the output buffer to be physically written to the
file. It causes the contents of the output buffer to be delivered to the
host operating environment, which may well have additional layers of
buffering. On most modern systems, this could result in the file living
in an operating-system level write cache for seconds or minutes, or for
that matter, living in a cache on the disk drive's internal
microcontroller for some time. (That "16MB cache!" you see advertised on
hard drive packaging is, in many cases, not something the C
implementation can force to be flushed out to the physical platters.)
</quote>
All very correct.
But absolutely non-suitable for a beginner that wants to learn C.
Arne