简体   繁体   English

不断询问用户输入直到满足条件 C++

[英]Keep asking for user input until condition met C++

I'm trying to write a script where the user will be inputting a radius and then the console will display the Volume and Surface Area of a sphere.我正在尝试编写一个脚本,用户将在其中输入半径,然后控制台将显示球体的体积和表面积。 If the input radius is negative, the user will be prompted to enter a positive radius until the condition is met.如果输入半径为负数,则提示用户输入正数半径,直到满足条件。 I've managed to do this but without validating the positive radius bit.我设法做到了这一点,但没有验证正半径位。 How can I achieve this?我怎样才能做到这一点?

My code:我的代码:

/*
 * Calculate the volume and surface area of a sphere.
 *
 */

#include <iostream>
#include <string>
#include <sstream>
#include <cmath> // Include cmath for M_PI constant
using namespace std;

int main()
{
    const double pi = M_PI; /// Value of PI defined by C++
    string input = "";      /// Temporary input buffer
    double r = 0.0;         /// Sphere radius
    double A = 0.0;         /// Sphere area
    double V = 0.0;         /// Sphere volume

    // Request radius
    cout << "Please enter radius of sphere (positive only): ";

    // Get string input from user (up to next press of <enter> key)
    getline(cin, input);

    // Try to convert input to a double
    r = stod(input);

    // making sure r is positive
    if (r > 0)
    {
        // Calculate area and volume
        // Ensure floating-point division instead of integer division by
        // explicitly writing 4.0/3.0
        A = 4.0 * pi * r * r;
        V = (4.0 / 3.0) * pi * r * r * r;

        // Write out result
        cout << "Sphere radius: " << r << endl;
        cout << "Sphere area:   " << A << endl;
        cout << "Sphere volume: " << V << endl;
    }
    else
    {
        while (r < 0)
        {
            cout << "Please enter radius of sphere (positive only): " << endl;
        }
    }

    // Return success
    return 0;
}

First, this code is not awful.首先,这段代码并不糟糕。 Compared to what I've seen from some other beginners, this code demonstrates that there is a decent understanding of fundamentals up to this point.与我从其他一些初学者那里看到的相比,这段代码表明到目前为止对基础知识的理解还算不错。

The biggest issue facing your code is the order of operations.您的代码面临的最大问题是操作顺序。 If you want input from the user, you need to validate it before processing it.如果您想要来自用户的输入,则需要在处理之前对其进行验证。 Currently, you're doing a bit of both at the same time.目前,您正在同时进行这两项工作。 As mentioned, create a loop that does not exit until you have valid inputs.如前所述,创建一个循环,在您获得有效输入之前不会退出。 Then go ahead and do your math.然后是 go 并计算一下。 This is separating your concerns and is a best practice.这是将您的关注点分开,是最佳做法。

Other nitpicks include using namespace std;其他挑剔包括using namespace std; as a bad practice , and one you should get out of doing sooner than later. 作为一种不好的做法,你应该早日摆脱这种做法。 Front-declaring your variables is also bad practice.前面声明你的变量也是不好的做法。 Declare at or near first use.在首次使用时或接近首次使用时声明。 std::string input; suffices for a default string, there is no need to = "";对于默认字符串就足够了,不需要= ""; . .

And as I commented, stod() can throw an exception and abort your program if the input cannot be converted.正如我评论的那样,如果无法转换输入, stod()可以抛出异常并中止您的程序。 You don't mention whether you're allowed to assume your input will always be a number or not so I can't assume it is.你没有提到是否允许你假设你的输入总是一个数字,所以我不能假设它是。

/*
 * Calculate the volume and surface area of a sphere.
 *
 */

#include <cmath>
#include <iostream>
#include <numbers>
#include <string>

int main() {
  double radius = 0.0;
  bool inputIsInvalid = true;

  do {
    std::string input;
    std::cout << "Enter a radius: ";
    std::getline(std::cin, input);

    std::size_t pos = 0;
    try {
      radius = std::stod(input, &pos);
    } catch (const std::exception& e) {
      std::cerr << "Unable to convert to double. Reason: " << e.what() << '\n';
      continue;
    }

    // We're still not done checking. We need to ensure that the entire string
    // was converted. If not, the input was invalid.
    if (pos != input.length()) {
      std::cerr << "Invalid characters added. Try again.\n";
      continue;
    }

    // Making it here means a valid double was typed in.
    // Now we ensure that the double is positive.
    if (radius < 0.0) {
      std::cerr << "Please enter a positive number. Try again.\n";
      continue;
    }

    // Making it here should mean that we have a valid input.
    inputIsInvalid = false;

  } while (inputIsInvalid);

  // Now we can do math!
  using namespace std::numbers;  // C++20 stuff for pi
  double surfaceArea = 4.0 * pi * std::pow(radius, 2);
  double volume = (4.0 / 3.0) * pi * std::pow(radius, 3);

  std::cout << "For a sphere of radius: " << radius << '\n'
            << "Surface area: " << surfaceArea << '\n'
            << "Volume: " << volume << '\n';
}

Output: Output:

❯ ./a.out 
Enter a radius: foo
Unable to convert to double. Reason: stod: no conversion
Enter a radius: 3o
Invalid characters added. Try again.
Enter a radius: -3
Please enter a positive number. Try again.
Enter a radius: 3
For a sphere of radius: 3
Surface area: 113.097
Volume: 113.097

As you can see, all of the getting of input and validation occurs within the big do/while loop.如您所见,所有输入和验证的获取都发生在大的 do/while 循环中。 If we are out of the loop, we know that we have a valid value, and doing the math is now very straightforward.如果我们在循环之外,我们就知道我们有一个有效值,现在做数学就非常简单了。

There is no need for complicated statements.不需要复杂的语句。

You just need to understand that IO operation notice, when there was a problem.你只需要了解IO操作通知,什么时候出现问题。 And then they set failure bits, which you can check.然后他们设置故障位,您可以检查。

Please look in the CPP reference at this link .请查看此链接中的 CPP 参考资料。 There is a long description of what could happen and what failure bit will be set and how it can be tested.对可能发生的情况、将设置的故障位以及如何对其进行测试有很长的描述。

So, if you use for example the stand extraction operator >> like in std::cin >> radius then this operator will try to read a value from the console and convert it to a double.因此,如果您使用例如 stand std::cin >> radius中的站提取运算符>> ,则此运算符将尝试从控制台读取一个值并将其转换为双精度值。 If it cannot do that, because you entered for example "abc" instead of a number, a failure bit will be set.如果它不能这样做,因为您输入了例如“abc”而不是数字,则会设置一个失败位。 The std::cin is then in state fail and does not work any longer. std::cin然后在 state 失败并且不再工作。

If you want to continue to use std::cin then you must clear the fail bits with the clear() function. So, you need to write std::cin.clear();如果你想继续使用std::cin那么你必须用clear() function 清除失败位。所以,你需要写std::cin.clear(); . .

But this is not sufficient.但这还不够。 There might be still some other characters in the input buffer.输入缓冲区中可能还有一些其他字符。 And those need to be removed.那些需要被删除。 Imagine that you enter "XYZ" instead of a number, then std::cin will go into failure state after reading the first character 'X'.想象一下,您输入“XYZ”而不是数字,那么std::cin在读取第一个字符“X”后会将 go 变为失败 state。 We need to eliminate all the wrong characters, because otherwise, they will be read again with the next >> operation.我们需要消除所有错误的字符,否则,它们将在下一个>>操作中再次读取。

For this we have the function ignore .为此,我们有 function ignore Please read here about the function and look at the example at the bottom of the page.在此处阅读有关 function 的信息,并查看页面底部的示例。 Exactly what you need.正是您所需要的。

Next: How can we check for an error of an IO operation?下一篇:我们如何检查IO操作的错误?

You may have heard that we can chain IO operations.您可能听说过我们可以链接 IO 操作。 For example: int a,b; std::cin >> a >> b;例如: int a,b; std::cin >> a >> b; int a,b; std::cin >> a >> b; or, for the output case std::cout << value << "\n";或者,对于 output 案例std::cout << value << "\n";

Why does this work?为什么这行得通? You need to understand the the extraction and inserter operator >> and << return a reference to the stream for which they have been called.您需要了解提取和插入运算符>><<返回对调用它们的 stream 的引用。

So, std::cin >> a;所以, std::cin >> a; will return a reference to std::cin .将返回对std::cin的引用。 And that is the reason why you can chain IO operations.这就是为什么您可以链接 IO 操作的原因。

std::cin >> a >> b; will first do std::cin >> a which will return std::cin .将首先执行std::cin >> a这将返回std::cin The rest of the expression will now be std::cin >> b;表达式的 rest 现在将是std::cin >> b; . . also this will be performed and again std::cin will be returned.这也将被执行并再次返回std::cin

And this we can use.我们可以使用这个。 The basic:ios has 2 operators to check the failure state. basic:ios有2个操作员检查故障state。

  1. the bool operator布尔运算符
  2. the not operator !不是运算符!

So, you can check the state of std::cin simply with if (std::cin) .因此,您可以简单地使用if (std::cin)检查std::cin的 state。 And because the if -statement expects a bool expression, it will call the streams bool operator.因为if语句需要一个 bool 表达式,所以它会调用流bool运算符。 And with that get the state.然后得到 state。

And now, what we learned above: if (std::cin >> a) will try to read a value into "a" then return std::cin and then its bool operator is called.现在,我们在上面学到了什么: if (std::cin >> a)将尝试将值读入“a”,然后返回std::cin ,然后调用其bool运算符。

Now we found a possibility to check for a correct IO operation with if (std::cin >> radius) But of course we can do more test in the if statement.现在我们发现可以使用if (std::cin >> radius)检查正确的 IO 操作,但当然我们可以在 if 语句中进行更多测试。 You have often seen and && or or ||你经常看到and && or or || operators in conditions.条件下的运营商。 You can make use of it.你可以利用它。 And especially you can make use of boolean shortcut evaluation.特别是您可以使用 boolean 快捷方式进行评估。

Meaning, if the outcome of a condition is already clear by evaluation the first term, then the second term will not be evaluated.意思是,如果条件的结果通过第一项的评估已经明确,那么第二项将不会被评估。

So, we can write if ((std::cin >> radius) and (radius > 0.0)) to check for a valid input.因此,我们可以编写if ((std::cin >> radius) and (radius > 0.0))来检查有效输入。 If the reading of the input fails, then the check for greater than 0 will not be executed.如果读取输入失败,则不会执行大于 0 的检查。 It will only be executed, if the input was successful.只有在输入成功时才会执行。

With all the above, we can now draft the below very simple solution:综上所述,我们现在可以起草以下非常简单的解决方案:

#include <iostream>
#include <limits>

int main() {
    double radius = 0.0;
    bool valueOK = false;

    while (not valueOK) {
        std::cout << "\n\nInsert radius. A positive value: ";
        if ((std::cin >> radius) and (radius > 0.0))
            valueOK = true;
        else {
            std::cout << "\n\n***Error: invalid input\n";
        }
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }

    const double PI = 3.14159265358979323846;
    std::cout << "\n\nSphere Radius:\t" << radius
        << "\nSphere area:\t" << 4.0 * PI * radius * radius
        << "\nSphere volume:\t" << 4.0 / 3.0 * PI * radius * radius * radius << '\n';
}

No need for complicated statements.不需要复杂的语句。

You could use std::from_chars within an infinite loop to read a radius of type double (see here ):您可以在无限循环中使用std::from_chars来读取 double 类型的半径(参见此处):

  • It receives a couple of const char* to the beginning and end of the string you want to parse, and a reference to a double value (that will be set if the parsing went OK).它在要解析的字符串的开头和结尾接收到几个const char* ,以及对双精度值的引用(如果解析成功,将设置该值)。
  • It returns a pointer pointing to the first character that didn't match the pattern for a double value, and an error.它返回一个指向第一个与双精度值模式不匹配的字符的指针,并返回一个错误。
  • If the error is "empty" (just value-initialized), and the pointer points to the end of the input string, it means that all the characters were used to parse the double value (ie there were no extra characters at the beginning or at the end in the input string).如果错误为“空”(只是值初始化),并且指针指向输入字符串的末尾,则意味着所有字符都用于解析双精度值(即开头没有多余的字符或在输入字符串的末尾)。

[Demo] [演示]

#include <charconv>  // from_chars
#include <iostream>  // cin, cout
#include <numbers>  // Include cmath for M_PI constant
#include <sstream>
#include <string>  // getline
#include <system_error>  // errc

int main() { 
    // Request radius
    double r = 0.0;  /// Sphere radius
    for (;;) {
        std::cout << "Please enter radius of sphere (positive only): ";

        // Get string input from user (up to next press of <enter> key)
        std::string input = "";  /// Temporary input buffer
        getline(std::cin, input);
        std::cout << input << "\n";

        // Try to convert input to a double
        auto [ptr, ec] = std::from_chars(input.data(), input.data() + input.size(), r);
        if (ec != std::errc{} or ptr != input.data() + input.size() or r < 0) {
            std::cout << "Invalid input: '" << input << "'\n";
        } else {
            break;
        }
    }

    // Calculate area and volume
    // Ensure floating-point division instead of integer division by
    // explicitly writing 4.0/3.0
    double A = 4.0 * std::numbers::pi_v<double> * r * r;  /// Sphere area
    double V = (4.0 / 3.0) * std::numbers::pi_v<double> * r * r * r;  /// Sphere volume

    // Write out result
    std::cout << "Sphere radius: " << r << "\n";
    std::cout << "Sphere area:   " << A << "\n";
    std::cout << "Sphere volume: " << V << "\n";
 
    // Return success
    return 0;
}

// Outputs:
//
//   Please enter radius of sphere (positive only): -5
//   Invalid input: '-5'
//   Please enter radius of sphere (positive only): hola
//   Invalid input: 'hola'
//   Please enter radius of sphere (positive only): 25abc
//   Invalid input: '25abc'
//   Please enter radius of sphere (positive only): 1.5
//   Sphere radius: 1.5
//   Sphere area:   28.2743
//   Sphere volume: 14.1372

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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