题
我只是在学习C ++和编程。我正在创建一个名为 Distance
的类。我想允许用户(程序员使用),我的班级能够将距离从一个度量单位转换为另一个度量单位。例如:英寸 - >厘米,英里 - >公里等...
我的问题是我想要一个名为 ConvertTo
的方法,它将转换为任何度量单位。
这是我到目前为止所拥有的:
// unit_of_measure is an enum containg all my supported lengths,
// (eg. inches, centimeters, etc...)
int Distance::ConvertTo(unit_of_measure convert_unit)
{
switch (convert_unit)
{
case inches:
if (unit != inches) {
if (unit == centimeters) {
distance *= CM_TO_IN;
unit = inches;
return 0;
} else {
cerr << "Conversion not possible (yet)." << endl;
return 1;
}
} else {
cout << "Warning: Trying to convert inches to inches." << endl;
return 2;
}
case centimeters:
if (unit != centimeters) {
if (unit == inches) {
distance /= CM_TO_IN;
unit = centimeters;
return 0;
} else {
cerr << "Conversion not possible (yet)." << endl;
return 1;
}
} else {
cout << "Warning: Trying to convert inches to inches." << endl;
return 2;
}
// I haven't written anything past here yet because it seems
// like a bad idea to keep going with this huge switch
// statement.
default:
cerr << "Undefined conversion unit." << endl;
return -1;
}
}
那我该怎么办?我应该解决这个问题,还是继续使用 HUGE 切换语句。
解决方案
将其分解为各种功能。你所拥有的东西将很难维持和使用。用户和程序员使用具有描述性名称的函数会更方便:
double inchesToCentimeters(double inches);
double centimetersToInches(double cent);
函数名称确切地告诉您要调用的函数,并且不需要传递跟踪单位的额外参数。
除此之外,为了防止必须跟踪测量所在的单位,最好始终将数字存储在程序中的任何位置的公共单元中,然后仅在将数字转换为显示单位时你需要。例如,我现在维护的程序将所有距离值保持为米,但可以转换为您能想到的任何距离单位。
使用通用装置时,可以节省大量的功能。假设您的公共距离单位是米,现在一旦您编写从米转换到您需要的每个其他单位的功能,并且从所有其他单位转换为米,您可以将它们组合起来从任何单位 - 到米 - 到任何其他单位
其他提示
我的方法是始终将距离存储在同一个单元中。这样可以避免在需要转换值时始终仔细检查单位。
代码的骨架可能如下所示:
class Distance
{
public:
float ConvertTo (unit_of_measure convert_unit)
{
return (_distanceInInches * getConversionFactor(convert_unit));
}
float SetValue (unit_of_measure unit, float value)
{
_distanceInInches = (value / getConversionFactor(unit));
}
private:
float getConversionFactor(unit_of_measure unit)
{
switch(unit)
{
// add each conversion factor here
}
}
float _distanceInInches;
}
如果您不介意依赖,请使用 Boost.Units
如果您想要准确保留当前的API,但要简化其实施,为什么不能根据某些任意标准(例如1米)来表示您的设备。至少,不是具有N ^ 2(source-&gt; dest)可能性,而是具有2 * N(source-&gt; std)(std-> dest)转换。
struct distance_unit {
char const* name;
double meters_per_unit;
distance_unit() : name("meters"),meters_per_unit(1.) {}
double to_meters(double in_units) { return in_units/meters_per_unit; }
double to_units(double in_meters) { return in_meters*meters_per_unit; }
};
struct distance {
double d;
distance_unit unit;
distance(double d,distance_unit const& unit) : d(d),unit(unit) {}
distance(double meters,distance_unit const& unit,bool _)
: d(unit.to_units(meters)),unit(unit) {}
distance convert_to(distance_unit const& to) {
return distance(unit.to_meters(d),to,false);
}
friend inline std::ostream& operator<<(std::ostream &o) {
return o << d << ' ' << unit.name;
}
};
当然,唯一的好处是可以准确表示的距离(就其单位而言)不会变得不精确。如果你不关心舍入和完全相等的和,这是更明智的:
struct distance {
double meters;
distance_unit preferred_unit;
distance(double d,distance_unit const& unit)
: meters(unit.to_meters(d)),preferred_unit(unit) {}
distance(double meters,distance_unit const& unit,bool _)
: meters(meters),preferred_unit(unit)
distance convert_to(distance_unit const& to) {
return distance(meters,to,false);
}
friend inline std::ostream& operator<<(std::ostream &o) {
return o << unit.to_units(meters) << ' ' << unit.name;
}
};
如果使用STL,请创建转换图的常量图。因此,您可以从“从”获得转换常量和“到”。
这样的事情:
std::map <unit_of_measure, std::map<unit_of_measure, double>> ConversionConstants_FromTo;
ConversionConstants_FromTo(inches)(centimeters) = ...;
ConversionConstants_FromTo(inches)(miles) = ...;
int Distance::ConvertTo(unit_of_measure convert_unit) {
return distance*ConversionConstants_FromTo(unit, convert_unit)
}
我会进行两个级别的分析。
首先是您为来电者提供的界面。调用者创建一个具有特定单位的Distance对象,然后convert方法更改单位和相应的距离,错误代码表示成功或否则。据推测,你也可以获得当前单位和相应的距离。
现在我不喜欢这样一个有状态的界面。为什么不是像
这样的界面 Distance {
Distance(unit, value) { // constructor
float getValue(unit) throws UnsupportedUnitException;
}
因此呼叫者无需了解距离的内部单位。没有有状态的行为。
然后switch语句显然是重复的。这必须是一些重构的候选人。
每次转换都可以表示为乘法。您可以拥有一个表,保留您支持的所有转换因子。你有
float getConversionFactor(fromUnit, toUnit) throws UnsupportedUnitException
执行转换因子的查找,然后将其应用于getValue()方法
getValue(requestedUnit) {
return value * getConversionfactor(myUnit, requestedUnit);
}
当你还在学习时,可能值得放弃使用switch和enum方法来支持一个结构体系列,每个处理单元一个,每个包含输入值和唯一标记类型以使它们不同。这有一些好处:
- 转换可以通过具有通用名称(“转换”可能?)的函数的重载来完成,如果您想在模板化代码中进行转换,这会使生活更轻松。
- 类型系统意味着您不会意外地将加仑转换为光年,因为您不会编写编译器可能与尺寸不合适的转换匹配的重载。
- 交换机的执行时间或嵌套,如果块取决于子句的数量,则在编译时切换重载方法 醇>
缺点是创建该类标记类的开销以及需要在类中包含您的参数(更可能是struct)。一旦被包装,除非你编写它们,否则你将没有通常的数字运算符。
一种值得学习的技术imo。
这里还有两件事需要考虑,因为这里已经有很多非常好的想法。
(1)如果你不打算将长度表示为值类型,那么我将使用一个充满自由函数而不是类的命名空间。这更像是我喜欢传播的风格 - 如果您没有状态或正在考虑 static
方法,只需使用命名空间。
namespace Convert {
double inchesToCentimeters(double inches) { ... }
double inchesToMeters(double inches) { ... }
} // end Convert namespace
(2)如果您打算使用值类型(这是我推荐的),那么考虑(我一直在调用的)“命名构造函数”而不是单位枚举以及单个单位表示。
class Convert {
public:
static Convert fromInches(double inches) {
return Convert(inches * 0.0254);
}
static Convert fromCentimeters(double cm) {
return Convert(cm / 100.0);
}
...
double toInches() const { return meters * 39.370079; }
double toCentimeters() const { return meters * 100.0; }
...
protected:
Convert(double meters_): meters(meters_) {}
private:
double meters;
};
这将使您的用户土地代码非常易读,您可以从选择任何内部单元使您的生活变得轻松中获益。
在源代码中更容易阅读单独的方法,但是当目标单元出现时,例如从用户选择中,您需要一个可以将其指定为参数的函数。
您可以使用缩放因子表,而不是switch语句,例如特定单位和国际单位制之间。
另外,请查看 Boost.Units ,它并没有完全解决你的问题,但它足够接近有趣。
我会把NawaMan和ph0enix的答案结合起来。而不是拥有地图地图,只需要有2个完整常量的地图。一个地图包含来自米的转换,另一个地图包含反转。然后该函数类似于(在伪代码中):
function convertTo (baseUnitName, destinationUnitName) {
let A = getInverseConstant(baseUnitName);
let B = getConstant(destinationUnitName);
return this.baseUnit*A*B;
}
这比你的山区切换开关语句短得多,并且我认为两个完整常量的映射比switch语句更容易维护。地图图基本上只是一个时间表,所以为什么不存储垂直和水平的cooeficients而不是n * m的内存块。
您甚至可以编写代码来读取文本文件中的常量,然后生成每个值上带有1 / x的反常数。