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

时间:2016-12-30 01:41:32

标签: delphi enums casting

我编写了以下记录类型,其中隐式运算符用于在此记录类型和字符串之间进行转换。它代表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个数字。实际上,您可以将其视为以下选项:

  • 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个属性组合成一个整数或字节,并将它们转换回来?

2 个答案:

答案 0 :(得分:5)

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

你说 - &#34;总是4个字符:1个字母和3个数字&#34;。 通过&#34;字符&#34;你的意思是较低的拉丁字符。 这些字符很好地用单字节AnsiChar类型表示。

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

同样适用于string - &gt; 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;

返回类似的事情。

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. 0到1 =&gt; 0..1 =&gt; 1位
  2. 0到6 =&gt; 0..7 =&gt; 3位
  3. 0到4 =&gt; 0..7 =&gt; 3位
  4. 0到2 =&gt; 0..3 =&gt; 2位
  5. 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)

您可以使用variant部分声明您的记录,如下所示

-std=gnu++11

在这种情况下,你可以分配单个字段或IntValue,它们将使用相同的内存。