Когда подходят побитовые операции - PullRequest
11 голосов
/ 23 апреля 2011

Мне известна основная предпосылка о том, что такое побитовая операция (хотя была бы признательна за объяснение "для чайников");однако я не знаю, когда уместно использовать эту технику.

Насколько я понимаю, старые архитектуры ЦП могут выполнять побитовые операции быстрее, чем другие операции , и поэтому было полезно знать, какиспользуй их.Учитывая, что это больше не так;уместно ли их выполнять, и если да, то с какой целью и на каких условиях?(Меня особенно интересует контекст C #, но я рад получить общие ответы)

Ответы [ 5 ]

6 голосов
/ 23 апреля 2011

Побитовые операции - отличный способ быстро проверить флаг, который может быть установлен для переменной.

В следующем примере подчеркиваются преимущества использования побитовых операций при перечислении Flag, а также сохранения битового поля в базе данных. Затем можно легко проверить битовое поле, чтобы увидеть, содержит ли оно одно значение или подмножество значений из перечисления Flag.

Пример:

A User таблица базы данных с полем tinyint с именем Permission. Поле заполняется с использованием значения, созданного с помощью перечисления, значения которого равны 2 ^ n.

[Flags]
public enum Permission : byte
{
    None = 0,
    ManageUsers = 1 << 0,
    CreateOrders = 1 << 1,
    PurchaseEquipment = 1 << 2,
    CancelOrders = 1 << 3,
}

Помимо побитовых операций, используемых для указания значений в перечислении (выполняется во время компиляции), вы можете использовать перечисление, чтобы проверить, содержит ли поле «Разрешение» в базе данных какое-либо подмножество возможных значений. Со стороны базы данных вы получаете возможность складывать значения в одно поле - устраняя необходимость иметь столбец для каждого разрешения, а на стороне кода вы получаете простой способ проверки значения.

Пример работы с битами (Grant ManageUsers и CreateOrders):

Permission userPermissions = Permission.ManageUsers | Permission.CreateOrders;

Пример проверки прав:

public bool HasPermissions(Permission userPermissions, Permission permissionsToCheckFor)
{
    return permissionsToCheckFor == Permission.None ? 
        false : 
        (userPermissions & permissionsToCheckFor) == permissionsToCheckFor;
}
5 голосов
/ 23 апреля 2011

Проблема не столько в том, что побитовые операции выполняются быстрее, чем целочисленные операции (хотя обычно это так), а в том, что они разные операции для различных целей.

Концептуально байты, шорты и целые числа - это действительно крошечные массивы битов и побитовые операторы являются операторами логических массивов.В настоящее время в C # побитовые операторы в основном используются для перечислений [Flags] и в вычислениях для GetHashCode, но существуют бесконечные способы использования массивов битов.

4 голосов
/ 23 апреля 2011

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

Битовые операторы полезны при работе со структурами данных, где фрагменты данных не выровнены по границе байта. Как правило, это делается, когда пропускная способность (или общий объем памяти) очень важна. Я работаю с программным обеспечением для потоковой передачи видео RTP, и побитовые операции используются как при чтении / построении пакетов передачи RTP, так и при чтении потоков видеокодеков, которые часто кодируются с использованием битов, а не байтов.

2 голосов
/ 23 апреля 2011

Один из способов использования время от времени побитовых операторов - генерировать подмножества заданной строки / массива с помощью двоичной маски, это выглядит так: (извините, код c ++)

string s = "abcde";
for(int i = 0; i < 1<<s.size(); i++) {
  string tmp;
  for(int j = 0; j < s.size(); j++) if(i & 1<<j) tmp.push_back(s[j]);
  cout<<tmp<<endl; 
}

Вы можете проверить это проницательное учебное пособие, ориентированное на конкурсы: Немного веселья: веселье с битами

2 голосов
/ 23 апреля 2011

Они могут быть действительно полезны во встроенных контрольных ситуациях, когда пространство стоит дорого. Например, один байт данных может представлять 8 бит ввода / вывода, и маски могут использоваться для извлечения представляющих интерес битов из порта ввода / вывода (например, если PIN0 = 1, PIN1 = 2, PIN2 = 4, PIN3 = 8 и т. Д., То:

  • Чтобы получить значение PIN0, мы можем сказать PORT0 & PIN0 == 0.
  • Чтобы установить PIN1 в 1, мы можем cay PORT0 |= PIN1.
  • Чтобы установить PIN2 в 0, мы можем сказать PORT0 &= ~PIN2.

Вдали от встроенного контроля, я редко вижу этот подход, используемый в наши дни. В мире C # гораздо чаще можно увидеть отдельные логические поля для каждого интересующего значения, поскольку на современном оборудовании издержки на самом деле не имеют большого значения (хотя вы можете столкнуться с одним или двумя случаями, когда побитовые операции используются для сохранения многих таких флаги в одной переменной).

Существует также интересное приложение в графике. Удобный трюк для курсоров или ограничивающих рамок состоит в том, чтобы добавить их, XOR'ing форму курсора с изображением ниже. Повторное выполнение этой же операции приведет к получению исходного изображения.

...