荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jjksam ([==面壁大师==]), 信区: Program
标  题: C++ FAQ (part 07 of 14)
发信站: 荔园晨风BBS站 (Mon Jul  1 14:36:03 2002), 转信


From: cline@parashift.com
Sender: cline@parashift.com
Newsgroups: comp.lang.c++,comp.answers,news.answers,alt.comp.lang.learn.c-c++
Subject: C++ FAQ (part 07 of 14)
Summary: Please read this before posting to comp.lang.c++
Followup-To: comp.lang.c++
Reply-To: cline@parashift.com (Marshall Cline)
Distribution: world
Approved: news-answers-request@mit.edu
Expires: +1 month

Archive-name: C++-faq/part07
Posting-Frequency: monthly
Last-modified: Jun 17, 2002
URL: http://www.parashift.com/c++-faq-lite/

AUTHOR: Marshall Cline / cline@parashift.com / 972-931-9470

COPYRIGHT: This posting is part of "C++ FAQ Lite."  The entire "C++ FAQ Lite"
document is Copyright(C)1991-2002 Marshall Cline, Ph.D., cline@parashift.com.
All rights reserved.  Copying is permitted only under designated situations.
For details, see section [1].

NO WARRANTY: THIS WORK IS PROVIDED ON AN "AS IS" BASIS.  THE AUTHOR PROVIDES NO
WARRANTY WHATSOEVER, EITHER EXPRESS OR IMPLIED, REGARDING THE WORK, INCLUDING
WARRANTIES WITH RESPECT TO ITS MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
PURPOSE.

C++-FAQ-Lite != C++-FAQ-Book: This document, C++ FAQ Lite, is not the same as
the C++ FAQ Book.  The book (C++ FAQs, Cline and Lomow, Addison-Wesley) is 500%
larger than this document, and is available in bookstores.  For details, see
section [3].

==============================================================================

SECTION [15]: Input/output via <iostream> and <cstdio>


[15.1] Why should I use <iostream> instead of the traditional <cstdio>?

Increase type safety, reduce errors, allow extensibility, and provide
inheritability.

printf() is arguably not broken, and scanf() is perhaps livable despite being
error prone, however both are limited with respect to what C++ I/O can do.  C++
I/O (using << and >>) is, relative to C (using printf() and scanf()):
 * More type-safe: With <iostream>, the type of object being I/O'd is known
   statically by the compiler.  In contrast, <cstdio> uses "%" fields to figure
   out the types dynamically.
 * Less error prone: With <iostream>, there are no redundant "%" tokens that
   have to be consistent with the actual objects being I/O'd.  Removing
   redundancy removes a class of errors.
 * Extensible: The C++ <iostream> mechanism allows new user-defined types to be
   I/O'd without breaking existing code.  Imagine the chaos if everyone was
   simultaneously adding new incompatible "%" fields to printf() and
   scanf()?!).
 * Inheritable: The C++ <iostream> mechanism is built from real classes such as
   std::ostream and std::istream.  Unlike <cstdio>'s FILE*, these are real
   classes and hence inheritable.  This means you can have other user-defined
   things that look and act like streams, yet that do whatever strange and
   wonderful things you want.  You automatically get to use the zillions of
   lines of I/O code written by users you don't even know, and they don't need
   to know about your "extended stream" class.

==============================================================================

[15.2] Why does my program go into an infinite loop when someone enters an
       invalid input character?

For example, suppose you have the following code that reads integers from
std::cin:

 #include <iostream>

 int main()
 {
   std::cout << "Enter numbers separated by whitespace (use -1 to quit): ";
   int i = 0;
   while (i != -1) {
     std::cin >> i;        // BAD FORM -- See comments below
     std::cout << "You entered " << i << '\n';
   }
 }

The problem with this code is that it lacks any checking to see if someone
entered an invalid input character.  In particular, if someone enters something
that doesn't look like an integer (such as an 'x'), the stream std::cin goes
into a "failed state," and all subsequent input attempts return immediately
without doing anything.  In other words, the program enters an infinite loop;
if 42 was the last number that was successfully read, the program will print
the message You entered 42 over and over.

An easy way to check for invalid input is to move the input request from the
body of the while loop into the control-expression of the while loop.  E.g.,

 #include <iostream>

 int main()
 {
   std::cout << "Enter a number, or -1 to quit: ";
   int i = 0;
   while (std::cin >> i) {    // GOOD FORM
     if (i == -1) break;
     std::cout << "You entered " << i << '\n';
   }
 }

This will cause the while loop to exit either when you hit end-of-file, or when
you enter a bad integer, or when you enter -1.

(Naturally you can eliminate the break by changing the while loop expression
from while (std::cin >> i) to while ((std::cin >> i) && (i != -1)), but that's
not really the point of this FAQ since this FAQ has to do with iostreams rather
than generic structured programming guidelines.)

==============================================================================

[15.3] How does that funky while (std::cin >> foo) syntax work?

See the previous FAQ[15.2] for an example of the "funky while (std::cin >> foo)
syntax."

The expression (std::cin >> foo) calls the appropriate operator>> (for example,
it calls the operator>> that takes an std::istream on the left and, if foo is
of type int, an int& on the right).  The std::istream operator>> functions
return their left argument by convention, which in this case means it will
return std::cin.  Next the compiler notices that the returned std::istream is
in a boolean context, so it converts that std::istream into a boolean.

To convert an std::istream into a boolean, the compiler calls a member function
called std::istream::operator void*().  This returns a void* pointer, which is
in turn converted to a boolean (NULL becomes false, any other pointer becomes
true).  So in this case the compiler generates a call to
std::cin.operator void*(), just as if you had casted it explicitly such as
(void*) std::cin.

The operator void*() cast operator returns some non-NULL pointer if the stream
is in a good state, or NULL if it's in a failed state.  For example, if you
read one too many times (e.g., if you're already at end-of-file), or if the
actual info on the input stream isn't valid for the type of foo (e.g., if foo
is an int and the data is an 'x' character), the stream will go into a failed
state and the cast operator will return NULL.

The reason operator>> doesn't simply return a bool (or void*) indicating
whether it succeeded or failed is to support the "cascading" syntax:

   std::cin >> foo >> bar;

The operator>> is left-associative, which means the above is parsed as:

   (std::cin >> foo) >> bar;

In other words, if we replace operator>> with a normal function name such as
readFrom(), this becomes the expression:

   readFrom( readFrom(std::cin, foo), bar);

As always, we begin evaluating at the innermost expression.  Because of the
left-associativity of operator>>, this happens to be the left-most expression,
std::cin >> foo.  This expression returns std::cin (more precisely, it returns
a reference to its left-hand argument) to the next expression.  The next
expression also returns (a reference to) std::cin, but this second reference is
ignored since it's the outermost expression in this "expression statement."

==============================================================================

[15.4] Why does my input seem to process past the end of file?

Because the eof state may not get set until after a read is attempted past the
end of file.  That is, reading the last byte from a file might not set the eof
state.  E.g., suppose the input stream is mapped to a keyboard -- in that case
it's not even theoretically possible for the C++ library to predict whether or
not the character that the user just typed will be the last character.

For example, the following code might have an off-by-one error with the count
i:

 int i = 0;
 while (! std::cin.eof()) {   // WRONG! (not reliable)
   std::cin >> x;
   ++i;
   // Work with x ...
 }

What you really need is:

 int i = 0;
 while (std::cin >> x) {      // RIGHT! (reliable)
   ++i;
   // Work with x ...
 }

==============================================================================

[15.5] Why is my program ignoring my input request after the first iteration?

Because the numerical extractor leaves non-digits behind in the input buffer.

If your code looks like this:

 char name[1000];
 int age;

 for (;;) {
   std::cout << "Name: ";
   std::cin >> name;
   std::cout << "Age: ";
   std::cin >> age;
 }

What you really want is:

 for (;;) {
   std::cout << "Name: ";
   std::cin >> name;
   std::cout << "Age: ";
   std::cin >> age;
   std::cin.ignore(INT_MAX, '\n');
 }

Of course you might want to change the for (;;) statement to while (std::cin),
but don't confuse that with skipping the non-numeric characters at the end of
the loop via the line: std::cin.ignore(...);.

==============================================================================

[15.6] How can I provide printing for my class Fred?

Use operator overloading[13] to provide a friend[14] left-shift operator,
operator<<.

 #include <iostream>

 class Fred {
 public:
   friend std::ostream& operator<< (std::ostream& o, const Fred& fred);
   // ...
 private:
   int i_;    // Just for illustration
 };

 std::ostream& operator<< (std::ostream& o, const Fred& fred)
 {
   return o << fred.i_;
 }

 int main()
 {
   Fred f;
   std::cout << "My Fred object: " << f << "\n";
 }

We use a non-member function (a friend[14] in this case) since the Fred object
is the right-hand operand of the << operator.  If the Fred object was supposed
to be on the left hand side of the << (that is, myFred << std::cout rather than
std::cout << myFred), we could have used a member function named operator<<.

Note that operator<< returns the stream.  This is so the output operations can
be cascaded[15.3].

==============================================================================

[15.7] But shouldn't I always use a printOn() method rather than a friend
       function?

No.

The usual reason people want to always use a printOn() method rather than a
friend function is because they wrongly believe that friends violate
encapsulation and/or that friends are evil.  These beliefs are naive and wrong:
when used properly, friends can actually enhance encapsulation[14.2].

This is not to say that the printOn() method approach is never useful.  For
example, it is useful when providing printing for an entire hierarchy of
classes[15.9].  But if you use a printOn() method, it should normally be
protected, not public.

For completeness, here is "the printOn() method approach." The idea is to have
a member function (often called printOn() that does the actual printing, then
have operator<< call that printOn() method.  When it is done wrongly, the
printOn() method is public so operator<< doesn't have to be a friend -- it can
be a simple top-level function that is neither a friend nor a member of the
class.  Here's some sample code:

 #include <iostream>

 class Fred {
 public:
   void printOn(std::ostream& o) const;
   // ...
 };

 // operator<< can be declared as a non-friend [NOT recommended!]
 std::ostream& operator<< (std::ostream& o, const Fred& fred);

 // The actual printing is done inside the printOn() method [NOT recommended!]
 void Fred::printOn(std::ostream& o) const
 {
   // ...
 }

 // operator<< calls printOn() [NOT recommended!]
 std::ostream& operator<< (std::ostream& o, const Fred& fred)
 {
   fred.printOn(o);
   return o;
 }

People wrongly assume that this reduces maintenance cost "since it avoids
having a friend function." This is a wrong assumption because:

 1. The member-called-by-top-level-function approach has zero benefit in terms
    of maintenance cost. Let's say it takes N lines of code to do the actual
    printing.  In the case of a friend function, those N lines of code will
    have direct access to the class's private/protected parts, which means
    whenever someone changes the class's private/protected parts, those N lines
    of code will need to be scanned and possibly modified, which increases the
    maintenance cost.  However using the printOn() method doesn't change this
    at all: we still have N lines of code that have direct access to the
    class's private/protected parts.  Thus moving the code from a friend
    function into a member function does not reduce the maintenance cost at
    all.  Zero reduction.  No benefit in maintenance cost.  (If anything it's a
    bit worse with the printOn() method since you now have more lines of code
    to maintain since you have an extra function that you didn't have before.)

 2. The member-called-by-top-level-function approach makes the class harder to
    use, particularly by programmers who are not also class designers. The
    approach exposes a public method that programmers are not supposed to call.
    When a programmer reads the public methods of the class, they'll see two
    ways to do the same thing.  The documentation would need to say something
    like, "This does exactly the same as that, but don't use this; instead use
    that." And the average programmer will say, "Huh? Why make the method
    public if I'm not supposed to use it?" In reality the only reason the
    printOn() method is public is to avoid granting friendship status to
    operator<<, and that is a notion that is somewhere between subtle and
    incomprehensible to a programmer who simply wants to use the class.

Net: the member-called-by-top-level-function approach has a cost but no
benefit.  Therefore it is, in general, a bad idea.

Note: if the printOn() method is protected or private, the second objection
doesn't apply.  There are cases when that approach is reasonable, such as when
providing printing for an entire hierarchy of classes[15.9].  Note also that
when the printOn() method is non-public, operator<< needs to be a friend.

==============================================================================

[15.8] How can I provide input for my class Fred?

Use operator overloading[13] to provide a friend[14] right-shift operator,
operator>>.  This is similar to the output operator[15.6], except the parameter
doesn't have a const[18]: "Fred&" rather than "const Fred&".

 #include <iostream>

 class Fred {
 public:
   friend std::istream& operator>> (std::istream& i, Fred& fred);
   // ...
 private:
   int i_;    // Just for illustration
 };

 std::istream& operator>> (std::istream& i, Fred& fred)
 {
   return i >> fred.i_;
 }

 int main()
 {
   Fred f;
   std::cout << "Enter a Fred object: ";
   std::cin >> f;
   // ...
 }

Note that operator>> returns the stream.  This is so the input operations can
be cascaded and/or used in a while loop or if statement[15.3].

==============================================================================

[15.9] How can I provide printing for an entire hierarchy of classes?

Provide a friend[14] operator<<[15.6] that calls a protected virtual[20]
function:

 class Base {
 public:
   friend std::ostream& operator<< (std::ostream& o, const Base& b);
   // ...
 protected:
   virtual void printOn(std::ostream& o) const;
 };

 inline std::ostream& operator<< (std::ostream& o, const Base& b)
 {
   b.printOn(o);
   return o;
 }

 class Derived : public Base {
 protected:
   virtual void printOn(std::ostream& o) const;
 };

The end result is that operator<< acts as if it was dynamically bound, even
though it's a friend[14] function.  This is called the Virtual Friend Function
Idiom.

Note that derived classes override printOn(std::ostream&) const.  In
particular, they do not provide their own operator<<.

Naturally if Base is an ABC[22.3], Base::printOn(std::ostream&) const can be
declared pure virtual[22.4] using the "= 0" syntax.

==============================================================================

[15.10] How can I "reopen" std::cin and std::cout in binary mode under DOS
        and/or OS/2?

This is implementation dependent.  Check with your compiler's documentation.

For example, suppose you want to do binary I/O using std::cin and std::cout.
Suppose further that your operating system (such as DOS or OS/2) insists on
translating "\r\n" into "\n" on input from std::cin, and "\n" to "\r\n" on
output to std::cout or std::cerr.

Unfortunately there is no standard way to cause std::cin, std::cout, and/or
std::cerr to be opened in binary mode.  Closing the streams and attempting to
reopen them in binary mode might have unexpected or undesirable results.

On systems where it makes a difference, the implementation might provide a way
to make them binary streams, but you would have to check the manuals to find
out.

==============================================================================

[15.11] How can I tell {if a key, which key} was pressed before the user
        presses the ENTER key? [NEW!]

[Recently created inspired by a posting by Alan Didey (in 5/02).]

This is not a standard C++ feature -- C++ doesn't even require your system to
have a keyboard!.  That means every operating system and vendor does it
somewhat differently.

Please read the documentation that came with your compiler for details on your
particular installation.

(By the way, the process on UNIX typically has two steps: first set the
terminal to single-character mode
<http://www.faqs.org/faqs/unix-faq/programmer/faq/>, then use either select()
or poll() to test if a key was pressed.  You might be able to adapt this code
<http://ccwf.cc.utexas.edu/~apoc/programs/c/kbhit.c>.)

==============================================================================

[15.12] How can make it so keys pressed by users are not echoed on the screen?
        [NEW!]

[Recently created (in 5/02).]

This is not a standard C++ feature -- C++ doesn't even require your system to
have a keyboard or a screen.  That means every operating system and vendor does
it somewhat differently.

Please read the documentation that came with your compiler for details on your
particular installation.

==============================================================================

[15.13] How can I move the cursor around on the screen? [NEW!]

[Recently created (in 5/02).]

This is not a standard C++ feature -- C++ doesn't even require your system to
have a screen.  That means every operating system and vendor does it somewhat
differently.

Please read the documentation that came with your compiler for details on your
particular installation.

==============================================================================

[15.14] Why can't I open a file in a different directory such as "..\test.dat"?

Because "\t" is a tab character.

You should use forward slashes in your filenames, even on operating systems
that use backslashes (DOS, Windows, OS/2, etc.).  For example:

 #include <iostream>
 #include <fstream>

 int main()
 {
   #if 1
     std::ifstream file("../test.dat");  // RIGHT!
   #else
     std::ifstream file("..\test.dat");  // WRONG!
   #endif

   // ...
 }

Remember, the backslash ("\") is used in string literals to create special
characters: "\n" is a newline, "\b" is a backspace, and "\t" is a tab, "\a" is
an "alert", "\v" is a vertical-tab, etc.  Therefore the file name
"\version\next\alpha\beta\test.dat" is interpreted as a bunch of very funny
characters.  To be safe, use "/version/next/alpha/beta/test.dat" instead, even
on systems that use a "\" as the directory separator.  This is because the
library routines on these operating systems handle "/" and "\" interchangeably.

Of course you could use "\\version\\next\\alpha\\beta\\test.dat", but that
might hurt you (there's a non-zero chance you'll forget one of the "\"s, a
rather subtle bug since most people don't notice it) and it can't help you
(there's no benefit for using "\\" over "/").  Besides "/" is more portable
since it works on all flavors of Unix, Plan 9, Inferno, all Windows, OS/2,
etc., but "\\" works only on a subset of that list.  So "\\" costs you
something and gains you nothing: use "/" instead.

==============================================================================

[15.15] How do I convert a value (a number, for example) to a std::string?

There are two easy ways to do this: you can use the <cstdio> facilities or the
<iostream> library.  In general, you should prefer the <iostream>
library[15.1].

The <iostream> library allows you to convert pretty much anything to a
std::string using the following syntax (the example converts a double, but you
could substitute pretty much anything that prints using the << operator):

 #include <iostream>
 #include <sstream>
 #include <string>

 std::string convertToString(double x)
 {
   std::ostringstream o;
   if (o << x)
     return o.str();
   // some sort of error handling goes here...
   return "conversion error";
 }

The std::ostringstream object o offers formatting facilities just like those
for std::cout.  You can use manipulators and format flags to control the
formatting of the result, just as you can for other std::cout.

In this example, we insert x into o via the overloaded insertion operator, <<.
This invokes the iostream formatting facilities to convert x into a
std::string.  The if test[15.3] makes sure the conversion works correctly -- it
should always succeed for built-in/intrinsic types, but the if test is good
style.

The expression os.str() returns the std::string that contains whatever has been
inserted into stream o, in this case the string value of x.

==============================================================================

[15.16] How do I convert a std::string to a number?

There are two easy ways to do this: you can use the <cstdio> facilities or the
<iostream> library.  In general, you should prefer the <iostream>
library[15.1].

The <iostream> library allows you to convert a std::string to pretty much
anything using the following syntax (the example converts a double, but you
could substitute pretty much anything that can be read using the >> operator):

 #include <iostream>
 #include <sstream>
 #include <string>

 double convertFromString(const std::string& s)
 {
   std::istringstream i(s);
   double x;
   if (i >> x)
     return x;
   // some sort of error handling goes here...
   return 0.0;
 }

The std::istringstream object i offers formatting facilities just like those
for std::cin.  You can use manipulators and format flags to control the
formatting of the result, just as you can for other std::cin.

In this example, we initialize the std::istringstream i passing the std::string
s (for example, s might be the string "123.456"), then we extract i into x via
the overloaded extraction operator, >>.  This invokes the iostream formatting
facilities to convert as much of the string as possible/appropriate based on
the type of x.

The if test[15.3] makes sure the conversion works correctly.  For example, if
the string contains characters that are inappropriate for the type of x, the if
test will fail.

==============================================================================


--
   mm       ★__      __  __ __★______ ______ __  __★
/^(  )^\      █      █  █/    █____ █__█ █∨█
\,(..),/  ▅__█  ▅__█  █\__  ▂__█ █  █ █  █
  V~~V   ▇▆▅▃▁I'm a bat. I'm very bad!^Q^_▃▄▆▇ 你好!^_^欢迎大家到linux

※ 修改:·jjksam 於 Jul  1 14:39:52 修改本文·[FROM: 192.168.0.146]
※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.0.146]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店