C++: устранение неполадок цикла while с вложенным оператором if/else

Я изучаю C++ и у меня есть несколько вопросов.

Эта программа должна принимать входные данные для наименования и цены товаров и выводить их в текстовый файл. Когда для имени предмета введено сигнальное значение 999, цикл while должен прекратиться и вывести все наборы входных данных (название предмета и цена) в текстовый файл.

У меня две проблемы с этой программой:

  1. Отображается только самый последний набор входных данных (имя, цена). Как сохранить все входные данные в памяти?

  2. Ввод 999 для имени элемента не приводит к выходу из программы. Вместо этого программа перестает выводить подсказки. Как правильно остановить программу?

Вероятно, мне следует использовать цикл for, но я не уверен, как это будет реализовано.

#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;
}

person 0x1000001    schedule 13.02.2016    source источник
comment
Компилятор может не заботиться о форматировании пробелов, но люди это делают. Пожалуйста, делайте правильный отступ в своем коде в будущем.   -  person James Adkison    schedule 13.02.2016


Ответы (2)


Как это сделать с помощью std::vector

Во-первых, некоторые из них включают:

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

Создайте структуру, чтобы связать название товара с ценой

struct item
{
    string name;
    double price;
};

и сделать вектор этой структуры

vector<item> items;

Затем, после того как вы прочитаете имя и цену, вставьте их в структуру и вставьте структуру в вектор.

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

О том, почему цикл while не работает... Это будет прогулка.

while(item_name != SENTINEL)

Это хорошее начало. Если item_name не SENTINEL, продолжайте. Абсолютно верно. Дело в том, что item-name не было установлено в первый раз, когда вы попали сюда, заставляя некоторую беличью логику внутри цикла. Общее эмпирическое правило: прочитайте, затем проверьте. Тестировать перед чтением не так уж и полезно. Во-первых, тестировать нечего, но настоящая проблема в том, что теперь вы используете непроверенные данные или вам нужно включить еще один тест, чтобы их отловить.

Прочитай, а потом тестируй.

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

Получить имя элемента. Заводной.

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

ХОРОШО. Неплохо, но зачем здесь еще одно item_name?

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

        break;

break выходит из цикла или switch. Итак, выходим.

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

Чтение числовых значений имеет несколько опасностей, которых вы должны остерегаться. Большой из них заключается в том, что если что-то, что набрал пользователь, не может быть преобразовано в price, cin переходит в режим ошибки и не вернется, пока ошибка не будет устранена. И прежде чем пытаться снова получить цену, нужно удалить мусорные данные.

Прелесть функции cin >> x в том, что она возвращает cin. Это позволяет складывать команды. cin>>a>>b>>c>>d. cin и все его потоковые собратья имеют встроенный логический оператор, который можно использовать в тестах. Если cin все еще находится в хорошем состоянии, все операции чтения завершены успешно, его можно протестировать, и он вернет true.

Это позволяет вам делать такие вещи, как if (cin>>a>>b>>c>>d), и проверять, что все чтения были хорошими за один раз.

    }
}

Применив read, затем test, получаем

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

Этот глупо выглядящий фрагмент кода — самый безопасный способ сделать это. Он даже поймает и выход cin внезапно закончится. С cin такое случается не так часто, но это отличный способ проверить конец файла.

{
    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.

На самом деле, это просто должно было начинаться с числа. cin >> довольно тупой и пропустит 1234abcd, взяв 1234 и оставив abcd для следующего чтения. Это может преподнести вам неприятные сюрпризы. В этом случае abcd станет следующим item_name. то, что должно было стать следующим item_name, станет следующим price, и оттуда покатится плохое джу-джу.

А теперь вернемся к коду.

    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.

Здесь вы можете немного сэкономить, добавив конструктор в item и используя метод emplace_back из vector. Поищи это. Очень удобно.

}

И снова без беглого комментария:

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.
}

Теперь у вас есть vector полный items для печати. В Stack Overflow полно примеров того, как это сделать, но лучше сначала попробовать.

person user4581301    schedule 13.02.2016

Почему у вас есть дополнительный вызов cin внутри оператора if, проверяющего запись? Я думаю, что это не нужно.

Что касается проблемы с вашими входными данными, вы сохраняете только самые последние введенные значения, потому что каждый раз, когда цикл запускается снова, он перезаписывает переменные.

Чтобы решить эту проблему, вам нужно будет использовать массив для хранения элементов. Если вы хотите сделать так, чтобы вы могли запускать цикл и вводить столько входных данных, сколько необходимо, вам нужно будет реализовать динамический массив.

Вот как реализовать динамический массив http://www.learncpp.com/cpp-tutorial/6-9a-dynamically-allocating-arrays/

person Dylan Steele    schedule 13.02.2016
comment
Почему бы просто не предложить использовать std::vector вместо того, чтобы изобретать велосипед? - person James Adkison; 13.02.2016
comment
Он учится, поэтому я решил, что лучше всего начать с того, как учится большинство людей. - person Dylan Steele; 13.02.2016
comment
Я бы не советовал новичку начинать с работы с необработанными указателями и управлением памятью; не все учебники хороши. - person James Adkison; 13.02.2016
comment
Присоединяюсь к Джеймсу Адкинсону. Большинство людей учатся программировать неправильно. Возможно, отчасти поэтому средняя карьера программиста так жалка. Я слышу такие вещи, как 3 и 5 лет. - person user4581301; 13.02.2016
comment
@James: Как бы вы посоветовали мне подойти к этой проблеме? Как применяется std::vector? - person 0x1000001; 13.02.2016
comment
@Henry Перейдите по ссылке на документацию, которую я предоставил. std::vector — это динамический массив (т. е. он может изменять размер во время выполнения). Я действительно думаю, что у вас была ошибка с использованием cin >> item_name, когда это не нужно (как указано выше), и std::vector позволит вам сохранить все данные, которые собираются во время выполнения. - person James Adkison; 13.02.2016