質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.00%

ビット演算 と 個数

解決済

回答 6

投稿

  • 評価
  • クリップ 0
  • VIEW 1,313

BeatStar

score 1607

ビット演算に関してです。

C/C++でやっています。

ビット演算を用いて Windows API の CreateWindow関数に ウィンドウスタイルを渡すような感覚で

できたらなぁと思い、チャレンジしてみました。

しかしなぜかうまくいきません。

やったことは

  1. enumで(ビット演算用)定数定義
enum TEST_ENUM{
     TEST_DEFALUT      = 0,
     TEST_ENUM_TYPE1   = 1,
     TEST_ENUM_TYPE2   = 2,
     ...
     TEST_ENUM_TYPE100 = 6
};

のように1~6 と デフォルトの 0 を定義します。

  1. unsigned int flag を引数として持つ関数 func に設定しながら渡す
// 定義部
void func( unsigned int flag ){

     if( flag & TEST_ENUM_TYPE1 ) cout << "TEST_ENUM_TYPE1が定義されている" << endl;

     if( flag & TEST_ENUM_TYPE2 ) cout << "TEST_ENUM_TYPE2が定義されている" << endl;

     // 上記のやつを TEST_ENUM_TYPE6ぐらいまで。
return; 
}

// main関数等の呼び出し側
func( TEST_ENUM_TYPE1 | TEST_ENUM_TYPE4 | TEST_ENUM_TYPE3 );

としました。

これを動かしてみると、

TEST_ENUM_TYPE1が定義されている
TEST_ENUM_TYPE2が定義されている
TEST_ENUM_TYPE3が定義されている
TEST_ENUM_TYPE4が定義されている
...

と立てていないフラグまで "立った" ことにされています。

なぜなのでしょうか?

Windows API だと 普通にできるのに...

順番がある?

でも CreateWindowに順番を逆にしても ( もちろん 一つの引数として ) 普通に想定した動きになります。

何がいけないのでしょうか?

もし

if( flag & TEST_ENUM_TYPE1 ){
           cout << "TEST_ENUM_TYPE1が定義されている" << endl;
}else if( flag & TEST_ENUM_TYPE2 ){
           cout << "TEST_ENUM_TYPE2が定義されている" << endl;
}else if( flag & TEST_ENUM_TYPE3 ){
           cout << "TEST_ENUM_TYPE3が定義されている" << endl;
}else if( ,,,
...
}else{ // フラグは"全く"立っていない
           cout << "フラグなし" << endl;
}

とする方法もありそうですが、これフラグ用定数が2つしかなくても 2通り ( 個別 ) + 4通り ( 組み合わせ ) だし、

3つ以上だと 1個だで設定されている場合, 2個設定されている場合, 3個設定... とそれの和になるだろうし...

分離するだけでも 100行以上ありそう...

これを 一番最初にやった方法みたいにできたらいいのですが...
( そうすれば パターンは考えなくても良さそうだし。 ただ 組み合わせで 切り替えする場合は別として。 )

どのようにやればいいのでしょうか。

「C言語 ビット演算」 ( C++版も同様 ) のように検索かけたり、「C言語 ビット演算 Windows API」みたいに検索しても

演算そのもの ( "&" でチェック, "|" で追加, "~" で削除 etc. ) しか載っていない場合が多いし。

つまり、パターン ( 一個だけ設定されたとき, 二個設定されたとき, 三個設定されたとき...また それぞれの組み合わせ ) を考えなくても

if( flag & 定数 )

みたいにすれば いいみたいな方法です。

もうちょっと調べてみると

ビット演算を理解してフラグを使いこなす - C#

がヒットしました。

上記サイトはC#ですが、シフト演算子というのでしょうか。

"<<" を使って定義しています。

なので

1 を基準にして、1以降は 1に n 分( ぶん ) シフトしてやると

enum TEST_ENUM{
     TEST_DEFALUT      = 0,
     TEST_ENUM_TYPE1   = 1,
     TEST_ENUM_TYPE2   = 1 << 1,
     TEST_ENUM_TYPE3   = 1 << 2,
     TEST_ENUM_TYPE4   = 1 << 3,
     TEST_ENUM_TYPE5   = 1 << 4,
     ...
};

としたところ、処理自体はうまくいきました。

また

ロベールのC++教室
の「先ずは基本から」-> 「第49章 ビットでフラグ」でも大体同様のやり方。

( 後者は マクロ定数にしていますが。 )

しかし、上記 enumに「TEST_ENUM_TYPE101 = 1 << 100」を追加しようとすると

(ファイルパス+ファイル名):(行):(列?): warning: left shift count >= width of type [-
shift-count-overflow]
...

とコンパイルエラー。

int ( 厳密には unsigned intですが。 ) なので 100 は 範囲内だと思うのですが...

もしかして こういうビット演算用定数 は 何十 ( 大体 30前後? ) までしかできないのでしょうか?

もし可能なら ファイル ( ディレクトリ を含んでもいいですが。 ) を列挙する関数 ( あるいはクラス? )を作成して、

「JPGファイル, BMPファイル, PNGファイル, GIFファイル ... を列挙対象にする」みたいに できるのですが...

拡張子は私が聞いたことがある ( よく使う ) ものだけでも 100ぐらいありそうですし。

( そこまではいかなくても、使える範囲が広がるし。 )

知りたいことは、簡単に言えば、

パターンを考えずに抽出&処理をし、また定数の個数が100や1000でも耐えうるビット演算方法

です。

[情報]
言語      : C/C++
WinAPI    : あり ( ただ、今回はあまり関係ないかも... )
コンパイラ: MinGW ( 同上 )

宜しくお願い致します。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 6

+7

ビット演算はその名の通り、ビットごとの演算を行う演算方法です。
なのでビットに意味を持たせたい場合は、値毎にビットの重複がないように設定しないといけません。
従って定義している値が10進数で0~6になっているのが問題です。
これらを2進数で表すと以下のようになります。

0|000
1|001
2|010
3|011
4|100
5|101
6|110


ご覧のように例えば10進数の3は011なので12でONビットが重複しています。
通常、ビットに意味を持たせたい場合は、前述したようにONビットが重複しないように設定します。

 0|000000
 1|000001
 2|000010
 4|000100
 8|001000
16|010000
32|100000

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+4

冒頭のenumの部分でお聞きしたいのですが、

enum TEST_ENUM{
     TEST_DEFALUT      = 0,
     TEST_ENUM_TYPE1   = 1,
     TEST_ENUM_TYPE2   = 2,
     ...
     TEST_ENUM_TYPE100 = 6
};

TEST_ENUM_TYPE3TEST_ENUM_TYPE4の定義を教えてください。

もし、以下のように書いているならばビットフラグの定義をもう一度確認して下さい。

enum TEST_ENUM{
     TEST_DEFALUT      = 0,
     TEST_ENUM_TYPE1   = 1,
     TEST_ENUM_TYPE2   = 2,
     TEST_ENUM_TYPE3   = 3,
     TEST_ENUM_TYPE4   = 4,
     TEST_ENUM_TYPE5   = 5,
     TEST_ENUM_TYPE100 = 6
};

正しく書くのであれば以下のようになります。

enum TEST_ENUM{
     TEST_DEFALUT      = 0,
     TEST_ENUM_TYPE1   = 1,
     TEST_ENUM_TYPE2   = 2,
     TEST_ENUM_TYPE3   = 4,
     TEST_ENUM_TYPE4   = 8,
     TEST_ENUM_TYPE5   = 16,
     ...
};

ビットフラグの値は2の乗数でなければなりません、2進数の定義と&演算の定義を確認してください。

通常、2の乗数を計算するのは面倒なので、参考にしているC#のサイトのようにシフト演算するのが一般的です。

>「パターンを考えずに抽出&処理をし、また定数の個数が100や1000でも耐えうるビット演算方法」

2の1000乗のパターンが必要なのでしょうか?これは天文学的な数値です。
通常、ファイルの拡張子を判別する用途であればビットフラグは使いません、拡張子はGIFでありTXTであるなどの組み合わせの状態がないので、ビットフラグの用途ではありません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/30 16:24

    ご回答ありがとうございます。

    >> ファイルの拡張子を判別する用途であ...

    "そのファイルがどの拡張子であるか" ではなく、
    ファイルの列挙を行うときに
    画像系拡張子と 動画系拡張子, および ディレクトリ
    みたいに指定する場合です。

    キャンセル

  • 2017/05/30 17:14 編集

    なるほど、理解しました!
    たぶん先程のコメントは的外れでしたので削除しました。
    可変長のクエリーを表現したいということでしょうか?
    でしたら、普通にクエリーをリストコンテナなどを使い、可変長にしておけば良いと思います。

    キャンセル

+2

TEST_ENUM_TYPE_100 はビットが二つ立ってますね。
1000 種類のフラグが必要なら 1000 ビット必要です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/05 12:43

    もしかして 10進数?を 2進数 に変換する作業 ( 素因数分解でしたっけ? ) でやるあれですか。
    数学苦手なのでそこは思いつきませんでした...

    キャンセル

+2

コンピュータの内部では全ての数字は二進数で扱われています。ビット演算は,それらの二進数における各桁に対して行われる演算です。

ビット演算でフラグを管理するときは,一般的にiを整数としてi番目のビットだけが1になっているような数を使います。それはつまり,1をシフト演算したような値のことです。これは端的には 2 の累乗数です。

もし定数として A=3 (=011) や B=6 (=110) のような値を使ってしまうと,3 と 6 は真ん中のビットが共通して 1 なので,たとえば

  • あるフラグ変数にはAが設定されている。これを何らかの関数に渡す。
  • その関数では,
  • そのフラグ変数にBが設定されているか調べたい
  • Aがセットしてある変数に対し「BとAND演算」を行う
  • 結果に真ん中のビットが残ってしまう
  • 演算結果が非ゼロなのでこれは真とされる
  • その関数は「Bがセットされている」と誤判定する

というようなことが起こります。
これを防ぐためには各定数のビットがどこも被っていなければよいわけです。これを一番効率良くしようとすると「どこか一個だけ1が立っている数」が便利なので,2の累乗数が使われるわけです。

100と言うと小さそうに見えますが,100個の項目に対してそれぞれON,OFFが存在するとすると,それは計 2^100 通りもの状態がありえます。普通ならとうていintで数え上げられるような量ではありません。intが128bitの処理系ならば別ですが・・・。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/05 12:42

    ご回答ありがとうございます。
    なるほど。
    そういう理由だったんですね。

    キャンセル

+1

intは10^9くらいで、1<<100は2^100 = 30桁となり、オーバーフローします。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

checkベストアンサー

0

「定数の個数が100や1000でも耐えうるビット演算方法」
として、bitsetはいかがですか?

#include <iostream>
#include <bitset>
using namespace std;

enum TEST_ENUM
{
    ENUM_DEFAULT = 0,
    ENUM_ENUM997 = 997,
    ENUM_ENUM998 = 998,
    ENUM_ENUM999 = 999,
    TEST_ENUM_MAX = 1000
};

typedef bitset<TEST_ENUM_MAX> bigbit;

void func(const bigbit& param)
{
    if (param[ENUM_ENUM997]) { cout << "hit 997" << endl; }
    if (param[ENUM_ENUM998]) { cout << "hit 998" << endl; }
    if (param[ENUM_ENUM999]) { cout << "hit 999" << endl; }
}


int main() {
    bigbit bits;

    bits[ENUM_ENUM997] = 1;
    bits[ENUM_ENUM999] = 1;
    func(bits);

    cout << bits.to_string() << endl;

    return 0;
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/02 14:11

    なるほど!
    std::bitset がありましたね。
    使ったことないので 忘れてました。
    今 手元にないので試すことができませんが、
    とりあえず試してみます。

    キャンセル

  • 2017/06/05 12:41

    帰宅してstd::bitsetを試してみました。
    ハラショー!
    これいいですね!
    今まではビット演算子の使い道とかがわからなかったり、
    勘違い ( std::listや std::set みたいに iteratorを使うしかない等 ) していたのでノータッチでした...

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.00%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る