使用switch语句进行范围检查

43

我的老师布置了一个练习,要求我们使用if-else语句和switch语句,以便了解如何实现两者。练习要求我们提示用户输入他们的体重和身高,分别以磅和米为单位。这是我的尝试:

没有使用switch语句

#include "stdafx.h"
#include <iostream>

using namespace std;


int main()
{
    double height, weight, BMI, heightMeters, weightKilo;
    const double KILOGRAMS_PER_POUND = 0.45359237;
    const double METERS_PER_INCH = 0.0245;

    cout << "Please enter your height (inches) and weight (pounds)" << endl;
    cin >> height >> weight;

    weightKilo = weight*KILOGRAMS_PER_POUND;
    heightMeters = height*METERS_PER_INCH;
    BMI = weightKilo / (heightMeters*heightMeters);

    if (BMI < 18.5) {
        cout << "You are underweight " << endl;
    }
    else if (BMI >= 18.5 && BMI < 25.0) {
        cout << "You are normal" << endl;
    }
    else if (BMI >= 25.0 && BMI < 30.0) {
        cout << "You are overweight" << endl;
    }
    else if (BMI >= 30.0 && BMI < 35) {
        cout << "You are obese" << endl;
    }
    else {
        cout << "You are gravely overweight" << endl;
    }
}

随着转变

#include "stdafx.h"
#include <iostream>

using namespace std;


int main()
{
    double height, weight, heightMeters, weightKilo;
    int BMI, q;
    const double KILOGRAMS_PER_POUND = 0.45359237;
    const double METERS_PER_INCH = 0.0245;

    cout << "Please enter your height (inches) and weight (pounds)" << endl;
    cin >> height >> weight;

    weightKilo = weight*KILOGRAMS_PER_POUND;
    heightMeters = height*METERS_PER_INCH;
    BMI = weightKilo / (heightMeters*heightMeters);

    if (BMI < 18.5) {
        q = 1;
    }
    else if (BMI >= 18.5 && BMI < 25.0) {
        q = 2;
    }
    else if (BMI >= 25.0 && BMI < 30.0) {
        q = 3;
    }
    else if (BMI >= 30.0 && BMI < 35) {
        q = 4;
    }
    else {
        q = 5;
    }

    switch (q) {
        case 1: cout << "You are underweight" << endl; break;
        case 2: cout << "You are a normal weight " << endl; break;
        case 3: cout << "You are overweight" << endl; break;
        case 4: cout << "You are obese" << endl; break;
        case 5: cout << "You are gravely overweight" << endl; break;
    }
}
这是我考虑使用switch语句的方式。有没有方法可以将第一个代码块实现为仅使用switch语句?我几乎确定不能使用范围或双精度数值(18.5)。我给我的老师发了一封电子邮件,他们给了我以下答复:
“这可能对你来说毫无意义,但有时你必须编写一个程序,它本身就没有意义。我并不是说你没有合理的问题,如果有人能够弄清楚,那就是你。但是,也许它无法被解决。这就是挑战”。
所以,我想问:是否有一种方法可以只使用switch语句来处理第一个代码块,或者我所做的是在代码中使用switch语句的最佳方式,即使这样做并不必要?

3
双精度浮点数不能使用开关。 - robert
2
无关的是,从英寸到米的转换率为0.0254 m/in。此外,在您的代码中看到命名的转换因子非常好。我无法告诉您有多少遗留代码中存在“神奇数字”,我无法弄清楚这个数字的含义。 - Carlton
6
有时候你可能需要编写一个看起来很奇怪的程序,尽管它可能对你来说没有意义。这句话可能会让学生感到困惑。 - Christian Hackl
4
@ChristianHackl 是的,没错。我相信这是因为她不知道自己在说什么。 - TEEBQNE
1
@nocomprende,我强烈不同意。当研究一个问题集时,解决方案可能不太适合用C++这样的语言来表达,因为你必须考虑到C++语言模拟了非常低级别的机器:指针、位类型等等。而用函数式语言编写的解决方案将会与问题集的数学模型非常相似。 - sleblanc
显示剩余8条评论
7个回答

110

像往常一样,在C++中,您应该优先使用标准库算法。在这种情况下,您想要进行范围查找。如果有一个排序好的边界序列,这很容易实现:

double const boundaries[] = { 18.5, 25, 30, 35 };

switch (upper_bound(begin(boundaries), end(boundaries), BMI) - boundaries) {
    case 0: cout << "You are underweight "       << endl; break;
    case 1: cout << "You are normal"             << endl; break;
    case 2: cout << "You are overweight"         << endl; break;
    case 3: cout << "You are obese"              << endl; break;
    case 4: cout << "You are gravely overweight" << endl; break;
};

实际上,我建议你:

Coliru上查看实时演示

#include <iostream>
#include <algorithm>

const char* bmi_classification(double bmi) {
    static double const boundaries[] = { 18.5, 25, 30, 35 };

    double const* lookup = std::upper_bound(std::begin(boundaries), std::end(boundaries), bmi);
    switch (lookup - std::begin(boundaries)) {
        case 0: return "underweight";
        case 1: return "normal";
        case 2: return "overweight";
        case 3: return "obese";
        case 4: return "gravely overweight";
    }
    throw std::logic_error("bmi_classification");
}

int main() {
    for (double BMI : { 0.0, 18.4999, 18.5, 24.0, 25.0, 29.0, 30.0, 34.0, 35.0, 999999.0 }) {
        std::cout << "BMI: " << BMI << " You are " << bmi_classification(BMI) << "\n";
    }
}

打印

BMI: 0 You are underweight
BMI: 18.4999 You are underweight
BMI: 18.5 You are normal
BMI: 24 You are normal
BMI: 25 You are overweight
BMI: 29 You are overweight
BMI: 30 You are obese
BMI: 34 You are obese
BMI: 35 You are gravely overweight
BMI: 999999 You are gravely overweight

额外加分

你可以更加优雅地完成,而无需使用 switch

在 Coliru 上实时运行

const char* bmi_classification(double bmi) {
    constexpr int N = 5;
    static constexpr std::array<char const*, N> classifications {
        { "underweight", "normal", "overweight", "obese", "gravely overweight" }};
    static constexpr std::array<double, N-1> ubounds {
        { 18.5, 25, 30, 35 }};

    auto lookup = std::upper_bound(std::begin(ubounds), std::end(ubounds), bmi);
    return classifications.at(lookup - std::begin(ubounds));
}

30
一份精美的实现。原帖作者会从研究每个部分中受益,就像我一样。 - Bathsheba
1
@P45即将发生的 错了,是删除了多余的 0 边界元素。谢谢! - sehe
2
在你的 switch 语句中,为什么不写成:upper_bound(...) - begin(boundaries)?在我看来,这样更通用。 - Pumkko
@Pumkko std::distance,甚至。 - user1804599
@Cthulhu和其他人:无论如何,我已经加入了一些样式改进,现在这个答案受到了如此多的关注。谢谢 :) - sehe
显示剩余7条评论

21

除非你有一个绝对可怕的编译器扩展,否则你不能在C++中使用范围进行switch

但是,如果您创建了一个BMI范围的std::vector,则可以优雅地使用switch

std::vector<double> v = {18.5, 25.0 /*等*/}

然后使用std::lower_boundstd::distance来获取上述范围中给定BMI的位置。这就是您要switch的数量。

然后,您可以进一步定义一个输出消息的std::vector<std::string>。然后您既不需要switch也不需要if块!所有选择逻辑都委托给std::lower_bound

我故意没有给出完整的代码:我相信这些提示足够了。


1
虽然我不使用gcc的case ranges,但我觉得称这样的扩展为可怕是有点过头的评论。 - Shafik Yaghmour

8

我们需要适应输入,因此,不使用以下代码:

if (BMI < 18.5) {
        q = 1;
    }
    else if (BMI >= 18.5 && BMI < 25.0) {
        q = 2;
    }
    else if (BMI >= 25.0 && BMI < 30.0) {
        q = 3;
    }
    else if (BMI >= 30.0 && BMI < 35) {
        q = 4;
    }
    else {
        q = 5;
    }

    switch (q) {
    case 1: cout << "You are underweight" << endl; break;
    case 2: cout << "You are a normal weight " << endl; break;
    case 3: cout << "You are overweight" << endl; break;
    case 4: cout << "You are obese" << endl; break;
    case 5: cout << "You are gravely overweight" << endl; break;

    }

你需要类似于这样的东西:
switch (1 + (BMI >= 18.5) + (BMI >= 25) + (BMI >= 30) + (BMI >= 35)) {
    case 1: cout << "You are underweight" << endl; break;
    case 2: cout << "You are a normal weight " << endl; break;
    case 3: cout << "You are overweight" << endl; break;
    case 4: cout << "You are obese" << endl; break;
    case 5: cout << "You are gravely overweight" << endl; break;
}

逻辑是将if-else转换为数学公式,返回一个整数。

1
难以维护:每当添加新的情况时,您必须记得更新“5”。 - user1804599
将BMI从数学上计算为整数是一个不错的想法。在这个特殊的问题中,未来可能没有需要添加的情况。BMI计算公式不会改变。 - Fanax
1
switch ((BMI >= 18.5)+(BMI >= 25.0)+(BMI >= 30)+...)怎么样?看起来更简单。 - chi
1
拒绝使用基于 1 的索引! - Yakk - Adam Nevraumont
在这种情况下,我们有的是案例,而不是索引。 - Lajos Arpad
显示剩余2条评论

5
您不能在 switch 语句中使用 double 类型。文档说明如下:
switch ( expression )
   case constant-expression : statement
   [default   : statement]

表达式必须是整型或者类类型,且存在一种明确的整型转换方式。整型提升的行为如同《Integral Promotions》中所描述。

另外需要说明的是:

有些编译器(例如Clang 3.5.1)允许case x ... y这样的语法扩展,但也仅限于整型数据类型。例如:

switch(x){
       case 0:
            cout << "Test1";
            break;
       case 0 ... 9:
            cout << "Test2";
            break;

3
在C++中,switch语句只能检查整数和字符的值。
BMI是double类型,因此无法在switch语句中检查其值。
在使用switch语句的解决方案中,您还应将变量BMI声明为double。如果将其声明为整数,则所有小数结果都将转换为整数,您将失去小数位。

2
您可以从数组/向量动态计算您的情况标签,而不是硬编码 if/else 表达式:
//#include "stdafx.h"
#include <iostream>

using namespace std;


inline int seg(double d){ //calculate segment for a BMI of d
  constexpr double segs[] = { 18.5, 25, 30, 35 };
  constexpr int n = sizeof(segs)/sizeof(double);
  int r; for(r=0; r<n; r++)
    if(d<segs[r]) return r;
  return r;
}

int main()
{
  double height, weight, heightMeters, weightKilo;
  int BMI, q;
  const double KILOGRAMS_PER_POUND = 0.45359237;
  const double METERS_PER_INCH = 0.0245;

  cout << "Please enter your height (inches) and weight (pounds)" << endl;
  cin >> height >> weight;

  weightKilo = weight*KILOGRAMS_PER_POUND;
  heightMeters = height*METERS_PER_INCH;
  BMI = weightKilo / (heightMeters*heightMeters);



  switch (seg(BMI)) {
    case 0: cout << "You are underweight" << endl; break;
    case 1: cout << "You are a normal weight " << endl; break;
    case 2: cout << "You are overweight" << endl; break;
    case 3: cout << "You are obese" << endl; break;
    case 4: cout << "You are gravely overweight" << endl; break;
  }

}

(如果您真的想这样做)甚至可以将seg函数设为constexpr

2
+1 表示解决问题。-1 表示代码格式不佳。-1 表示以更难阅读的方式重新实现 std::upper_bound - Yakk - Adam Nevraumont

1
你可以这样做:

switch ((round)BMI)
{
    case 1: case 2: case 3: .... case 15: case 16: case 17: cout<< "You are underweight " << endl; break;
    case 18: ... case 24: cout << "You are normal" << endl; break;
    case 25: ... case 29: cout << "You are overweight" << endl; break;
    case 30: ... case 34: cout << "You are obese" << endl; break;
    default: cout << "You are gravely overweight" << endl;
}

我注意到你在使用if-else,因此可以避免在else-if语句中使用第一个条件,例如:

if (BMI < 18.5)
{
    cout << "You are underweight " << endl;
}
else if (BMI < 25.0)
{
    cout << "You are normal" << endl;
}
else if (BMI < 30.0)
{
    cout << "You are overweight" << endl;
}
else if(BMI < 35)
{
    cout << "You are obese" << endl;
}
else
{
    cout << "You are gravely overweight" << endl;
}

除此之外,你们两个的实现看起来都很不错。

在4行代码中打34次case对我来说似乎很疯狂。 - Teepeemm
很简单!使用宏即可! - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接