ヘッダファイルとモジュールの作成 |
さて,ヘッダファイルには何を書けばいいのでしょう。それを知るために,実験をしてみましょう。
次のソースコードを見て下さい。
これは2次元ベクトルを表す構造体と,2次元ベクトルの加算を行う vAdd( )関数を定義しています。
そして,それらを利用するmain( )関数が定義されています。
main.c
#include <stdio.h> typedef struct vector2d { double x, y; } vector2d; vector2d vAdd( vector2d a, vector2d b ) { vector2d v; v.x = a.x + b.x; v.y = a.y + b.y; return v; } int main() { vector2d v1 = { 2.0, 3.0 }; vector2d v2 = { 4.0, 1.0 }; vector2d vResult = vAdd(v1, v2); printf("(%g, %g)\n", vResult.x, vResult.y); getchar(); return 0; }
これを,次の様に2つのソースファイルに分けてみましょう。
vector2d.c
#include <stdio.h> typedef struct vector2d { double x, y; } vector2d; vector2d vAdd(vector2d a, vector2d b) { vector2d v; v.x = a.x + b.x; v.y = a.y + b.y; return v; }main.c
int main() { vector2d v1 = { 2.0, 3.0 }; vector2d v2 = { 4.0, 1.0 }; vector2d vResult = vAdd(v1, v2); printf("(%g, %g)\n", vResult.x, vResult.y); getchar(); return 0; }
すると,vector2d.c では特に問題は出ません(vector2d.cをプリプロセッサで処理した翻訳単位はコンパイルできる,ということ。)。
しかし,main.c では,多くのエラーが発生します。
原因は,
・main.c ではprintf( )関数とgectchar( )関数を使うのに,必要な stdio.h のインクルード命令がvector2d.cに移動してしまっている。
・vector2d型を使っているのに,コンパイラにはvector2d型がどのような型が分からずコンパイルできない(vector2d型の定義がvector2d.cに移動してしまっている)。
・
関数vAdd()を使っているが,『vAdd()関数がどのような仮引数構成かわからないし,どのような値を返すか』もわからないので,
コンパイラはvAdd()関数を呼び出している部分をコンパイルできない(vAdd()関数の定義が,vector2d.cに移動してしまっているため)。
といったところです。
では,main.cに不足しているものを補ってみましょう。上掲のvector2d.cと下のmain.cの組み合わせでコンパイルしてみて下さい。
vector2d.c
#include <stdio.h> typedef struct vector2d { double x, y; } vector2d; vector2d vAdd(vector2d a, vector2d b) { vector2d v; v.x = a.x + b.x; v.y = a.y + b.y; return v; }main.c
#include "stdio.h> typedef struct vector2d { double x, y; } vector2d; vector2d vAdd(vector2d a, vector2d b) { vector2d v; v.x = a.x + b.x; v.y = a.y + b.y; return v; } int main() { vector2d v1 = { 2.0, 3.0 }; vector2d v2 = { 4.0, 1.0 }; vector2d vResult = vAdd(v1, v2); printf("(%g, %g)\n", vResult.x, vResult.y); getchar(); return 0; }
#include <stdio.h> typedef struct vector2d { double x, y; } vector2d; vector2d vAdd(vector2d a, vector2d b); /* vAdd()関数の関数プロトタイプ宣言 */ vector2d vAdd(vector2d a, vector2d b) { vector2d v; v.x = a.x + b.x; v.y = a.y + b.y; return v; }main.c
#include <stdio.h> typedef struct vector2d { double x, y; } vector2d; vector2d vAdd(vector2d a, vector2d b); /* vAdd()関数の関数プロトタイプ宣言 (ここではvAdd()関数の外部参照宣言の役割を果たしている) */ int main() { vector2d v1 = { 2.0, 3.0 }; vector2d v2 = { 4.0, 1.0 }; vector2d vResult = vAdd(v1, v2); printf("(%g, %g)\n", vResult.x, vResult.y); getchar(); return 0; }
さて,これでエラーはなくなりましたが,両者の3〜7行目はまったく同じですので,vector2d.hというファイルに集約して,
vector2d.cとmain.cの両方からインクルードしてみましょう。
vector2d.h
typedef struct vector2d { double x, y; } vector2d; vector2d vAdd(vector2d a, vector2d b); /* vAdd()関数の関数プロトタイプ宣言 */
#include <stdio.h> #include "vector2d.h" vector2d vAdd(vector2d a, vector2d b) { vector2d v; v.x = a.x + b.x; v.y = a.y + b.y; return v; }main.c
#include <stdio.h> #include "vector2d.h" int main() { vector2d v1 = { 2.0, 3.0 }; vector2d v2 = { 4.0, 1.0 }; vector2d vResult = vAdd(v1, v2); printf("(%g, %g)\n", vResult.x, vResult.y); getchar(); return 0; }
以上の基本を踏まえて,ヘッダファイルとソースファイルに何を書く必要があるのかを整理します。
■モジュールヘッダファイルに書く内容
ヘッダファイルから見ていきましょう。
![]() |
まず,モジュールで定義されている型・列挙定数・マクロ定義は,モジュールを利用する側でも必要になるでしょうから,それらをヘッダファイルに入れる必要があります。 さて,これらのヘッダファイルの要素をよくみると,(1)と(2)はモジュールのソースファイルでも必要なものです。しかし,いちいちヘッダファイルとソースファイルで同じ内容を書くというのもめんどうですし,それらを矛盾の無いように常に一致するようにするのは困難です。 |
![]() |
一方,モジュールのソースファイル(.c)は, |
/*** *** vector2d.h *** - 2次元ベクトル演算モジュールヘッダファイル - ***/ #if !defined( _VECTOR2D_H_ ) #define _VECTOR2D_H_ /* 2次元ベクトルの型定義 */ typedef struct vector2d { double x, y; } vector2d; /* 外部参照宣言 */ extern const vector2d gXUnitVector; /* x方向単位ベクトル */ extern const vector2d gYUnitVector; /* y方向単位ベクトル */ /* 関数プロトタイプ */ vector2d VAdd( vector2d a, vector2d b ); /* 加算 */ vector2d VSub( vector2d a, vector2d b ); /* 減算 */ double InnerProduct( vector2d a, vector2d b );/* 内積 */ double Norm( vector2d a ); /* ベクトルの大きさ */ #endif /* _VECTOR2D_H_ */
/*** *** vector2.c *** - 2次元ベクトル演算モジュールソースファイル - ***/ #include <math.h> #include "vector2d.h" /* 単位ベクトル (大域のconst定数)の定義 */ const vector2d gXUnitVector = { 1, 0 }; const vector2d gYUnitVector = { 0, 1 }; /* 公開する関数のstatic無し定義 */ vector2d VAdd( vector2d a, vector2d b ) { /* 加算 */ vector2d v; v.x = a.x + b.x; v.y = a.y + b.y; return v; } vector2d VSub( vector2d a, vector2d b ) { /* 減算 */ vector2d v; v.x = a.x - b.x; v.y = a.y - b.y; return v; } double InnerProduct( vector2d a, vector2d b ) { /* 内積 */ return (a.x * b.x) + (a.y * b.y); } double Norm( vector2d a ) { /* ベクトルの大きさ */ return sqrt( InnerProduct( a, a ) ); }
■モジュール化のまとめ
下図に,マニュアルを兼ねたモジュール化のまとめを示す。
■C言語モジュール化課題
次のソースファイルは,2次元図形を扱う型・定数・関数を定義して利用している例である。
ソースファイル内の指示に従って,モジュール化せよ。
提出は
・2次元図形モジュールを構成するヘッダファイル fig2d.h と ソースファイル fig2d.cpp)
・上記のモジュールを利用している main関数を含む main.cpp
の3ファイルを zip ファイルに圧縮し,WebClassのこちらに提出すること。
※ちゃんとプログラムとして動作する様にすること。
◆正解例はこちら。
#include <stdio.h> // 2次元の点を表す構造体 typedef struct Point { double x; // x座標 double y; // y座標 } Point; // 2次元の円を表す構造体 typedef struct Circle { Point center; double r; } Circle; // 2次元の矩形を表す構造体 typedef struct Rectangle { Point leftTop; // 左上の頂点 Point rightBottom; // 右下の頂点 } Rectangle; // 原点中心の単位円(半径1.0)を表す大域定数 const Circle unitCircle = { {0.0, 0.0}, 1.0 }; // 円周率を表す定数 #define PI (3.14159265359) // 円の面積を計算して返す関数 double calcCircleArea( const Circle c ) { return PI * c.r * c.r; } // 矩形の面積を計算して返す関数 double calcRectangleArea( const Rectangle r ) { return (r.rightBottom.x - r.leftTop.x) * (r.rightBottom.y - r.leftTop.y); } /* ここから上を ・モジュールヘッダファイル fig2d.h (インクルードガードを施すこと) ・モジュールソースファイル fig2d.cpp として別のファイルにまとめ直し、ここで stdio.h と fig2d.h をインクルードせよ。 */ int main() { // 底面が半径3.0の円で高さ10.0の円柱がある。この円柱の体積を求めよ。 Circle c = { {0.0, 0.0}, 3.0 }; printf("円柱の面積は%g\n", calcCircleArea( c ) * 10.0 ); // 高さ6.0の直方体がある。底面の矩形は左上頂点が(0.0, 2.3), 右下頂点が(5.0, 4.0) // で2次元平面上にある。この直方体の体積を求めよ。 Rectangle r = { { 0.0, 2.3 }, { 5.0, 4.0 } }; printf("直方体の面積は%g\n", calcRectangleArea(r) * 6.0); // 単位円の面積を求めよ。 printf("単位円の面積は%g\n", calcCircleArea(unitCircle)); getchar(); return 0; }