★#defineの罠★


#define MAX 100

これは「MAX」と書いてある箇所を「100」に置き換えるという意味です。

#defineは非常に便利な仕組みで、主にプログラムの変更やメンテナンス等に役立ちます。


例えば、次の例を見てください。

5個の数値を配列に入力し、合計計算後、入力したデータと合計を表示するプログラムです。


<sample program col024-01>

#include <stdio.h>

int main( void )
{
    int data[5];

    int i;

    int sum = 0;

    for( i=0; i<5; i++ ) {
        scanf( "%d", &data[i] );
    }

    for( i=0; i<5; i++ ) {
        sum += data[i];
    }

    printf( "入力データ\n" );

    for( i=0; i<5; i++ ) {
        printf( "data[%d] = %d\n", i, data[i] );
    }

    printf( "合計 = %d\n", sum );

    return 0;
}

<実行結果 VC++ Express Edition>

5
3
4
2
6
data[0] = 5
data[1] = 3
data[2] = 4
data[3] = 2
data[4] = 6
合計 = 20
続行するには何かキーを押してください・・・

このプログラムを次のように書き換えようとすると・・・

 ※10個の数値を配列に入力し、合計計算後、入力したデータと合計を表示するプログラム

5と書いてある箇所をすべて10に書き換えなければなりませんね。

全部で4箇所ありますが、もし1箇所でも変更し忘れると大変なことになります

そこで「#define」の出番です。

上のプログラムを最初から次のように書いておきます。


<sample program col024-02>

#include <stdio.h>

#define COUNT 5

int main( void )
{
    int data[COUNT];

    int i;

    int sum = 0;

    for( i=0; i<COUNT; i++ ) {
        scanf( "%d", &data[i] );
    }

    for( i=0; i<COUNT; i++ ) {
        sum += data[i];
    }

    printf( "入力データ\n" );

    for( i=0; i<COUNT; i++ ) {
        printf( "data[%d] = %d\n", i, data[i] );
    }

    printf( "合計 = %d\n", sum );

    return 0;
}

<実行結果 VC++ Express Edition>

5
3
4
2
6
data[0] = 5
data[1] = 3
data[2] = 4
data[3] = 2
data[4] = 6
合計 = 20
続行するには何かキーを押してください・・・

プログラムの上部に、

#define COUNT 5

と書いておき、

for文の中の5をすべて「COUNT」に書き換えました。

実行結果は同じですね。


なぜ、このような面倒なことをするのかというと、

 ※10個の数値を配列に入力し、合計計算後、入力したデータと合計を表示するプログラム

に変更したい場合、最初のプログラムと比べて変更箇所が格段に少なくなるからです。

このプログラムの場合は、

#define COUNT 5



#define COUNT 10

に書き換えるだけで変更完了です。


また、次のような場合も有効に活用できます。

ちょっと例が悪いかも知れませんが・・・


<sample program col024-03>

#include <stdio.h>

int main( void )
{
    int select;

    printf( "選択してください\n" );
    printf( "1.宿に泊まる\n" );
    printf( "2.武器屋に行く\n" );
    printf( "3.防具屋に行く\n" );
    printf( "4.冒険に出る\n " );

    scanf( "%d", &select );

    switch( select ) {
        case 1:
            printf( "宿に泊まります\n" );
            break;
        case 2:
            printf( "武器屋に行きます\n" );
            break;
        case 3:
            printf( "防具屋に行きます\n" );
            break;
        case 4:
            printf( "冒険に出ます\n" );
            break;
    }

    return 0;
}

<実行結果 VC++ Express Edition>

選択してください
1.宿に泊まる
2.武器屋に行く
3.防具屋に行く
4.冒険に出る
1
宿に泊まります
続行するには何かキーを押してください・・・

とあるゲームの一部分とイメージしてみてください。

プレイヤーの行動を選択する時に、番号を入力して選択させています。

※入力チェック等は省略しています。

これを次のように書き換えます。


<sample program col024-04>

#include <stdio.h>

#define YADOYA  1
#define BUKIYA  2
#define BOUGUYA 3
#define BOUKEN  4

int main( void )
{
    int select;

    printf( "選択してください\n" );
    printf( "1.宿に泊まる\n" );
    printf( "2.武器屋に行く\n" );
    printf( "3.防具屋に行く\n" );
    printf( "4.冒険に出る\n " );

    scanf( "%d", &select );

    switch( select ) {
        case YADOYA:
            printf( "宿に泊まります\n" );
            break;
        case BUKIYA:
            printf( "武器屋に行きます\n" );
            break;
        case BOUGUYA:
            printf( "防具屋に行きます\n" );
            break;
        case BOUKEN:
            printf( "冒険に出ます\n" );
            break;
    }

    return 0;
}

<実行結果 VC++ Express Edition>

選択してください
1.宿に泊まる
2.武器屋に行く
3.防具屋に行く
4.冒険に出る
1
宿に泊まります
続行するには何かキーを押してください・・・

行動を選択するための番号を「#define」で設定しています。

1箇所しか出てこない数値を置き換えることに何の意味があるのか?と思うかも知れません。

しかし、プログラムを組む時に

 え〜っと、1は何だったっけ? 3は何だったっけ?

と考えなくても、

 宿に泊まるんだから「YADOYA」だな、防具屋に行くんだから「BOUGUYA」だな

と「言葉」で覚えることが出来ます。

数値よりも言葉の方が覚えやすいと思いますし、後々メンテナンスをすることを考えると、昔作ったプログラムの数値の意味なんて覚えて無いことがほとんどです・・・

このように「数値に名前を付ける」という使い方も有効だと思います。


で、ここからが本題です!

タイトルは「#defineの罠」ですから、「」があるのです。

「#define」は次のように使うこともできます。


トランプのゲームを作ることを考えます。※実際に全部作るわけではありません。

カードのマークを「SUIT」と言います。数値は「NUMBER」としましょう。

トランプはジョーカーを除くと、マークは4種類で、1種類につき13枚ありますね。

#define SUIT 4
#define NUMBER 13

このように設定し、全部で「4×13=52枚」ありますので、

#define CARD 52

と書くこともできますが、この52という数値は「SUIT×NUMBER」の結果ですから、

#define CARD SUIT * NUMBER

と書くこともできます。

確かめるため、プログラムを書きますね。


<sample program col024-05>

#include <stdio.h>

#define SUIT 4
#define NUMBER 13
#define CARD SUIT * NUMBER

int main( void )
{
    printf( "マークの種類は%dです\n", SUIT );
    
    printf( "1つのマークにつき%d枚のカードがあります\n", NUMBER );

    printf( "全部で%d枚のカードがあります\n", CARD );

    return 0;
}

<実行結果 VC++ Express Edition>

マークの種類は4種類です
1つのマークにつき13枚のカードがあります
全部で52枚のカードがあります
続行するには何かキーを押してください・・・

さて、この52枚のカードから乱数を使って1枚だけ選んでみたいと思います。

乱数を使って、52種類の数値を出そうとすると、

 rand() % 52

と書けば、「0〜51」までの範囲内の数値が出てくるはずです。

※忘れた!という方はこちらを読み返してください。


<sample program col024-06>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SUIT 4
#define NUMBER 13
#define CARD SUIT * NUMBER

int main( void )
{
    int select;

    srand( (unsigned int)time( NULL ) );

    printf( "マークの種類は%dです\n", SUIT );
    
    printf( "1つのマークにつき%d枚のカードがあります\n", NUMBER );

    printf( "全部で%d枚のカードがあります\n", CARD );

    select = rand() % CARD;

    printf( "%d番目のカードが選ばれました\n", select );

    return 0;
}

<実行結果 VC++ Express Edition>

マークの種類は4種類です
1つのマークにつき13枚のカードがあります
全部で52枚のカードがあります
13番目のカードが選ばれました
続行するには何かキーを押してください・・・

上手くいっているように見えますね。

では、カードを選ぶ部分をループさせて10枚選んでみましょう。


<sample program col024-07>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SUIT 4
#define NUMBER 13
#define CARD SUIT * NUMBER

int main( void )
{
    int select;

    int i;

    srand( (unsigned int)time( NULL ) );

    printf( "マークの種類は%dです\n", SUIT );
    
    printf( "1つのマークにつき%d枚のカードがあります\n", NUMBER );

    printf( "全部で%d枚のカードがあります\n", CARD );

    for( i=0; i<10; i++ ) {
    
        select = rand() % CARD;

        printf( "%d番目のカードが選ばれました\n", select );
    }

    return 0;
}

<実行結果 VC++ Express Edition>

マークの種類は4種類です
1つのマークにつき13枚のカードがあります
全部で52枚のカードがあります
13番目のカードが選ばれました
0番目のカードが選ばれました
13番目のカードが選ばれました
26番目のカードが選ばれました
39番目のカードが選ばれました
0番目のカードが選ばれました
0番目のカードが選ばれました
13番目のカードが選ばれました
13番目のカードが選ばれました
26番目のカードが選ばれました
続行するには何かキーを押してください・・・

何だか同じような数値ばかりが選ばれているように見えますね。

よく見ると「13の倍数」ばかりに見えます。

これが「」なのです。

どこが問題なのかというと、

select = rand() % CARD;

の行です。

この箇所は「CARD」という名前が使われていますので、(見た目には分かりませんが)置き換えが行われています。

「CARD」は「SUIT * NUMBER」の置き換えですから、段階を追って展開していくと、

select = rand() % SUIT * NUMBER;

となり、「SUIT」は「4」、「NUMBER」は「13」に置き換わるので、

select = rand() % 4 * 13;

という式に展開されます。

演算子には優先順位があります。

「%」と「*」の優先順位は同じです。

ですので、左から順に計算されますので、まず

rand() % 4

が計算され、「0〜3」のどれかの数値が発生します。

これに「13」を乗算するのですから、「0」「13」「26」「39」のどれかしか出てきません

プログラムを見ただけでは、

select = rand() % CARD;

と書いてあるため、「52」で割った余り「0〜51」が表示されると勘違いしますが、実際はそうではないのです。

これを「副作用」と言います。

副作用」を避けるためには次のように書く必要があります。

#define CARD (SUIT * NUMBER)

括弧が付いていれば先に乗算を行いますので、上のプログラムの実行結果もこうなります。


<sample program col024-08>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SUIT 4
#define NUMBER 13
#define CARD (SUIT * NUMBER)

int main( void )
{
    int select;

    int i;

    srand( (unsigned int)time( NULL ) );

    printf( "マークの種類は%dです\n", SUIT );
    
    printf( "1つのマークにつき%d枚のカードがあります\n", NUMBER );

    printf( "全部で%d枚のカードがあります\n", CARD );

    for( i=0; i<10; i++ ) {
    
        select = rand() % CARD;

        printf( "%d番目のカードが選ばれました\n", select );
    }

    return 0;
}

<実行結果 VC++ Express Edition>

マークの種類は4種類です
1つのマークにつき13枚のカードがあります
全部で52枚のカードがあります
0番目のカードが選ばれました
11番目のカードが選ばれました
8番目のカードが選ばれました
18番目のカードが選ばれました
33番目のカードが選ばれました
38番目のカードが選ばれました
51番目のカードが選ばれました
39番目のカードが選ばれました
33番目のカードが選ばれました
6番目のカードが選ばれました
続行するには何かキーを押してください・・・

上手くいきました。

より安全に使うために、「SUIT」や「NUMBER」においても、

#define SUIT (4)
#define NUMBER (13)

と書くこともあります。

「#define」を使うときは「副作用」に注意して使ってください。


ブラウザの戻るボタンで戻ってください。