N煎ログブログ

n番煎じと言っても過言ではない今更な、でも個人的に躓いたUnityやUE4等での開発についての云々を書いていきます

複数のフラグを1つの変数のみで管理する

目次

はじめに

先日、ある場でプログラミングにおける「フラグ」の扱いについて学ばせていただいたことがあったので、自分の中での復習として、またこの記事を見てくれた読者の方の学習の一助となればと思います。プログラミングに達者な方は「え?そんなの当たり前でしょ?今更?」な話になるかもしれません。ブログタイトルが如し。

フラグとは?

まず、フラグ(flag, 旗)とは何なのかという話ですが、これは単に「〇〇を満たしているか否か」であると考えて行きます。つまり「ONかOFFか」です。

フラグ情報は一つに

今までの私はフラグの管理を以下のように定義、使用していました。

bool flagA; //int の場合もあり

bool flagB;

bool flagC;

...

 しかしこれは必要になった分だけ変数が作られるため非常に非効率であり、管理しにくくなります。定義した変数だけで何十行も使用するなど、余程のことでもない限り避けるべきでしょう(私も人のことを言えた義理ではありませんが...)。

そこで、この対策としてどうするかと言いますと「全フラグを一つにまとめてしまう」事です。具体的には、

unsigned int flags;

の一行で大体のフラグ情報は事足りるようになります。何故かは順を追って説明していきます。

bool[] flags; //intの場合もあり

これでも可能ですが、使用するたびに配列要素の検索・アクセスというオーバーヘッド(ざっくり言えば余分な処理のこと)が発生するため、すっきりするかと言うと怪しくもあります。

[フラグ] = [ON or OFF] = [2通り] = [0 or 1]

 冒頭で、フラグ情報はONかOFFで考えると述べました。つまり、1つのフラグ情報の持つパターンはON,OFFの2パターンしかないのです。そこで、それらフラグを「0 or 1」で考えてみることにします。つまり、一つのフラグは

OFF:0 / ON:1

 となるわけです。

0と非0

ここで余談ですが、boolも内部的には「0 or 1」で処理されています。しかし正しくは「0 or 非0」で処理されています(boolに限らず)。ここで「非0」と言ったのは、「0でない時『正』とみなされる」からです。プログラム本等でwhileを用いたループ処理のコードで、

while(1){ 略 }

というのを見たことないでしょうか。これは「非0」が「正」であるという仕組みを利用しているのです。その為「1が正である」と勘違いされがち(私もそうでした)なのかもしれませんが、実際は「0以外が正である」というのが正しい認識と言えると思います。

その為、bool値をインクリメントした場合、その時点で0ではなくなるため、ifで処理したとき「正」とみなされます。しかし逆に、デクリメントした場合は「偽」とみなされる保証はありません。なぜならば、「非0」が「正」となるため、値が「0」でない可能性もあるからです。

参考:bool (C++)

フラグの2進数化

さて、フラグを「0 or 1」にすることでどのようなメリットがあるのでしょうか。それは「フラグ情報を2進数化することができる」ということに尽きるでしょう。

2進数とは、0と1の集まりです。

www.pc-master.jp

つまり、例えば「生きているか(aliveFlag)」「移動しているか(moveFlag)」「回転しているか(rotateFlag)」「ジャンプしているか(jumpFlag)」等といった情報を、

aliveFlag, moveFlag, rotateFlag, jumpFlag 

true, false, false, true

1, 0, 0, 1

1001

と置き換えることができるのです。

 「1001(2進数)」は整数になおすと「9」です。4つのフラグ情報がたったこれだけになります。整数にできるということはint変数に格納することができます。これが、フラグの2進数化の魅力と言えるでしょう(実際にはマイナス情報はいらないので、符号情報を消すunsingedを付けます)。

フラグ情報へのアクセス

フラグ情報を整数にする仕組みはわかりましたが、ではフラグ情報の追加や、整数からフラグ情報へのアクセスはどのように行うのでしょうか。

ここで「ビット演算」を用います。

ビット演算についてはこの(複雑な演算子)サイトがわかりやすいかと。

フラグの作成(フラグを立てる)

フラグをまとめるには、もちろん1つ1つのフラグ情報が必要です。では、そのフラグ情報を作成するにはどうするかというと「ビットシフト」という仕組みを用います。具体的には、

1 << n

で作成します。これは「1」、つまり「0001」という基本のビット情報を「指定整数値分左にずらした」ビット情報を作成するということになります。つまり、前項での「生きているか(aliveFlag)」「移動しているか(moveFlag)」「回転しているか(rotateFlag)」「ジャンプしているか(jumpFlag)」といった情報を例にとると、

1 << 0

であれば、「0001」つまり「jumpFlag」をtrueにし、

1 << 1

であれば、「0010」つまり「rotateFlag」をtrueにすると言った感じで情報を作成できます。

フラグの格納

フラグ情報の作成はできるようになりましたが、では、肝心の「情報を1つにまとめる」にはどうすればよいでしょうか。

ここでビットの「OR演算」というものを使用します。

例えば、以下のような情報があるとします、

1 << 0 => jumpFlagをtrue

1 << 3 => aliveFlagをtrue

 これら情報をunsigned int 型変数「flags」に格納するには、

flags = 0;

flags |= 1 << 0;

flags |= 1 << 3;

 と記述することで各情報を合成し、以下のような流れで格納させることができます。

1. 0000    //flagsを0で初期化(本来はunsigned int なので32個の0がある)

2. 0001    //1 << 0情報(jumpFlagをtrue)を合成

3. 1001    //1 << 3情報(aliveFlagをtrue)を合成

フラグの比較

では、実際に作成したフラグ情報の中に、あるフラグが立っているかどうかを確認するにはどうすればよいでしょうか。

ここでビットの「AND演算」というものを使用します。

 例えば、先ほど作成した変数flagsに格納されている値があるとすると、

flags & 1 << 0    //0001となる

flags & 1 << 1    //0000となる

flags & 1 << 2    //0000となる

flags & 1 << 3    //1000となる

となります。

あとはこれらをif文内で用いればよいわけです。

しかし注意すべき点として、if文の条件式では0か非0かで判断するとは言っても「0以外の値を無条件に受け入れる」というわけではないので、

 if(flags & 1 << 3)

ではなく、

if((flags & 1 << 3) != 0)

等と言うようにきちんと書いてやる必要があります。

(補足)

C++では「 if(flags & 1 << 3)」でも通るようです。

とはいえ関数の返り値をintではなく「bool」にしてると言った場合にはやはり必要となります。

フラグの削除(フラグを折る)

既に存在するフラグ情報から、任意のフラグを折りたい場合はどのようにすればよいでしょうか。

フラグを折る場合は、複数の演算子を組み合わせる必要があります。具体的には、

flags &= ~(1 << 3)

とすれば、以下のような流れで指定したフラグの情報を0(false)にすることが可能となります。

・flags                        //元フラグ                        1001

・1 << 3                     //折りたいフラグ      1000

1. ~(1 << 3)                //折りたいフラグの反転 0111

2. flags &= ~(1 << 3)  //AND演算(1001 & 0111)  0001

まとめ

ビット演算によるフラグの管理の方法をまとめました。

自分自身、最近になるまで演算の方法をよく知りませんでしたので良い勉強になりました。過去の自分のソースコードをぶん殴る機会も出てきそうですね。

また、今回の記事では通しでunsigned int型を使用していましたが、フラグ情報が32個もいらないという場合はもっとビット数の少ない変数で管理するとメモリの節約にもなるかと思われます。

どの型がどのくらいのサイズなのかはここで見れます(1バイト = 8ビット)。

データ型の範囲