如何将记录的属性转换为整数并返回?

Jer*_*dge 7 delphi enums casting

我编写了以下记录类型,其中隐式运算符用于在此记录类型和字符串之间进行转换.它代表标准天气代码,简要描述当前的天气状况:

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;
Run Code Online (Sandbox Code Playgroud)

可以转换为此类型的字符串的示例是......

  • d310 =多云和小雨(白天)
  • d440 =有雨和雷暴的阴天(白天)
  • n100 =大部分晴朗(夜晚)

此字符串将始终采用此格式,并始终为4个字符:1个字母和3个数字.实际上,您可以将其视为以下选项:

  • 0,1
  • 0,1,2,3,4,5,6
  • 0,1,2,3,4
  • 0,1,2

我想做的还是提供一个选项,隐式地将它强制转换为整数,甚至是字节,如果我能够足够小的话.但我讨厌必须添加大量的if/then/else或case语句.

我知道可以使用给定(小)字符集并将它们转换为单个值.但是,我不知道它是如何完成的.我知道,例如,这种技术用于诸如标志DrawTextEx和其他类似的WinAPI调用之类的地方.我认为这可能与shr/ 的使用有关,shl但我不知道如何使用它们,并且在这种类型的数学上很糟糕.

如何将这4个属性组合成一个整数或字节,并将它们转换回来?

Ari*_*The 5

最简单的方法是将其打包成32位有符号或无符号整数.我更喜欢未签名的(又名Cardinal)

你说 - "将永远是4个字符:1个字母和3个数字".而"字符"则表示较低的拉丁字符.这些字符很好地用单字节AnsiChar类型表示.

警告:在不同的Delphi版本中Char是一个WideCharAnsiChar类型的快捷方式.在进行低级别类型转换时,您要避免模糊的高级快捷方式并使用原始类型.

同样适用于string- > UnicodeStringAnsiString含糊不清.

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;
Run Code Online (Sandbox Code Playgroud)

类似的事情要回去.

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;
Run Code Online (Sandbox Code Playgroud)

UPD.关于断言的好问题如下.我希望或多或少现代Delphi中的断言应该被重写为编译时检查.我不完全确定语法,我几乎从不使用它,但它应该是这样的:

 //  Assert( SizeOf( R ) = SizeOf ( Result ) );
 {$IF SizeOf( R ) <> SizeOf ( Result )} 
    {$MESSAGE FATAL 'Data size mismatch, byte-tossing operations are broken!'}
 {$IFEND}
Run Code Online (Sandbox Code Playgroud)

现在回到打包成一个字节....简单的位骑师,将你的字节切成四个独立的部分,它不会.请参阅 - https://en.wikipedia.org/wiki/Power_of_two

您的组件需要以下单元格:

  1. 0到1 => 0..1 => 1位
  2. 0到6 => 0..7 => 3位
  3. 0到4 => 0..7 => 3位
  4. 0到2 => 0..3 => 2位

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' }
 ))));
Run Code Online (Sandbox Code Playgroud)

这样可以解决问题,但我对此非常谨慎.数据打包越多 - 冗余越少.冗余越少 - 随机不稳定值看起来像一些合法数据的可能性就越大.这也让你没有进一步扩展的空间,

想象一下,未来#3元素将从0, 1, 2, 3, 4范围扩展到0 to 5范围.你必须改变公式.但是......你真的知道这样做吗?或者您的旧程序是否会被新数据提供?如果你改变公式 - 你怎么能告诉用新旧公式计算的字节?

坦率地说,我做了类似的技巧,将日期打包成两个字节.但是我有100%的保修期我永远不会延长这些日期,额外的我有100%的意思来告诉非初始化空间的书面日期,而且 - 还有一个额外的 - 如果需要延期,我知道我可以放弃完全废弃的方案,并在另一个内存位置重新开始.不需要与旧数据兼容.我对你的情况不太确定.