Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4464.html
 

Битовые маски и множества

Опубликовано 31.03.2011 г. 22:18

Наверное, вы достаточно часто используете различные перечисления и основанные на них множества в работе. Работа с ними весьма проста и удобна, однако вы не можете передать множество в другую программу/библиотеку. И для этих целей обычно используются битовые маски.

Как пользоваться множествами все прекрасно знают. Определяем перечисление, и соответствующее множество:
    TTestEnum = (etZero, etOne, etTwo, etThree, etFour, etFive, etSix);
    TTestSet = set of TTestEnum;
Далее мы можем проводить над множеством различные операции: добавления, удаления и т.п. в том числе нас будет более интересовать операция определения вхождения элемента в множество, либо итерации по множеству, например, для перебора каких либо свойств.
var te : TTestEnum;
    ts : TTestSet;
begin
     if te in ts then DoSomething();
     for te in ts do Something();
end;
Теперь к битовым маскам. Аналогично перечислению мы можем определить набор констант по степеням двойки:
const
    ET_ZERO     = $00;
    ET_ONE      = $01;
    ET_TWO      = $02;
    ET_THREE    = $04;
    ET_FOUR     = $08;
    ET_FIVE     = $10;
    ET_SIX      = $20;
Что дает нам в двоичном представлении один значащий бит для каждой константы:
    ET_ZERO     -  00000000
    ET_ONE      =  00000001
    ET_TWO      =  00000010
    ET_THREE    =  00000100
    ET_FOUR     =  00001000
    ET_FIVE     =  00010000
    ET_SIX      =  00100000
Что бы определить набор констант - маску, следует применить к ним логическую операцию ИЛИ, т.е сложить. Тогда ET_ONE + ET_THREE + ET_FOUR = 1 + 2 + 8 = 11 = 00001101, где установлены нужные нам биты. Теперь, чтобы проверить наличие константы в маске достаточно выполнить логическое И: ET_THREE and 1101 = 0100 and 1101 = 0100 = true или ET_TWO and 1101 = 0010 and 1101 = false Очевидно, что с помощью битовых сдвигов можно перебрать все константы. Теперь допустим мы хотим внутри своей программы использовать привычные нам множества, а с внешним миром взаимодействовать с помощью битовых масок. В принципе это конечно же не проблема. К чему вообще эта статья: В модуле SysUtils объявлен тип данных TIntegetSet:
  TIntegerSet = set of 0..SizeOf(Integer) * 8 - 1;
Битовая маска в виде целого числа дает нам 4 байта, или 32 значащих бита, что равносильно множеству с максимальным числом элементов равным 32. В общем то тип TIntegerSet и определяет доступ к битовым маскам с помощью множества. С помощью явного приведения целое число приводится к данному типу (ключевой пример статьи):
var mask : integer;
   intset : TIntegerSet;
begin
   mask := ET_ONE + ET_THREE + ET_FOUR;
   intset := TIntegerSet(mask);         // intset =  [1, 3..4]
end;
Как мы видим, до преобразования полученного множества в наше необходимо привести его нужному виду. К сожалению, с помощью привидения типов этого сделать нельзя. Так что мы можем реализовать вспомогательную структуру, для конвертации множества в битовую маску и обратно:
    TBM2EConverter = record
        class function Convert(mask : integer) : TTestSet; overload; static;
        class function Convert(enum : TTestSet) : integer; overload; static;
    end;
Перевод из маски во множество сделаем с помощью вышеуказанного TIntegerSet:
class function TBM2EConverter.Convert(mask: integer): TTestSet;
var b : byte;
begin
    result := [];
    for b in TIntegerSet(mask) do
        include(result, TTestEnum(b + 1));
end;
Перевод из множества в маску с помощью битовых сдвигов:
class function TBM2EConverter.Convert(enum: TTestSet): integer;
var e : TTestEnum;
begin
    result := 0;
    for e in enum do
        inc(result, 1 shl (byte(e) - 1));
end;
Если вы вдруг задались вопросом "зачем нужна эта статья", то она посвящена TIntegerSet, про который я впервые узнал несколько недель назад, так что быть может кому то пригодится. Жаль, что нельзя написать обобщение для подобной записи, было бы удобно.
Метки:  sets 

Комментарии

GunSmoker
31.03.2011 в 23:45
К сожалению, с помощью привидения типов этого сделать нельзя.


Ээээ... почему это? Это вполне работоспособно:

intset := TIntegerSet(mask)

Равно как и обратное Mask := Integer(intset);

Это даже с пользовательским типом множества работать будет, например:

type
  TMyValue = (mvOne, mvTwo, mvThree, mvFour, mvFive,
              mvReserved = 31); // <- hack
  TMySet = set of TMyValue;

procedure TForm1.Button1Click(Sender: TObject);
var
  intset: TIntegerSet;
  mask: Integer;
  myset: TMySet;
begin
  intset := TIntegerSet(mask);
  mask := Integer(intset);
  myset := TMySet(mask);
  mask := Integer(myset);
end;
ter
01.04.2011 в 01:09
Ээээ… почему это? Это вполне работоспособно:
intset := TIntegerSet(mask)
именно так в тексте и написано.

Равно как и обратное Mask := Integer(intset);
а вот о таком я что то даже не подумал (:

про хак с пользовательским тоже интересно (:

а вобще фраза "К сожалению, с помощью привидения типов этого сделать нельзя." относилась к привидению одного типа множества к другому. Такого то мы сделать не можем ведь? по крайней мере если у них размеры разные.
GunSmoker
04.04.2011 в 13:45
Такого то мы сделать не можем ведь? по крайней мере если у них размеры разные.

Напрямую - понятно, нет.

Но можно через Move - с учётом, что часть бОльшего множества останется без изменений (т.е. её надо обнулять отдельно), а можно через двойное преобразование: множество в промежуточное число, затем число в множество. Число брать максимальной размерности - тогда размерность будет автоматически повышена как для чисел (Byte в Integer).

Хотя мне не очень понятен смысл приведения множеств разной мощности друг к другу.
Striker Loner
05.04.2011 в 12:34
Всегда пользуюсь такой конструкцией
type
TTest = (tvFirst, tvSecond, tvTree, tvFour)l
TTestSet = set of TTest

TConvert: array [0..7] of Cardinal;

function Convert(Value: TTestSet): TConvert;
var
DataSet: TTestSet;
DataConvert: TConvert absolute DataSet
begin
DataSet := Value;
Result := DataConvert;
end;

Содержимое множество связывается с представлением в массиве
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно