ほぷしぃ

納得C言語!

[第17回]プリプロセッサ

プリプロセッサ


1.プリプロセッサとは

コンパイルを行う前にソースファイルに対して行う前処理のことをプリプロセッサといいます。
今まで、おまじないと思ってもらっていた「#include」もプリプロセッサの1つなのです。
ここでは主なプリプロセッサである「#define」と「#include」について説明します。


2.#define

#defineにはマクロ置換マクロ関数と呼ばれているものがあります。

(1) マクロ置換

マクロ置換は以下のように使います。

マクロ置換の使い方

マクロ置換は指定した置換文字列を数値または文字列に置き換えます。
何度も出てくる数値や文字列に対してマクロ置換を使うことで数値や文字列を変更したい時に#defineの数値または文字列を変更するだけで置換されている全ての値が変わります。
こうすることで、一部分だけ数値を変え忘れるというミスをなくすことができます。
置換文字列は一般的に数値または文字列と区別するために、大文字のアルファベットを用います。
さらに、#defineはプリプロセッサなので変数のスコープとは違い、どんな場所でも1回定義すればそれ以降はどの関数でも使えます。

さて、実際に#defineを使ってみましょう。
ここでは、演習問題Vの(4)のプログラムを書き換えてみたいと思います。

例題1 演習問題Vの(4)を#defineを使って書き換えます

#include <stdio.h>

// IN を10 と定義する
#define IN 10

void avg(int a[IN][2]);

int main()
{
    int a[IN][2];
    int b = 0 ,c = 0, d = 0 ;

    printf("点数を入力してください\n");

    for(b=0;b<IN;b++){
        printf("%d人目:",b+1);
        scanf("%d",&a[b][0]);

        if(a[b][0]>100){
            printf("入力エラー\nもう一度入力してください\n");
            b--;
            continue;
        }
    }

    for(b=0;b<IN;b++){
        a[b][1]=1;
    }

    for(b=0;b<IN;b++){
        for(d=0;d<IN;d++){
            if(a[b][0]>a[d][0]){
                a[d][1]++;
            }
        }
    }

    for(b=0;b<IN;b++){
        printf("%2d人目->点数:%3d,順位:%3d\n",b+1,a[b][0],a[b][1]);
    }

    avg(a);
    return 0;

}

void avg(int a[IN][2])
{
    int sum = 0,avg = 0, i;

    for(i=0;i<IN;i++){
        sum = sum + a[i][0];
    }

    avg = sum / IN;
    printf("平均点は%d\n",avg);

    for(i=0;i<IN;i++){
        if(a[i][0]<avg){
            printf("平均点以下は%d番で\t%d点です\n",i+1,a[i][0]);
        }
    }

}

結果

10人の試験の点数と順位

結果はまったく同じになりましたね。

#defineの利点

A「#defineってどんな利点があるの?」
B「例題1では、プログラムの中に"10"がいっぱい出てくるよね?」
A「うん、これは人数だよね?」
B「そう、このプログラムで人数を変更したい時、演習問題Vの(4)のプログラムだと何回も書き直さないといけなくなるよね」
A「1, 2, 3, 4, ... んー何回だろう?」
B「直す回数が多いと、それだけミスが出る確率が高くなるよね」
A「あ、例題1のプログラムだと1回書き換えれば終わりなんだね!! すごい!!」


(2) マクロ関数

マクロ関数は以下のように使います。

マクロ関数の使い方

マクロ関数は指定した置換文字列を処理内容に置き換えます。
呼び出し先から渡され、引数で指定された変数は処理内容でも同じように使うことができます。
早速、例題2を見てみましょう。

例題2 2つの引数を足すマクロ関数

#include <stdio.h>

//SUM(A, B)をa+bに置き換える
#define SUM(I,J) I+J

int main()
{
    int a=0,b=0,sum=0;

    for(a=0;a<10;a++){
        for(b=0;b<10;b++){
            sum+=SUM(a,b);
            //sum += a + b;
            //という風になります
        }
    }
    printf("%d\n",sum);
    return 0;
}

結果)

マクロ関数の例

#define SUM(I, J) I + Jとマクロ関数で宣言されています。
この場合は、SUM(a, b)が出てくるたびにaとbがIとJに置き換わってI + Jをします。

マクロ関数の利点

A「関数までマクロにする利点って何なの?」
B「何度も呼ばれる小さな関数でも処理が遅くなるんだ。でもマクロならコンパイル前に置き換えられるだけだからね」
A「結局はこれも置換なんだね」

マクロ関数を使う時は小さな関数のときや決まりきった数式のときに使うのが良いでしょう。
複雑なマクロや巨大なマクロを作ると無駄な処理や、ソースコードの容量が大きくなるかもしれません。
また、演算を行う優先順位が予想と違った動きをしてしまうかもしれません。


3.#include

#includeで指定したファイルを読み込み、読み出した#includeがある行にファイルを挿入します。
今まで書いていた「#include <stdio.h>」というのは「stdio.h」というファイルを読み込み、挿入するという意味だったのです。
この「〜.h」をヘッダファイルと呼びます
#includeの使い方は以下のとおりになっております。

includeの使い方

(1)の場合はコンパイラが標準で用意しているヘッダファイルを読み込む時に表記します。
(2)の場合は自作ヘッダファイルを読み込む時に表記します。
自作ヘッダファイルとは、文字通り自分で作ったヘッダファイルのことを指し、#defineや関数のプロトタイプ宣言、構造体の型宣言、グローバル変数などを自作ヘッダファイルにまとめることができます。
宣言部分が多くなってプログラムが見にくくなった時に、その部分をヘッダファイルにして分割すればメインプログラムのソースコードはすっきりします。

ヘッダファイルの読み出し方の違い

A「(1)と(2)って同じじゃないの?」
B「どこから読み出すかが違うんだ」
A「どう違うの?」
B「(1)は標準のヘッダファイルが置いてあるフォルダから探して読み出し、(2)はプログラムが置いてあるフォルダを最初に探して、そこになかったら(1)のフォルダを探すんだ」
A「じゃあ、、、(2)だけにすればいいんじゃね?」
B「それでも間違いではないけど、(2)だと2ヵ所探すからコンパイルするに少しだけ時間がかかるよ」
A「へぇー」

基本的に標準のヘッダファイルはコンパイラが置いてあるフォルダ(VC++2005の場合は" C:\Program Files\Microsoft Visual Studio 8\VC\include"にありますがインストール場所によって" C:\Program Files"の部分は変わります)にありますので、もし時間があるときに見てみるのもいいでしょう。

例題3 #defineを自作ヘッダファイルに書き込んで読み出す

01.cpp

#include <stdio.h>

//自作ヘッダファイル「my.h」読込
#include "my.h"

int main(){
    printf("%d\t%d\n",A,B);
    return 0;
}

my.h

#define A 100
#define B 200

結果

自作インクルードファイルの読込

my.hから読み込まれていますね。


4.練習問題

(1)第13回の演習問題(2)で作ったプログラムのプロトタイプ宣言を自作ヘッダファイルに書き出し、読み出すプログラムを書いてみよう。
(2)第8回の演習問題(2)で作ったプログラムの計算部分を関数を使わないで、マクロ関数にしてみよう。


[第16回]ファイル入出力 ページのトップ 解答