ゼロから始める「クラス」の理解
こんにちは。夕凪ぺぽです。お久しぶりです。
先日、投稿数のバッジを貰いました(5個)。
自分はnoteを使って近況報告とか決意表明のようなことしかしてきませんでしたが、いろんな方に見ていただけて嬉しく思います。
ありがとうございます。
最近はUnityの勉強を主にやっています。プログラミング自体はC言語やprocessingを一通りやった経験や、UE4のノードベースプログラミングをやっていたことがあるので、忌避感というものは特にないのですが、いかんせんコーディングが久しぶりすぎて色々思い出しながらやっている状態です。
やっていくうちに慣れていけたらいいなと思ってます。
ただ、勉強している際に特に悩んだのがクラスの概念です。
クラスやインスタンス、コードの中に現れるthisなど、「なにこれ?」と思わされる部分が多かったです。
もちろんかなり基礎な要素なので、ネットで調べれば分かりやすい記事なんてのはごまんと出てきます。
なので、あくまで自分が理解するためにどういうイメージで考えたかを備忘録的な感じで残そうと思います。
アウトプットした方が知識として残りやすいので。
もし間違ってる所があれば優しく教えてください。
プログラミングの先生として色々質問に答えてくれた同期に感謝です。
説明に使っているコードも彼の作ってくれたものを借りています。
(以下、C#の内容であり変数や関数、引数などは既知のものとして扱っています。)
クラスとは?
クラスとは、プログラム上の設計図、元本のようなイメージです。
例えばゲームのキャラクターを作るとしましょう。キャラクターにはHPや攻撃力のステータス(変数)と、ダメージを与えたり受けたりした際のイベント(メソッド)が必要だとします。こうした情報がバラバラだと、変数とメソッドの関連性が分かりにくくなってしまいます。クラスを使えばまとめて扱うことができるので、スクリプトが管理しやすくなります。
そして、作成したクラスはintやstringのように型として扱えます(厳密にはクラスと型は別らしいのですが)。つまり、Playerクラスを作ればPlayer型を使うことができるようになります。
Player myPlayer;
上記のように、Player型のmyPlayer変数を作りました(厳密にはクラスを宣言していないのでこれだけでは作れない)。しかし、この状態では変数の箱に何も入っていません。int型の変数には数値を代入するように、Player型の変数にはプレイヤーの実体を代入します。この実体がインスタンスと呼ばれるものです。
詳しくは後ほど説明します。
実例
自分が攻撃する際に必要な情報は、「攻撃する相手」「自分の攻撃力」です。
逆に攻撃を受けた際に必要なのは「自分のHP」「ダメージ」です。
そして、「自分が攻撃する=相手がダメージを受ける」なので、攻撃するメソッドにダメージを受けるメソッドを入れます。
以下に説明用のコードを載せます(説明用なので分かりやすくするために一旦端折っています)。
public class Player{
int HP;
int power;
public void Attack(Player enemy){ //攻撃する
enemy.TakeDamage(this.power);
}
void TakeDamage(int damage){ //ダメージを受ける
this.HP -= damage;
System.Console.WriteLine(this.HP);
}
}
このとき、HPやpowerといった変数をメンバ変数、AttackやTakeDamageといったメソッド(関数)をメンバメソッドと呼びます。 Attackメソッドには攻撃する相手が必要なので、引数にPlayer型のenemy変数を入れています。
その中で、enemy変数をもとにTakeDamageメソッドを呼び出しています。この説明は次節のインスタンスのところで詳しくやります。
そして、TakeDamageメソッドは攻撃力に応じてダメージを受けるメソッドなので、power変数を引数に取っています。
…this?
インスタンスとnewキーワード
前節でthisとかいう謎の単語が出てきましたが、thisを理解する前にインスタンスを理解する必要があります。
インスタンスはクラスという設計書、元本をもとに作られた写しのイメージです。
インスタンスを作るには
Player player1 = new Player();
という風に、newキーワードをクラス名の前につけます。このとき、Player型変数player1にはPlayerクラスのインスタンスが代入されていることになります。
こうすることで、Player型変数player1はインスタンスが持つメンバ変数やメンバメソッドを呼び出すことができます。
具体例として、先程のコードに少し付け足します。
public class Player{
int HP;
int power;
public void Attack(Player enemy){ //攻撃する
enemy.TakeDamage(this.power);
}
void TakeDamage(int damage){ //ダメージを受ける
this.HP -= damage;
System.Console.WriteLine(this.HP);
}
}
public class Game{
public void GameStart(){
Player player1 = new Player();
Player player2 = new Player();
player1.Attack(player2);
player2.Attack(player1);
}
}
新しくクラスGameを作成しました。GameStartメソッドが呼ばれるとインスタンスをplayer1とplayer2に代入し、player1とplayer2がお互いを攻撃するという少し変なプログラムですが、説明用なのでご容赦を。
インスタンスが持つメソッドを呼び出すには、
player1.Attack(player2);
のように(インスタンスを代入したクラス型の変数名).(メンバメソッド)の形式で記述します。
ひとまずこれで、インスタンスの意味と使い方が理解できたかと思います。
では、thisキーワードの話に移りましょう。
thisキーワード
先程のコードの中でthisが出ている部分は、
public class Player{
int HP;
int power;
public void Attack(Player enemy){ //攻撃する
enemy.TakeDamage(this.power);
}
void TakeDamage(int damage){ //ダメージを受ける
this.HP -= damage;
System.Console.WriteLine(this.HP);
}
}
このコードの6行目と9行目ですね。
察しのいい方は気づいたかもしれませんが、thisキーワードはメンバメソッドの中だけに登場し、thisの位置はメンバ変数を呼び出すためのクラス型変数の位置にあります。
クラスの説明の時に、クラスは設計図のようなものと言ったことを覚えているでしょうか?
つまりインスタンスとして実際に使う時に、設計図に何をするかを全て書いておかなくてはいけません。
でも、メソッドでクラス型変数を使ってメンバ変数を呼び出したいのに、クラスを書く段階ではクラス型変数の名前は分かりません。
そこで、インスタンスとして使う時にクラス型変数の名前をthisキーワードの部分に代入することで、その問題を解決するわけです。
ちょっと難しいと思うので実際の例を見てみましょう。
public class Game{
public void GameStart(){
Player player1 = new Player();
Player player2 = new Player();
player1.Attack(player2);
player2.Attack(player1);
}
}
このGameクラスでPlayerクラスのインスタンスを作ってAttackメソッドを実行していますね。
つまり
player1.Attack(player2);
の部分では
public void Attack(Player enemy){ //攻撃する
enemy.TakeDamage(this.power);
}
を参照して
player2.TakeDamage(player1.power);
という風にクラス型変数が代入されているわけです。
しかし、これだけだと複数のインスタンスを作っても、ステータスであるメンバ変数が変わらないのでプレイヤーごとに使い分けが出来ませんね(しかも変数に数値すら代入されていない)。
それを解決するのがコンストラクタです!
コンストラクタ
コンストラクタは通常メンバ変数の初期化を行うときに使われるメソッドです。インスタンスを生成するときに呼び出されます。
public class Player{
int HP;
int power;
public void Attack(Player enemy){
enemy.TakeDamage(this.power);
}
void TakeDamage(int damage){
this.HP -= damage;
System.Console.WriteLine(this.HP);
}
public Player(int HP,int power){ //コンストラクタ
this.HP = HP;
this.power = power;
}
}
特徴はクラスの名前とコンストラクタの名前が同じであるということです。
クラスの名前と同じメンバメソッドを作ると、それがコンストラクタになります。
ここでは引数のHPとpowerの変数を、メンバ変数のHPとpowerに代入しています。
ややこしいですね。
別に引数の変数の名前はHPとpowerでなくても良いのですが、同じ意味を表す変数なのに違う名前にするのってなんか嫌ですよね。
そこでメンバ変数の前にthisをつけることで、「あ、これは引数をメンバ変数に代入しているんだな」とコンピュータ(と人間)が理解できるわけです。
では、コンストラクタの実例を見ていきましょう。
public class Game{
Player player1, player2;
public void GameStart(){
player1 = new Player(100,25);
player2 = new Player(50,3);
player1.Attack(player2);
player2.Attack(player1);
}
}
Playerクラスのインスタンスを代入しているときに何やら引数に値を入れてますね。
これは
public Player(int HP,int power){ //コンストラクタ
this.HP = HP;
this.power = power;
}
コンストラクタで引数を使ってメンバ変数を初期化(初期値の代入を)しているので、引数に値が必要になるわけです。
こうすることでインスタンスごとに値を変えられるようになったので、使い分けができるようになりましたね。
まとめ
public class Player{
int HP;
int power;
public void Attack(Player enemy){ //攻撃する
enemy.TakeDamage(this.power);
}
void TakeDamage(int damage){ //ダメージを食らう
this.HP -= damage;
System.Console.WriteLine(this.HP);
}
public Player(int HP,int power){ //コンストラクタ
this.HP = HP;
this.power = power;
}
}
public class Game{
Player player1, player2;
public void GameStart(){
player1 = new Player(100,25);
player2 = new Player(50,3);
player1.Attack(player2);
player2.Attack(player1);
}
}
いかがでしたでしょうか。
かなり初心者向けの内容ではありましたが、いざ自分で説明するとなるとちゃんとした理解が求められるので、記事の作成に時間がかかってしまいました。
元々は自分の理解のために始めた内容ですが、このあたりの概念すら知らない人に向けて出来るだけ分かりやすく書くように心がけたつもりです。
初心者が書いたものなのでこの分野の情報をすべて網羅したわけではなく、厳密性はかなり薄いと思います。ご容赦ください。
ただ明らかに間違えていること等あればご指摘いただけると幸いです。
最後まで読んでいただきありがとうございました。
それではまた。
この記事が気に入ったらサポートをしてみませんか?