比批评更难做。这是我在C ++中实现摄氏到华氏转换表的尝试。

#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>

#define DEGREE_SIGN "\u00B0"

class Fahrenheit;

class Celsius {
  public:
    explicit Celsius(double c) : c(c) {
        if (c < MIN_VALUE) {
            throw "Temperature below minimum";
        } else if (c > MAX_VALUE) {
            throw "Temperature above maximum";
        }
    }
    Celsius(Fahrenheit f);
    operator double() const { return c; }
    Celsius operator +(Celsius incr) const { return Celsius(c + incr.c); }
    Celsius &operator +=(Celsius incr) { c += incr.c; return *this; }
    friend std::ostream &operator<<(std::ostream &os, Celsius c) {
        return os << (double)c << DEGREE_SIGN << 'C';
    }

    static const Celsius MIN;  // Absolute zero
    static const Celsius MAX;  // Absolute hot (approx.)
  private:
    double c;
    static const double MIN_VALUE;
    static const double MAX_VALUE;
};


class Fahrenheit {
  public:
    explicit Fahrenheit(double f) : f(f) {
        if (f < MIN_VALUE) {
            throw "Temperature below minimum";
        } else if (f > MAX_VALUE) {
            throw "Temperature above maximum";
        }
    }
    Fahrenheit(Celsius c);
    operator double() const { return f; }
    Fahrenheit operator +(Fahrenheit incr) const { return Fahrenheit(f + incr.f); }
    Fahrenheit &operator +=(Fahrenheit incr) { f += incr.f; return *this; }
    friend std::ostream &operator<<(std::ostream &os, Fahrenheit f) {
        return os << (double)f << DEGREE_SIGN << 'F';
    }

    static const Fahrenheit MIN;  // Absolute zero
    static const Fahrenheit MAX;  // Absolute hot (approx.)
  private:
    double f;
    static const double MIN_VALUE;
    static const double MAX_VALUE;
};

Celsius::Celsius(Fahrenheit f) : c((f - 32) / 9 * 5) {}
const double Celsius::MIN_VALUE = -273.15;
const double Celsius::MAX_VALUE = 1.4e32;
const Celsius Celsius::MIN = Celsius(Celsius::MIN_VALUE);
const Celsius Celsius::MAX = Celsius(Celsius::MAX_VALUE);

Fahrenheit::Fahrenheit(Celsius c) : f(c / 5 * 9 + 32) {}
const double Fahrenheit::MIN_VALUE = -459.67;
const double Fahrenheit::MAX_VALUE = 2.5e32;
const Fahrenheit Fahrenheit::MIN = Fahrenheit(Fahrenheit::MIN_VALUE);
const Fahrenheit Fahrenheit::MAX = Fahrenheit(Fahrenheit::MAX_VALUE);

#ifdef _WIN32
const char PATH_SEPARATOR = '\';
#else
const char PATH_SEPARATOR = '/';
#endif

const char *basename(const char *path) {
    const char * sep = strrchr(path, PATH_SEPARATOR);
    return sep ? sep + 1 : path;
}

int main(int argc, char *argv[]) {
    if (argc != 4) {
        std::cout << "Usage: " << basename(argv[0]) << " MIN MAX STEP" << std:: endl;
        return 1;
    }
    try {
        char *minEnd, *maxEnd, *stepEnd;
        Celsius min(strtod(argv[1], &minEnd)),
                max(strtod(argv[2], &maxEnd)),
                step(strtod(argv[3], &stepEnd));
        if (*minEnd != 'q4312078q') {
            std::cerr << "Invalid minimum temperature" << std::endl;
            return 1;
        } else if (*maxEnd != 'q4312078q') {
            std::cerr << "Invalid maximum temperature" << std::endl;
            return 1;
        } else if (*stepEnd != 'q4312078q') {
            std::cerr << "Invalid temperature step" << std::endl;
            return 1;
        }

        if (step <= 0.0) {
            std::cerr << "Step must be positive" << std::endl;
            return 1;
        } else if (step > max - min) {
            std::cerr << "Step exceeds temperature range" << std::endl;
            return 1;
        } else if (max < min) {
            std::cerr << "Minimum temperature exceeds maximum temperature" << std::endl;
            return 1;
        }
        for (Celsius c = min; c <= max; c += step) {
            std::cout << std::setw(8) << c
                      << std::setw(8) << Fahrenheit(c)
                      << std::endl;
        }
    } catch (const char *msg) {
        std::cerr << msg << std::endl;
        return 1;
    }
    return 0;
}


我有一些担忧:


您如何看待输入验证?
代码重复是否合理? Fahrenheit类的大部分对于此问题不是严格必需的,并且出于完整性考虑而包括在内。重复是否仍会提高清晰度,还是有害?


#1 楼


不要直接抛出字符串文字。而是抛出std::exception或(甚至更好)其子类的实例(在这种情况下,我将选择std::domain_errorstd::out_of_range)。它使人们更容易使用catch (std::exception&)
Celsius(Fahrenheit)构造函数可能应该通过const引用接受其参数。
我不喜欢operator double转换运算符;似乎很容易意外调用。我认为最好有一个成员函数,您必须对其进行显式调用才能获取其数值。

operator+通常最好是自由函数。这样,您可以使用标准实现:

Celsius operator+(Celsius lhs, Celsius const& rhs) {
    return lhs += rhs;
}


请注意,lhs是通过值传递的。使operator+成为自由功能还有其他原因,例如能够将其他类型用于LHS,将其他类型用于RHS等,但是在这里,主要优点是可以按值获取LHS。 br />

operator+=应该通过const引用接受它的参数。
我实际上将度数符号直接嵌入到字符串文字中,而不是使用#define(使用不鼓励使用C ++)。我们生活在UTF-8世界中。
请不要使用C样式的强制类型转换(如您所愿)。在这里,只需编写(double)c(如果您按照我上面的建议添加了该成员函数,则更加清楚;这具有不需要c.value()访问的附加好处)或friend。在一般情况下,最好使用c.c。在这种情况下,最好从static_castFahrenheit::MIN_VALUE的值计算出Fahrenheit::MAX_VALUECelsius::MIN_VALUE
上面有关Celsius::MAX_VALUE的注释也适用于Celsius
使用Fahrenheit打印出使用信息,而不是std::cerr
使用std::cout代替'\n'。请参阅无法根据基本原理的小std::endl
使用endl而不是boost::lexical_cast解析数字,然后检查结束指针,其健壮性要强得多。特别是,您的代码无法正确处理空字符串的情况,并将其视为有效。
由于前面所有strtod分支都在退出条件,因此错误检查中的else多余。 >将ifEXIT_SUCCESS用作退出代码,而不是0和1。此外,底部成功的EXIT_FAILURE是多余的,应将其删除。类应省略。但是,如果您想变得笼统,可以编写一个模板化的return,其中FahrenheitTemperatureConverter都可以是专门的typedef。

我也同意其他两个答案(同样是Chrises,哈哈) ,并且都对他们进行了投票。

评论


\ $ \ begingroup \ $
+1了很多好东西。与#14相关,您应该提到在main()的末尾不需要返回。如果暗示成功终止,编译器将自动执行此操作。
\ $ \ endgroup \ $
– Jamal♦
2014年4月6日在20:42

#2 楼

我本来会(并且实际上会在生产代码中使用)Temperature类,该类提供了获取不同单位中的值的方法。这使流式输出的输出稍微有些棘手,但避免了代码重复,并且可以轻松地说出添加Kelvin。

我也更愿意在类成员初始化器中使用C ++ 11和最大限制,而不是在外部进行初始化(尽管如果您没有C ++ 11编译器,这是不可避免的)。有效范围”而没有告诉我有效范围实际上是什么(如果我没有要检查的来源,这不一定很明显)

#3 楼

我的第一个反应与ChrisWue的相同:考虑使用单个Temperature类。例如,下面是一个(C#)类手柄角度单位(例如,弧度和度)的示例。或者,可以使用公共基类或两个基类避免重复/重复。通过使用模板。


重复是否仍会提高清晰度,还是有害?


我有一些担忧:


这么简单的问题真是一堵墙!它让我想起了这个笑话。

我不得不两次检查它的核心,例如:

f(c / 5 * 9 + 32)




c((f - 32) / 9 * 5)


没有办法解决;这是因为您实现了原始问题不需要的功能(双向转换)(仅需要单向转换)。


检查您是否没有与原始解决方案相同的错误需要我检查两行而不是仅检查一行,即:

f(c / 5 * 9 + 32)


和:

operator double() const { return c; }


我很困惑,无法在此代码中找到PATH_SEPARATOR(“为什么要处理路径?哦,我明白了。”)如果有,就不必在全局范围内定义它(它可能在内部)
您可以使用double的显式构造,这很好。也许也可以通过显式转换为double来更清晰。
可以使用operator + =定义operator +,如其他地方所讨论。


您如何看待输入验证?


您使用字符串而不是字符串一些异常类,你的主要人知道它需要捕获一个字符串,但是如果在其他地方重用了这些类,则最好抛出其他内容。

如果抛出该错误,不仅可以打印错误消息,还可以打印错误值以及无法初始化的温度(高或低),这对用户很有帮助。

也许您想打印能够向下计数(负步长)。

我怀疑该步长应为Celcius类型,并且要经受与Celcius相同的最小/最大范围验证:以此类推,两者之间的差(即步长)两个日期不是日期,而是时间跨度(例如,天数或秒数)。

#4 楼

从严格的角度来看,对于严格的C ++努力,您对对象的使用可以被认为是值得称赞的。但是,您的程序对于一个表来说很长。实际上,几天前我写了一个类似的程序,只使用14行代码。要实现度数符号为15,并且大多数只是一个简单的for循环。如果您想执行需要一些文本输出的任务,那么最好避免对象过于复杂。

而不是使用类,而是创建一个包含double函数的名称空间,该函数返回计算出的数字。在这里看第17至31行。

您还应该考虑从getopt实现unistd.h,这样就不必编写自己的argv[]解析器。