前回、基本的なファイル分割について説明しましたが、あれだけでは不十分です。
何が不十分なのか、プログラムを作りながら説明します。
まず、プロジェクトを作成し次の4つのファイルを追加してください。
main.h main.cpp sub.h sub.cpp
今回のプログラムでは、main.cpp と sub.cpp で共通の構造体を使います。
構造体の宣言は次の通りです。
typedef struct { int hp; int mp; } Player; |
何度も使った構造体ですね。
main.cppで実体を作り、sub.cppでステータス表示用の関数を作ることにしましょう。
その際に、この宣言をどこに書くのか?が重要になります。
前回説明した通り、他のファイルでも使用する関数のプロトタイプ宣言や構造体の宣言はヘッダファイルに記述します。
Player構造体は、main.cpp と sub.cpp で使う構造体ですから、main.hで宣言します。
<sample program 160-01>
/* main.h */
#pragma once typedef struct { int hp; int mp; } Player; |
※今回は「#pragma once」が必要です。
※Visual Studio Community 2015以外の方で「#pragma once」が使えない方はこちらを見て記入してください。。
次にmain.cppを作成します。
<sample program 160-02>
/* main.cpp */
#include "main.h" int main(void) { Player player; player.hp = 100; player.mp = 50; return 0; } |
この時点でmain.cppをコンパイルしてもエラーにはなりません。
では、このプレイヤー情報を表示するStatus関数をsub.cppに書きましょう。
<sample program 160-03>
/* sub.cpp */
#include <stdio.h> void Status(const Player player) { printf("Status\n"); printf("HP = %d\n", player.hp); printf("MP = %d\n", player.mp); } |
コンパイルすると、引数であるPlayer構造体の個所でエラーが発生しています。
当然、Player構造体はmain.hで宣言されていますから、sub.cppには関係がありません。
main.hをインクルードすることで、Player構造体が使えるようになりますので、次のようにsub.cppを変えます。
<sample program 160-04>
/* sub.cpp */
#include "sub.h"
#include <stdio.h>
void Status(const Player player)
{
printf("Status\n");
printf("HP = %d\n", player.hp);
printf("MP = %d\n", player.mp);
}
|
これで、sub.cppとsub.hがつながりました。
他のヘッダファイルをインクルードするのは、ヘッダファイルの役割の1つです。
そこで、sub.hでmain.hをインクルードします。
<sample program 160-05>
/* sub.h */
#pragma once
#include "main.h"
|
これで、sub.cppをコンパイルしてもエラーは出なくなりました。
では、main.cppでStatus関数を呼び出しましょう。
<sample program 160-06>
/* main.cpp */
#include "main.h"
int main(void)
{
Player player;
player.hp = 100;
player.mp = 50;
Status(player);
return 0;
}
|
Status関数のところでエラーが出ます。
当然、Status関数はsub.cppで作られていますので、main.cppには関係がありません。
関係を持たせるためには、次のようにプログラムを変更します。
<sample program 160-07>
/* sub.h */
#pragma once
#include "main.h"
void Status(const Player player);
|
これで、他のファイルでStatus関数が使えるようになりました。
次にmain.hからsub.hをインクルードします。
<sample program 160-08>
/* main.h */
#pragma once
#include "sub.h"
typedef struct {
int hp;
int mp;
} Player;
|
これで、main.cppをコンパイルしてみましょう。
error C4430: 型指定子がありません - int と仮定しました。メモ: C++ は int を既定値としてサポートしていません error C2146: 構文エラー: ')' が、識別子 'player' の前に必要です。 error C3646: 'player': 不明なオーバーライド指定子です error C2059: 構文エラー: ')' error C2664: 'void Status(const int)': 引数 1 を 'Player' から 'const int' へ変換できません。 note: この変換を実行可能なユーザー定義変換演算子がないか、または演算子を呼び出せません。
エラー出まくりですね・・・
「#」で始まる命令は「プリプロセッサ」と言います。
「プリ」は「前」、「プロセス」は「処理」という意味です。
「前処理」という意味ですね。
何の「前に処理」をするかというと、コンパイルの「前に処理」するのです。
「include」は「含む」という意味でしたね。
コンパイルの前にヘッダファイルを含む処理をしているのです。
ということは、コンパイル前に main.cpp は main.h を含んで、以下のようになっているはずです。
※説明がややこしくなるので、 #pragma once は書きません。
<sample program 160-09>
/* main.cpp */
#include "sub.h"
typedef struct {
int hp;
int mp;
} Player;
int main(void)
{
Player player;
player.hp = 100;
player.mp = 50;
Status(player);
return 0;
}
|
※青い部分が、元々 main.h が include されていた場所です。
main.h の内容が main.cpp に展開されています。
↑のプログラムには sub.h が含まれていますので、これも展開します。
<sample program 160-10>
/* main.cpp */
#include "main.h"
void Status(const Player player);
typedef struct {
int hp;
int mp;
} Player;
int main(void)
{
Player player;
player.hp = 100;
player.mp = 50;
Status(player);
return 0;
}
|
※青い部分が sub.h を展開したコードです。
またmain.hがインクルードされていますが、それについては次回説明しますので、ここでは無視します。
問題なのは、
void Status(const Player player); typedef struct { int hp; int mp; } Player; |
この箇所です。
構造体宣言の前に関数のプロトタイプ宣言が含まれています。
構造体の宣言が無ければ、その構造体は使えませんのでエラーが発生しているのです。
つまり、構造体宣言の後でプロトタイプ宣言が来なければエラーになるのです。
ということは、このエラーを解消するには構造体宣言の後でsub.hをインクルードする必要があるということです。
そこで、main.hを次のように変更します。
<sample program 160-11>
/* main.h */
#pragma once typedef struct { int hp; int mp; } Player; |
変更した後でmain.cppをコンパイルしてみてください。
エラーが無くなっているはずです。
エラーが消えたら実行してみましょう。
・ ・ ・
<実行結果>
Status HP = 100 MP = 50 続行するには何かキーを押してください・・・
このように、ヘッダファイルをインクルードする順番を間違えると上手く動かなくなります。
インクルードされる順番を考えながら作らなければなりません。
次回は #pragma once について説明します。