Turn on thread page Beta
    • Thread Starter
    Offline

    2
    ReputationRep:
    #include<iostream>
    #include <string>
    #include <istream>
    #include <stdlib.h>

    using namespace std;

    int main()
    {
    char repeat = 'n';

    string circle;
    string triangle;
    string rectangle;
    string shape;

    long double areaCircle;
    long double areaTriangle;
    long double areaRectangle;
    do
    {
    cout << "Type the shape you want to find the area for.\n";
    cout << "Circle\n";
    cout << "Triangle\n";
    cout << "Rectangle\n";
    getline(cin,shape);

    if (shape == "circle")
    {
    long double r;
    cout << "Enter the radius of the circle.\n";
    cin >> r;
    areaCircle = (3.14)*(r*r);
    cout << "The area of the circle is " << areaCircle << endl;
    cout << "Enter 'Y' to repeat, 'N' to quit\n";
    cin >> repeat;
    }
    else if (shape == "triangle")
    {
    long double h, b;
    cout << "Enter the base.\n";
    cin >> b;
    cout << "Enter the height.\n";
    cin >> h;
    areaTriangle = (b*h) / 2;
    cout << "The area of the triangle is " << areaTriangle << endl;
    cout << "Enter 'Y' to repeat, 'N' to quit\n";
    cin >> repeat;
    }
    else if (shape == "rectangle")
    {
    long double l, w;
    cout << "Enter the length.\n";
    cin >> l;
    cout << "Enter the width.\n";
    cin >> w;
    areaRectangle = l * w;
    cout << "The area of the rectangle is " << areaRectangle << endl;
    cout << "Enter 'Y' to repeat, 'N' to quit\n";
    cin >> repeat;
    }
    else
    {
    cout << shape << "is not a valid shape.\n";
    cout << "Enter 'Y' to repeat, 'N' to quit\n";
    cin >> repeat;
    }
    }
    while (repeat == 'y');
    system("pause");
    return 0;
    }
    Offline

    18
    ReputationRep:
    Welcome to C++ programming! To put it simply, you've found one of the most common "gotchas" which trip up nearly everybody who has ever attempted to learn how to program with C++!

    To put it simply, you're trying to mix up std::getline and std::cin >> in the same program. The two of them actually don't play very nicely together. Well, that's not quite true, they do play nicely together, but you have to understand what's actually going on and how to avoid it.

    Firstly, you need to know how the std::getline function and the std::cin >> operators work:

    Both of them have something in common - they both read from 'stdin'. In the case of a Windows Console app, 'stdin' is the name of the keyboard input buffer which is attached to the Console Window instance that your program happens to be running in. (so far, so good..)

    Now for the important difference:

    std::getline - it reads from stdin, up to and including the first newline '\n' character it encounters (i.e. it also consumes that newline character from the console input buffer, and includes it in the string - I'll explain why this is important later..)

    std::cin >> - The "chevrons" >> also read from stdin, but works in a slightly different way:

    1) Firstly, it skips any white-space data in the input buffer to find the beginning of any non-whitespace data (that could be any other character which isn't a space ' ', tab '\t', linefeed '\r'or newline '\n')
    2) Secondly, it reads and consumes that data up-to but not including any white-space characters. So, any trailing spaces or new-line characters will ALWAYS be left in the stream... (can you see why this might be a problem...? No? OK, keep reading..)

    Here's what's actually happening in your program:

    The First Iteration,

    First thing you do is use std::getline to read some input. At this point, there shouldn't be any new-line characters in your input buffer. No problems here!
    Code:
    getline(cin,shape);
    Oh, and std::getline also removes the newline from the input buffer. Everything is good so far.

    Later on in that same loop, you read a whole bunch of stuff using std::cin >> - remember, the chevrons >> always skip over any leading white-space, so all of this stuff works rather happily:
    Code:
    cin >> r;
    Code:
    cin >> repeat;
    Code:
    cin >> b;
    Code:
    cin >> h;
    Code:
    cin >> i;
    .. etc. So far, nothing is causing a problem, because of the way the std::cin >> chevrons work, skipping over whitespace at the start of the input buffer..

    But there is a disaster looming - unlike std::getline, the std::cin >> chevrons leave any trailing white-space characters alone, which could include a pesky newline '\n'.

    No problems yet, but...

    The Second Iteration,
    Straight off the bat, the first thing you do (again) is use 'getline' to read some input. Remember the difference between getline-vs-chevrons:
    • The std::cin >> chevrons have left a newline '\n' character at the start of the stdin input buffer.
    • std::getline ceases to read and consume data as soon as it meets a newline '\n' character.

    So, you're doing this.
    Code:
    getline(cin,shape);
    Let's imagine for a second that you (the user, sitting at your keyboard..) type in "triangle".
    • The shape variable is a std::string,
    • The input buffer contains the data "\ntriangle\n"

    Question: What does the shape variable contain after std::getline reads from the input buffer..?
    Answer: It only contains a single solitary newline '\n' character. the text you've typed in - "triangle" is still sitting in the input buffer, because getline already found its newline at the beginning, consumed that newline character, and then left the rest of the text in the input buffer.

    At this point, things can only go from bad to worse... Let's see the next thing your loop is doing..
    Code:
    long double r;
     			cout << "Enter the radius of the circle.\n";
     			cin >> r;
    **BOOM** - it's all over. The input buffer contains the string data "triangle\n". std::cin >> chevrons have failed in their attempt to read the content of the input buffer into the long double variable called 'r'.

    The input buffer contains the text string "triangle\n". The text string "triangle\n" doesn't convert into a long double. The chevrons fail,

    Worse still, the next thing that the chevrons will do, following a failed attempt to read "triangle" into a long double, is that it will set the failbit on the cin stream.

    What does that mean? failbit is just a true/false flag which indicates that std::cin has failed. You can't do anything with std::cin when it's in a failed state. You need to fix the problem before you can use it again. (i.e. get rid of the garbage from the stream and clear the failed flag).

    At this point, std::cin has failed, your program is still attempting to use std::cin, and before you know it, you're stuck in "cin hell".

    So, That didn't work. What's the Solution...?

    To put it bluntly. do not use chevrons to read from std::cin.
    You can do it, of course, but it's not pretty. It's not worth the hassle. or the pain, or the tears. Instead, do this:
    1. Always use std::getline with std::cin
    2. Never use std::cin >> with chevrons.
    3. Always read from std::getline(cin, ...) into a std::string (because getline into a string isn't going to fail as easily)
    4. Convert from a std::string into whatever type you really wanted (e.g. int, double, etc.) - you can use a std::istringstream for this


    Here's a little handy little template helper function which I use in a C++ header file called input_retry.h - I find find it to be rather useful when writing C++ console apps which need to read user input from the console window:
    Code:
    // input_retry.h
    #ifndef INPUT_RETRY_H 
    #define INPUT_RETRY_H
    
     #include <string> 
    #include <sstream> 
    #include <iostream>
     #include <ostream> 
    
    /**
     * input_retry function - prints a message to the screen, requesting the user input data.
     * if the data fails, then it prints a retry message and asks the user again.
     * the function returns the input value if the input data is valid.
     */
     template <typename T>
      T input_retry<T>(const std::string& message, const std::string& retry_message) 
    {
        // This just loops every time invalid input is typed in...
        while (true)
        {
            // Value of whatever will actually be returned..
            T value;
            std::string line;
    
            // Send the user the message, then read their input into a string..
            std::cout << message << std::endl;
            std::getline(std::cin, line);
    
            // istringstream is a good utility for converting from a string into something else..
            std::istringstream converter(line);
    
            // OK - use the chevrons here on the istringstream, it doesn't matter if they fail because
            // if it fails, the stringstream will be destroyed anyway, and created fresh next time
            if (converter >> value)
            {
                // Successful conversion!   Return out of the function!
                return value;
            }
            else
            {
                // Not so successful - Dump out the retry message, and let the loop start all over again...
                std::cout << retry_message << std::endl;
            }
        }
    }
    
     #endif
    Handling user input from a console window in C++ is one of those slightly unpleasant things which really needs a helper function because everybody always encounters the same problem every time. Having a helper function like the one above means you can write read/input code like this instead:
    Code:
    #include <iostream>
    #include <string>
    #include <ostream>
     #include "input_retry.h" 
    
    // Testing the input_retry function ...
    int main()
    {
        std::string s;
        std::cout << "Enter a string:" << std::endl;
        std::getline( std::cin, s ) ;
        std::cout << "You typed: " << s << std::endl;
    
        auto x = input_retry<double>( "Next, type a double", "You didn't type a valid double, try again" ) ;
        std::cout << "You typed: " << x << std::endl;
    
        auto y = input_retry<int>( "Now, type an int", "You didn't type a valid int, try again" ) ;
        std::cout << "You typed: " << y << std::endl;
    
        std::cout << "Enter another one last string:" << std::endl;
        std::getline( std::cin, s ) ;
        std::cout << "You typed: " << s << std::endl;
    }
    I hope this makes sense?
 
 
 
Reply
Submit reply
Turn on thread page Beta
Updated: February 13, 2018

University open days

  1. University of Bradford
    University-wide Postgraduate
    Wed, 25 Jul '18
  2. University of Buckingham
    Psychology Taster Tutorial Undergraduate
    Wed, 25 Jul '18
  3. Bournemouth University
    Clearing Campus Visit Undergraduate
    Wed, 1 Aug '18
Poll
How are you feeling in the run-up to Results Day 2018?

The Student Room, Get Revising and Marked by Teachers are trading names of The Student Room Group Ltd.

Register Number: 04666380 (England and Wales), VAT No. 806 8067 22 Registered Office: International House, Queens Road, Brighton, BN1 3XE

Write a reply...
Reply
Hide
Reputation gems: You get these gems as you gain rep from other members for making good contributions and giving helpful advice.