C言語基礎|オブジェクト形式マクロ

配列を使うと、同じ種類のデータをまとめて扱えて便利ですよね。
でも、配列とセットでほぼ必ず出てくる悩みがこれです。

  • 人数が 5 人から 8 人に変わった。
  • 配列の要素数も、ループ回数も、平均の割り算も全部直す必要がある。
  • でも、同じ数字 5 が「表示の桁数」など別の意味でも使われていることがある。

こういうとき、コード中の 5 を全部 8 に置換すると事故りがちです。
そこで登場するのが オブジェクト形式マクロ(object-like macro)
要するに「定数に名前を付けて、変更を1か所に集約する」仕組みです。

オブジェクト形式マクロってなに?

オブジェクト形式マクロは、プリプロセッサの指令で定義します。
ここで使うのが #define 指令 です。

#define の基本形(書式)

#define a b
部分呼び名意味
aマクロ名この単語を
b置換後の内容これに置き換える(翻訳時)

ポイントは「実行時に計算する」のではなく、翻訳時(コンパイル前)に文字として置換することです。

なんで必要?(マジックナンバー問題)

プログラム中に直接書いた 5 や 8 など、意味が分かりにくい数字は マジックナンバー と呼ばれます。

書き方読む人の気持ち
for (i = 0; i < 5; i++)5って何?人数?要素数?
sum / 55って何の5?
int a[5];5固定でいいの?

マクロで名前を付けるとこうなります。

書き方伝わる意味
for (i = 0; i < COUNT; i++)COUNT 回まわす
sum / COUNTCOUNT 件の平均
int a[COUNT];COUNT 個の配列

数字の意味が読めるようになります。これが大きい!

サンプルプログラム

ここでは「1週間の支出(円)」を入力して合計と平均を出す例にします。

プロジェクト名:chap5-5-1 ソースファイル名:chap5-5-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// 1週間の支出を読み込んで合計と平均を表示(件数をマクロで定義)
#include <stdio.h>

#define DAYS 7   // 入力する日数

int main(void)
{
    int cost[DAYS];
    int sum = 0;

    printf("%d日分の支出(円)を入力してください。\n", DAYS);

    for (int i = 0; i < DAYS; i++) {
        printf("%d日目:", i + 1);
        scanf("%d", &cost[i]);
        sum += cost[i];
    }

    printf("合計支出:%d円\n", sum);
    printf("平均支出:%.1f円\n", (double)sum / DAYS);

    return 0;
}

この例の「直す場所」が1か所になる

日数を 10 日にしたいなら、ここだけ変えればOKです。

#define DAYS 10

配列サイズ、ループ回数、平均の割り算が全部まとめて追従します。

翻訳時にこう置き換わる

マクロは「プログラムを実行する前に、文章として置換される」イメージです。

置換前(あなたが書くコード)

#define DAYS 7
int cost[DAYS];
for (i = 0; i < DAYS; i++) ...
avg = sum / DAYS;

置換後(翻訳時のイメージ)

int cost[7];
for (i = 0; i < 7; i++) ...
avg = sum / 7;

この置換をしているのが プリプロセッサ です。

マクロ名は大文字にする慣習

マクロ名は変数と区別しやすくするため、大文字にする慣習があります。

種類備考
変数sum, i, cost小文字が多い
マクロDAYS, NUMBER, MAX_SIZE大文字が多い

混ざると読みやすさが落ちるので、慣習に乗るのが吉です。

置換されないケース(地味に重要)

マクロは「単語として一致したもの」が置換対象です。
だから、次のようなものは置換されません。

置換されない例なぜ?
"DAYS = "(文字列の中)文字列リテラルは対象外
DAYS1(識別子の一部)単語として一致していない。
int DAYS;(変数名として使う)これは事故の元なのでそもそも避ける。

ここで登場する命令の役割(まとめ)

この節の主役は「Cの文」ではなく、プリプロセッサ指令です。

名前書式何をする?
#define 指令#define 名前 置換文字列翻訳時に名前を置換する(オブジェクト形式マクロ)

あわせて、入力と表示でおなじみの関数も使います。

関数書式何をする?
printfprintf(書式, ...);画面に表示する
scanfscanf(書式, 格納先アドレス);キーボード入力を変数へ格納
forfor (初期化; 条件; 更新) { }繰り返し処理(配列走査)

オブジェクト形式マクロを使うメリット(実務目線)

メリットうれしい理由
値の管理を1か所に集約変更漏れが起きにくい。
定数に名前を付けられる読みやすい、意図が伝わる。
マジックナンバー削減保守性が上がる、レビューが通りやすい。

「動けばOK」だけじゃなく、育つコードにするための基本テクです。