第49章 ビットでフラグ

 いよいよ今回はビット演算の花形、フラグ処理について話したいと思います。1変数に多くの情報を入れられるというビット処理のもっとも基本的な利用法です。プログラムをしていく上で、欠かすことのできない技術です。


 今回の要点です。


 では、いってみましょう。


 今回のお題はフラグ処理です。フラグとは条件が満たされているかどうかを示すもの、または条件を指定するもので、通例0が不満足、1が満足を表します。1ビットの情報のために1変数を使うのは大変もったいないことです。条件が1個なら仕方のないことですが、3個、4個となってくると、流石にばかばかしくなってきます。

 例えば次の関数を見て下さい。

// 文字列をコピーする関数
void strcpy_ex(char* pszDest, const char* pszSource,
               int fAdd, int fTrim, int fUpper, int fLower)
{
    if(fAdd)
        for(; *pszDest; pszDest++);
    if(fTrim)
        for(; *pszSource == ' ' || *pszSource == '\t'; pszSource++);

    for(; *pszSource; pszSource++, pszDest++)
    {
        *pszDest = fUpper ? toupper((unsigned char)*pszSource) :
                   fLower ? tolower((unsigned char)*pszSource) :
                   *pszSource;
    }
    pszDest--;
    if(fTrim)
        for(; *pszDest == ' ' || *pszDest == '\t'; pszDest--);
    pszDest[1] = '\0';
}

 これは pszSource から pszDest へ文字列をコピーする関数です。この関数では引数にフラグを4つ取っています。名前の先頭に f の付いているものがそうです。詳しい構造については特に今は考える必要はありません。今はフラグの所にのみ目を向けて下さい。

 fAdd は文字列を追加するようにするフラグです。このフラグが1だと(正確には「0以外」になりますが)、pszDest の文字列の後に pszSource の文字列を追加するようになります。

 fTrim は両端の空白、タブを除いてコピーするようにするフラグです。このフラグが1だと、pszSource の両端の空白、タブは pszDest にはコピーされません。

 fUpper は大文字にできるところは全て大文字にしてコピーするフラグです。fLower は小文字にするフラグです。同時に両方のフラグが立っている場合、大文字のフラグが優先されます。実は上記の処理法では全角文字が入っている場合に不具合が生じるのですが、今は気にしないことにします。

 さて、この関数を使うときは、

strcpy_ex(szDest, szSource, 1, 1, 1, 0);

のようにするわけですが、とてもまどろっこしいですね。まどろっこしいだけでなく、引数が多いので少し処理が多いです。しかも、何番目の引数がなんのフラグなのかも一目では分からず、後から見てもとても読みにくい関数になってしまいました。

 ビット演算を使えばこの不満を解消することができます。以下のプログラムを見て下さい。

プログラム
// Flag1.cpp
#include <iostream.h>
#include <ctype.h>

#define BIT(num)                 ((unsigned int)1 << (num))
#define SCEX_COPY                0
#define SCEX_ADD                 BIT(0)
#define SCEX_TRIM                BIT(1)
#define SCEX_UPPER               BIT(2)
#define SCEX_LOWER               BIT(3)

void strcpy_ex(char* pszDest, const char* pszSource, unsigned int flags);

int main()
{
    char buf[512];

    strcpy_ex(buf, "The ", SCEX_COPY);
    strcpy_ex(buf, "\t\t Country Is Called  ", SCEX_ADD | SCEX_TRIM | SCEX_LOWER);
    strcpy_ex(buf, " usa.", SCEX_ADD | SCEX_UPPER);
    cout << buf;

    return 0;
}

// 文字列をコピーする関数
void strcpy_ex(char* pszDest, const char* pszSource, unsigned int flags)
{
    if(flags & SCEX_ADD)
        for(; *pszDest; pszDest++);
    if(flags & SCEX_TRIM)
        for(; *pszSource == ' ' || *pszSource == '\t'; pszSource++);

    for(; *pszSource; pszSource++, pszDest++)
    {
        *pszDest = (flags & SCEX_UPPER) ? toupper((unsigned char)*pszSource) :
                   (flags & SCEX_LOWER) ? tolower((unsigned char)*pszSource) :
                   *pszSource;
    }
    pszDest--;
    if(flags & SCEX_TRIM)
        for(; *pszDest == ' ' || *pszDest == '\t'; pszDest--);
    pszDest[1] = '\0';
}
実行結果
The country is called USA.

 strcpy_ex 関数を見ると、フラグに使っている変数は flags 1個だけになっています。フラグの判定は

if(flags & SCEX_ADD)

のように、ANDを使って判定しています。そして、使うときは

strcpy_ex(buf, "\t\t Country Called  ", SCEX_ADD | SCEX_TRIM | SCEX_LOWER);

のように、ORを使って指定しています。そしてフラグは

#define SCEX_ADD                 BIT(0)

のように定義されています。

 以上がビット演算を利用したフラグ処理の方法です。このようにすれば関数はすっきりし、フラグが意味のある文字列で表現できるので読みやすくなります。(前者ではマクロが通用しないことに注意!)


 では、何故これでフラグ処理ができるのでしょうか。

 先ず、引数の方から見ていきましょう。引数は次のようにマクロをORで繋いでいました。

SCEX_ADD | SCEX_TRIM | SCEX_LOWER

さて、これを計算してみるとどうなるでしょうか? ORはビットで合成する演算でしたので、答は2進数で1011になりますね。

 この1011という数字には4つの情報が入っています。第0ビットには「文字列を追加する」、第1ビットには「空白、タブを除いてコピー」、第2ビットには「大文字にしてコピーしない」、第3ビットには「小文字にしてコピーする」という情報が入っています。非常にコンパクトですね。

 ただ、コンパクトになっても、その情報が扱えなければ意味がありません。では、条件を判定する方を見てみましょう。

flags & SCEX_ADD

 flags を1011として上の計算をしてみるとどうなるでしょうか? ANDは特定のビットを取り出す演算です。SCEX_ADD は第0ビットが1、他が0なので、答は1になりますね。

 それでは flags を110として上の計算をしてみるとどうなるでしょうか? そうです。0です。

 さて、この文は if 文の中に入っていました。if 文はカッコの中が真か偽かだけでなく、0か0でないかでも判定ができるのでした。つまり上の条件では、flags が1011の時の計算では答が1なので真、flags が110の時の計算では答が0なので偽になります。このようにして条件を判定できるのです。


 では、まとめてみましょう。

 まとめてみると極々簡単なことしかしていなかったことに気付きます。


 あとは細かいことをいくつか。

 ビット演算をするときは、変数を unsigned(符号無し)にします。というのも、負の値に対するビット演算の結果は、環境に依存する可能性があるからです。まぁ、難しいことは抜きにして、ビット演算をする変数は符号無しにする、と覚えておいて下さい。

 あとは、SCEX_COPY について。これは立てるフラグの無いとき用に用意されたフラグです。「立てるフラグがない = 0を指定する」なので別に0をただ指定してもいいのですが、「フラグが全て立っていないときの動作は何か」というのを覚える必要があります。そのために、このようなマクロを用意しておくのが親切というものでしょう。


 では今回の要点です。


 今回でひとまずビット演算は終了です。次回は50章ということで、25章のように少し変わったことをしたいと思います。あ、もちろん前回の問題の解答も行います。

 それでは次回まで。


第48章 ビットでいじろう3 | 第50章 命名法

Last update was done on 2000.7.4

この講座の著作権はロベールが保有しています