- Что вы использовали для побитовых операций?
- Почему они так удобны?
- Кто-нибудь может рекомендовать ОЧЕНЬ простой учебник?
Практические применения побитовых операций
Ответ 1
Хотя каждый, кажется, подключен к флагом usecase, это не единственное приложение побитовых операторов (хотя, вероятно, наиболее распространенное). Кроме того, С# - достаточно высокий уровень языка, который, вероятно, будет редко использоваться другими методами, но он все равно стоит их знать. Вот о чем я могу думать:
Операторы <<
и >>
могут быстро умножаться на мощность 2. Конечно, оптимизатор .NET JIT, вероятно, сделает это за вас (и любой достойный компилятор другого языка), но если вы "действительно беспокоиться за каждую микросекунду, вы просто можете написать это, чтобы быть уверенным.
Другим распространенным применением этих операторов является наложение двух 16-разрядных целых чисел на одно 32-разрядное целое число. Как:
int Result = (shortIntA << 16 ) | shortIntB;
Это обычное для прямого взаимодействия с функциями Win32, которые иногда используют этот трюк по причинам, связанным с наследием.
И, конечно же, эти операторы полезны, когда вы хотите запутать неопытных, например, при предоставлении ответа на вопрос о домашнем задании.:)
В любом реальном коде, хотя вам будет намного лучше, если использовать умножение, потому что он получил гораздо лучшую читаемость, и JIT оптимизирует его для инструкций shl
и shr
в любом случае, чтобы не было штрафа за производительность.
Довольно много любопытных трюков связаны с оператором ^
(XOR). Это действительно очень мощный оператор из-за следующих свойств:
-
A^B == B^A
-
A^B^A == B
- Если вы знаете
A^B
, тогда невозможно сказать, чтоA
иB
, но если вы знаете один из них, вы можете рассчитать другой. - Оператор не страдает от переполнений, таких как умножение/деление/сложение/вычитание.
Несколько трюков, которые я видел с помощью этого оператора:
Перемещение двух целых переменных без промежуточной переменной:
A = A^B // A is now XOR of A and B
B = A^B // B is now the original A
A = A^B // A is now the original B
Сопоставленный список с одной дополнительной переменной на элемент. Это будет мало использовать в С#, но это может пригодиться для низкоуровневого программирования встроенных систем, где каждый байт подсчитывается.
Идея заключается в том, что вы отслеживаете указатель на первый элемент; указатель на последний элемент; и для каждого элемента вы отслеживаете pointer_to_previous ^ pointer_to_next
. Таким образом, вы можете перемещать список с любого конца, но накладные расходы составляют всего половину от обычного связанного списка. Здесь код С++ для перемещения:
ItemStruct *CurrentItem = FirstItem, *PreviousItem=NULL;
while ( CurrentItem != NULL )
{
// Work with CurrentItem->Data
ItemStruct *NextItem = CurrentItem ^ PreviousItem;
PreviousItem = CurrentItem;
CurrentItem = NextItem;
}
Чтобы пройти с конца, вам просто нужно изменить самую первую строку от FirstItem
до LastItem
. Это еще одна экономия памяти.
Другое место, где я регулярно использую оператор ^
в С#, - это когда я должен вычислить HashCode для моего типа, который является составным типом. Как:
class Person
{
string FirstName;
string LastName;
int Age;
public int override GetHashCode()
{
return (FirstName == null ? 0 : FirstName.GetHashCode()) ^
(LastName == null ? 0 : LastName.GetHashCode()) ^
Age.GetHashCode();
}
}
Ответ 2
Я использую побитовые операторы для обеспечения безопасности в своих приложениях. Я буду хранить разные уровни внутри Enum:
[Flags]
public enum SecurityLevel
{
User = 1, // 0001
SuperUser = 2, // 0010
QuestionAdmin = 4, // 0100
AnswerAdmin = 8 // 1000
}
И затем назначьте пользователю их уровни:
// Set User Permissions to 1010
//
// 0010
// | 1000
// ----
// 1010
User.Permissions = SecurityLevel.SuperUser | SecurityLevel.AnswerAdmin;
И затем проверьте разрешения в выполняемом действии:
// Check if the user has the required permission group
//
// 1010
// & 1000
// ----
// 1000
if( (User.Permissions & SecurityLevel.AnswerAdmin) == SecurityLevel.AnswerAdmin )
{
// Allowed
}
Ответ 3
Я не знаю, насколько практично, решая судоку, которую вы считаете, но допустим, что это так.
Представьте, что вы хотите написать решение sudoku или даже просто простую программу, которая показывает вам правление и позволяет решить головоломку самостоятельно, но гарантирует, что действия законны.
Сама плата, скорее всего, будет представлена двумерным массивом вроде:
uint [, ] theBoard = new uint[9, 9];
Значение 0
означает, что ячейка по-прежнему пуста, а значения из диапазона [1u, 9u] являются фактическими значениями на плате.
Теперь представьте, что вы хотите проверить, является ли какое-то движение законным. Очевидно, вы можете сделать это с помощью нескольких циклов, но битмаски позволяют вам делать вещи намного быстрее. В простой программе, которая просто гарантирует соблюдение правил, это не имеет значения, но в решателе это может быть.
Вы можете поддерживать массивы битмаск, которые хранят информацию о числах, которые уже вставлены в каждую строку, каждый столбец a и каждый 3x3.
uint [] maskForNumbersSetInRow = new uint[9];
uint [] maskForNumbersSetInCol = new uint[9];
uint [, ] maskForNumbersSetInBox = new uint[3, 3];
Отображение из числа в битпаттерн с одним битом, соответствующим этому набору чисел, очень просто
1 -> 00000000 00000000 00000000 00000001
2 -> 00000000 00000000 00000000 00000010
3 -> 00000000 00000000 00000000 00000100
...
9 -> 00000000 00000000 00000001 00000000
В С# вы можете вычислить битпаттерс таким образом (value
- uint
):
uint bitpattern = 1u << (int)(value - 1u);
В строке выше 1u
, соответствующей битпаттеру 00000000 00000000 00000000 00000001
, сдвигается влево на value - 1
. Если, например, value == 5
, вы получаете
00000000 00000000 00000000 00010000
В начале маска для каждой строки, столбца и поля 0
. Каждый раз, когда вы помещаете некоторый номер на доску, вы обновляете маску, поэтому устанавливается бит, соответствующий новому значению.
Предположим, что вы вставляете значение 5 в строку 3 (строки и столбцы пронумерованы от 0). Маска для строки 3 хранится в maskForNumbersSetInRow[3]
. Пусть также предположим, что перед вставкой в строке 3 уже были числа {1, 2, 4, 7, 9}
. Битовая диаграмма в маске maskForNumbersSetInRow[3]
выглядит так:
00000000 00000000 00000001 01001011
bits above correspond to:9 7 4 21
Цель состоит в том, чтобы установить бит, соответствующий значению 5 в этой маске. Вы можете сделать это с помощью побитового или оператора (|
). Сначала вы создаете битовый шаблон, соответствующий значению 5
uint bitpattern = 1u << 4; // 1u << (int)(value - 1u)
а затем вы используете operator |
, чтобы установить бит в маске maskForNumbersSetInRow[3]
maskForNumbersSetInRow[3] = maskForNumbersSetInRow[3] | bitpattern;
или используя более короткую форму
maskForNumbersSetInRow[3] |= bitpattern;
00000000 00000000 00000001 01001011
|
00000000 00000000 00000000 00010000
=
00000000 00000000 00000001 01011011
Теперь ваша маска указывает, что в этой строке (строка 3) есть значения {1, 2, 4, 5, 7, 9}
.
Если вы хотите проверить, если какое-то значение находится в строке, вы можете использовать operator &
, чтобы проверить, установлен ли соответствующий бит в маске. Если результат применения этого оператора к маске и битовый шаблон, соответствующий этому значению, отличен от нуля, это значение уже находится в строке. Если результат равен 0, значение не находится в строке.
Например, если вы хотите проверить, находится ли значение 3 в строке, вы можете сделать это следующим образом:
uint bitpattern = 1u << 2; // 1u << (int)(value - 1u)
bool value3IsInRow = ((maskForNumbersSetInRow[3] & bitpattern) != 0);
00000000 00000000 00000001 01001011 // the mask
|
00000000 00000000 00000000 00000100 // bitpattern for the value 3
=
00000000 00000000 00000000 00000000 // the result is 0. value 3 is not in the row.
Ниже приведены методы для установки нового значения на доске, поддержания актуальных битмаксов в актуальном состоянии и проверки правильности хода.
public void insertNewValue(int row, int col, uint value)
{
if(!isMoveLegal(row, col, value))
throw ...
theBoard[row, col] = value;
uint bitpattern = 1u << (int)(value - 1u);
maskForNumbersSetInRow[row] |= bitpattern;
maskForNumbersSetInCol[col] |= bitpattern;
int boxRowNumber = row / 3;
int boxColNumber = col / 3;
maskForNumbersSetInBox[boxRowNumber, boxColNumber] |= bitpattern;
}
Имея маски, вы можете проверить, разрешено ли перемещение следующим образом:
public bool isMoveLegal(int row, int col, uint value)
{
uint bitpattern = 1u << (int)(value - 1u);
int boxRowNumber = row / 3;
int boxColNumber = col / 3;
uint combinedMask = maskForNumbersSetInRow[row] | maskForNumbersSetInCol[col]
| maskForNumbersSetInBox[boxRowNumber, boxColNumber];
return ((theBoard[row, col] == 0) && ((combinedMask & bitpattern) == 0u);
}
Ответ 4
Здесь представлены десятки бит-скрипичных примеров
Код находится в C, но вы можете легко адаптировать его к С#
Ответ 5
Они могут использоваться для всей загрузки различных приложений, вот те вопросы, которые я ранее размещал здесь, который использует побитовые операции:
Побитовое И, Побитовое включение вопроса ИЛИ в Java
В других примерах просмотрите (скажем) помеченные перечисления.
В моем примере я использовал побитовые операции для изменения диапазона двоичного числа от -128... 127 до 0..255 (изменение его представления из подписанного в unsigned).
статью MSN здесь →
http://msdn.microsoft.com/en-us/library/6a71f45d%28VS.71%29.aspx
полезно.
И, хотя эта ссылка:
очень технический, он охватывает все.
НТН
Ответ 6
В любое время, когда у вас есть опция 1 или более в комбинации элементов, тогда побитовое соединение обычно является простым исправлением.
Некоторые примеры включают в себя биты безопасности (ожидание образца Justin..), дни планирования и т.д.
Ответ 7
Я бы сказал, что одним из наиболее распространенных применений является изменение битовых полей для сжатия данных. Обычно вы видите это в программах, пытающихся быть экономичными с пакетами.
Ответ 8
Одной из наиболее частых вещей, которые я использую для них в С#, является создание хэш-кодов. Там есть некоторые разумные методы хэширования, которые их используют. Например. для координированного класса с X и Y, которые могли бы использоваться как int, я мог бы использовать:
public override int GetHashCode()
{
return x ^ ((y << 16) | y >> 16);
}
Это быстро генерирует число, которое гарантировано будет равным при создании равным объектом (при условии, что равенство означает, что оба параметра X и Y одинаковы для обоих объектов, сравниваемых), а также не создают шаблоны конфликтов для низкоценных объектов ( вероятно, будет наиболее распространенным в большинстве приложений).
Другим является объединение флаговых перечислений. Например. RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase
Есть некоторые операции низкого уровня, которые обычно не нужны, когда вы кодируете фреймворк вроде .NET(например, в С# мне не нужно писать код для преобразования UTF-8 в UTF-16, там для меня в рамках), но, конечно, кому-то пришлось написать этот код.
Существует несколько способов сгибания бит, например округление до ближайшего двоичного числа (например, округление от 1010 до 10000):
unchecked
{
--x;
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return ++x;
}
Что полезно, когда они вам нужны, но это не очень часто.
Наконец, вы также можете использовать их для микро-оптимизации математики, таких как << 1
вместо * 2
, но я включаю это только для того, чтобы сказать, что это вообще плохая идея, поскольку она скрывает намерение реального кода, практически ничего не сохраняет в производительности и может скрывать некоторые тонкие ошибки.
Ответ 9
Если вам когда-либо понадобится общаться с оборудованием, вам нужно будет использовать бит-скрипинг в какой-то момент.
Извлечение значений RGB значения пикселя.
Так много вещей
Ответ 10
- Они могут использоваться для передачи многих аргументов функции через одну ограниченную переменную размера.
- Преимущества - низкая накладная память или низкая стоимость памяти: поэтому повышенная производительность.
- Я не могу написать учебник на месте, но они там, я уверен.
Ответ 11
Двоичный сортировка. Были проблемы, когда в реализации использовался оператор деления, а не оператор битовой смены. Это вызвало отказ BS после того, как коллекция получила размеры выше 10 000 000
Ответ 12
Вы будете использовать их по разным причинам:
- Сохранение (и проверка!) флажков опций в эффективном режиме памяти
- Если вы выполняете вычислительное программирование, вам может потребоваться оптимизировать некоторые из ваших операций, используя побитовые операции вместо математических операторов (остерегайтесь побочных эффектов).
- Серый код!
- создание нумерованных значений
Я уверен, что вы можете думать о других.
Говоря, иногда вам нужно спросить себя: стоит ли наращивать память и производительность, приложив все усилия. После написания такого кода, дайте ему немного отдохнуть и вернуться к нему. Если вы с этим справитесь, перепишите с помощью более удобного кода.
С другой стороны, иногда имеет смысл использовать побитовые операции (думаю, криптография).
Еще лучше, прочитайте его кем-то еще и документируйте.
Ответ 13
Игры!
В те дни я использовал его для воспроизведения частей игрока Reversi. Это 8X8, поэтому мне потребовался тип long
, а не, например, если вы хотите знать, где все на борту - вы or
обе части игроков.
Если вы хотите, чтобы все возможные шаги игрока, скажем вправо, вы >>
представляете фигуры игроков одним, а AND
с фигурами противника, чтобы проверить, есть ли теперь общий 1 (это означает, что есть противник часть справа от вас). Тогда вы продолжаете это делать. если вы вернетесь к своим собственным предметам - не двигайтесь. Если вы доберетесь до ясной бит - вы можете переместиться туда и захватить все части в пути.
(Этот метод широко используется во многих играх в настольных играх, включая шахматы)