GrapheneCarbide
Badges: 2
Rep:
?
#1
Report Thread starter 3 years ago
#1
#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;
}
0
reply
winterscoming
Badges: 19
Rep:
?
#2
Report 3 years ago
#2
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?
0
reply
X

Quick Reply

Attached files
Write a reply...
Reply
new posts
Back
to top
Latest
My Feed

See more of what you like on
The Student Room

You can personalise what you see on TSR. Tell us a little about yourself to get started.

Personalise

Are you tempted to change your firm university choice on A-level results day?

Yes, I'll try and go to a uni higher up the league tables (20)
28.17%
Yes, there is a uni that I prefer and I'll fit in better (7)
9.86%
No I am happy with my choice (40)
56.34%
I'm using Clearing when I have my exam results (4)
5.63%

Watched Threads

View All