【问题标题】:Keep asking for user input until condition met C++不断询问用户输入直到条件满足 C++
【发布时间】:2022-12-18 19:40:56
【问题描述】:

我正在尝试编写一个脚本,用户将在其中输入半径,然后控制台将显示球体的体积和表面积。如果输入半径为负数,则提示用户输入正数半径,直到满足条件。我设法做到了这一点,但没有验证正半径位。我怎样才能做到这一点?

我的代码:

/*
 * 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;
}

【问题讨论】:

  • 如果输入非双精度值,stod() 将使您的程序崩溃。例如“富”。你的评论告诉你你需要做什么。 stod() 需要在 try 块内。
  • 您需要在 while 循环内调用 getline(cin, input);r = stod(input);,但是如果您得到一个正数,则需要跳回 ok 部分。制作一个 inout 函数并在其中执行 while
  • 此外,从 C++20 开始,&lt;numbers&gt; 定义了 std::numbers::pi
  • 最后,我的建议是注意获取数据并确保其正确无误,做任何计算。您部分验证您的输入,进行计算,然后再次检查您的输入。在编写代码之前讨论这些事情。

标签: c++ loops if-statement while-loop conditional-statements


【解决方案1】:

首先,这段代码并不糟糕。与我从其他一些初学者那里看到的相比,这段代码表明到目前为止对基础知识的理解还算不错。

您的代码面临的最大问题是操作顺序。如果您想要来自用户的输入,则需要在处理之前对其进行验证。目前,您正在同时进行这两项工作。如前所述,创建一个循环,在您获得有效输入之前不会退出。然后继续做你的数学。这是将您的关注点分开,是最佳做法。

其他吹毛求疵的人包括using namespace std; as a bad practice,你应该早点放弃。前面声明你的变量也是不好的做法。在首次使用时或接近首次使用时声明。 std::string input; 足以作为默认字符串,不需要 = "";

正如我评论的那样,如果无法转换输入,stod() 可以抛出异常并中止您的程序。你没有提到是否允许你假设你的输入总是一个数字,所以我不能假设它是。

/*
 * 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() << '
';
      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.
";
      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.
";
      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 << '
'
            << "Surface area: " << surfaceArea << '
'
            << "Volume: " << volume << '
';
}

输出:

❯ ./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

如您所见,所有输入和验证的获取都发生在大的 do/while 循环中。如果我们在循环之外,我们就知道我们有一个有效值,现在做数学就非常简单了。

【讨论】:

    【解决方案2】:

    不需要复杂的语句。

    当出现问题时,您只需要了解 IO 操作通知即可。然后他们设置故障位,您可以检查。

    请在link 中查看 CPP 参考。对可能发生的情况、将设置的故障位以及如何对其进行测试有很长的描述。

    因此,如果您使用例如 std::cin &gt;&gt; radius 中的站提取运算符 &gt;&gt;,那么此运算符将尝试从控制台读取一个值并将其转换为双精度值。如果它不能这样做,因为您输入了例如“abc”而不是数字,则会设置一个失败位。 std::cin然后处于失败状态并且不再工作。

    如果您想继续使用std::cin,那么您必须使用clear()函数清除失败位。所以,你需要写std::cin.clear();

    但这还不够。输入缓冲区中可能还有一些其他字符。那些需要被删除。想象一下,您输入“XYZ”而不是数字,那么std::cin 将在读取第一个字符“X”后进入失败状态。我们需要消除所有错误的字符,否则,将在下一个&gt;&gt;操作中再次读取它们。

    为此,我们有函数ignore。请阅读here了解该功能并查看页面底部的示例。正是您所需要的。

    下一篇:我们如何检查IO操作的错误?

    你可能听说过我们可以链式 IO 操作。例如:int a,b; std::cin &gt;&gt; a &gt;&gt; b; 或者,对于输出案例 std::cout << value << " ";

    为什么这行得通?您需要了解提取和插入运算符 &gt;&gt;&lt;&lt; 返回对调用它们的流的引用。

    所以,std::cin &gt;&gt; a; 将返回对 std::cin 的引用。这就是为什么您可以链接 IO 操作的原因。

    std::cin &gt;&gt; a &gt;&gt; b; 将首先执行 std::cin &gt;&gt; a,它将返回 std::cin。表达式的其余部分现在将是 std::cin &gt;&gt; b;。这也将被执行并再次返回std::cin

    我们可以使用这个。 basic:ios 有 2 个操作员来检查故障状态。

    1. bool 运营商
    2. not运营商!

      所以,你可以简单地用if (std::cin)来检查std::cin的状态。因为 if-statement 需要一个 bool 表达式,所以它将调用流 bool 运算符。并以此获得状态。

      现在,我们在上面了解到:if (std::cin &gt;&gt; a) 将尝试将值读入“a”,然后返回 std::cin,然后调用其 bool 运算符。

      现在我们发现可以使用 if (std::cin &gt;&gt; radius) 检查正确的 IO 操作,但当然我们可以在 if 语句中进行更多测试。你经常看到and && 或or||条件下的运营商。你可以利用它。特别是您可以使用布尔快捷方式求值。

      意思是,如果条件的结果通过第一项的评估已经明确,那么第二项将不会被评估。

      所以,我们可以写if ((std::cin &gt;&gt; radius) and (radius &gt; 0.0))来检查有效输入。如果读取输入失败,则不会执行大于 0 的检查。只有在输入成功时才会执行。

      综上所述,我们现在可以起草以下非常简单的解决方案:

      #include <iostream>
      #include <limits>
      
      int main() {
          double radius = 0.0;
          bool valueOK = false;
      
          while (not valueOK) {
              std::cout << "
      
      Insert radius. A positive value: ";
              if ((std::cin >> radius) and (radius > 0.0))
                  valueOK = true;
              else {
                  std::cout << "
      
      ***Error: invalid input
      ";
              }
              std::cin.clear();
              std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '
      ');
          }
      
          const double PI = 3.14159265358979323846;
          std::cout << "
      
      Sphere Radius:	" << radius
              << "
      Sphere area:	" << 4.0 * PI * radius * radius
              << "
      Sphere volume:	" << 4.0 / 3.0 * PI * radius * radius * radius << '
      ';
      }
      

      不需要复杂的语句。

    【讨论】:

      【解决方案3】:

      您可以在无限循环中使用 std::from_chars 来读取 double 类型的半径(参见 here):

      • 它在要解析的字符串的开头和结尾接收一对const char*,以及对双精度值的引用(如果解析成功,将设置该值)。
      • 它返回一个指向第一个与双精度值模式不匹配的字符的指针,并返回一个错误。
      • 如果错误为“空”(只是值初始化),并且指针指向输入字符串的末尾,则意味着所有字符都用于解析双精度值(即没有多余的字符在输入字符串的开头或结尾)。

      [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 << "
      ";
      
              // 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 << "'
      ";
              } 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 << "
      ";
          std::cout << "Sphere area:   " << A << "
      ";
          std::cout << "Sphere volume: " << V << "
      ";
       
          // 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
      

      【讨论】:

        猜你喜欢
        • 2019-03-02
        • 1970-01-01
        • 2021-01-13
        • 2022-11-23
        • 2015-06-27
        • 2021-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多