関数を覚えると、プログラムの規模が一気に大きくなっていきます。関数の数が増えていくためです。すると、いろいろな問題が出てきますので、それを解決する手法がプロトタイプ宣言です。この指定を使ってのファイル分割まで説明します。
※ USB経由でHDD等のストレージを接続するのなら、こういうのを使用したい。じゃないと折角の高速性能が全く出なくなっちゃいます。ACアダプタ付きなのでHDDやSSD接続も安心です。

【楽天】
Type c ハブ USBハブ 4ポート USB3.1 USB3.0 USB2.0 USB1.1 USB PD 充電対応 バスパワー セルフパワー ACアダプタ付き USB C ハブ USB3.0ハブ Type C Hub Type-c MacBook MacBook Pro ドッキングステーション typec USB-C おしゃれ
・main.cpp
--
いきなりここに飛んで来ちゃった人は、よろしければ下記からご覧ください。
- 関数の呼び出し順
突然ですが、前回の解説で用いたプログラムですが、記述の順番を変更して main を始めに記述するとどうなるでしょうか。具体的には以下のプログラムコードです。
int main(void) { char msgs[][4] = { "No", "Yes", }; printf(LOCATE, 1, 1); printf("カラーに設定しますか?(y/n) "); int ans = GetAnswer(); if (ans < 0) return EXIT_FAILURE; printf(LOCATE, 1, 29); if (ans > 0) printf(FYELLOW); printf(msgs[ans]); return EXIT_SUCCESS; } int GetAnswer() { while (true) { rewind(stdin); char key = getchar(); if (key == 'Y' || key == 'y') return 1; if (key == 'N' || key == 'n') return 0; if (key == '\n') break; Sleep(1); } return -1; }
これだけでコンパイルエラーとなります。今時のコンパイラからすると信じられないかもしれませんが、上から順番に走査してコンパイルしているので、main 関数の解釈時にはまだ GetAnswer が見つかっていないので、コンパイルが出来なくなります。記述の順番に影響を受けるとか、めちゃくちゃ不便ですよね…。
これを防ぐ方法は、最初にこんな名前の関数があるよと定義しておく事です。その指定に使用するのが extern 修飾子です。これをプログラムコードの先頭に記述しておく事で、コンパイルが正しく通るようになります。これをプロトタイプ宣言と言います。
これで問題なくコンパイルが通るようになります。ただ、main 関数は常にファイルの一番下に置いておくのが良いとは思います。
// プロトタイプ宣言 extern int GetAnswer(); // メイン int main(void) {
// 省略 } // Yes/No の入力 int GetAnswer() {
// 省略 }
- ファイル分割
関数がここにあるという事が分かるプロトタイプ宣言があれば、機能別にファイルを分ける事で、よりプログラム全体が把握しやすくなります。例えば…
・main.cpp
・main.cpp
void DrawMonster(EType mons, int x, int y); void DrawPlayer(int x, int y); void IsHit(int x, int y); void IsDamage(int x, int y); void DrawShot(int x, int y); void DrawItem(EItem item, int x, int y; void DrawStar(void); void MovePlayer(void); void MoveMonster(void); void MoveShot(void); void AddScore(int scr); void DrawInformation(void); void StartBGM(int bgm); void StartEffect(int effect); void Pause(bool enable); int main(void)
こんな感じで関数がたくさんあったとします。この状態だと、チェックする関数を見つけるのがとても煩わしくなります。プログラムが大規模になってくると、このような関数は数百数千場合によっては数万個にも及びますので、より一層目的の場所にたどり着くのは困難となります。検索すれば良い?毎回検索するのは面倒じゃないです?
そこで機能別にファイルを分けてしまいます。例えばこんな感じ。
・main.cpp
・sound.cpp
int main(void)
・charactor.cpp
void StartBGM(int bgm); void StartEffect(int effect); void Pause(bool enable);
void MovePlayer(void); void MoveMonster(void); void MoveShot(void);
・graphics.cpp
・system.cpp
void DrawMonster(EType mons, int x, int y); void DrawPlayer(int x, int y); void DrawShot(int x, int y); void DrawItem(EItem item, int x, int y; void DrawStar(void); void DrawInformation(void);
void AddScore(int scr); void IsHit(int x, int y); void IsDamage(int x, int y);
これでかなり探しやすくなりました。ただ、このままだと各ファイルに分散配置された関数などの呼び出しが出来ないため、ヘッダーファイル .h を作成して、必要に応じて #include することで、ファイルを跨いで関数を相互に呼び出せるようになります。
サンワダイレクト
【楽天】
Type c ハブ USBハブ 4ポート USB3.1 USB3.0 USB2.0 USB1.1 USB PD 充電対応 バスパワー セルフパワー ACアダプタ付き USB C ハブ USB3.0ハブ Type C Hub Type-c MacBook MacBook Pro ドッキングステーション typec USB-C おしゃれ
ヘッダーファイルは以下のような書き方になります。
・sound.h
・charactor.h
#pragma once extern void StartBGM(int bgm); extern void StartEffect(int effect); extern void Pause(bool enable);
・graphics.h
extern void MovePlayer(void); extern void MoveMonster(void); extern void MoveShot(void);
・system.h
#pragma once extern void DrawMonster(EType mons, int x, int y); extern void DrawPlayer(int x, int y); extern void DrawShot(int x, int y); extern void DrawItem(EItem item, int x, int y; extern void DrawStar(void); extern void DrawInformation(void);
これでヘッダーファイルの準備が出来ました。main.h を作成していないのは、main は他から呼び出させる事を想定していないためです。逆に main からは様々な機能を呼び出しますので、#include をかなり記述する事になります。main.cpp は例えばこんな記述となります。
#pragma once extern void AddScore(int scr); extern void IsHit(int x, int y); extern void IsDamage(int x, int y);
・main.cpp
自分で作成したヘッダファイルは "" でファイル名を括って指定します。C 言語が標準で用意しているヘッダファイルを指定する時は <> を使います。
#include <stdio.h> #include <stdlib.h> #include "sound.h" #include "charactor.h" #include "graphics.h" #include "system.h" int main(void) { // 処理 }
- #pragma once
自分で作ったヘッダファイルの先頭に #pragma once と記述しましたが、これはヘッダファイルの多重読み込みを防止する指定となります。例えばですが、もし、charactor.cpp からも sound.h を #include していたら、main.cpp からも sound.h を #include していますから、extren の多重定義となってエラーが発生してしまいます。そのため、一度読み込んだヘッダファイルは二度目からは読み込まない仕組みが #pragma once なのです。
なお、古いコンパイラでは #pragma once が使えない事があります。その場合は、#pragma once の指定の代わりにこんな書き方をしてください。sound.h を例にサンプルを提示してみます。
#ifndef は「このラベルがなかったら」というマクロです。この条件に合致した場合は、#endif までの記述内容を有効とします。そして「ラベルがなかったら」に合致した直後に「ラベルを定義」しますので、二度目の読み込みではラベルがあるため、extren はもう定義されないという仕組みとなります。
#ifndef _SOUND_H #define _SOUND_H extern void StartBGM(int bgm); extern void StartEffect(int effect); extern void Pause(bool enable); #endif
- ファイルの分け方
さて、上記の分け方は表示や音響などの機能別にファイル分解しました。このように機能別にファイルを分けて管理する方法は、構造化プログラミングとなります。実はこれは現在はあまり推奨されていません。では、どういう分け方が良いかというと、対象物毎にまとめて分解するのです。上記のファイル分解を、対象物別に分解してみます。
・player.cpp
・monster.cpp
void MovePlayer(void); void DrawPlayer(int x, int y); void IsDamage(int x, int y);
・shot.cpp
void MoveMonster(void); void DrawMonster(EType mons, int x, int y); void DrawItem(EItem item, int x, int y;
・common.cpp
void MoveShot(void); void DrawShot(int x, int y); void IsHit(int x, int y);
・main.cpp
void AddScore(int scr); void DrawStar(void); void DrawInformation(void); void StartBGM(int bgm); void StartEffect(int effect); void Pause(bool enable);
int main(void)
どうでしょうか。プレイヤー、怪物、プレイヤーの攻撃、共有処理、メインという分け方です。このように対象物別に処理をとりまとめる考え方をオブジェクト指向プログラミングと呼びます。こちらの考え方は現在(2022年)主流となっています。オブジェクト指向という考え方がなかった時代に、C言語は生まれていますから言語にそれをサポートする仕様は入っていませんが、プログラマ側がそれを意識する事で、それに近い作り方をする事は出来ます。この考え方に慣れておく事を推奨します。
オブジェクト指向に関しては、オブジェクト指向の思考方法で詳しく解説してありますので、興味があれば是非一度ご参照ください。…但し、内容は C言語ではありませんが😓
※ ACアダプタなしの高速ハブ。USBメモリとかならこちらで良いと思います。持ち歩きにも便利ですしね。
※ ACアダプタなしの高速ハブ。USBメモリとかならこちらで良いと思います。持ち歩きにも便利ですしね。
![サンワダイレクト USB-Cハブ USB3.1 Gen2 [USB-C×2ポート/USB-A×2ポート] 【PD対応】 バスパワー セルフパワー 両対応 ACアダプタ付き ブラック 400-HUB075BK](https://m.media-amazon.com/images/I/41iJeEPw8xL._SL160_.jpg)

コメント