1

I am very much new into C++ and I ran into the following problem.

My goal: I want to have a code which does the following:

  1. The user enters a line containing doubles separated somehow
  2. The doubles are being parsed into an array of doubles
  3. A computation(*) on the array takes place. For example its sum
  4. If the user doesn't brake the loop, it reads a new line, and loop back to 1.
  5. Once the user broke the first loop (by entering empty line or something like that), a new one starts.
  6. The user enters a line containing doubles separated somehow
  7. The doubles are being parsed into an array of doubles
  8. A computation(**) on the array takes place. For example its average.
  9. User brakes the second loop.
  10. Program quits.

My code:

#include <iostream>

int main()
{
    do {
        double x1, x2, x3, y1, y2, y3;
        std::cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;
        if (!std::cin){   
            break;
        }
        std::cout << "\n The sum is: " << (x1+y1+x2+y2+x3+y3) << "\n";
    } while (1);

    do {
        double x1, x2, x3, y1, y2, y3;
        std::cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;
        if (!std::cin){   
            break;
        }
        std::cout << "\n The average is: " << (x1+y1+x2+y2+x3+y3)/6 << "\n";
    } while (1);
    return 0;
}

The Problem: When I try to stop the first loop, and move to the second by either hitting CTRL-D or giving a letter as input, then the program quits and skips the second loop. I realized that it relates to the cin mechanism, but I failed to cure it.

The question: How should I program this? What is the least painful manner to overcome the problem? Thanks in advance!

Dror
  • 12,174
  • 21
  • 90
  • 160

4 Answers4

2

The Problem: When I try to stop the first loop, and move to the second by either hitting CTRL-D or giving a letter as input, then the program quits and skips the second loop. I realized that it relates to the cin mechanism, but I failed to cure it.

This is because when you try and read a letter or EOF (Ctrl-D) this sets the state of the stream into a bad state. Once this happens all operations on the stream fail (until you reset it). This can be done by calling clear()

std::cin.clear();

The question: How should I program this? What is the least painful manner to overcome the problem?

I would not use this technique.
I would use something like an empty line as a separator between loop. Thus the code looks like this:

while(std::getline(std::cin, line) && !line.empty())
{
      // STUFF
}
while(std::getline(std::cin, line) && !line.empty())
{
      // STUFF
}

Try this:
Note: in C++ streams work best when numbers are space separated. So it is easy just use space or tab to separate the numbers.

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>

struct Average
{
    Average(): result(0.0), count(0)            {}
    void operator()(double const& val)          { result  += val;++count;}
    operator double()                           { return (count == 0) ? 0.0 : result/count;}
    double  result;
    int     count;
};
struct Sum
{
    Sum(): result(0.0)                          {}
    void operator()(double const& val)          { result  += val;}
    operator double()                           { return result;}
    double result;
};

int main()
{
    std::string line;

    // Read a line at a time.
    // If the read fails or the line read is empty then stop looping.
    while(std::getline(std::cin, line) && !line.empty())
    {
        // In C++ we use std::Vector to represent arrays.
        std::vector<double>     data;
        std::stringstream       lineStream(line);

        // Copy a set of space separated integers from the line into data.
        // I know you wanted doubles (I do this next time)
        // I just wanted to show how easy it is to change between the types being
        // read. So here I use integers and below I use doubles.
        std::copy(  std::istream_iterator<int>(lineStream),
                    std::istream_iterator<int>(),
                    std::back_inserter(data));

        // Sum is a functor type.
        // This means when you treat it like a function then it calls the method operator()
        // We call sum(x) for each member of the vector data
        Sum sum;
        sum = std::for_each(data.begin(), data.end(), sum);
        std::cout << "Sum: " << static_cast<double>(sum) << "\n";
    }

    // Read a line at a time.
    // If the read fails or the line read is empty then stop looping.
    while(std::getline(std::cin, line) && !line.empty())
    {
        // In C++ we use std::Vector to represent arrays.
        std::vector<double>     data;
        std::stringstream       lineStream(line);

        // Same as above but we read doubles from the input not integers.
        // Notice the sleigh difference from above.
        std::copy(  std::istream_iterator<double>(lineStream),
                    std::istream_iterator<double>(),
                    std::back_inserter(data));

        // Average is a functor type.
        // This means when you treat it like a function then it calls the method operator()
        // We call average(x) for each member of the vector data
        Average average;
        average = std::for_each(data.begin(), data.end(), average);
        std::cout << "Average: " << static_cast<double>(average) << "\n";
    }
}

// Or we could templatize the code slightly:

template<typename T, typename F>
void doAction()
{
    std::string line;

    // Read a line at a time.
    // If the read fails or the line read is empty then stop looping.
    while(std::getline(std::cin, line) && !line.empty())
    {
        std::stringstream       lineStream(line);

        // F is a functor type.
        // This means when you treat it like a function then it calls the method operator()
        // We call action(x) for each object type 'T' that we find on the line.
        // Notice how we do not actual need to store the data in an array first
        // We can actually processes the data as we read it from the line
        F action;
        action = std::for_each(  std::istream_iterator<T>(lineStream),
                                 std::istream_iterator<T>(),
                                 action);
        std::cout << "Action Result: " << static_cast<double>(action) << "\n";
    }
}

int main()
{
    doAction<int, Sum>();
    doAction<double, Average>();
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • Hi Martin. Good write-up, only nitpick - first paragraph can easily be interpreted as implying that `clear()` can somehow undo the EOF state on the stream. Cheers, Tony. – Tony Delroy Mar 22 '11 at 18:01
  • I though it did: http://www.cplusplus.com/reference/iostream/ios/clear/ . Mind you it will not do much good unless the stream is std::cin and you presses . – Martin York Mar 22 '11 at 18:21
  • Martin: I'm pretty sure that even on stdin control-D closes the stream and - while you can tell the std::istream to forget about that momentarily by clearing it's state, that won't allow the subsequent streaming attempts to keep reading: they'll immediately reencounter the EOF condition. Maybe that's not consistent across all OSes, but it's my understanding.... Something for the OP to test anyway. Cheers. – Tony Delroy Mar 22 '11 at 18:29
  • @Tony: In most normal situations clearing the EOF flag is not going to help (as the stream still does not have any more data). But for the special case of std::cin when connected to the input device (keyboard) I think (but it could be platform dependent) it will reset the EOF state correctly as the (or Ctrl-Z on windows) sends the EOF character to the stream (thus causing it to shut down). But the actual stream is unaffected it continues to push characters from the keyboard. Redirect a file or other character source to the std-in from the command line it will not re-open that. – Martin York Mar 22 '11 at 18:46
0

Use std::cin.get(). It will return after the first entered character. You won't need loops in this case, but rather get smth. like this:

double x1, x2, x3, y1, y2, y3;
std::cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;
std::cin.get();
std::cout << "\n The sum is: " << (x1+y1+x2+y2+x3+y3) << "\n";

double x1, x2, x3, y1, y2, y3;
std::cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;
std::cin.get();
std::cout << "\n The average is: " << (x1+y1+x2+y2+x3+y3)/6 << "\n";
weekens
  • 8,064
  • 6
  • 45
  • 62
0

If std::cin gets input of wrong type it goes into fail-state - which you test with (!std::cin). But also the input-data is left on the stream.

So additionally to check the fail-state you have to clear the fail-state (std::cin.clear();)and remove the unread data from input-stream (std::cin.ignore();):

  #include <iostream>

  int main()
  {
     do {
        std::cout << "\nInsert 6 Numbers for sum or anything else to break:\n";
        double x1, x2, x3, y1, y2, y3;
        std::cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;
        if (!std::cin){
           std::cin.clear();
           std::cin.ignore();
           break;
        }
        std::cout << "\n The sum is: " << (x1+y1+x2+y2+x3+y3) << "\n";
     } while (1);

     do {
        std::cout << "\nInsert 6 Numbers for average or anything else to break:\n";
        double x1, x2, x3, y1, y2, y3;
        std::cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;
        if (!std::cin){   
           std::cin.clear();
           std::cin.ignore();
           break;
        }
        std::cout << "\n The average is: " << (x1+y1+x2+y2+x3+y3)/6 << "\n";
     } while (1);

     return 0;
  }

I also added an cout which shows the actual loop for usability.

MacGucky
  • 2,494
  • 17
  • 17
0

You really need to rethink you UI.

How about

SUM 1.9 1.3
AVERAGE 1 4 6
DONE

Use cout >> aString to get the keyword, then get the numbers the same way you do now.

  • You're 100% right but I only use it as an example and a test case for some other tools. Thanks anyway! – Dror Mar 22 '11 at 15:04