| ヘッダファイルとモジュールの作成 |
さて,ヘッダファイルには何を書けばいいのでしょう。それを知るために,実験をしてみましょう。
次のソースコードを見て下さい。
これは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.cint 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言語モジュール化課題 (2025/10/31 出題)
次のソースファイルは,2次元図形を扱う型・定数・関数を定義して利用している例である。
ソースファイル内の指示に従って,モジュール化せよ。
提出は 11/7(金) 9:15 まで に,
・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;
}