14.クラス |
|
クラスを実際に作りながら、クラスの作り方を理解します。
関数の参照などより、カプセル化が楽にできます。すごく便利で、その上簡単です。
クラスは早く覚えたほうがいいかもしれません。
理由は二つあります。
関数でスマートなプログラムを書くことに慣れすぎると、使いにくさを感じるかもしれない。
クラスを使い出すと、フローチャート的なプログラミングの考え方が邪魔になる。
部品になるのですから、関連図や回路設計図的な考え方に切り替えないといけない。
よくプログラムの本の最初に、フローチャートの書き方などが載っていますが、
覚えるだけ邪魔になる可能性があります。
|
クラスとは? |
クラスとは、オブジェクト指向プログラミングを実現するための道具です。
オブジェクトと言う言葉はよく出てきます。意味だけ覚えておきましょう。
意味は、実体と訳すのがあっているように思います。
オブジェクト指向は、部品中心(本位)と訳していいかな?。
しかし、オブジェクト指向の話はしません。 ややこしくなるだけです。
大きいプログラムを組んでいけば、自然とオブジェクトプログラムになっていきます。
ですから、オブジェクト指向プログラミングのことは考えないでいいでしょう。
クラスをそれなりに使えるようになってから勉強しても遅くないと思います。
|
クラスの文法 |
class PlanePosition
{
private:
//変数
public:
PlanePosition(){ 初期値 }
//関数
}; |
[class] [区切り] [クラス名]
[ { ]
[private:]
//変数
[public:]
[クラス名]() { 初期値 }
//関数
[ } ] [ ; ]
|
構造体のように、型の宣言のようなものと理解しておいてください。
構造体とキーワード(class)が違うだけで、書式は一緒です。
変数と関数の入れる場所をわける。
これだけ覚えているだけで、十分クラスは使えます。
クラスはもっと複雑ですが、これだけで十分使えるのです。
後でもう少しだけ解説します。
|
初期値は、クラス名と同じ名前の関数で宣言できます。
オブジェクトを作ったときに、この関数内を自動的に処理します。
|
クラスに入れるデータを集める |
まず、player_position_xについて考えてみましょう。
この変数に関係があるデータを集めます。
まず、斜め上に移動するので、player_position_y を仲間に入れます。
下記に、集めたものを書き出します。
|
player_position_x //
player_position_y //一緒に処理する可能性がある
PLAYER_SIZE //描画時に player_position_xといっしょに使う
PLAYER_MOVE_SPEED // player_position_xの変化の度合いを決める
MINI_MOVE_PLAYER_RANGE_X // player_position_xの範囲補正に使う
MINI_MOVE_PLAYER_RANGE_Y // ・
MAX_MOVE_PLAYER_RANGE_X // ・
MAX_MOVE_PLAYER_RANGE_Y // ・
player_gun_graph //描画時にplayer_position_xを使う
下は関係ありますが、今回は無視
player_gun_position_x //発射位置の初期値としてplayer_position_xを使う
player_gun_position_y //
・
|
上の変数(定数)に関係のある関数を集める |
UpdatePlayerPosition(); //ジョイスティックの入力で移動
PlayerRange(); //移動範囲の補正
DrawGraph(); //描画
|
クラスに格納する |
class PlanePosition
{
private:
//変数
int player_position_x; //
int player_position_y; //一緒に処理する可能性がある
//定数も変数に変えます。
// 名前も変えていいですが、このクラス内では定数のようなものです。
int PLAYER_SIZE; //描画時に player_position_xといっしょに使う
int PLAYER_MOVE_SPEED; // player_position_xの変化の度合いを決める
int MINI_MOVE_PLAYER_RANGE_X; // player_position_xの範囲補正に使う
int MINI_MOVE_PLAYER_RANGE_Y; // ・
int MAX_MOVE_PLAYER_RANGE_X; // ・
int MAX_MOVE_PLAYER_RANGE_Y ; // ・
int player_gun_graph; //描画時にplayer_position_xを使う
public:
//関数
PlanePosition()
{
player_position_x=320; //
player_position_y=450; //一緒に処理する可能性がある
PLAYER_SIZE=50; //描画時に player_position_xといっしょに使う
PLAYER_MOVE_SPEED=5; // player_position_xの変化の度合いを決める
MINI_MOVE_PLAYER_RANGE_X=160; // player_position_xの範囲補正に使う
MINI_MOVE_PLAYER_RANGE_Y=0; // ・
MAX_MOVE_PLAYER_RANGE_X=480; // ・
MAX_MOVE_PLAYER_RANGE_Y=480; // ・
player_graph=LoadGraph("Player.png");//描画時にplayer_position_xを使う
}
//player_positionの出力
getposition_x() { return player_position_x; }
getposition_y() { return player_position_y; }
//ジョイスティック入力処理
int updatePosition();
//移動範囲補正処理(範囲外にX座標Y座標を更新しない)
int PlayerRange();
//機体表示
int DrawPosition(); //描画
};
|
player_gun_position_x のように、player_position_x変数を必要とする変数や関数があります。
なので、getposition_x() { return player_position_x; }を書いています。
getposition_x() は戻り値が、player_position_xになります。
|
実際にクラスを使ったC++プログラムソースを実行してみましょう。 |
//シューティングゲーム サンプルプログラム 14 クラス
//ライブラリ宣言
#include "DxLib.h"
//定数の宣言---------------------------------------------------------
#define MINI_RANGE_X 160
#define MINI_RANGE_Y 0
#define MAX_RANGE_X 480
#define MAX_RANGE_Y 480
#define GUN_MOVE_SPEED 20
#define GUN_SIZE 12
#define ENEMY_SIZE 50
//-------------------------------------------------------------------
//**クラス**********************************************************
class PlanePosition
{
private:
//変数
int player_position_x; //ここにこの変数との関係を書いている
int player_position_y; //一緒に処理する可能性が高い
int PLAYER_SIZE; //描画時にいっしょに使う
int PLAYER_MOVE_SPEED; //変化の度合いを決める
int MINI_MOVE_PLAYER_RANGE_X; //範囲補正に使う
int MINI_MOVE_PLAYER_RANGE_Y; // ・
int MAX_MOVE_PLAYER_RANGE_X; // ・
int MAX_MOVE_PLAYER_RANGE_Y; // ・
int player_graph; //描画時にplayer_position_xを使う
public:
PlanePosition()
{
player_position_x=320; //
player_position_y=450; //一緒に処理する可能性がある
PLAYER_SIZE=50; //描画時に player_position_xといっしょに使う
PLAYER_MOVE_SPEED=5; // player_position_xの変化の度合いを決める
MINI_MOVE_PLAYER_RANGE_X=160; // player_position_xの範囲補正に使う
MINI_MOVE_PLAYER_RANGE_Y=0; // ・
MAX_MOVE_PLAYER_RANGE_X=480; // ・
MAX_MOVE_PLAYER_RANGE_Y=480; // ・
player_graph=LoadGraph("Player.png");//描画時にplayer_position_xを使う
}
//変数の出力
getposition_x() { return player_position_x; }
getposition_y() { return player_position_y; }
//関数
//ジョイスティック&キー 移動入力
//[解説] ジョイスティック入力によってX座標Y座標を更新する
int updatePosition();
//]解説] 移動範囲補正処理(範囲外にX座標Y座標を更新しない)
int playerRange();
//[ライブラリ] #include "DxLib.h"
//自機の表示
int DrawPosition(); //描画
};
//**クラス**********************************************************
//構造体-----------------------------------------------------------
//敵用 構造体 型宣言
struct enemy_set
{
int graph; //画像ハンドル(車のハンドルと同じ意味。操作する部分)
int position_x; //座標X
int position_y; //座標Y
};
//-----------------------------------------------------------------
//自作関数 プロトタイプ宣言--------------------------------------------
int UpdatePlayerGunPosition( int* p_player_gun_position_x, int* p_player_gun_position_y,
int player_position_x,int player_position_y,int GUN_SPEED,int f_player_gun_flag);
//-----------------------------------------------------------------
//WinMain関数
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
// DXライブラリの設定
// SetOutApplicationLogValidFlag(TRUE);
//SetGraphMode(640,480,16);
ChangeWindowMode( TRUE ) ;
if( DxLib_Init()==-1) return-1;
// SetMouseDispFlag( TRUE ) ;
SetDrawScreen(DX_SCREEN_BACK);
//クラスのオブジェクト(実体)を作る
PlanePosition Plane;
//変数の宣言と初期化----------------------------------------------
int f_player_gun_flag=FALSE;//弾が発射されているか フラグ
int player_gun_position_x=Plane.getposition_x();// 弾の位置 X座標
int player_gun_position_y=Plane.getposition_y();// 弾の位置 Y座標
int background_graph=LoadGraph("BackGroundGraph.png"); //背景
int player_gun_graph=LoadGraph("Gun.png"); // 弾画像
enemy_set m_enemy[2]= //敵 構造体 2体
{
LoadGraph("Enemy.png") , 240 , 100,
LoadGraph("Enemy.png") , 400 , 100
} ;
//-------------------------------------------------------------------
while(ProcessMessage()==0)
{
//**メインループ ********************************************************
//ジョイスティック&キー入力 キャラクターの移動
Plane.updatePosition();
//プレイヤー移動範囲補正
Plane.playerRange();
//弾の発射 移動
f_player_gun_flag=UpdatePlayerGunPosition (&player_gun_position_x,
&player_gun_position_y,Plane.getposition_x(),Plane.getposition_y(),
GUN_MOVE_SPEED,f_player_gun_flag);
//画像描画
ClsDrawScreen() ;
DrawGraph(MINI_RANGE_X,MINI_RANGE_Y, background_graph,TRUE);
if(f_player_gun_flag==TRUE)DrawGraph(player_gun_position_x-GUN_SIZE/2,
player_gun_position_y-GUN_SIZE/2,player_gun_graph,TRUE);
Plane.DrawPosition();
// DrawGraph(player_position_x-PLAYER_SIZE_HALF,
// player_position_y-PLAYER_SIZE_HALF, player_graph,TRUE);
DrawGraph( m_enemy[0].position_x-ENEMY_SIZE/2,
m_enemy[0].position_y-ENEMY_SIZE/2, m_enemy[0].graph,TRUE);
DrawGraph( m_enemy[1].position_x-ENEMY_SIZE/2,
m_enemy[1].position_y-ENEMY_SIZE/2, m_enemy[1].graph,TRUE);
ScreenFlip();
//**メインループ **************************************************
// ESCキーが押されたらループから抜ける
if(CheckHitKey(KEY_INPUT_ESCAPE)==TRUE)break;
}
//終了処理
DxLib_End();
return 0;
}
//自作関数---------------------------------------------------------
//----------------------------------------------------------------
//ジョイスティック&キー 移動入力
//[解説] ジョイスティック入力によってX座標Y座標を更新する
int PlanePosition::updatePosition()
{
int joypad_state=GetJoypadInputState(DX_INPUT_KEY_PAD1);
if((joypad_state&PAD_INPUT_UP)!=0) player_position_y-=PLAYER_MOVE_SPEED;
if((joypad_state&PAD_INPUT_DOWN)!=0) player_position_y+=PLAYER_MOVE_SPEED;
if((joypad_state&PAD_INPUT_LEFT)!=0) player_position_x-=PLAYER_MOVE_SPEED;
if((joypad_state&PAD_INPUT_RIGHT)!=0) player_position_x+=PLAYER_MOVE_SPEED;
return 0;
}
//-----------------------------------------------------------------
//[ライブラリ] #include "DxLib.h"
//[引数] X座標(アドレス),Y座標(アドレス),発射場所X,発射場所Y,動くドット数,発射フラグ
//[戻り値] 発射フラグ
//]解説] ジョイスティック入力によって発射。弾の移動処理(X座標Y座標を更新する)
int UpdatePlayerGunPosition( int* p_player_gun_position_x, int* p_player_gun_position_y,
int player_position_x,int player_position_y,int GUN_SPEED,int f_player_gun_flag)
{
int joypad_state=GetJoypadInputState(DX_INPUT_KEY_PAD1);
if(((joypad_state& PAD_INPUT_A) !=0)&&(f_player_gun_flag==FALSE))
{
f_player_gun_flag=TRUE;
*p_player_gun_position_x= player_position_x ;
*p_player_gun_position_y= player_position_y ;
}
if(f_player_gun_flag==TRUE) *p_player_gun_position_y-=GUN_SPEED;
if(*p_player_gun_position_y<1) f_player_gun_flag=FALSE;
return f_player_gun_flag;
}
//-----------------------------------------------------------------
//]解説] 移動範囲補正処理(範囲外にX座標Y座標を更新しない)
int PlanePosition::playerRange()
{
if(player_position_x>MAX_MOVE_PLAYER_RANGE_X-PLAYER_SIZE/2)
player_position_x=MAX_MOVE_PLAYER_RANGE_X-PLAYER_SIZE/2;
if(player_position_x<MINI_MOVE_PLAYER_RANGE_X+PLAYER_SIZE/2)
player_position_x=MINI_MOVE_PLAYER_RANGE_X+PLAYER_SIZE/2;
if(player_position_y>MAX_MOVE_PLAYER_RANGE_Y-PLAYER_SIZE/2)
player_position_y=MAX_MOVE_PLAYER_RANGE_Y-PLAYER_SIZE/2;
if(player_position_y<MINI_MOVE_PLAYER_RANGE_Y+PLAYER_SIZE/2)
player_position_y=MINI_MOVE_PLAYER_RANGE_Y+PLAYER_SIZE/2;
return 0;
}
//自機を表示する
int PlanePosition::DrawPosition()
{
DrawGraph(player_position_x-PLAYER_SIZE/2,
player_position_y-PLAYER_SIZE/2, player_graph,TRUE);
}
|
クラスの宣言 |
PlanePosition Plane;
型宣言のように宣言します。クラス名を書いて、オブジェクト名を書きます。
これでオブジェクトが使えるようになります。 |
Plane.updatePosition();
メンバ関数と呼ばれます。
メンバ関数名は、最初小文字で始まる「_」を使わない書き方が多いようです。
オブジェクト名を「.(ドット)」で区切って関数を書きます。
()の中がなくなっています。
クラス内に、使う変数がすべてそろっています。
わざわざ受け渡しをしなくてもカプセル化できるのです。
参照渡しの関数を3個プログラムするより、クラスを作るほうが楽です。
これが6個も7個もになると、クラスしか使う気がなくなります。 |
|
int PlanePosition::updatePosition()
クラス内の関数もプロトタイプ宣言が出来ます。
それがクラス内にあるupdatePosition();です。
クラスの外で本体も書かなければなりません。
それが、この文章です。
クラス名と関数名の間に「::」を入れます。
|
クラスの解説 |
ちょっと詳しく解説します。
private:
この後に書いた変数はクラス以外からアクセスできません。
それだけの意味です。
だからこそカプセル化できるのです。
public:
この後は、クラス外からアクセスできます。
ここに関数を書くのは、当然ですね。 |
オブジェクトを作ったときに、自動で実行される関数をコンストラクタと言います。
初期関数とでも書けばいいのに、なぜかどこの本にもコンストラクタと書いてあります。
言葉の意味だけ理解しておきましょう。
初期値の設定に、コンストラクタを使うのもいいですし、初期化関数を作るのもいいでしょう。
コンストラクタも、知らなくてもいい非常に便利な機能です。
|
変数をカプセル化する。流し読みゾーン |
クラスを使う前と、クラスを使った後では大きな違いがあります。
player_position_xという変数がメインプログラムからなくなっています。
クラス内に吸収されているのです。
その上、クラス以外のプログラムからplayer_position_xを変更できなくなってることもわかると思います。
こうやって、少しずつバグが出来なくなっていくのです。
クラスをずっと使わないでいけば、プログラムを書く時間より、
バグ取りの時間のほうが長くなるかもしれません。 |
他に、ポインタも使えますし、デストラクタなど色々使えますが、
後々説明していく機会があればお話します。 |
クラスと関数の違いは「処理を中心に構成する」か、
「データを中心に構成するか」の違いと言われます。
私流に理由を考えます。
たとえば「プレイヤーの処理」を中心に考えると、攻撃力や、防御力、
発射する弾、弾のスピード・・・とたくさんの要素があります。
すべてをひとつのクラスに収めるのは本末転倒です。
プログラムを見やすく、作りやすくするためにあるはずが、クラス自体が大きくなりすぎては、見にくく作りにくいものになります。(クラス内にバグが出来やすくなる)
プレイヤーのX座標と攻撃力には処理上の関連性がないのです。
だからこそ、切り離せるものは切り離しましょうって感じです。 |
昔、Cからプログラムを組んでいた人にとって、オブジェクト指向は難しいのかもしれません。
何も知識がなければ、楽にクラスを使えそうな気がします。
道具(関数)をカプセル化することに慣れた人が、データをカプセル化することへの変換が難しいため、オブジェクト指向プログラミングが難しいということになっているのではないかと思っています。
大きなプログラムを作る場合、独立した部分(ブラックボックス)を作っていかないと、無理がでてきます。
|
あとがき(関数の名前の変更) |
関数からクラスに変えたことで、ネーミングのニュアンスが少しへんてこになっています。
クラスの中は、クラスの説明書を書く気分で書きましょう。
次のページでは、ネーミングやコメントを変えることにします。
|