1
#include <iostream>
#include <string>
using namespace std;


struct UserInfo{

    string userPhoneNumber;
    string userName ;
    string userAddress ;

};


int main ()
{
    cout << "How many Entries do you want to enter?";
    int userAmountSelection;
    cin >> userAmountSelection;

    UserInfo userOne [userAmountSelection];

    for (int i = 0; i < userAmountSelection; i++){
        cout << "Please enter your first and last name: ";
        cin.ignore(); // possible problem in code 
        getline (cin, userOne[i].userName, '\n');
        cout << "Please Enter your address, " << userOne[i].userName << ":";
        cin.ignore(); // possible problem in code 
        getline (cin, userOne[i].userAddress, '\n');
        cout << "Please enter your phone#: ";
        cin.ignore (); // possible problem in code 
        getline (cin, userOne[i].userPhoneNumber);
    }
    for (int i = 0; i < userAmountSelection; i++){
        cout << userOne[i].userName << "        " << 
                userOne[i].userAddress << "         " << 
                userOne[i].userPhoneNumber << endl;
    }

    return 0;
}

As you can see its a simple code for learning structs and experimenting. The problem i run into appears to be from cin.ignore () code. it ignores the first characters of the input strings on output. The code does compile, however input and output are skewed.

For example when i enter a name of Terry, it will output erry.

I have tried removing the cin.ignore (), however when i do that, the output skips over the parts where the user needs to enter data and leaves areas blank. I have scoured the forums and found suggestions such as adding an argument to the cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');. for example, however this does not solve the problem and only adds to the list of errors I'm experiencing.

MFisherKDX
  • 2,741
  • 3
  • 11
  • 25
Caedus
  • 11
  • 3
  • 1
    Place `ignore`s AFTER an operation that leaves unwanted stuff in the stream. If you `ignore` BEFORE an operation, you often find yourself losing data you wanted because there was nothing to `ignore`. – user4581301 Mar 20 '19 at 21:25
  • Don’t mix extractors with `getline`. They fight. Instead of `cin >> userAmountSelection;` read the input into a string with `getline` and convert the text in the string to an `int`. – Pete Becker Mar 20 '19 at 21:30
  • @pete becker --- i see what youre saying and i just tried implementing that, if im doing it correctly, it gives me errors with using userAmountSelection variable. Im not longer able to use it in the UserInfo userOne [userAmountSelection]; declaration. – Caedus Mar 20 '19 at 21:42
  • Using `userAmountSelection` for the size of an array isn’t legal. Some compilers provide it as an extension. – Pete Becker Mar 20 '19 at 21:56

2 Answers2

2

The Problem

The problem is with the placement of ignores to prevent the bug outlined in Why does std::getline() skip input after a formatted extraction? The ignores have been placed before the getlines, and while this solves the getline skipping problem, it causes the problem the Asker has encountered. There isn't always something that needs to be ignored.

For example

cin >> userAmountSelection;

will leave a line ending in the stream if the user typed in the amount and then hit enter.

cout << "Please enter your first and last name: ";
cin.ignore(); // possible problem in code 
getline (cin, userOne[i].userName, '\n');

inside the for loop Would trip over this line ending if not for the ignore. But getline does not leave a newline in the stream , so the second and subsequent iterations of the loop have no newline to ignore. Part of the requiured data is ignored instead.

After

cin >> userAmountSelection;

rather than before

getline (cin, userOne[i].userName, '\n');

would be a good place to place an ignore so the newline is removed from the stream only after it has been left in the stream, but...

The Solution

The best way to handle this is to always read entire lines with getline and then parse those lines (see option 2 of this answer) into the pieces you want.

std::string line;
std::getline(std::cin, line);
std::istringstream(line) >> userAmountSelection;

This always works (Note: Requires #include <sstream>) and now you only have one type of reading going on, not a game of mix-n-match where you may forget you need an ignore.

Feel free to stop reading now.

The ignore approach requires some extra smarts. It's not as simple as it looks, in addition to fallibility of the human memory. You should place ignores AFTER an operation that leaves unwanted stuff in the stream. If you ignore BEFORE an operation, you often find yourself losing data you wanted because there was nothing to ignore.

std::cin >> userAmountSelection; // read a number
std::cin.ignore(); // discard the next character

Works a lot of the time, but what if the user typed in the amount and then a space and then hit enter or typed in all of the input they needed to type because they new darn well what the next prompt was? You gotta get a bit craftier.

std::cin >> userAmountSelection;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

This ignore annihilates everything until it hits the end of the line or runs out of space in the stream. (Note: Requires #include <limits>)

Community
  • 1
  • 1
user4581301
  • 29,019
  • 5
  • 26
  • 45
  • #include and #include are very new to me, i'll get to work learning about those tonight. thank you very much for going into such depth to not only answer the question but to explain it so well. Your time means a lot to me. – Caedus Mar 20 '19 at 21:49
  • @Caedus sstream is the header for `stringstream`, basically `cin` and `cout` that read from or write to a `string` instead of the console. – user4581301 Mar 20 '19 at 22:30
  • Good point. I proceeded from the users's code and left it out. Might as well add it back in for completeness. – user4581301 Mar 20 '19 at 22:46
0

The problem i run into appears to be from cin.ignore () code. it ignores the first characters of the input strings on output.

Looking at the istream::ignore declaration

istream& ignore (streamsize n = 1, int delim = EOF);

It will discard the first character by default.


I have tried removing the cin.ignore (), however when i do that, the output skips over the parts where the user needs to enter data and leaves areas blank.

That's because of the std::cin performed which left a residual newline which was then consumed by the getline. You'll only need ignore between the std::cin and getline calls:

std::cin >> userAmountSelection;
std::cin.ignore(); //---
...
for (int i = 0; i < userAmountSelection; i++) {
    ...
    getline (...);
    ...
    getline (...)
}

Also, there may be instances where you don't know if an std::cin will precede the getline. In that case, you may check if the input stream contains new line before doing ignore:

    ...

    // some I/O operations happening here

    ...

    // ignore if there's white space
    if (std::iswspace(std::cin.peek())) std::cin.ignore();

    std::getline(std::cin, somestring);
impulse
  • 206
  • 2
  • 15