简体   繁体   中英

C++: Troubleshooting while loop with nested if/else statement

I'm learning C++ and have a few questions.

This program should take inputs for the name and price of items and output them to a text file. When the sentinel value 999 is entered for the item name, the while loop should cease and output all sets of inputs (item name and price) to the text file.

I have two problems with this program:

  1. Only the most recent set of inputs (name, price) is displayed. How do I keep all inputs in memory?

  2. Entering 999 for the item name does not cause the program to exit. Instead, the program ceases to display prompts. How do I stop the program correctly?

I should probably use a for loop, but I'm not sure how that would be implemented.

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

int main()
{
    string item_name;
    double price;
    int item_number;

    const string SENTINEL = "999";

    ofstream myfile ("invoice1.txt");


    while(item_name != SENTINEL)
    {
        cout<<"Enter the name of the item."<<'\n';
        cin>>item_name;

        if (item_name == SENTINEL)
        {
            cin>>item_name;
            myfile<<"Thank you for your entries"<<'\n';
            myfile<<item_name<<"#"<<price<<endl;
            myfile.close();

            break;
        }
        else
        {
            cout<<"Enter the price of the item."<<'\n';
            cin>>price;
        }
    }

    myfile<<"Thank you for your entries"<<'\n';
    myfile<<item_name<<"#"<<price<<endl;
    myfile.close();

    return 0;
}

How to do this with a std::vector

First, a few includes:

#include <vector> // obviously. Can't do vectors without the vector header.
#include <limits> // for a trick I'll use later

Create a structure to link the item name with a price

struct item
{
    string name;
    double price;
};

and make a vector of that structure

vector<item> items;

Then after you read in a name and price, stuff it in a structure and stuff the structure into the vector.

item temp;
temp.name = item_name;
temp.price = price;
items.push_back(temp);

On why the while loop doesn't work... That'll take a walk through.

while(item_name != SENTINEL)

This is a good start. If item_name isn't SENTINEL , keep going. Exactly right. Thing is, item-name hasn't been set the first time you get here, forcing some squirrelly logic inside the loop. General rule of thumb is read, then test. Test before read is not so useful. For one thing, there's nothing to test, but the real problem is now you're using untested data or have to include another test to catch it.

Read, then test.

{
    cout<<"Enter the name of the item."<<'\n';
    cin>>item_name;

Get an item name. Groovy-ish.

    if (item_name == SENTINEL)
    {
        cin>>item_name;

OK. Not bad, but why get another item_name here?

        myfile<<"Thank you for your entries"<<'\n';
        myfile<<item_name<<"#"<<price<<endl;
        myfile.close();

        break;

break exits a loop or a switch . So out we go.

    }
    else
    {
        cout<<"Enter the price of the item."<<'\n';
        cin>>price;

Reading in numerical values has a few dangers you have to watch out for. The big one is if whatever the user typed can't be turned into price , cin goes into error mode and won't get back out until the error is cleared. And before you try to get price again, the garbage data needs to be removed.

A neat thing about cin >> x is it returns cin . This lets you stack commands. cin>>a>>b>>c>>d . cin , and all its streaming brethren, have a built in boolean operator you can use in tests. If cin is still in a good state, all of the reads completed successfully, it can be tested and will return true .

This lets you do stuff like if (cin>>a>>b>>c>>d) and test that all of the reads were good in one shot.

    }
}

Applying read, then test, we get

while(cin>>item_name && // read in an item name
      item_name != SENTINEL) // and then test that it isn't the sentinel

This goofy-looking bit of code is about the safest way to do this. It will even catch and exit cin comes to a sudden end. Doesn't happen all that often with cin , but this is a great way to test for the end of a file.

{
    while (!(cin >> price)) // keep looping until the user gives a number
    {  // if we're here, the user didn't give a good number and we have to clean up 
       // after them. Bad user. Bad. Bad.

As an aside, don't do this trick with a file. The file could have ended. You would have to test the file for end of file and exit here before continuing with the clean up.

        // clear the error
        cin.clear(); 
        // throw out everything the user's typed in up to the next line
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
    }
    // user got out of the while of doom. They must have entered a number.

Welllll actually, it just had to start with a number. cin >> is pretty dumb and will let 1234abcd through, taking the 1234 and leaving the abcd for the next read. This can leave you with some nasty surprises. In this case the abcd will wind up as the next item_name . what should have been the next item_name will become the next price and the bad ju-ju rolls on from there.

And now back to the code.

    item temp; // make a temporary item
    temp.name = item_name; // set the values correctly
    temp.price = price;
    items.push_back(temp); // put them in the list.

You can save a bit of work here by adding a constructor to item and using vector 's emplace_back method. Look it up. Very handy.

}

And again without the running commentary:

while(cin>>item_name && // read in an item name
      item_name != SENTINEL) // and then test that it isn't the sentinel
{
    while (!(cin >> price)) // keep looping until the user gives a number
    {  // if we're here, the user didn't give a good number and we have to clean up 
       // after them. Bad user. Bad. Bad.
        // clear the error
        cin.clear(); 
        // throw out everything the user's typed in up to the next line
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
    }
    // user got out of the while of doom. They must have entered a number.
    item temp; // make a temporary item
    temp.name = item_name; // set the values correctly
    temp.price = price;
    items.push_back(temp); // put them in the list.
}

Now you have a vector full of item s to print. Stack Overflow is full of examples on how to do this, but it's best if you take a shot at it first.

Why do you have an extra cin call inside the if statement checking the entry? I think that is not necessary.

For the issue with your inputs you are only storing the most recent entered values because each time the loop runs again it will over write the variables.

To solve this you will need to use an array to store items. If you want to make it so that you can run the loop an enter as many inputs as needed you will need to implement a dynamic array.

Here is how to implement a dynamic array http://www.learncpp.com/cpp-tutorial/6-9a-dynamically-allocating-arrays/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM