Удобным решением для получения различного поведения от некоторой функции является использование битовых флагов, которые передаются ей в качестве входных параметров. Давайте разберемся, когда уместно применять и в чем заключается этот прием.
Постановка задачи
Предположим, нам нужна функция для управления светодиодными индикаторами клавиатуры (Scroll Lock, Num Lock и Caps Lock). Чтобы не усложнять приложение, реализуем его схематично. Будем выводить на консоль состояние, соответствующее переданным битовых флагам. Для этого используем следующую функцию:
1 2 3 4 5 6 7 8 |
[crayon-675ad296b72aa400377840 inline="true" class="lang-cpp"]void printLEDState( bool on ) { if( on ) { std::cout << " * "; } else { std::cout << " _ "; } } |
[/crayon]
Если индикатор включен, то на консоли отобразится символ *, иначе _.
Применение битовых флагов
Первым делом определим следующее перечисление:
1 2 3 4 5 6 7 8 |
[crayon-675ad296b72ad579092704 inline="true" class="lang-cpp"]enum KeyboardLED { NONE = 0, // 0000 NUM_LOCK = 1, // 0001 CAPS_LOCK = 1 << 1, // 0010 SCROLL_LOCK = 1 << 2, // 0100 ALL = NUM_LOCK | CAPS_LOCK | SCROLL_LOCK // 0111 }; |
[/crayon]
Рядом с каждым значением в виде комментария мы указали его двоичное представление. Обратите внимание, что значения
NUM_LOCK,
CAPS_LOCK и
SCROLL_LOCK выбраны таким образом, что единицы в их бинарном представлении не накладываются. Для определения этих значений мы использовали оператор бинарного сдвига
<< на один бит влево. Это означает, что бинарное умножение любого из них даст в результате ноль, то есть они в совокупности образуют набор битовых флагов.
Еще один интересный прием, который мы здесь наблюдаем, заключается в использовании комбинированного флага ALL. Он представляет собой побитовое сложение | всех возможных битовых флагов.
Реализуем саму функцию для установки состояния индикаторов:
1 2 3 4 5 6 7 |
[crayon-675ad296b72b5975673565 inline="true" class="lang-cpp"]void setKeyboardLEDState( unsigned int state ) { printLEDState( state & NUM_LOCK ); printLEDState( state & CAPS_LOCK ); printLEDState( state & SCROLL_LOCK ); std::cout << std::endl; } |
[/crayon]
На вход она принимает параметр
state. Проверка того, должен ли быть включен каждый индикатор или нет, осуществляется с помощью оператора побитового умножения
&. Например, для
CAPS_LOCK мы видим:
1 |
[crayon-675ad296b72ba566433297 inline="true" class="lang-cpp"]state & CAPS_LOCK |
[/crayon]
Если биты в
state и
CAPS_LOCK наложатся, то в функцию
printLEDState() будет передана истина, иначе ложь.
Рассмотрим пример, чтобы лучше понять, как это работает. Передадим функции setKeyboardLEDState() параметр state, равный NUM_LOCK | CAPS_LOCK (то есть 0001 | 0010 = 0011). Во время проверок state & NUM_LOCK ( 0011 & 0001 = 0001) и state & CAPS_LOCK ( 0011 & 0010 = 0010) мы видим, что единицы в бинарном представлении накладываются и получаются ненулевые значения, которые соответствуют true. Однако в последней проверке state & SCROLL_LOCK ( 0011 & 0100 = 0000) получается ноль, который соответствует false. В результате на экран будет выведено: * * _.
А вот еще несколько примеров запуска с указанием результатов, которые будут выведены на консоль:
1 2 3 4 |
[crayon-675ad296b72cc852395905 inline="true" class="lang-cpp"]setKeyboardLEDState( NONE ); // _ _ _ setKeyboardLEDState( NUM_LOCK | CAPS_LOCK ); // * * _ setKeyboardLEDState( ALL ); // * * * |
[/crayon]
Заключение
Использование битовых флагов — довольно удобный и одновременно мощный прием. Его сильной чертой является возможность комбинирования различных режимов работы.