Index |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
C++の壺・ビット篇
C++の基本テクニックのTipsです。ビットは、オンとオフを表現する単位で、コンピュータが動作する最小の単位でもあります。C++では、これを制御するプログラミングが可能です。ビット篇では、ビット演算と、ビットを利用した基本的なデータ処理の手法を扱います。
sasaraan programming ![]() Exposition ●ビット演算
プログラムでは、通常、1バイト単位でデータを扱いますが、ビット単位で制御することも可能です。1ビットは 0 か 1 のいずれかの値を取ります(2進法)。これが八つ並ぶと1バイトとなります。16進法は、4ビット単位の値の組み合わせからなり、例えば1バイトは、上位4ビットと下位4ビットとに分かれます。
いずれもメモリと値の関係を視覚的に捉えやすい表現方法といえます。ただし、プログラムの記述で2進数の表現手段は用意されていないので、通例は 16進数でビット演算をすることになります。 1バイトの表現方法の比較
また、シフトによって、はみ出した部分は破棄され、新たに割り当てられたビットには 0 が入ります。ただし、符号あり整数 (signed) の右シフトに限り、値を 2倍 または 1/2 にする整合性を保つため、左側は、最上位ビット(符号ビット)と同じ値(+の場合は 0, −の場合は 1)で埋められます。 ただし、符合に関わらず 0 を入れるコンパイラもあるので確認が必要です。このため、ビットシフトは unsigned 整数のみを対象にすることが多いようです。
●ビット・フラグ
フラグは、オン (1) とオフ (0) を切り替えて使用するデータの呼称です。C++ では、bool 型 (true / false) がフラグの表現に適したデータ型です。ただし、bool 型は、通常は int 型と同じバイト数 (32ビット環境時で 4バイト) を消費してしまいます。メモリへの負担を軽くしたい時は、以下のようなビット・フラグを使用します。
// ビット・フラグの例 1:三原色の組み合わせ
#include <stdio.h> // printf
#include <conio.h> // getch
#define RED 0x01 // 00000001 : 1
#define GREEN 0x02 // 00000010 : 2
#define BLUE 0x04 // 00000100 : 4
#define WHITE (RED | GREEN | BLUE) // 00000111 : 7
// 色名の出力
void get_color_name(unsigned int c)
{
// 下位3ビットのみを抽出
unsigned char flag = (c & RED) | (c & GREEN) | (c & BLUE);
// 分岐処理
switch (flag)
{
case RED: printf("Red\n"); break;
case GREEN: printf("Green\n"); break;
case BLUE: printf("Blue\n"); break;
case RED | GREEN: printf("Yellow\n"); break;
case RED | BLUE: printf("Purple\n"); break;
case GREEN | BLUE: printf("Aqua\n"); break;
case WHITE: printf("White\n"); break;
default: printf("Black\n");
}
}
// main 関数
void main()
{
unsigned char color;
// GREEN をオン
color = GREEN; // = 00000010
get_color_name(color); // "Green"
// RED と BLUE をオン ( | )
color |= RED | BLUE; // 00000010 | 00000101 = 00000111
get_color_name(color); // "White"
// BLUE をオフ ( ~ と & ) 00000111 & ~00000100
color &= ~BLUE; // 00000111 & 11111011 = 00000011
get_color_name(color); // "Yellow"
// RED がオンかどうかを調べる
if ((color & RED) == RED) printf("赤が含まれています\n");
else printf("赤は含まれていません\n");
// WHITE がオンかどうかを調べる
if ((color & WHITE) == WHITE) printf("白です\n");
else printf("白ではありません\n");
if (getch()) return;
}
上記中、#define では、10進数を使用しても同じ機能となります。各データの出し入れはビット演算子を用いて行います。1 にしたいときは or ( | )、0 にしたい時は not ( ~ ) 〜 and ( & ) で設定することができます。(0 にする際、オンになっていることが分かっていれば xor ( ^ ) でも可)また、あるフラグがオンかどうかを知るには、上例のように、(color & RED) が、RED と同じになるかを調べます。この時、フラグがひとつのビットのオン/オフしか制御していないことが明らかなら、下例の get_days 関数の (n & (1<<i)) のように、値が 0 かどうかを調べるだけで判断できます。 一方、下記の例は、ビットシフトを利用してフラグを表現したケースです。ビットシフトの実行は、ここではマクロで定義しています。この例では、通常なら bool 型の配列を32個用意するところを、unsigned int 1個分のメモリ (32ビット) で済むことになります。 // ビット・フラグの例 2:日付フラグ
#include <stdio.h> // printf
#include <conio.h> // getch
// マクロ定義 (FLAG_DAY(2) は ...0100、FLAG_DAY(3)は ...1000 を表す)
#define FLAG_DAY(N) (1 << (N)) // N の分だけ 1 を左シフト
// 日付の出力
void get_days(unsigned int n) {
int i ;
for (i=0; i<32; i++) {
if (n & ( 1<< i )) printf("%d日, ", i); // 1 になっているビットを取り出す
}
printf("\n");
}
// main 関数
void main()
{
unsigned int days;
// 10日と15日をオン : 00000000 00000000 10000100 00000000
days = FLAG_DAY(10) | FLAG_DAY(15);
get_days(days); // 10日, 15日,
// 20日をオン : 00000000 00010000 10000100 00000000
days |= FLAG_DAY(20);
get_days(days); // 10日, 15日, 20日,
// 15日をオフ : 00000000 00010000 00000100 00000000
days &= ~FLAG_DAY(15);
get_days(days); // 10日, 20日,
if (getch()) return;
}
●ビットフィールド
構造体の要素には、割り当てるビット数を指定することができます。指定するには、要素の末尾に ":ビット幅" の記述を付け加えます。データ型は unsigned int でも消費は 指定したビット数のみとなります。下記は、ビット・フラグをビットフィールド内で適用したものです。
// ビットフィールドの例 1:フラグ
#include <stdio.h> // printf
#include <conio.h> // getch
#include <string.h> // memset
// primcolor 構造体
struct primcolor {
unsigned int red :1; // 1 ビット割り当て
unsigned int green :1; // 1 ビット割り当て
unsigned int blue :1; // 1 ビット割り当て
};
// 色名の出力
void get_color_name(primcolor *pc)
{
if (pc->red && pc->green && pc->blue) printf("White\n");
else if (pc->red && pc->green) printf("Yellow\n");
else if (pc->red && pc->blue) printf("Purple\n");
else if (pc->green && pc->blue) printf("Aqua\n");
else if (pc->red) printf("Red\n");
else if (pc->green) printf("Green\n");
else if (pc->blue) printf("Blue\n");
else printf("Black\n");
}
// main 関数
void main()
{
primcolor pc;
memset(&pc, '0', sizeof(primcolor)); // 0 で初期化
get_color_name(&pc); // "Black"
pc.green = 1; // green をオン
get_color_name(&pc); // "Green"
pc.red = 1; // red をオン
get_color_name(&pc); // "Yellow"
pc.blue = 1; // blue をオン
get_color_name(&pc); // "White"
pc.green = 0; // green をオフ
get_color_name(&pc); // "Purple"
if (getch()) return;
}
下記は、ビットフィールドで数値を扱う例です。数値を扱う際も、効率的にメモリを使用することができます。宣言時にビット幅を付け加えているだけで、実際の使用は普通の構造体と変わりません。
// ビットフィールドの例 2:日付
#include <stdio.h> // printf
#include <conio.h> // getch
#include <string.h> // memset
// bitdate 構造体
struct bitdate {
unsigned int month : 4; // 4 ビット割り当て(使用可能範囲 2^4 : 0...15)
unsigned int day : 5; // 5 ビット割り当て(使用可能範囲 2^5 : 0...31)
unsigned int week : 3; // 3 ビット割り当て(使用可能範囲 2^3 : 0...7)
};
// 日付の出力
void get_date(bitdate *bd)
{
char *wd[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
printf("%d/%d(%s)", bd->month, bd->day, wd[bd->week]);
}
// main 関数
void main()
{
bitdate bd;
memset(&bd, '0', sizeof(bitdate)); // 0 で初期化
bd.month = 9; // 月を設定
bd.day = 23; // 日を設定
bd.week = 5; // 曜日を設定
get_date(&bd); // 出力 "9/23(Fri)"
if (getch()) return;
}
ビットフィールドでは、ビット数 0 の要素をはさむことにより、次の要素を次のバイトの頭に移動させことが可能です(ビット・パディング)。また、ビットフィールドではないデータとの混在も可能です。
struct bitdate {
unsigned int month : 4; // 1バイト目 (4ビット消費)
unsigned int : 0; // ビット・パディング (1バイト目の残り4ビットを飛ばす)
unsigned int day : 5; // 2バイト目の頭へ (5ビット消費)
unsigned int : 0; // ビット・パディング (2バイト目の残り3ビットを飛ばす)
unsigned int week : 3; // 3バイト目の頭へ (3ビット消費)
char memo[1024]; // 通常の 文字列
};
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
www.sasaraan.net |
(c) morijoh |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||