Я пытаюсь написать программу для малышей, но у меня обработка исключений работает неправильно. Вместо этого я получаю следующий вывод, показывающий вывод if (DaysNumber < 7)
вместо сообщения об ошибке:
У меня есть следующий код (это мой основной файл; у меня есть ссылка на мой репозиторий GitHub для других файлов внизу этого вопроса, если вы хотите увидеть и эти файлы):
#include <iostream>
#include <iomanip>
#include <string>
#include <cstdlib>
#include <stdexcept>
#include "tatertot.h"
using namespace std;
int main() {
int DaysNumber;
// Asks the user how long it’s been since they ate tater tots
cout << "How long has it been since you had some yummy tots?" << endl;
cout << "Enter the number of days: " << endl;
cin >> DaysNumber;
// Handles invalid inputs
try {
// If it’s been a week (or more) since the user ate tater tots…
if (DaysNumber >= 7) {
cout << "Yay! I bet those tots were delicious. Here's a recipe to try next time you want to eat tater tots: " << endl;
cout << "https://www.allrecipes.com/recipe/236749/tater-tots-nachos/";
abort();
}
// If the user ate tater tots recently…
else {
cout << "It. Is. TATER TOT TIME!" << endl;
cout << "Go enjoy some yummy tots!" << endl;
cout << "https://www.allrecipes.com/recipe/236749/tater-tots-nachos/";
abort();
}
}
catch (string e) {
cin.clear();
cout << "I'm sorry, but your input is invalid. Could you please try again?" << endl;
cin >> DaysNumber;
}
catch (double e) {
cin.clear();
cout << "I'm sorry, but your input is invalid. Could you please try again?" << endl;
cin >> DaysNumber;
}
return 0;
};
Я пробовал использовать другие методы обработки исключений и определять DaysNumber
в файле класса и файле средства доступа-мутатора, но безрезультатно. Может быть, мне здесь чего-то не хватает?
Вот ссылка на мой репозиторий GitHub: https://github.com/NEE64/potate-repository
Спасибо всем, кто предложил помощь и совет! Я обязательно запомню эти советы, когда через пару дней вернусь на занятия в колледже (и когда мне снова захочется написать программу).
Ваш неверный ввод просто приводит к чтению 0 в переменную. Ничто не вызывает исключений, которые вы пытаетесь перехватить.
Ставьте лайк, посмотрите https://en.cppreference.com/w/cpp/io/basic_istream/operator_gtgt :
Если извлечение не удается (например, если вместо ожидаемой цифры была введена буква), в значение записывается ноль и устанавливается бит ошибки.
Кроме того, стандартная библиотека C++ никогда не генерирует исключений string
или double
. Я думаю, вы ожидаете, что они будут выданы, когда пользователь вводит строку или двойное число вместо целого числа? C++ не творит подобных чудес.
Вы можете попросить operator>>
выдать исключение при неверном вводе. См. метод cin.Exceptions() . Но он выдаст исключение std::ios_base::failure.
@RemyLebeau да, можешь. Но я только что перепроверил, он этого не делает - и он говорил о переходе в случай "<7", а не о получении неперехваченного исключения. В каком-то смысле моя стратегия отладки обычно сосредоточена на том, что было сделано, а не на том, что можно было сделать — я смотрю на симптомы, я смотрю на код и пытаюсь понять, смогу ли я объяснить симптомы с помощью кода.
Структура вашей программы имеет смысл только для людей. Компьютеры слишком тупы, чтобы знать, как начать все сначала, если вы не объясните это явно.
⟶ For each of the following snippets I will list all the required includes. Generally you need only list each include once, all together at the top of the source file.
Текстовый ввод обычно следует получать в виде полной строки текста, а затем попытаться преобразовать его в то, что вы запрашивали. Это потому, что пользователь всегда будет нажимать Enter после каждого запрошенного ввода!
У меня есть небольшая служебная функция, которую я использую, чтобы попытаться преобразовать вводимые пользователем данные в нужный тип данных. Он возвращает либо успешно преобразованное значение, либо недопустимое состояние.
#include <ciso646>
#include <optional>
#include <sstream>
#include <string>
template <typename T>
auto string_to( const std::string & s )
{
T value;
std::istringstream ss( s );
return ((ss >> value) and (ss >> std::ws).eof())
? value
: std::optional <T> { };
}
Мы можем использовать его следующим образом:
// EXAMPLE
std::cout << "Gimme a Double on the double: ";
std::string s;
getline( std::cin, s );
auto value = string_to <double> ( s );
if (value)
std::cout << "Yay! " << *value << "\n";
else
std::cout << "Booo!\n";
Это приглашение, получение строки, попытка преобразования и проверка — это обычное дело, которое мы делаем снова и снова. Давайте напишем еще одну вспомогательную функцию, которая сделает все это за нас:
#include <iostream>
#include <string>
template <typename T>
auto ask( const std::string & prompt )
{
std::cout << prompt;
std::string s;
getline( std::cin, s );
return string_to <T> ( s );
}
Это облегчает нам жизнь:
// EXAMPLE
auto value = ask <int> ( "How many days? " );
if (value)
std::cout << "Good job! You entered " << *value << " days!\n";
else
std::cout << "That was not an INTEGER value.\n";
Мы можем еще больше улучшить нашу жизнь, написав еще одну небольшую утилиту, которая сделает за нас всю проверку и вернет нужный нам тип данных без неприятной std::optional
-обертки:
#include <stdexcept>
#include <string>
template <typename T, typename Verify>
T ask( const std::string & prompt, Verify verify, const std::string & error_message )
{
auto value = ask <int> ( prompt );
if (!value or !verify( *value ))
throw std::runtime_error( error_message );
return *value;
}
Эта версия ask<>()
выдает нам исключение во время выполнения, если функция проверки завершается сбоем. Через секунду мы увидим, как его использовать.
Теперь мы готовы использовать наши удобные вспомогательные функции, чтобы упростить работу с функцией main().
#include <exception>
#include <iostream>
int main()
try
{
// Ask the user for an integer value
int daysNumber = ask <int> (
"How many days has it been since you have had some yummy tots? ",
// Here is our validation function AND the message
// to complain with should validation fail.
[]( int value ) { return value >= 0; },
"Number of days must be an integer value ≥ 0." );
// At this point we KNOW we have an integer number of days ≥ 0
if (daysNumber > 7)
{
std::cout
<< "Yay! I bet those tots were delicious.\n"
<< "Here's a recipe to try next time you want to eat tater tots:\n"
<< "https://www.allrecipes.com/recipe/236749/tater-tots-nachos/\n";
}
else
{
std::cout
<< "It. Is. TATER TOT TIME!\n"
<< "Go enjoy some yummy tots!\n"
<< "https://www.allrecipes.com/recipe/236749/tater-tots-nachos/\n";
}
// return 0; // This can be omitted in C++
}
// Here we catch all errors derived from the standard exception
// class. We print the error message and terminate nicely, complete
// with an error code.
catch (const std::exception & e)
{
std::cerr << e.what() << "\n";
return 1;
}
На данный момент у нас есть полная программа! Просто вставьте все вышеперечисленное (кроме ПРИМЕРОВ) в файл .cpp, скомпилируйте его и приступайте!
⟶ Here, I did it for you: https://godbolt.org/z/YszzvTdTK
Ход выполнения нашей основной программы обычно не должен давать пользователю слишком много возможностей ввести неправильный ввод.
⟶ Save that kind of stuff for interactive UI type programs, such as an editor or something that runs from a menu.
Другими словами, если пользователь вводит что-то неправильно, скажите ему: «Крепкие бобы!» и умереть (с сообщением об ошибке).
Кроме того, использование abort()
— это своего рода удар в спину вашей программе, хотя вы ей все еще нравитесь. Предпочитаю красиво завершить программу. (Вы можете сделать это с исключениями.)
C++ делает обработку исключений исключительно простой (к сожалению, это каламбур).
Эта странная штука []( int value ) { ... }
называется лямбда. Это просто способ написания встроенной безымянной функции. Мы могли бы написать обычную функцию:
bool validate_ge_0( int value )
{
return value >= 0;
}
И использовал его так:
int daysNumber = ask <int> (
"How many days has it been since you have had some yummy tots? ",
validate_ge_0, // (this is the name of the validation function)
"Number of days must be an integer value ≥ 0." );
Вы все еще можете делать что-то с циклами. Просто не забудьте указать это явно и дать пользователю возможность уйти. Помните, вы можете написать это так, как вам нравится!
template <typename T, typename Validate>
auto ask_n_times(
unsigned n,
const std::string & prompt,
Validate validate,
const std::string & error_message )
{
if (n == 0) n = 1;
while (n--)
{
auto value = ask <T> ( prompt );
if (value and verify( *value ))
return *value;
std::cout << error_message << "\n";
std::cout << n << " more tries to answer.\n";
}
throw std::runtime_error( error_message.c_str() );
}
А потом:
int main()
{
int evenNumber = ask_n_times <int> (
3,
"Even number? ",
[]( int value ) { return !(value & 1); },
"That was not an even number." );
...
Это должно дать вам хорошее представление о том, как организовать ваш код. Помните, что повторяющиеся или общие задачи могут быть обернуты небольшими вспомогательными функциями, и все, что вы хотите сделать несколько раз, должно выполняться в явном цикле. В противном случае программа выполняется так же, как вы ее читаете, сверху вниз, начиная с функции main()
.
Удачи!
Я не вижу, чтобы вы
throw
создавали исключение, так как же вы ожидали, что блокиcatch
будут вызваны?