我编写了以下记录类型,其中隐式运算符用于在此记录类型和字符串之间进行转换。它代表standard weather code,简要描述了当前的天气状况:
type
TDayNight = (dnDay, dnNight);
TCloudCode = (ccClear = 0, ccAlmostClear = 1, ccHalfCloudy = 2, ccBroken = 3,
ccOvercast = 4, ccThinClouds = 5, ccFog = 6);
TPrecipCode = (pcNone = 0, pcSlight = 1, pcShowers = 2, pcPrecip = 3, pcThunder = 4);
TPrecipTypeCode = (ptRain = 0, ptSleet = 1, ptSnow = 2);
TWeatherCode = record
public
DayNight: TDayNight;
Clouds: TCloudCode;
Precip: TPrecipCode;
PrecipType: TPrecipTypeCode;
class operator Implicit(const Value: TWeatherCode): String;
class operator Implicit(const Value: String): TWeatherCode;
function Description: String;
function DayNightStr: String;
end;
implementation
{ TWeatherCode }
class operator TWeatherCode.Implicit(const Value: TWeatherCode): String;
begin
case Value.DayNight of
dnDay: Result:= 'd';
dnNight: Result:= 'n';
end;
Result:= Result + IntToStr(Integer(Value.Clouds));
Result:= Result + IntToStr(Integer(Value.Precip));
Result:= Result + IntToStr(Integer(Value.PrecipType));
end;
class operator TWeatherCode.Implicit(const Value: String): TWeatherCode;
begin
if Length(Value) <> 4 then raise Exception.Create('Value must be 4 characters.');
case Value[1] of
'd','D': Result.DayNight:= TDayNight.dnDay;
'n','N': Result.DayNight:= TDayNight.dnNight;
else raise Exception.Create('First value must be either d, D, n, or N.');
end;
if Value[2] in ['0'..'6'] then
Result.Clouds:= TCloudCode(StrToIntDef(Value[2], 0))
else
raise Exception.Create('Second value must be between 0 and 6.');
if Value[3] in ['0'..'4'] then
Result.Precip:= TPrecipCode(StrToIntDef(Value[3], 0))
else
raise Exception.Create('Third value must be between 0 and 4.');
if Value[4] in ['0'..'2'] then
Result.PrecipType:= TPrecipTypeCode(StrToIntDef(Value[4], 0))
else
raise Exception.Create('Fourth value must be between 0 and 2.');
end;
function TWeatherCode.DayNightStr: String;
begin
case DayNight of
dnDay: Result:= 'Day';
dnNight: Result:= 'Night';
end;
end;
function TWeatherCode.Description: String;
begin
case Clouds of
ccClear: Result:= 'Clear';
ccAlmostClear: Result:= 'Mostly Clear';
ccHalfCloudy: Result:= 'Partly Cloudy';
ccBroken: Result:= 'Cloudy';
ccOvercast: Result:= 'Overcast';
ccThinClouds: Result:= 'Thin High Clouds';
ccFog: Result:= 'Fog';
end;
case PrecipType of
ptRain: begin
case Precip of
pcNone: Result:= Result + '';
pcSlight: Result:= Result + ' with Light Rain';
pcShowers: Result:= Result + ' with Rain Showers';
pcPrecip: Result:= Result + ' with Rain';
pcThunder: Result:= Result + ' with Rain and Thunderstorms';
end;
end;
ptSleet: begin
case Precip of
pcNone: Result:= Result + '';
pcSlight: Result:= Result + ' with Light Sleet';
pcShowers: Result:= Result + ' with Sleet Showers';
pcPrecip: Result:= Result + ' with Sleet';
pcThunder: Result:= Result + ' with Sleet and Thunderstorms';
end;
end;
ptSnow: begin
case Precip of
pcNone: Result:= Result + '';
pcSlight: Result:= Result + ' with Light Snow';
pcShowers: Result:= Result + ' with Snow Showers';
pcPrecip: Result:= Result + ' with Snow';
pcThunder: Result:= Result + ' with Snow and Thunderstorms';
end;
end;
end;
end;
可以转换为此类型的字符串的示例是......
d310
=多云和小雨(日)d440
=有雨和雷暴的阴天(白天)n100
=非常清楚(夜晚)此字符串将始终采用此格式,并且始终为4个字符:1个字母和3个数字。实际上,您可以将其视为以下选项:
我想做的还是提供一个选项,可以隐式地将其强制转换为整数,甚至是字节,如果我能够将它变得足够小的话。但我讨厌必须添加大量的if / then / else或case语句。
我知道可以使用给定(小)字符集并将它们转换为单个值。但是,我不知道它是如何完成的。我知道,例如,这种技术用于诸如DrawTextEx
上的标志和其他类似的WinAPI调用之类的地方。我认为这可能与shr
/ shl
的使用有关,但我不知道如何使用这些,并且在这种类型的数学上很糟糕。
如何将这4个属性组合成一个整数或字节,并将它们转换回来?
答案 0 :(得分:5)
最简单的方法是将其打包成32位有符号或无符号整数。
我更喜欢无符号的(又名Cardinal
)
你说 - &#34;总是4个字符:1个字母和3个数字&#34;。
通过&#34;字符&#34;你的意思是较低的拉丁字符。
这些字符很好地用单字节AnsiChar
类型表示。
警告:在不同的Delphi版本中,Char
是WideChar
或AnsiChar
类型的快捷方式。在进行低级别类型转换时,您需要避免模糊的高级快捷方式并使用原始类型。
同样适用于string
- &gt; UnicodeString
或AnsiString
含糊不清。
class operator TWeatherCode.Implicit(const Value: TWeatherCode): Cardinal;
var R: packed array [1..4] of AnsiChar absolute Result;
begin
Assert( SizeOf( R ) = SizeOf ( Result ) );
// safety check - if we ( or future Delphi versions ) screwed data sizes
// or data alignment in memory - it should "go out with a bang" rather
// than corrupting your data covertly
case Value.DayNight of
dnDay: R[1] := 'd';
dnNight: R[1] := 'n';
else raise ERangeError.Create('DayNight time is incorrect!');
// future extensibility safety check needed.
// Compiler is right with Warnings here
end;
R[2] := AnsiChar(Ord('0') + Ord(Value.Clouds));
R[3] := AnsiChar(Ord('0') + Ord(Value.Precip));
R[4] := AnsiChar(Ord('0') + Ord(Value.PrecipType));
end;
返回类似的事情。
class operator TWeatherCode.Implicit(const Value: Cardinal): TWeatherCode;
var V: packed array [1..4] of AnsiChar absolute Value;
B: array [2..4] of Byte;
begin
Assert( SizeOf( V ) = SizeOf ( Value ) );
// safety check - if we ( or future Delphi versions ) screwed data sizes
// or data alignment in memory - it should "go out with a bang" rather
// than corrupting your data covertly
case UpCase(V[1]) of
'D': Value.DayNight:= TDayNight.dnDay;
'N': Value.DayNight:= TDayNight.dnNight;
else raise .....
end;
B[2] := Ord(V[2]) - Ord('0');
B[3]....
B[4]....
// again: error-checking is going first, before actual processing legit values. that makes it harder to forget ;-)
if (B[2] < Low(Value.Clouds)) or (B[2] > High(Value.Clouds)) then
raise ERangeError(ClassName + ' unpacking from Cardinal, #2 element is: ' + V[2] );
Value.Clouds := TCloudCode( B[2] );
.......
end;
UPD。关于断言的好问题如下。我希望或多或少现代Delphi中的断言应该被重写为编译时检查。我不完全确定语法,我几乎从未使用它,但它应该是这样的:
// Assert( SizeOf( R ) = SizeOf ( Result ) );
{$IF SizeOf( R ) <> SizeOf ( Result )}
{$MESSAGE FATAL 'Data size mismatch, byte-tossing operations are broken!'}
{$IFEND}
现在回到打包成一个字节....简单的位骑师,将你的字节切成四个独立的部分,它不会。 见 - https://en.wikipedia.org/wiki/Power_of_two
您的组件需要以下单元格:
1 + 3 + 3 + 2 = 9> 8位= 1个字节。
仍然可以尝试与变基https://en.wikipedia.org/wiki/Positional_notation
相处组合总数为2 * 7 * 5 * 3 = 210种组合。一个字节小于256。
所以你可能会逃避这样的事情:
Result :=
Ord(Value.DayNight) + 2*(
Ord(Value.Clouds) + 7*(
Ord(Value.Precip) + 5*(
Ord(Value.PrecipType) + 3*(
0 {no more 'digits' }
))));
这可以解决问题,但我对此非常谨慎。数据打包越多 - 冗余越少。冗余越少 - 随机不稳定值看起来像一些合法数据的概率就越大。 此外,您无法继续扩展,
想象一下,#3元素将来会从0, 1, 2, 3, 4
范围扩展到0 to 5
范围。你必须改变公式。但是......你真的知道这样做吗?或者您的旧程序是否会被新数据提供?如果你改变公式 - 你怎么能告诉用新旧公式计算的字节?
坦率地说,我做了类似的技巧,将日期打包成两个字节。 但是我有100%的保修期我永远不会延长这些日期,额外的我有100%的意思来告诉非初始化空间的书面日期,而且 - 还有一个额外的 - 如果需要延期,我知道我可以放弃完全废弃的方案,并在另一个内存位置重新开始。不需要与旧数据兼容。我对你的案子不太确定。
答案 1 :(得分:3)