学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

C++ | 18 | 分割コンパイル

↓↓クリックして頂けると励みになります。



17 | switch】 << 【ホーム】 >> 【19 | 配列


モジュールと分割コンパイル
モジュールと分割コンパイル



C++の分割コンパイル(Separate Compilation)は、大規模なプロジェクトや複数のソースファイルを持つプログラムを効率的に管理するための手法です。
この手法では、プログラムを複数の小さな単位(ソースファイル)に分割し、それぞれを個別にコンパイルします。
そして、最終的にそれらのコンパイル済みのオブジェクトファイルをリンクすることで、完全なプログラムを生成します。

分割コンパイルの利点はいくつかあります:

効率的なビルド: 大規模なプロジェクトでは、全てのソースファイルを一度に再コンパイルするのではなく、変更があった部分だけを再コンパイルすることができます。これにより、ビルド時間を短縮することができます。

モジュール化と保守性: ソースコードを小さなモジュールに分割することで、それぞれのモジュールの役割や責任を明確にすることができます。また、特定のモジュールに変更があった場合、そのモジュールのみを再コンパイルすることで、コードの保守性が向上します。

コンパイル時間の削減: 一度コンパイルされたオブジェクトファイルは再利用されるため、同じコードが複数の場所で使用される場合でも、コンパイル時間が削減されます。

C++の分割コンパイルを行うには、以下の手順が一般的です。

ソースファイルを分割する: プログラムを複数のソースファイルに分割します。関連する関数やクラスは同じソースファイルにまとめ、それぞれのファイルの役割や責任を明確にします。

ソースファイルをコンパイルする: 分割された各ソースファイルを個別にコンパイルし、オブジェクトファイルを生成します。この際、コンパイル時に適切なオプションを指定して、オブジェクトファイルが生成されるようにします。

オブジェクトファイルをリンクする: 生成されたオブジェクトファイルをリンクして、最終的な実行可能ファイルを生成します。リンク時には、必要なライブラリや依存関係を解決し、全てのオブジェクトファイルを結合します。

分割コンパイルは、C++において大規模なプロジェクトの開発やメンテナンスを行う際に重要な手法です。




Visual Studio Codeで以下の2つのcppファイル「Bunkatsu1.cpp」「Bunkatsu2.cpp」、1つのヘッダーファイル「head.h」を作成して下さい。


新規作成1 【Bunkatsu1.cpp】

/* file : Bunkatsu1.cpp */

#include <iostream>
#include "head.h"

int main() {

    std::cout << "test1 " << test1(3.0, 4.0) << std::endl;
    std::cout << "test2 " << test2("string") << std::endl;
}



新規作成2 【Bunkatsu2.cpp】

/* file : Bunkatsu2.cpp */

#include<iostream>
#include "head.h"

float test1(float x, float y) {

	return x*x*y*y;
}

const char *test2(const char *s) {
	return s+1;
}



新規作成3 【head.h】

/* header file : head.h */

float test1(float x, float y);
const char *test2(const char *s);



C言語のプログラムは機械語に翻訳された後に必要なライブラリ(#includeなど)とリンクされることによって実行可能な形式となります。
これらの過程は厳密に前者がコンパイルと呼ばれ、後者がリンクと呼ばれています。
リンク自体はライブラリのリンクに限らず、開発者が自由に行えるようになっています。
つまり開発者がプログラムを分割して個別にコンパイルし、できたオブジェクトをリンクすることも可能になっています。
この分割されたプログラムの断片をモジュールといいます。


プログラムソースを2つに分けて開発すると、両方のプログラムソースの先頭に宣言が必要となります。
関数宣言はリンクさせたいモジュールの先頭にコピーすることになります。
たくさんの関数やモジュールがある場合はこの記述が面倒なので、ヘッダーファイルというファイルにまとめて記述して「include」で読み込ませた方が便利です。
ヘッダファイル名は以下のようにします。

ファイル名.h

これをプログラムソースに取り込むにはプログラムソースの先頭に以下のように記述します。

include ”ヘッダファイル名”

このようにincludeでヘッダファイル名をダブルコーテーション(””)で囲みます。
この記述をすべてのモジュールに宣言します。


分割コンパイルの方法について説明します。


順番として、まずヘッダファイルを作成します。
次にmain関数のある関数を作成、コンパイルします。


では2つのプログラムをコンパイルして実行します。


ターミナルで「g++」コマンドを使って実行プログラムを作成します。
「Bunkatsu1.cpp」「Bunkatsu2.cpp」の2つのファイルを指定し、-oオプションで実行プログラム名を指定します。
今回の実行プログラム名は「main」にしました。
g++ Bunkatsu1.cpp Bunkatsu2.cpp -o main
作成された実行プログラムを実行するには、./mainとして実行プログラム名を指定します。

~/Desktop/Programming/CPP $ g++ Bunkatsu1.cpp Bunkatsu2.cpp -o main
~/Desktop/Programming/CPP $ ./main
test1 144
test2 tring




前のセクションで作成したサンプルゲームをファイル分割してみます。
Visual Studio Codeで以下の2つのcppファイル「」「」、1つのヘッダーファイル「Game_head.h」を作成して下さい。


新規作成1 【Game_head.h】

/* header file : Game_head.h */

int yesno();

void current_point();

void enemy_create();

void hero_create();

int escape();

void calc_parameter(int suit, int strong);

void hero_die();

void show_status();

void map_create();

void get_drug(int exist, int strong);

int pos_compare(int a_x, int a_y, int b_x, int b_y);

void walk_around(char move);

void use_drug();

void get_enemyitem();

void fight();

void command();

#ifdef MAIN

/* enemy */

int enemy_x;
int enemy_y;
int enemy_life;
int enemy_offence;
int enemy_defence;
int enemy_item_suit;
int enemy_item_strong;

/* map */

int map_wide_x;
int map_wide_y;
int map_ent_x;
int map_ent_y;
int map_floor = 0;
int map_item_exist;
int map_item_strong;
int map_item_x;
int map_item_y;

/* hero */

int hero_point;
int hero_x;
int hero_y;
int hero_life;
int hero_life_upper;
int hero_offence;
int hero_defence;
int hero_drug;
int hero_sword;
int hero_buckler;
int hero_armer;

#else

/* enemy */

extern int enemy_x;
extern int enemy_y;
extern int enemy_life;
extern int enemy_offence;
extern int enemy_defence;
extern int enemy_item_suit;
extern int enemy_item_strong;

/* map */

extern int map_wide_x;
extern int map_wide_y;
extern int map_ent_x;
extern int map_ent_y;
extern int map_item_exist;
extern int map_item_strong;
extern int map_item_x;
extern int map_item_y;
extern int map_floor;

/* hero */

extern int hero_point;
extern int hero_x;
extern int hero_y;
extern int hero_life;
extern int hero_life_upper;
extern int hero_offence;
extern int hero_defence;
extern int hero_drug;
extern int hero_sword;
extern int hero_buckler;
extern int hero_armer;

#endif



新規作成2 【Game_main.cpp】

#include <iostream>
#include <stdlib.h>

#define MAIN

#include "Game_head.h"

int main() {

	int input;

        hero_create();
        map_create();
        current_point();

        std::cout << "Input senario Number => ";
		std::cin >> input;
		std::cin.clear();
		std::cin.ignore();
		
        srand(input);

        while(true) {

                if(enemy_life <= 0) {

                        enemy_create( );
                }

				std::cout << "現在位置 (" << hero_x << "," << hero_y << "):地下 " << map_floor << "階" << std::endl << std::endl;

                command();

                if( pos_compare(map_item_x,map_item_y,hero_x,hero_y)){

                        get_drug(map_item_exist, map_item_strong);
                        map_item_exist=0;
                }

                if( pos_compare(map_ent_x,map_ent_y, hero_x,hero_y)) {

					std::cout << "階段があった。下に降りますか? ";

                        switch(yesno()) {

                                case 1: map_create();
                                        enemy_create();
                                        current_point();
										std::cout << std::endl << std::endl << "地下 " << map_floor << "階に降りた。" << std::endl << std::endl;
                                        break;

			}

                }

                if (pos_compare(hero_x,hero_y, enemy_x,enemy_y)) {

                        int point=hero_point;
                        fight();

                        if(point < hero_point) {

                                enemy_create();
                        }
                }
        }

}

void show_status( ) {

	std::cout << std::endl << "現在の状況:" << std::endl;
	std::cout << "体力: " << hero_life << "/" << hero_life_upper << "\t攻撃力:" << hero_offence << "\t守備力:" << hero_defence << std::endl;

	std::cout << "\t経験値:" << hero_point << std::endl;
	std::cout << std::endl;
	std::cout << "持ち物のレベル" << std::endl;
	std::cout << "剣:" << hero_sword << "\t盾:" << hero_buckler << "\t鎧:" << hero_armer << std::endl << std::endl;

	std::cout << "薬の回復力:" << hero_drug << std::endl << std::endl << std::endl;
	return;
}

void hero_die( ) {

	std::cout << "Hero is died at floor " << map_floor << "." << std::endl;
	std::cout << std::endl;
	std::cout << "status" << std::endl;
	std::cout << "hero level " << hero_point/10 << std::endl;
	std::cout << std::endl;
	std::cout << "Game Over" << std::endl;
	exit(0);
}

void calc_parameter( int suit, int strong ) {

	switch(suit) {

                case 1: hero_offence = hero_offence - hero_sword + strong;
                        hero_sword = strong; break;

                case 2: hero_defence = hero_defence - hero_buckler + strong;
                        hero_buckler = strong; break;

                case 3: hero_defence = hero_defence - hero_armer + strong;
                        hero_armer = strong; break;
        }

        return;

}

void use_drug( ) {

	if(hero_drug > 0) {

		hero_life += hero_drug;

		if(hero_life_upper < hero_life) {

			hero_life = hero_life_upper;
		}

		hero_drug=0;
		std::cout << std::endl << "\t薬を使った。\b" << std::endl << std::endl;
		show_status();
	}

	else {

		std::cout << std::endl << "\t薬は今持ってない。" << std::endl << std::endl;
	}
}

void get_enemyitem( ) {

	if ( enemy_item_suit!=0 ) {

                show_status();

				std::cout << "\t敵はアイテムを持っていた。" << std::endl;

                switch(enemy_item_suit) {

                        case 1: std::cout << "剣:level " <<enemy_item_strong << std::endl;
                                break;

                        case 2: std::cout << "盾:level " <<enemy_item_strong << std::endl;
                                break;

                        case 3: std::cout << "鎧:level " << enemy_item_strong << std::endl;
                                break;
                }

                std::cout << "装備するか?";

                if(yesno()) {

					std::cout << "\tヒーローの状態が変わった。\b" << std::endl<< std::endl;
                        calc_parameter(enemy_item_suit, enemy_item_strong);
                        show_status();
                }

                else {

					std::cout << "\tアイテムを捨てた!" << std::endl << std::endl;
                }
        }
        else {

			std::cout << "\t敵はアイテムを持っていなかった。" << std::endl << std::endl;
        }
        return;

}

int escape() {

	int x, y;

	x = rand()%10;
	y = rand()%10;

	if( x < y ) {

		return !0;
	}

	else {

		return 0;
	}
}

void fight() {

	int dum;

        while(true) {
                show_status( );
				std::cout <<"敵と戦いますか?";

                if ( yesno()==0 && escape() == 1){
					std::cout << "\t敵から逃げました。" << std::endl << std::endl;
                        break;
                }
                else {
					std::cout << "\t敵と戦います。" << std::endl << std::endl;
                }
				std::cout << "Heroの攻撃:";
                dum = (hero_offence*(rand()%10)) / (enemy_defence+1);
                enemy_life -= dum;
				std::cout << "敵に " << dum << "のダメージ" << std::endl << std::endl;

                if(enemy_life -= dum){
					std::cout << "\t敵を倒した!\b" << std::endl << std::endl;
                        hero_point ++;
                        hero_life_upper+=10;
						std::cout << "\tレベルが上がった。\b" << std::endl << std::endl;
                        get_enemyitem( );
                        break;
                }
				std::cout << "敵の攻撃:";
                dum = (enemy_offence*(rand()%10)) / (hero_defence+1);
                hero_life -= dum;
				std::cout << dum << "のダメージを受けた!" << std::endl << std::endl;

		if (hero_life <=0) {
                        hero_die();
                }
        }
        return;

}

void command() {
	char input;
	std::cout << "Command mode" << std::endl;
	std::cout << "         up:   k" << std::endl;
	std::cout << "left:h  status:s          right:l" << std::endl;
	std::cout << "          down: j" << std::endl << std::endl;
	std::cout << "use drug:d         Quit:q" << std::endl;
	for(;;) {
		std::cout << std::endl << "comand input => ";
		std::cin >> input;

		if(!std::cin) {
			std::cout << "Error:";
            std::cin.clear();
		    std::cin.ignore();
		}
		else {
			if (input=='d') {
				use_drug();
                std::cin.clear();
		        std::cin.ignore();
				break;
			}
			else if (input == 's' ) {
				show_status();
                std::cin.clear();
		        std::cin.ignore();
				break;
			}
			else if (input=='j' || input=='k' || input=='l' || input=='h') {
				walk_around(input);
                std::cin.clear();
		        std::cin.ignore();
				break;
			}
			else if(input=='q') {
                std::cin.clear();
                std::cin.ignore();
				exit(0);
			}
		}
	}
	return;
}

/* move  lower j:  left l:  right h:  upper k */
void walk_around( char move ) {
	if(move=='l') {
		if( hero_x == map_wide_x) {
			std::cout << "その方向には歩けません!" << std::endl;
			return;
		}
		hero_x++;
	}
	else if(move=='k') {
		if(hero_y == map_wide_y) {
			std::cout << "その方向には歩けません!" << std::endl;
			return;
		}
		hero_y++;
	}
	else if (move=='h'){
		if(hero_x == 0) {
			std::cout << "その方向には歩けません!" << std::endl;
			return;
		}
		hero_x--;
	}
	else if (move=='j') {
		if(hero_y == 0) {
			std::cout << "その方向には歩けません!" << std::endl;
			return;
		}
		hero_y--;
	}
	return;
}

int pos_compare(int a_x, int a_y, int b_x, int b_y) {

	if ( a_x == b_x && a_y == b_y ) {

		return !0;
	}
	else {

		return 0;
	}
}

void get_drug(int exist, int strong) {

	if(exist == 1) {

		std::cout << std::endl;
		std::cout << "\t薬(" << strong << ")を拾った!\b" << std::endl << std::endl;
		hero_drug+=strong;
	}
	return;
}

void current_point() {

	hero_x = rand()%map_wide_x;
	hero_y = rand()%map_wide_y;
	return;
}

int yesno() {

	char input;

    while(true){
        std::cout << "yes or no (y/n) => ";
        std::cin >> input;

        if(!std::cin) {
            std::cout << "Error:" << std::endl << std::endl;
        }
        else {
            switch(input) {
                case 'y':
                case 'Y': return !0;
                    std::cin.clear();
                    std::cin.ignore();
                    break;
                case 'n':
                case 'N': return 0;
                    std::cin.clear();
                    std::cin.ignore();
                    break;
            }
        }
    }
}



新規作成3 【Game_create.cpp】

/* file : Game_create.cpp */

#include <iostream>
#include <stdlib.h>

#include "Game_head.h"

void map_create( ) {

    map_floor++;
    map_wide_x = rand()%( 2 + map_floor) + 10;
    map_wide_y = rand()%( 2 + map_floor) + 10;
    map_ent_x = rand()%map_wide_x;
    map_ent_y = rand()%map_wide_y;
    map_item_exist=1;
    map_item_strong= rand()%( 10+10*map_floor) + 30;
    map_item_x=rand()%map_wide_x;
    map_item_y=rand()%map_wide_y;
    return;
}

void hero_create() {

    hero_point = 0;
    hero_life_upper = 100 + rand()%10;
    hero_life = hero_life_upper;
    hero_x = 0;
    hero_y = 0;
    hero_offence = rand()%5 + 5;
    hero_defence = rand()%5 + 5;

    hero_drug = 0;

    hero_sword = rand()%3 +1;
    hero_buckler=rand()%3;
    hero_armer = rand()%2 +1;
    return;
}

void enemy_create() {

    int i;

    enemy_x = rand() % map_wide_x;
    enemy_y = rand() % map_wide_y;
    enemy_life = rand() % 100 + map_floor*50;
    enemy_offence=rand() % 10 + map_floor;
    enemy_defence=rand() % (map_floor+2);

    if ( rand() % 2 == 1 ) {

        i=rand()%3+1;
        enemy_item_suit=i;

        if(i==1) {
            enemy_item_strong = rand()%(10 + map_floor);
        }
        else if ( i==2) {
            enemy_item_strong = rand() % (5 + map_floor);
        }
        else {
            enemy_item_strong = rand() % (7 + map_floor);
        }

    }
    else {
        enemy_item_suit = 0;
        enemy_item_strong = 0;
    }
    return;
}



前に作成したダンジョンゲームをcreate()関数とそのほかの関数、ヘッダファイルに分割しました。


外部変数は関数の外側で宣言した変数です。
外部変数はその性質上プログラムの起動時に生成され、プログラムの終了まで消滅することはありません。
有効範囲はすべてのモジュールにわたり、extern宣言を行うことで他のモジュールから外部変数を参照することが可能になります。
他のモジュールで外部変数として確保された変数は、以下のようにして別のモジュールから参照可能になります。

extern 変数の型 変数名;

extern宣言は「どこか別の場所でその変数が定義されているものを使う」という宣言ですので、別の場所でexternなしの変数を作成する必要があります。


C言語ではプリプロセッサという機能が用意されています。
プリプロセッサとは簡単に言うと前処理のことであり、前に出てきた#include機能もその一つです。

#define 記号定数名 値

このように宣言します。
文の終わりに「;」は必要ありません。
この宣言はコンパイルされる前にプリプロセッサによって処理され、実際にコンパイルされるときにはコンパイラーから見れば存在しない文となります。
この宣言は標準ヘッダーでも使われています。


例えば

#define EOF (-1)

と宣言しておけば、プログラムの中で「EOF」と書くと「-1」となります。


define文にはもう一つ別の機能があります。
それがこのマクロ機能です。


マクロ機能は次のようにして使います。

#define マクロ名(引数) 引数を含む文字列

例えば

#define sq (x) ((x)*(x))

のように宣言すれば関数と同じようにsq(x)を使うことができます。
引数の型の宣言は必要ありません。
プログラム上でsq(3)とすれば、計算値「9」を返してくれます。


プリプロセッサにおける条件文として「#ifdef文」があります。


これは以下のように使います。

#define DEBUG

・・・・・

#ifdef DEBUG

    std::cout << "Debug: hensuu = " << hensuu << std::endl;

#endif

つまりDEBUGというマクロが定義されていたならば、#ifdef#endifの間の行が有効になります。
もし最初の行に「#define DEBUG」がなければこの#ifdef#endifの間の行はないものとしてプリプロセッサに処理されます。


この#ifdef文を使って外部変数をうまく使うことができます。

#ifdef THIS_MODULE

    int common;    /* このモジュールで使います */

 #else

    extern int common;    /* 他のモジュールから見ます */

 #endif

common変数を主に使いたいモジュールの先頭で「#define THIS_MODULE」を定義してこのファイルをインクルードしておけば良いわけです。
もし「#define THIS_MODULE」の定義がなければ、#elseの後の記述が読み込まれます。


ではサンプルプログラムの説明に移ります。


メイン関数のある「Game_main.cpp」の最初には「define MAIN」の記述がありますので、ヘッダファイルの#ifdefから#elseの手前までが処理されます。
create()関数のある「Game_create.cpp」には「define MAIN」の記述がありませんので、#elseから#endifまでの記述が処理されます。
Game_create.cppではexternが用いられますので、Game_main.cppで使われた変数を参照する形になっています。
後の記述は前と同じです。
実行結果も前と同じです。

~/Desktop/Programming/CPP $ g++ Game_main.cpp Game_create.cpp -o game
~/Desktop/Programming/CPP $ ./game

Input senario Number => 1
現在位置 (2,3):地下 1階

Command mode
         up:   k
left:h  status:s          right:l
          down: j

use drug:d         Quit:q

comand input => l
現在位置 (3,3):地下 1階

Command mode
         up:   k
left:h  status:s          right:l
          down: j

use drug:d         Quit:q

comand input => ^C



17 | switch】 << 【ホーム】 >> 【19 | 配列




↓↓クリックして頂けると励みになります。

関連記事(外部サイト)