納得C言語
[第17回]プリプロセッサ
1.プリプロセッサとは
コンパイルを行う前にソースファイルに対して行う前処理のことをプリプロセッサといいます。
今まで、おまじないと思ってもらっていた「#include」もプリプロセッサの1つなのです。
ここでは主なプリプロセッサである「#define」と「#include」について説明します。
2.#define
#defineにはマクロ置換とマクロ関数と呼ばれているものがあります。
(1) マクロ置換
マクロ置換は以下のように使います。
マクロ置換は指定した置換文字列を数値または文字列に置き換えます。
何度も出てくる数値や文字列に対してマクロ置換を使うことで数値や文字列を変更したい時に#defineの数値または文字列を変更するだけで置換されている全ての値が変わります。
こうすることで、一部分だけ数値を変え忘れるというミスをなくすことができます。
置換文字列は一般的に数値または文字列と区別するために、大文字のアルファベットを用います。
さらに、#defineはプリプロセッサなので変数のスコープとは違い、どんな場所でも1回定義すればそれ以降はどの関数でも使えます。
さて、実際に#defineを使ってみましょう。
ここでは、演習問題Ⅲの(4)のプログラムを書き換えてみたいと思います。
例題1 演習問題Ⅲの(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]); } } }
結果
結果はまったく同じになりましたね。
#defineの利点
A「#defineってどんな利点があるの?」
B「例題1では、プログラムの中に"10"がいっぱい出てくるよね?」
A「うん、これは人数だよね?」
B「そう、このプログラムで人数を変更したい時、演習問題Ⅲの(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の使い方は以下のとおりになっております。
(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)で作ったプログラムの計算部分を関数を使わないで、マクロ関数にしてみよう。
(1)解答プログラム
17_1.cpp
#include <stdio.h> //my.hの読込 #include "my.h" int main() { int s = 0,a = 0,b = 0,c = 0; //変数の宣言と初期化 int *p1,*p2; p1=&a; p2=&b; while(1){ printf("\n 簡易計算機\n"); printf("加算=>1, 減算=>2 ,乗算=>3 ,除算=>4 ,乗除=>5 ,終了=>0\n"); printf("\nどの計算をしますか ="); scanf("%d",&s); if(s == 0){ printf("終了します\n"); break; } printf("数値Aを入力してください="); scanf("%d",&a); printf("数値Bを入力してください="); scanf("%d",&b); switch(s){ case 1: c = add(a,b); printf("加算A+Bの答えは%d\n",c); break; case 2: c = sub(a,b); printf("減算A-Bの答えは%d\n",c); break; case 3: c = mul(a,b); printf("乗算A*Bの答えは%d\n",c); break; case 4: c = div(a,b); printf("除算A/Bの答えは%d\n",c); break; case 5: div2(a, b, p1, p2); printf("剰余A/Bは%d余り%d",*p1,*p2); break; default: printf("数値が正しくありません\n"); } } return 0; } int add(int i,int n) //自作関数add { int kai; kai = i + n; //加算 return kai; //kaiを返す } int sub(int i,int n) //自作関数sub { int kai; kai = i - n; //減算 return kai; //kaiを返す } int mul(int i,int n) //自作関数mul { int kai; kai = i * n; //乗算 return kai; //kaiを返す } int div(int i,int n) //自作関数div { int kai; kai = i / n; //除算 return kai; //kaiを返す } void div2(int a, int b, int *i, int *n) { *i = a/b; *n = a%b; }
my.h
//関数のプロトタイプ宣言 int add(int i,int n); int sub(int i,int n); int mul(int i,int n); int div(int i,int n); void div2(int a, int b, int *i, int *n);
解説
結果は第13回の演習問題(2)と同じなので省略します
関数のプロタイプをmy.hに書いて、17_1.cppの先頭で#includeして読み込みます。
(2)解答プログラム
#include <stdio.h> #define KAZAN(i,f) i+f //加算マクロ関数 #define GENZAN(i,f) i-f //減算マクロ関数 #define ZYOUZAN(i,f) i*f //乗算マクロ関数 #define ZYOZAN(i,f) i/f //除算マクロ関数 int main() { int s = 0,a = 0,b = 0,c = 0; while(1){ printf("\n簡易計算機\n"); printf("加算=>1, 減算=>2 ,乗算=>3 ,除算=>4 ,終了=>0\n"); printf("\nどの計算をしますか ="); scanf("%d",&s); if(s == 0){ printf("終了します\n"); break; } printf("数値Aを入力してください="); scanf("%d",&a); printf("数値Bを入力してください="); scanf("%d",&b); switch(s){ case 1: c = KAZAN(a,b); printf("加算A+Bの答えは%d\n",c); break; case 2: c = GENZAN(a,b); printf("減算A-Bの答えは%d\n",c); break; case 3: c = ZYOUZAN(a,b); printf("乗算A*Bの答えは%d\n",c); break; case 4: c = ZYOZAN(a,b); printf("除算A/Bの答えは%d\n",c); break; default: printf("数値が正しくありません\n"); break; } } return 0; }
結果
関数群をマクロにしました。
なんだかすっきりしたプログラムになりましたね。
1行程度の関数ならマクロにしてしまったほうが楽になるかもしれません。
コメントの投稿
トラックバックURL
http://www.isl.ne.jp/cgi-bin/mt/mt-tb.cgi/1105