首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >现代C++温度转换器

现代C++温度转换器
EN

Code Review用户
提问于 2015-09-21 13:48:03
回答 1查看 412关注 0票数 6

受早期问题的启发,我制作了一个温度转换器,它可以从一种温度转换到另一种温度,并可选择性地输出一系列转换。

这是在Visual 2015中完成的,但它也使用Debian上的g++和clang++进行编译,没有警告。

使用

代码语言:javascript
复制
tc.exe -40 c f

Celcius           | Fahrenheit
------------------+------------------
           -40.00 |            -40.00

代码语言:javascript
复制
tc.exe -40 c f 10 10
Celcius           | Fahrenheit
------------------+------------------
           -40.00 |            -40.00
           -30.00 |            -22.00
           -20.00 |             -4.00
           -10.00 |             14.00
             0.00 |             32.00
            10.00 |             50.00

如果论点是:

代码语言:javascript
复制
Usage: tc.exe Degrees InitialUnit ConvertedUnit [InitialUnitStepSize InitialUnitUpperBoundInclusive]

stdafx.h

代码语言:javascript
复制
#pragma once

#include <stdio.h>
#include <string>
#include <cstring>
#include <sstream>
#include <memory>
#include <iostream>
#include <limits>
#include <cmath>

Temperature.h

代码语言:javascript
复制
#pragma once

#include "stdafx.h"

namespace hest {

enum class TemperatureUnit
{
    Invalid,
    Celcius,
    Fahrenheit,
    Kelvin,
    Rankine,
};

class TemperatureData;
class Kelvin;

class Temperature {
public:
    typedef std::unique_ptr<Temperature> UniqueTemperature;

    static std::string ToShortString(TemperatureUnit const unit);
    static std::string ToString(TemperatureUnit const unit);
    static TemperatureUnit StringToUnit(std::string const & unit);
    static TemperatureUnit CharToUnit(char unit);
    static UniqueTemperature CreateTemperature(double degrees, TemperatureUnit unit);
    static UniqueTemperature Convert(Temperature const & from, TemperatureUnit const to);

    double degrees() const;
    TemperatureUnit unit() const;


    std::string ToString() const;

    virtual Kelvin ToKelvin() const = 0;

protected:
    Temperature(double degrees, TemperatureUnit unit);

private:
    std::shared_ptr<TemperatureData> data_;
};

class Kelvin final : public Temperature {
public:
    Kelvin(double degrees);

    Kelvin ToKelvin() const;
    UniqueTemperature ConvertTo(TemperatureUnit const unit) const;
};

class Celcius final : public Temperature {
public:
    Celcius(double degrees);

    Kelvin ToKelvin() const;
};

class Rankine final : public Temperature {
public:
    Rankine(double degrees);

    Kelvin ToKelvin() const;
};

class Fahrenheit final : public Temperature {
public:
    Fahrenheit(double degrees);

    Kelvin ToKelvin() const;
};

inline bool operator==(Kelvin const & lhs, Kelvin const & rhs) { return lhs.degrees() == rhs.degrees(); }
inline bool operator!=(Kelvin const & lhs, Kelvin const & rhs) { return !operator==(lhs, rhs); }
inline bool operator< (Kelvin const & lhs, Kelvin const & rhs) { return lhs.degrees() < rhs.degrees(); }
inline bool operator> (Kelvin const & lhs, Kelvin const & rhs) { return  operator< (rhs, lhs); }
inline bool operator<=(Kelvin const & lhs, Kelvin const & rhs) { return !operator> (lhs, rhs); }
inline bool operator>=(Kelvin const & lhs, Kelvin const & rhs) { return !operator< (lhs, rhs); }

inline bool operator==(Temperature const & lhs, Temperature const & rhs) { return lhs.ToKelvin() == rhs.ToKelvin(); }
inline bool operator!=(Temperature const & lhs, Temperature const & rhs) { return !operator==(lhs, rhs); }
inline bool operator< (Temperature const & lhs, Temperature const & rhs) { return lhs.ToKelvin() < rhs.ToKelvin(); }
inline bool operator> (Temperature const & lhs, Temperature const & rhs) { return  operator< (rhs, lhs); }
inline bool operator<=(Temperature const & lhs, Temperature const & rhs) { return !operator> (lhs, rhs); }
inline bool operator>=(Temperature const & lhs, Temperature const & rhs) { return !operator< (lhs, rhs); }

inline Kelvin operator+(Kelvin const & lhs, double rhs) { return Kelvin(lhs.degrees() + rhs); }
inline Kelvin operator+(Kelvin const & lhs, Kelvin const & rhs) { return Kelvin(lhs.degrees() + rhs.degrees()); }
inline Kelvin operator-(Kelvin const & lhs, double rhs) { return Kelvin(lhs.degrees() - rhs); }
inline Kelvin operator-(Kelvin const & lhs, Kelvin const & rhs) { return Kelvin(lhs.degrees() - rhs.degrees()); }

inline Temperature::UniqueTemperature operator+(Temperature const & lhs, double rhs) {
    return Temperature::CreateTemperature(lhs.degrees() + rhs, lhs.unit());
}
inline Temperature::UniqueTemperature operator+(Temperature const & lhs, Temperature const & rhs) {
    if (lhs.unit() == rhs.unit()) {
        return Temperature::CreateTemperature(lhs.degrees() + rhs.degrees(), lhs.unit());
    }

    auto converted = Temperature::Convert(lhs, rhs.unit());
    return Temperature::CreateTemperature(rhs.degrees() + converted->degrees(), rhs.unit());
}
inline Temperature::UniqueTemperature operator-(Temperature const & lhs, double rhs) {
    return Temperature::CreateTemperature(lhs.degrees() - rhs, lhs.unit());
}
inline Temperature::UniqueTemperature operator-(Temperature const & lhs, Temperature const & rhs) {
    if (lhs.unit() == rhs.unit()) {
        return Temperature::CreateTemperature(lhs.degrees() - rhs.degrees(), lhs.unit());
    }

    auto converted = Temperature::Convert(lhs, rhs.unit());
    return Temperature::CreateTemperature(rhs.degrees() - converted->degrees(), rhs.unit());
}

}

Temperature.cpp

代码语言:javascript
复制
#include "stdafx.h"
#include "temperature.h"

namespace hest {

constexpr double kCelciusToKelvinOffset = 273.15;
constexpr double kFahrenheitToRankineOffset = 459.67;

static inline double RankineToKelvin(double rankineDegrees) {
    return rankineDegrees * 5.0 / 9.0;
}

static inline double KelvinToRankine(double kelvinDegrees) {
    return kelvinDegrees * 9.0 / 5.0;
}

class TemperatureData {
private:
    double const degrees_;
    TemperatureUnit const unit_;
public:
    TemperatureData(double degrees, TemperatureUnit unit) : degrees_(degrees), unit_(unit) {
        if (!std::isfinite(degrees)) {
            throw std::invalid_argument("Degrees must be finite");
        }
        if (unit == TemperatureUnit::Invalid) {
            throw std::invalid_argument("Invalid unit");
        }
    }

    double degrees() const { return degrees_; }
    TemperatureUnit unit() const { return unit_; }
};

Temperature::Temperature(double degrees, TemperatureUnit unit) : data_(std::make_shared<TemperatureData>(degrees, unit)) {}

Kelvin::Kelvin(double degrees) : Temperature(degrees, TemperatureUnit::Kelvin) { }
Celcius::Celcius(double degrees) : Temperature(degrees, TemperatureUnit::Celcius) { }
Rankine::Rankine(double degrees) : Temperature(degrees, TemperatureUnit::Rankine) { }
Fahrenheit::Fahrenheit(double degrees) : Temperature(degrees, TemperatureUnit::Fahrenheit) { }

Kelvin Kelvin::ToKelvin() const {
    return Kelvin(degrees());
}
Kelvin Celcius::ToKelvin() const {
    return Kelvin(degrees() + kCelciusToKelvinOffset);
}
Kelvin Rankine::ToKelvin() const {
    return Kelvin(RankineToKelvin(degrees()));
}
Kelvin Fahrenheit::ToKelvin() const {
    return Kelvin(RankineToKelvin(degrees() + kFahrenheitToRankineOffset));
}

std::string Temperature::ToShortString(TemperatureUnit const unit)
{
    switch (unit)
    {
    case hest::TemperatureUnit::Kelvin:
        return "K";
    case hest::TemperatureUnit::Celcius:
        return "C";
    case hest::TemperatureUnit::Rankine:
        return "R";
    case hest::TemperatureUnit::Fahrenheit:
        return "F";
    default:
        return "";
    }
}

std::string Temperature::ToString(TemperatureUnit const unit)
{
    switch (unit)
    {
    case hest::TemperatureUnit::Kelvin:
        return "Kelvin";
    case hest::TemperatureUnit::Celcius:
        return "Celcius";
    case hest::TemperatureUnit::Rankine:
        return "Rankine";
    case hest::TemperatureUnit::Fahrenheit:
        return "Fahrenheit";
    default:
        return "";
    }
}

TemperatureUnit Temperature::StringToUnit(std::string const & unit) {
    auto length = unit.length();

    switch (length)
    {
    case 1:
        return CharToUnit(unit[0]);
    case 6:
        if (unit.compare("Kelvin") == 0 || unit.compare("kelvin") == 0) {
            return TemperatureUnit::Kelvin;
        }
    case 7:
        if (unit.compare("Celcius") == 0 || unit.compare("celcius") == 0) {
            return TemperatureUnit::Celcius;
        }
        if (unit.compare("Rankine") == 0 || unit.compare("rankine") == 0) {
            return TemperatureUnit::Rankine;
        }
    case 10:
        if (unit.compare("Fahrenheit") == 0 || unit.compare("fahrenheit") == 0) {
            return TemperatureUnit::Fahrenheit;
        }
    }

    return TemperatureUnit::Invalid;
}

TemperatureUnit Temperature::CharToUnit(char unit) {
    switch (unit)
    {
    case 'c':
    case 'C':
        return TemperatureUnit::Celcius;
    case 'f':
    case 'F':
        return TemperatureUnit::Fahrenheit;
    case 'k':
    case 'K':
        return TemperatureUnit::Kelvin;
    case 'r':
    case 'R':
        return TemperatureUnit::Rankine;
    default:
        return TemperatureUnit::Invalid;
    }
}

auto Temperature::CreateTemperature(double degrees, TemperatureUnit unit) -> UniqueTemperature {
    switch (unit)
    {
    case hest::TemperatureUnit::Kelvin:
        return std::make_unique<Kelvin>(degrees);
    case hest::TemperatureUnit::Celcius:
        return std::make_unique<Celcius>(degrees);
    case hest::TemperatureUnit::Rankine:
        return std::make_unique<Rankine>(degrees);
    case hest::TemperatureUnit::Fahrenheit:
        return std::make_unique<Fahrenheit>(degrees);
    default:
        throw std::invalid_argument("Invalid unit");
    }
}

auto Temperature::Convert(Temperature const & from, TemperatureUnit const to) -> UniqueTemperature {
    auto kelvin = from.ToKelvin();
    return kelvin.ConvertTo(to);
}

double Temperature::degrees() const {
    return data_->degrees();
}

TemperatureUnit Temperature::unit() const {
    return data_->unit();
}

std::string Temperature::ToString() const {
    std::ostringstream format_stream;
    format_stream << degrees() << ToShortString(unit());
    return format_stream.str();
}

auto Kelvin::ConvertTo(TemperatureUnit const unit) const -> UniqueTemperature {
    switch (unit)
    {
    case hest::TemperatureUnit::Kelvin:
        return std::make_unique<Kelvin>(degrees());
    case hest::TemperatureUnit::Celcius:
        return std::make_unique<Celcius>(degrees() - kCelciusToKelvinOffset);
    case hest::TemperatureUnit::Rankine:
        return std::make_unique<Rankine>(KelvinToRankine(degrees()));
    case hest::TemperatureUnit::Fahrenheit:
        return std::make_unique<Fahrenheit>(KelvinToRankine(degrees()) - kFahrenheitToRankineOffset);
    default:
        throw std::invalid_argument("Invalid unit");
    }
}

}

TemperatureConverter.cpp

代码语言:javascript
复制
#include "stdafx.h"
#include "temperature.h"
#include <iomanip>

constexpr int kMaxDigits = std::numeric_limits<double>::digits10;
constexpr int kColumnWidth = kMaxDigits + 2;
constexpr auto kRowSeparator = " | ";
static const auto kRowSeparatorLength = std::strlen(kRowSeparator);

std::string PrintResultHeader(hest::TemperatureUnit const initial, hest::TemperatureUnit const converted) {
    std::ostringstream format_stream;
    format_stream << std::left;
    format_stream << std::setw(kColumnWidth) << hest::Temperature::ToString(initial);
    format_stream << std::setw(0) << kRowSeparator;
    format_stream << std::setw(kColumnWidth) << hest::Temperature::ToString(converted);

    auto column_header_separator = std::string(kColumnWidth + kRowSeparatorLength / 2, '-');
    format_stream << std::setw(0) << "\n" << column_header_separator;
    if (kRowSeparatorLength % 2 == 1) {
        format_stream << "+";
    }
    format_stream << column_header_separator;

    return format_stream.str();
}

std::string PrintResultRow(hest::Temperature::UniqueTemperature const & initial, hest::Temperature::UniqueTemperature const & converted) {
    std::ostringstream format_stream;
    format_stream << std::right << std::setprecision(2) << std::fixed;
    format_stream << std::setw(kColumnWidth) << initial->degrees();
    format_stream << std::setw(0) << kRowSeparator;
    format_stream << std::setw(kColumnWidth) << converted->degrees();

    return format_stream.str();
}

template<typename TResult> bool TryParse(std::string const & input, TResult & result) {
    std::istringstream input_stream(input);
    input_stream >> result;
    return !input_stream.fail() && input_stream.eof();
}

int main(int argc, char* argv[]) {
    if (!(argc == 4 || argc == 6)) {
        std::cout << "Usage: " << argv[0] << " Degrees InitialUnit ConvertedUnit [InitialUnitStepSize InitialUnitUpperBoundInclusive]" << std::endl;
        return EXIT_FAILURE;
    }

    bool has_errors = false;

    auto degrees_argument = std::string(argv[1]);
    double degrees = 0;
    if (!TryParse(degrees_argument, degrees)) {
        std::cerr << "Degrees: " << degrees_argument << " is not a valid number" << std::endl;
        has_errors = true;
    }

    auto initial_unit_argument = std::string(argv[2]);
    auto initial_unit = hest::Temperature::StringToUnit(initial_unit_argument);
    if (initial_unit == hest::TemperatureUnit::Invalid) {
        std::cerr << "InitialUnit: " << initial_unit_argument << " is not a valid unit" << std::endl;
        has_errors = true;
    }

    auto converted_unit_argument = std::string(argv[3]);
    auto converted_unit = hest::Temperature::StringToUnit(converted_unit_argument);
    if (converted_unit == hest::TemperatureUnit::Invalid) {
        std::cerr << "ConvertedUnit: " << converted_unit_argument << " is not a valid unit" << std::endl;
        has_errors = true;
    }

    /* step_size and upper_bound are initialized such that if they're not specified
       the conversion loop will run exactly once. */
    double step_size = std::numeric_limits<double>::max();
    double upper_bound = degrees;
    if (argc == 6) {
        auto step_size_argument = std::string(argv[4]);
        if (!TryParse(step_size_argument, step_size)) {
            std::cerr << "InitialUnitStepSize: " << step_size_argument << " is not a valid number" << std::endl;
            has_errors = true;
        }
        if (!has_errors && step_size <= 0) {
            std::cerr << "InitialUnitStepSize must be positive" << std::endl;
            has_errors = true;
        }

        auto upper_bound_argument = std::string(argv[5]);
        if (!TryParse(upper_bound_argument, upper_bound)) {
            std::cerr << "InitialUnitUpperBoundInclusive: " << upper_bound_argument << " is not a valid number" << std::endl;
            has_errors = true;
        }
        if (!has_errors && upper_bound <= 0) {
            std::cerr << "InitialUnitUpperBoundInclusive must be greater than Degrees" << std::endl;
            has_errors = true;
        }
    }

    if (has_errors) {
        return EXIT_FAILURE;
    }

    try
    {
        std::cout << PrintResultHeader(initial_unit, converted_unit) << std::endl;

        for (auto initial_temperature = hest::Temperature::CreateTemperature(degrees, initial_unit); 
             initial_temperature->degrees() <= upper_bound; 
             initial_temperature = *initial_temperature + step_size) 
        {
            auto converted_temperature = hest::Temperature::Convert(*initial_temperature, converted_unit);
            std::cout << PrintResultRow(initial_temperature, converted_temperature) << std::endl;
        }
    }
    catch (const std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
EN

回答 1

Code Review用户

发布于 2015-09-22 06:44:55

我不认为我会使用类层次结构来表示不同类型的温度。最后,它们代表着相同的事物(系统中的能量量)。您应该选择一个单元来存储数据,然后将所有类型隐藏到这个全局单元中。

代码语言:javascript
复制
class Temperature
{
    double kelvin;
    public:
       // Explicit: We don't want auto-conversion.
       explicit Temperature(double kelvin)
           : kelvin(kelvin)
       {}
 };

然后编写将特定比例转换为所选表单的make_temp_from_X()函数。

代码语言:javascript
复制
 Temperature make_temp_from_kelvin(double k)  {return Temperature(k);}
 Temperature make_temp_from_celcius{double c) {return Temperature(c-273.15);}
 ... etc

传递一个Temperature类型的对象的成本与传递一个double (所以您不需要担心这个问题)相同。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/105261

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档