Ignore Spaces Using getline in C++ [duplicate]
Asked Answered
D

5

3

Hey, I'm trying to write a program that will accept new tasks from people, add it to a stack, be able to display the task, be able to save that stack to a text file, and then read the text file. The issue comes when I am trying to accept input from the user, whenever you enter a string with a space in it, the menu to choose what to do just loops. I need a way to fix this. Any help would be greatly appreciated.

// basic file io operations
#include <iostream>
#include <fstream>
#include <stack>
#include <string>
using namespace std;

int main () {
    //Declare the stack
    stack<string> list;

    //Begin the loop for the menu
    string inputLine;
    cout << "Welcome to the to-do list!" << endl;

    //Trying to read the file
    ifstream myfile ("to-do.txt");
    if(myfile.is_open()){

        //read every line of the to-do list and add it to the stack
        while(myfile.good()){
            getline(myfile,inputLine);
            list.push(inputLine);
        }
        myfile.close();
        cout << "File read successfully!" << endl;
    } else {
        cout << "There was no file to load... creating a blank stack." << endl;
    }

    int option;

    //while we dont want to quit
    while(true){
        //display the options for the program
        cout << endl << "What would you like to do?" << endl;
        cout << "1. View the current tasks on the stack." << endl;
        cout << "2. Remove the top task in the stack." << endl;
        cout << "3. Add a new task to the stack." << endl;
        cout << "4. Save the current task to a file." << endl;
        cout << "5. Exit." << endl << endl;

        //get the input from the user
        cin >> option;

        //use the option to do the necessary task
        if(option < 6 && option > 0){
            if(option == 1){
                //create a buffer list to display all
                stack<string> buff = list;
                cout << endl;
                //print out the stack
                while(!buff.empty()){
                    cout << buff.top() << endl;
                    buff.pop();
                }
            }else if (option == 2){
                list.pop();
            }else if (option == 3){
                //make a string to hold the input
                string task;
                cout << endl << "Enter the task that you would like to add:" << endl;
                getline(cin, task); // THIS IS WHERE THE ISSUE COMES IN
                cin.ignore();

                //add the string
                list.push(task);
                cout << endl;
            }else if (option == 4){
                //write the stack to the file
                stack<string> buff = list;
                ofstream myfile ("to-do.txt");
                if (myfile.is_open()){
                    while(!buff.empty()){
                        myfile << buff.top();
                        buff.pop();
                        if(!buff.empty()){
                            myfile << endl;
                        }
                    }
                }
                myfile.close();
            }else{
                cout << "Thank you! And Goodbye!" << endl;
                break;
            }
        } else {
            cout << "Enter a proper number!" << endl;
        }
    }
}
Dialectics answered 2/2, 2011 at 16:22 Comment(10)
You can try to use a cin.ignore() before cin >> option.Lamellibranch
You need to perform error checking for all input operations (by testing the stream, e.g. if (!std::cin) { /* handle error */ }, and your input loop is incorrect: for how to write a correct input loop, see this answer to another question.Hoeve
Just for esthetism: a switch/case/default block would be far more readable than all these if/else if/else...Mechanic
How do you mean "just loops"? Could you add sample output capture?Prickle
Yeah Emmanuel, I am coming over to C++ from C# so I have no idea how those work. I was just going with what I know.Dialectics
why dont you use switch cases first.Assuming
@Arkadiy Example, I'll input "3" to choose to input something to the stack, then I'll type in a task to do like "Go make more coffee." and from there, the menu where you choose what to do, EX, add to the stack, display the stack, save to file, open file. Just displays on the screen and repeats in rapid succession. i55.tinypic.com/2cs6mgl.pngDialectics
Repeats in rapid succession with no more input from user?Prickle
And if there are no spaces in in the input?Prickle
@Arkadiy Then it works as intended and just adds to the stack.Dialectics
T
3

You have to add cin.ignore() right after options is chosen:

//get the input from the user
cin >> option;
cin.ignore();

And cin.ignore() is not necessary after your getline:

    getline(cin, task); // THIS IS WHERE THE ISSUE COMES IN
        //cin.ignore();

The problem is in options - if you didn't call cin.ignore() after it, options will contain end of line and loop will continue...

I hope this helps.

Tight answered 2/2, 2011 at 17:5 Comment(1)
What if the user enters "2 \n"? Is ignore() really the right thing to do here you think?Mizzle
G
2

Don't do this:

    while(myfile.good())
    {
        getline(myfile,inputLine);
        list.push(inputLine);
    }

The EOF flag is not set until you try and read past the EOF. The last full line read read up-to (bit not past) the EOF. So if you have have zero input left myfile.good() is true and the loop is enetered. You then try and read a line and it will fail but you still do the push.

The standard way of reading all the lines in a file is:

    while(getline(myfile,inputLine))
    {
        list.push(inputLine);
    }

This way the loop is only entered if the file contained data.

Your other problem seems to stem from the fact that you have:

 std::getline(std::cin,task); // THIS is OK
 std::cin.ignore();           // You are ignoring the next character the user inputs.
                              // This probably means the next command number.
                              // This means that the next read of a number will fail
                              // This means that std::cin will go into a bad state
                              // This means no more input is actually read.

So just drop the cin.ignore() line and everything will work.

Gaylene answered 2/2, 2011 at 17:5 Comment(0)
M
1

Instead of using ">>" directly on your stream you might consider using getline and then attempting to fetch your option from that. Yes, it's less "efficient" but efficiency isn't generally an issue in such situations.

You see, the problem is that the user could enter something silly here. For example, they could enter something like "two", hit enter, and then your program is going to pitch a fit as it happily continues trying to decipher an empty option over and over and over and over again. The user's only recourse the way you have it set up (and the way those recommending use of ignore() are recommending) is to kill your program. A well behaved program doesn't respond in this way to bad input.

Thus your best option is not to write brittle code that can seriously break down with the most modest of user ignorance/malfunction, but to write code that can handle error conditions gracefully. You can't do that by hoping the user enters a number and then a newline. Invariably, someday, you'll bet poorly.

So, you have two options to read your option. First, read a full line from the user, make sure the stream is still good, and then turn the string you get into a stream and try to read your integer out of it, making sure this other stream is still good. Second option, attempt to read a number, verify that the stream is still good, read a line and make sure the stream is still good and that your string is empty (or just ignore it if it isn't, your choice).

Mizzle answered 2/2, 2011 at 17:30 Comment(0)
P
1

@Vladimir is right. Here is the mechanism behind the bug:

When you enter option '3', what you actually put into stream is "3\n". cin >> option consumes "3" and leaves "\n". getline() consumes "\n" and your call to ignore() after getline() waits for user input.

As you can see, teh sequence of events is already not what you expected.

Now, while ignore() is waiting for input, you type in your line. That line you're typing is what will go to "cin >> option.

If you just give it one symbol, ignore() will dispose of it for you, and option will be read correctly. However, if you give it non-numeric symbols, stream will set failbit when trying to read the option. From that point on, your stream will refuse to do anything. Any << or getline will not set any new values in the variables they are supposed to change. You'll keep 3 in option and "" in task, in a tight loop.

Things to do:

  • always check cin.eof(), cin.fail() and cin.bad().
  • always initialize your variables and declare them in the narrowest scope possible (declare option=0 right before it's read).
Prickle answered 2/2, 2011 at 17:43 Comment(0)
D
0

I just figured out a way to kind of hack through it, not the greatest but it works. Create a character array, and then accept input in the array, and then put everything into the array into the string.

char buff[256];
            cout << endl << "Enter the task that you would like to add:" << endl;
            cin >> task;
            task += " ";
            cin.getline(buff, 256);
            for(int i = 1; buff[i] != 0; i++){
                task += buff[i];
            }
Dialectics answered 2/2, 2011 at 17:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.