2016年9月26日月曜日

ビット演算によるフラグ管理





 ーーーーサンプル-----
// フラグ位置を表す定数
const unsigned int a = (1 << 0);
const unsigned int b = (1 << 1);
const unsigned int   c = (1 << 2);

//フラグ全体を持つ変数
unsigned int flag = 0;

//フラグaをオンにする
flag |= a;

// フラグ a, b, c をオンにする
flag |= a | b | c ;

//フラグaをオフにする.
flag &= ~a;

 // フラグa ,b, c をオフにする.
flag &= ~( a | b | c );


// フラグaがオンならtrue
if(flag & a)

// フラグa,b,cどれか一つでもオンならtrue
if(flag & ( a | b | c ) )

// フラグa,b,c 全てがオンならtrue
if( (flag & ( a | b | c ) ) == (a| b | c ) )


// フラグaのオンオフ切り替え
flag ^= a

ーーーー解説-----
メモリの量と情報量で述べた様に
変数はビットの状態の組み合わせによって状態を表す事が出来ます。

従って、例えばある人が3つの果物のうち
どれを持っていてどれを持っていないのかは

bool型を用いて

bool apple = true; //リンゴは持っている
bool banana = false; //バナナは持っていない
bool cherry = false; //サクランボは持っていない

という風に表せるのは勿論ですが

int fruit = 1;

という風に書いておき、

fruit = 0 なら何も持っていない
fruit = 1 ならリンゴだけを持っている
fruit = 2 ならバナナだけ持っている
fruit = 3ならサクランボだけ持っている
fruit = 4 ならリンゴとバナナは持っている
fruit = 5 ならバナナとサクランボは持っている
fruit = 6 なら リンゴとサクランボは持っている
fruit = 7なら全てのフルーツを持っている

みたいな解釈をする事で
一つの変数で複数の状態の設定が出来る事になります。

ただしこの場合、メモリの量は節約できるかも知れませんが
可読性が下がり兼ねないので
ビット演算によるフラグ管理(状態管理)が便利になってきます。

ビット演算を用いるとメモリの節約だけでなく
可読性や操作性においても効力を期待できます。

(勿論、規模や用途や様々な理由で不要に感じられる
場合も多いと思います。必要に応じて読んでください。)


ビット演算をすると変数中のメモリに対して
ビット単位で0か1の状態を指定できるため、
リンゴを持っているかいないか等を
そのまま特定のメモリの状態で表現できます。

以下はリンゴを a, バナナをb, サクランボをcとして
フラグ管理をする例です。

 サンプルを見れば使い方は分かるはずですが
「&」や 「|」 などのビット演算子の意味を理解しようとする場合は
次の事に注意してください。

・ビット演算は見た目としては二つの変数に対して行われるが
 実際は各変数を表すビット配列中の各ビット位置ごとに
 演算が適用される。
 つまり変数(ビット配列)同士の集団戦という見た目だが
 実際は1ビット対1ビットの個人戦がビットの数だけ行われる。

・優先順位が同じ演算が複数並んでいれば左から行われる。
 したがって 例えば
   A |  B |  C
とあれば AとBを |(or演算)した後に
その結果に対して Cと | (or演算)する。



ではいくつか例を見ながら考えてみましょう。

const unsigned int a = 1;  //リンゴを持っている
const unsigned int b = 2;  //バナナを持っている
const unsigned int   c = 4;  //サクランボを持っている

unsigned int flag = 0; //何も持っていない
 
現時点で各変数は下3桁のビットが以下の様になっています。

a = 001
b = 010
c = 100
flag = 000

constが付いているa,b,cはビット配列中、
どの部分が1かによってどのビット位置に注目するかを
表すための変数なので変更する必要がない
ため定数となっています。

一方、flag は、ある人が現在どの様な果物を
持っているか等、変更し得る状態を表すための
変数だとします。
ビット配列中、どの位置が1かで
どの果物を持っているかのフラグにしたいので
現在flagに0が入っているという事は
二進数でも000000…となるので
全てのビットが0になっているので
何も持っていない事を意味します。

なお、これらの定義は

const unsigned int a = (1 << 0);
const unsigned int b = (1 << 1);
const unsigned int c = (1 << 2);

という様にビットシフトで書く事も出来ます。

さてflagがリンゴを持っている事にしたいなら

or演算「 | 」が使えます。
「 | 」の効果は
 「二つのビットのうちどちらかが1なら結果が1になる」
 というものです。

これによりflagのリンゴの位置のビットが
0(持っていない)だとしても
1(すでに持っている)だとしても
リンゴと同じ1(持っている状態)
に変える事が出来ます。

筆算で表すと一目瞭然です。

  000(何も持っていないflag)
  001  (a)
-------- |
  001  (結果)


  001(リンゴを持っているflag)
  001  (a)
-------- |
  001  (結果)


flag |= a と書くと
flag = flag |  a と同じ意味なので
flag | a の演算結果をflagに代入する事が出来ます。

//フラグaをオンにする
flag |= a;
// フラグa,b,cをオンにする
flag |= a | b | c ;

--------------------------------------------
現在のフラグの確認は&で出来ます。

// flag中のaのビットがオンならtrue
if(flag & a)
 
&は「両方とも1なら1, それ以外は0にする」演算子です。


001 (flag)
001 (a)
--------&
001  = true


000 (flag)
001 (a)
---------&
000  = false


110 (flag)
001 (a)
-------&
000 = false


011 (flag)
001 (a)
-------&
001 = true

if文の中の評価は
0は偽だけどそれ以外は全て真になるんで
ビット演算した結果、全てが0にならないと
変数として0にならないので
論理値としては真なのだという事に
注意してください。


 従って
// フラグa,b,cのどれか一つでもオンならtrueになる
if(flag & ( a | b | c ) )

---------------------------

// フラグa,b,c 全てがオンならtrue
if( (flag & ( a | b | c ) ) == (a| b | c ) )

flagを0111とした時の計算順序は
以下の様になります。

0001 a
0010 b
0100 c
-------|
0111 (a|b|c)
0111 flag
--------&
0111 flag
0111 (a|b|c)
---------- =
   true


次はflagを1111で考えてみましょう。

0001 a
0010 b
0100 c
-------|
0111 (a|b|c)
1111 flag
--------&
0111 flag
0111 (a|b|c)
--------=
  true

---------------------------------------
//a のビットをオフにする.
flag &= ~a;

~を使えばビットを反転できるため
変数全体がそのフラグと真逆の状態となります。
例えば
a (001)は~を付けて~aにすると
110になります。これは
bやcのフラグが立ってしまう訳ですが
この状態で&演算すると

000(flag)
110(~a)
------&
000

001 (flag)
110 (~a)
-----&
000

111 (flag)
110 (~a)
-----&
110

という風にaの位置を確実にオフにし、
他のフラグはそのままにする効果が得られます。

従ってaのビットをオフにする処理は
flag &= ~a;
で出来ます。

--------------------------------------------
 // a と b のビットをオフにする.flag &= ~(a | b);


これは3つの演算が行われています。

まず
(a | b)で
001
010
-----
011

それを~で反転し
100になり、

それを&演算で
100 (~(a|b))
111(flag)
-----&
100

という風に目的の場所は
オフにするけどそれ以外は
そのまま残す様な処理が出来ます。
---------------------------------------------
フラグaのオンオフを切り替えたい場合は

flag ^= a

で出来ます。
^は「両方のビットが同じなら0、異なるなら1にする」演算子です。
これを筆算で見ると

001 (flag)
001 (a)
---- ^
000 (aの位置は同じなので0)


100 (flag)
001 (a)
---- ^
101 (aの位置は異なるので1)

という風にflagがどちらの状態でも
結果は反転し、
かつフラグと無関係の位置は
そのまま残ります。

0 件のコメント: