coin flip problem

Started by
29 comments, last by rip-off 10 years, 7 months ago

To expand on SiCrane's earlier advice, another way to approach reading integers is to read a full line, and try to extract an integer from it.

There is a class std::stringstream, which as the name hints is a C++ stream, but "backed" by a string. It allows you to build a string using the << operator, like so:

#include <iostream>
#include <sstream>
#include <string>
 
int main() {
    std::stringstream stream;
    stream << "Hello" << ',' << " World!";
    std::string string = stream.str();
    std::cout << string << "\n";
}
 

You can also create a stringstream from an existing stream, and then read values out of it, like so:

#include <iostream>
#include <sstream>
#include <string>
 
int main() {
    std::string input = "1 2 3 42";
    std::cout << "Input is: " << input << "\n";
 
    std::stringstream stream(input);
    int number;
    while(stream >> number) {
        std::cout << "Extracted a number: " << number << "\n";
    }
    std::cout << "Goodbye.\n";
}

So, back to reading numbers from the command line. We can use std::getline() to grab an entire line of text in one go, put that text in a stringstream, and try to read a string out of it:

#include <iostream>
#include <sstream>
 
int main() {
    bool success = false;
    while(!success) {
        std::cout << "Please enter a number: ";
        std::string line;
        std::getline(std::cin, line);
        std::stringstream stream(line);
        int x;
        if(stream >> x) {
            std::cout << "You entered: " << x << "\n";
            success = true;
        } else {
            std::cout << "Hey, you didn't enter a number!\n";
        }
    }
    std::cout << "That's all, folks!\n";
}

I think it is a little simpler than the ignore/clear approach.

It is still possible to get an infinite loop here. One way is if std::cin "closes". You might not be aware, but it is possible to tell your terminal to close standard input by issuing a keyboard command, CTRL-D on my Linux box here, and I believe CTRL-Z (possibly followed by return) on Windows. Another way is to tell the terminal that the input for the program should come from a file or another program (which I won't go into).

This case can be handled by checking if reading the line succeeded:

#include <iostream>
#include <sstream>
 
int main() {
    bool success = false;
    while(!success) {
        std::cout << "Please enter a number: ";
        std::string line;
        if(!std::getline(std::cin, line)) {
            std::cerr << "Fatal I/O error occurred, sorry.\n";
            return 1;
        }
        std::stringstream stream(line);
        int x;
        if(stream >> x) {
            std::cout << "You entered: " << x << "\n";
            success = true;
        } else {
            std::cout << "Hey, you didn't enter a number!\n";
        }
    }
    std::cout << "That's all, folks!\n";
}

The same can occur for std::cout, and can be handled in a similar manner, but I won't bore you entirely to tears with this level of detail, this post is probably going to be long enough as is.

Now, what we have is pretty robust, but still maybe less than perfect. If you type input such as "17 42" or "17 magic jelly beans", it will accept the "17" but ignore the rest of the user's input. We can extend the above to ensure the user enters only a single number. The way we can do this is by seeing if there is any non-whitespace after the user's input. A neat way to achieve this is to use std::ws, which "eats" whitespace, and testing whether our stream is now at the end of the file:

#include <iostream>
#include <sstream>
 
int main() {
    bool success = false;
    while(!success) {
        std::cout << "Please enter a number: ";
        std::string line;
        if(!std::getline(std::cin, line)) {
            std::cerr << "Fatal I/O error occurred, sorry.\n";
            return 1;
        }
        std::stringstream stream(line);
        int x;
        if((stream >> x >> std::ws) && stream.eof()) {
            std::cout << "You entered: " << x << "\n";
            success = true;
        } else {
            std::cout << "Hey, you didn't enter a number!\n";
        }
    }
    std::cout << "That's all, folks!\n";
}

Of course, that is very tedious and complex to be doing in the middle of the interesting application logic. We can move it to a helper function:

#include <iostream>
#include <sstream>
#include <stdexcept>
 
int readInt(const std::string &prompt) {   
    while(true) {
        std::cout << prompt;
        std::string line;
        if(!std::getline(std::cin, line)) {
            throw std::runtime_error("Fatal I/O error occurred, sorry.");
        }
        std::stringstream stream(line);
        int result;
        if((stream >> result >> std::ws) && stream.eof()) {
            return result;
        } else {
            std::cout << "Hey, you didn't enter a number!\n";
        }
    }
}
 
int main() {
    for(int i = 0 ; i < 3 ; ++i) {
        int x = readInt("Please enter a number: ");
        std::cout << "You entered: " << x << "\n";
    }
    std::cout << "That's all, folks!\n";
}

You might notice that I've used an exception to handle the case where std::cin fails. You could also just print an error message and use a system call such as exit() to terminate the process. One downside of that approach is that it is hard to develop tests for functions that terminate the application.

Hope this helps.

This topic is closed to new replies.

Advertisement