情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引

第9章 プリプロセッサ

  1. 9.1 #define
    1. (プログラム例 9.1 ) 記号定数とマクロの定義
  2. 9.2 #undef
  3. 9.3 #if
    1. (プログラム例 9.2 ) 記号定数の定義による出力先制御
    2. (プログラム例 9.3 ) 単位変換
  4. 9.4 #include
    1. (プログラム例 9.4 ) ファイルの組み込み
  5. 演習問題9

  プロプロセッサは,C/C++ にない機能を補うためのものであり,他のファイルを挿入したり( #include ),定数や式を定義したり( #define ),また,条件によりコンパイル方法を変更させたり( #if,#else 等)するために使用されます.これらの機能により,1 つのプログラムを異なったコンパイラや OS で処理させたり,また,各プログラム(ファイル)に共通なことがらを一々書く手間を省略することが可能になります.

 プリプロセッサに対する命令(疑似命令,#define 等のこと)は,区切りにセミコロンを使用せず,1 つの命令は 1 行に書く必要があります.

9.1 #define

  #define 疑似命令は,プログラム中の定数または文(文字列)に対し,意味のある名前(識別子)を定義します.定義後,その識別子が見つかるたびに,文字列で置き換えます.その一般形式は以下の通りです.
#define 識別子 文字列
#define 識別子([パラメータリスト]) 文字列		
  文字列は,1 個以上の定数,キーワード,または,文で構成されます.識別子が定数式で置き換えられるとき,この識別子を記号定数と言います.識別子をパラメータを持つ式で置き換えるとき,この識別子をマクロと言います.

  文字列が複数行にわたる場合,各行の終りに円記号(\)を置いて,次の行に続けることができます.文字列をカッコで囲むことにより,文字列が式であるかどうか,また先頭に負符号があるかどうかを正しく評価できます.また,文字列を空の文字列にして,ファイルから識別子を取り去ることもできます.

  次の例では,識別子 PI が現れる度に,定数 3.141592654 と置き換えられます.
#define PI 3.14159265		
  識別子の後にパラメータリストがあれば,識別子(パラメータリスト)は,各パラメータに実引数を代入した文字列で置き換えられます.実引数とパラメータの数は等しくなければなりません.

  パラメータリストは,カンマで区切られ,カッコで囲まれた 1 個以上のパラメータ名から構成されます.識別子と '(' の間に空白を入れないでください.文字列中のパラメータ名は,実際の値と置き換える場所を表します.

  次の例におけるマクロ SQUARE は,その引数の乗算として定義されています
#define SQUARE(arg) ((arg) * (arg))		
  確かに,以上述べた機能によってプログラムの記述が簡単になります.しかし,プログラムを書いた本人以外の人は,「この文字列は何を意味していたのだろうか?」と,定義されている箇所に戻らざるを得ないようなことも起こりがちです.これは,goto 文を避ける理由とほとんど同じです.特別な理由が無い限り,使用すべきではありません.例えば,以下に示す例において,SIKI(v1, v2) や rep(i, k, n) といった定義は避けるべきですし,CON に対してはもっと分かりやすい名前を付けるべきです.

(プログラム例 9.1 ) 記号定数とマクロの定義

  次は,記号定数とマクロの簡単な定義例です

/****************************/
/* 記号定数とマクロの定義   */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

#define CON 10
#define SIKI(v1, v2) (CON * v1 + v2)   // 10 * x + y の定義
#define rep(i, k, n) for (int i = k; i <= n; i++)   // for 文

int main()
{
	int x = CON;
	int y = SIKI(2, 5);
	int s = 0;
	rep(i, 1, 5)
		s += i;

	printf("x %d y %d s %d\n", x, y, s);

	return 0;
}
		

  上のプログラムの出力結果は以下の通りです.
x 10 y 25		

9.2 #undef

  #undef 疑似命令は,形式,
#undef 識別子		
によって,識別子の現在の定義を削除します.識別子は,必ず以前に #define 命令で定義されていなければなりません.

9.3 #if

  #if 疑似命令の一般形式は以下の通りです.
#if 定数式
	[文1]
[#else
	文2]
#endif		
#if 疑似命令は,定数式をチェックし,真( 0 でないとき)になれば文 1 (複数の文でも良い)を処理します.真とならず,かつ,#else 節があれば,#else 節中の文 2 (複数の文でも良い)を処理します.

  定数式は評価すると定数になるものであれば,どんな式でもかまいません.また定数式に論理演算子や defined 演算子を含むことができます.

  定数式に sizeof 演算子, 型キャスト, 及び,float 型や enum 型を含むことはできません.しかし,文 1 や文 2 には C/C++ のコードやプリプロセッサの命令を含むことができます.

(プログラム例 9.2 ) 記号定数の定義による出力先制御

  次のプログラムは,記号定数 OUT が定義されているか否かにより出力先を変更するプログラムを 2 つの方法で書いています.

/************************************/
/* 記号定数の定義による出力先の変更 */
/*      coded by Y.Suganuma         */
/************************************/
#include <stdio.h>

#define OUT "data"

int main()
{
	int x[3] = {1, 2, 3};
	FILE *stream;
/*
	 ケース1
*/
#if defined OUT						 /* OUTが定義,#ifdef OUT でも同じ*/
	stream = fopen(OUT, "w");
	fprintf(stream, "%d %d %d\n", x[0], x[1], x[2]);
	printf("データはファイル「data」に出力されました\n");
#else								   /* OUTが未定義 */
	printf("%d %d %d\n", x[0], x[1], x[2]);
#endif
										/* OUTの定義解除 */
#undef OUT
/*
	 ケース2
*/
#if !defined OUT						/* OUTが未定義,#ifndef OUT でも同じ*/
	printf("%d %d %d\n", x[0], x[1], x[2]);
#else								   /* OUTが定義 */
	stream = fopen(OUT, "w");
	fprintf(stream, "%d %d %d\n", x[0], x[1], x[2]);
	printf("データはファイル「data」に出力されました\n");
#endif

	return 0;
}
		

(プログラム例 9.3 ) 単位変換

  このプログラムでは,データにより,単位変換の係数を変えます

/**************************************************/
/* 記号定数の定義・未定義により単位変換の方向変更 */
/*      coded by Y.Suganuma                       */
/**************************************************/
#include <stdio.h>

#define DEG

#if defined DEG
   #define UC 0.017453292
#else
   #define UC 57.29577951
#endif

int main()
{
	double x = 90.0;
	double y = x * UC;

#if defined DEG								/* 度からラジアン */
	printf("%f 度は %f ラジアンです\n", x, y);
#else										  /* ラジアンから度 */
	printf("%f ラジアンは %f 度です\n", y, x);
#endif

	return 0;
}
		

9.4 #include

  #include 疑似命令は,
#include "ファイル名" 
#include <ファイル名>   // 一般に,システムによって準備されたファイルを使用する場合		
のように記述され,ファイル名で指定されたファイル(ヘッダファイル)の内容を,現在のファイルに読み込みます.複数のプログラムで,共通の前処理等をする場合に便利です.一般に,使用する関数によって,必要とするシステムによって準備されたヘッダファイルは決まってきます.例えば,三角関数,対数等,数学関係の関数を使用するときは <math.h> が必要となります.C++ の場合は,<cmath> を使用しても構いません.ただし,<cmath> をインクルードした時の関数名は std 名前空間内の関数を使用しますが,<math.h>をインクルードした時は C の標準関数(グローバル関数)を使用することになります.一般に,<iostream> や,後に述べる コンテナ 内の vector クラスを使用する際に必要な <vector> など,拡張子が .h でないヘッダファイルの場合は,クラス名,関数名などが std 名前空間内に所属するようになります.

  ファイル名がドライブとすべてのパス指定を含む(フルパス)とき,そのファイルはディレクトリを検索することなく組み込まれます.ファイル名がダブルクォーテーション(")で囲まれていれば,その #include 命令を含むファイルが存在するディレクトリが検索されます.現在のファイルもインクルードファイルであれば,親ファイルのディレクトリが検索されます.この検索は,元のソースファイルのディレクトリが検索されるまで,ネストされたインクルードファイルに対して再帰的に繰り返されます.

  ファイルが発見できないとき,または,ファイル名が山形カッコ(<>)で囲まれているとき,次に検索されるディレクトリは,コンパイラの「-I」コマンドラインオプションで指定されたディレクトリです.これらの検索の後で,コンパイラは INCLUDE 環境変数で設定されているディレクトリを検索します.

(プログラム例 9.4 ) ファイルの組み込み

  このプログラムでは,カレントディレクトリにあり,その内容が
#define PI 3.141592654
#define UC 0.017453292		
である角度に関する定数を定義したファイル ang.h を組み込みます.

/****************************/
/* ファイルの組み込み       */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>
#include "ang.h"

int main()
{
	double x = PI;
	double y = UC;

	printf("x %f y %f\n", x, y);

	return 0;
}
		

演習問題9

[問1] 2 次元平面上の 2 点間の距離を計算するマクロを定義し,そのマクロを利用して,入力された 2 点間の距離を出力するプログラムを書け.

[問2]与えられた平均値と標準偏差をもつ正規乱数を生成する関数を作成せよ.ただし,条件により,一様乱数発生関数として,drand48 または rand を使用するように書け.

情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引