宣言と定義とファイル分割(C/C++)
C/C++にて、コンパイルの対象となるのはソースファイルです。例として以下のようなものが挙げられます。
// main.cpp #include <iostream> using namespace std; int mul2(int x) { return x*2; } int main() { cout << mul2(3) << endl; return 0; }
また、次のように書く事もできます。
// main.cpp #include <iostream> using namespace std; int mul2(int x); int main() { cout << mul2(3) << endl; return 0; } int mul2(int x) { return x*2; }
ここで重要になのは、宣言と定義です。C/C++では宣言と定義を切り離すことができます。
int mul2(int x); というのが宣言
int mul2(int x){ return x*2; } というのが定義
宣言は中身を持ちません。XXXというものがどこかにありますよってことを述べる文です。
定義は中身そのものです。XXXとはこういうものだ、と定めることです。
また、上記のint main(){}は定義と宣言を同時に行っています。
では、なぜ宣言と定義を分けるのでしょうか。
主に挙げられる理由としてブラックボックス化というものがあります。
上記のmain関数を見てください、mul2という関数を利用しています。
コンパイラは上から下に順番に読み込んでいきますが、この時mul2について分かっていることは
・mul2は関数である
・引数はintだけである
・返り値はintである
中身であるreturn x*2;は分かっていません。
しかしながら、mainではmul2の中身を知る必要はないのです。
mainでは以下のように記述されています。
cout << mul2(3) << endl;
この段階で必要なものは、mul2が関数で、引数がintだけで、intやchar*を返してくれるという情報です。
「何を引数として与え、何を返し値として返してくれるのか」が重要です。
中身が必要になってくるのはコンパイルの段階ではありません。
ターゲットである全てのソースファイルをコンパイルして、コンパイルされた全てのファイルを結合する(リンク)段階です。
つまり、この段階でmul2の定義が存在すればよいのです。
ですから以下のように分割することが可能です。
// main.cpp #include <iostream> using namespace std; int mul2(int x); int main() { cout << mul2(3) << endl; return 0; }
// mul2.cpp int mul2(int x){ return x*2; }
このように2つのcppファイルに分割することができます。
コンパイラ等の動きを見ると、1main.cppをコンパイル 2mul2.cppをコンパイル 3リンクするです。
さて、今度は4倍する関数mul4を作ってみましょう。
// main.cpp #include <iostream> using namespace std; int mul4(int x); int mul2(int x); int main() { cout << mul2(3) << endl; cout << mul4(3) << endl; return 0; }
// mul2.cpp int mul2(int x){ return x*2; }
// mul4.cpp int mul2(int x); int mul4(int x){ return mul2( mul2(x) ); }
きちんと6と12が出力されたと思います。
ここで分かることですが、int mul2();という宣言がいたるところにあります。
そうです、宣言は何度やってもいいんです。
その利点を生かして、ヘッダファイルというものを作ってみましょう。
// mul.h int mul2(int x); int mul4(int x);
// main.cpp #include <iostream> using namespace std; #include "mul.h" int main() { cout << mul2(3) << endl; cout << mul4(3) << endl; return 0; }
// mul2.cpp int mul2(int x){ return x*2; }
// mul4.cpp #include "mul.h" int mul4(int x){ return mul2( mul2(x) ); }
そうです。
これがヘッダファイルです。
ヘッダファイルはいくつかのソースファイルで共有します。
各種ソースでヘッダを利用したい場合は#includeを利用します。
#include "ヘッダファイル名"
このように記述すればコンパイルの時点で指定されたヘッダファイルがソースファイルに挿入されます。
ここで注意する必要があります。
#includeの数だけヘッダファイルが読み込まれるということです。
思い出してください。
宣言に関しては何度も行ってよいものでした。
しかしながら定義は1度しか行ってはいけないのです。
複数のソースに分割、共通部分をヘッダに置くという発想はよろしいです。
しかしながらヘッダは何度も読み込まれる可能性があるということを覚えて置いてください。
ヒント:インクルードガード