
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 / 5 | 5って何の5? |
| int a[5]; | 5固定でいいの? |
マクロで名前を付けるとこうなります。
| 書き方 | 伝わる意味 |
|---|---|
| for (i = 0; i < COUNT; i++) | COUNT 回まわす |
| sum / COUNT | COUNT 件の平均 |
| 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 名前 置換文字列 | 翻訳時に名前を置換する(オブジェクト形式マクロ) |
あわせて、入力と表示でおなじみの関数も使います。
| 関数 | 書式 | 何をする? |
|---|---|---|
| printf | printf(書式, ...); | 画面に表示する |
| scanf | scanf(書式, 格納先アドレス); | キーボード入力を変数へ格納 |
| for | for (初期化; 条件; 更新) { } | 繰り返し処理(配列走査) |
オブジェクト形式マクロを使うメリット(実務目線)
| メリット | うれしい理由 |
|---|---|
| 値の管理を1か所に集約 | 変更漏れが起きにくい。 |
| 定数に名前を付けられる | 読みやすい、意図が伝わる。 |
| マジックナンバー削減 | 保守性が上がる、レビューが通りやすい。 |
「動けばOK」だけじゃなく、育つコードにするための基本テクです。
