Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

プログラマ勉強会 第5回

Last updated at Posted at 2024-07-18

クラス

以下のコードは、Position構造体のXY座標を0にリセットする関数の例です。

struct Position {
    int32 x;
    int32 y;
};

void resetPosition(Position& pos) {
    pos.x = 0;
    pos.y = 0;
};

void Main() {
    Position pos{ 400, 300 };

    resetPosition(pos);

    while (System::update()) {
    }
}

以下の箇所で、Position構造体のインスタンスであるposを引数に渡して、resetPosition関数内でx, yを0にリセットしています。

resetPosition(pos);

メソッド

ですが、せっかくx, y変数をPosition構造体にまとめたのに、結局関数は別に作らないといけないのは気持ち悪くないですか?
ここで使えるのがクラスです。
クラスは、基本的には構造体と同じですが、変数に加えて関数もまとめて管理できるという利点があります。

さっきのコードをクラスを使って書き換えると、以下のコードになります。

# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
public:
	int32 x;
	int32 y;

	void reset() {
		this->x = 0;
		this->y = 0;
	}
};

void Main()
{
	Position pos{ 400, 300 };
	Print << pos.x; // 400
	Print << pos.y; // 300

	pos.reset();
	Print << pos.x; // 0
	Print << pos.y; // 0

	while (System::Update()) {
		
	}

}

以下の箇所でPositionクラスを宣言しています。
クラスを宣言するときは、class クラス名 {まとめる変数、関数};というように書きます。
構造体にはなかったpublic:という記述はアクセス指定子といって、メンバ変数、関数へのアクセス権を表しています。これについては後で詳しく説明します。

class Position {
public:
	int32 x;
	int32 y;

	void reset() {
		this->x = 0;
		this->y = 0;
	}
};

以下の箇所で、Positionクラスに属する関数を定義しています。
クラスに属した関数では、関数内でthisという特殊な変数を使用することができます。
thisは、自分自身のインスタンスのアドレスが格納されたポインタで、this->メンバ変数名と書くことで自分自身のメンバ変数を使用できます。
また、クラスに属した関数のことをメソッド、もしくはメンバ関数と呼びます。

	void reset() {
		this->x = 0;
		this->y = 0;
	}

以下の箇所でPositionクラスのメソッドを呼び出しています。
ここで、pos変数のアドレスが自動的にメソッドに渡され、メソッド内でthisという変数で使用できるようになります。
thisはポインタなので、インスタンスのデータにアクセスするには(*this).xのように書くか、アロー演算子を使ってthis->xというように書く必要があります。

pos.reset();

アロー演算子
構造体やクラスのポインタからメンバ変数やメンバ関数にアクセスする場合は以下の2通りの書き方があります。
->を使った書き方をアロー演算子といいます。

(*ポインタ型の変数).メンバ変数or関数
ポインタ型の変数->メンバ変数or関数

class Position {
public:
    int32 x;
    int32 y;

    void reset() {
        this->x = 0;
        this->y = 0;
    }
};

void Main() {
    Position pos{ 400, 300 };
    Position* posAddress = &pos;

    // 以下はどちらもresetメンバ関数を呼び出している
    (*posAddress).reset();
    posAddress->reset();

    // 以下はどちらもx, yメンバ変数を参照している。
    Print << (*posAddress).x << U" : " << (*posAddress).y;
    Print << posAddress->x << U" : " << posAddress->y;

    while (System::Update()) {
    }
}

this
メソッドではthisという変数を使用して自分自身のインスタンスにアクセスできますが、実はthisは省略することが可能です。

class Position {
    int32 x;
    int32 y;

    void reset() {
        this->x = 0;
        y = 0; // thisは省略可能
    }
};

コンストラクタ

コンストラクタとは、クラスのインスタンス化時に実行される関数のようなものです。
クラスにコンストラクタを定義することで、クラスのインスタンス化(生成)時に実行する処理を指定することが出来ます。
コンストラクタではメンバ変数の初期化などを行います。

いままで、インスタンス化時にメンバ変数の初期値を指定していたのを、コンストラクタを使った書き方に書き換えると以下のようになります。

class Position {
public:
	int32 x;
	int32 y;

	Position(int32 initX, int32 initY) : x(initX), y(initY) {
        // 初期化処理
    }
};

以下の箇所がコンストラクタです。
コンストラクタはクラスと同じ名前の関数を定義するような書き方で定義することが出来ます。
コンストラクタには戻り値が存在しないので戻り値の型は書きません。
: x(initX), y(initY)の部分でメンバ変数x, yの初期値を指定して初期化しています。
このようにメンバ変数を初期化する書き方をコンストラクタの初期化子リストといいます。

	Position(int32 initX, int32 initY) : x(initX), y(initY) {
        // 初期化処理
    }

初期化子リストで初期化を行わなかったメンバ変数は、初期化がされませんが、以下のようにコンストラクタ内でメンバ変数に代入することが出来ます。

	Position(int32 initX, int32 initY) {
        this->x = initX;
        y = initY;
    }

コンストラクタの引数は、何もメンバ変数と同じ型を受け取る必要があるわけではありません。
以下のコードでは、コンストラクタの引数で文字列を受け取り、その文字列に応じてメンバ変数x, yの初期値を決めています。

# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
public:
	int32 x;
	int32 y;

	Position(String posText) {
		if (posText == U"center") {
			x = 400;
			y = 300;
		}
		else if (posText == U"left") {
			x = 0;
			y = 300;
		}
		else if (posText == U"right") {
			x = 800;
			y = 300;
		}
		else if (posText == U"top") {
			x = 400;
			y = 0;
		}
		else if (posText == U"bottom") {
			x = 400;
			y = 600;
		}
		else {
			x = 0;
			y = 0;
		}
	}
};

void Main()
{
	Position pos1{ U"center" };
	Print << pos1.x << U", " << pos1.y; // 400, 300

	Position pos2{ U"left" };
	Print << pos2.x << U", " << pos2.y; // 0, 300

	Position pos3 = Position{ U"right" };
	Print << pos3.x << U", " << pos3.y; // 800, 300

	while (System::Update()) {

	}

}

コンストラクタの引数は以下のようにクラスのインスタンス化時に、メンバ変数の初期値の代わりに指定できます。
ここで指定した値は、メンバ変数の初期値として使われるのではなく、コンストラクタの引数として渡され、メンバ変数の初期化自体はコンストラクタ内で行われることになります。

	Position pos1{ U"center" };
	Position pos3 = Position{ U"right" };

デストラクタ

コンストラクタとは逆に、以下のようにして、インスタンスが破棄されるタイミングで実行される関数を定義することが出来ます。
この関数をデストラクタといいます。

# include <Siv3D.hpp> // Siv3D v0.6.14

class Object {
public:
	String name;

	Object(String initName) : name(initName) {
		Print << name << U" インスタンスが生成されました";
	}

	~Object() {
		Print << name << U"インスタンスが破棄されました";
	}
};

void Main()
{
	{
		Object obj1{ U"obj1"}; // obj1のインスタンスが生成される
		{
			Object obj2{ U"obj2" }; // obj2のインスタンスが生成される
		} // obj2のスコープを抜けるので、obj2は破棄され、デストラクタが呼ばれる
	}// obj1のスコープを抜けるので、obj1は破棄され、デストラクタが呼ばれる

	while (System::Update()) {

	}

}

アクセス指定子

クラスのメンバ変数、メソッドはデフォルトではインスタンス.メンバ変数orメソッドのようにして外部からアクセスすることは出来ません。

以下のコードでは、pos.x = 400の箇所でアクセス権が無いためエラーになります。

# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
	int32 x;
	int32 y;
};

void Main()
{
	Position pos;
	pos.x = 400; // 外部からはアクセス権が無いためエラーになる

	while (System::Update()) {

	}

}

外部からメンバ変数にアクセスできるようにするにはアクセス指定子を使って、外部からの編集のアクセスを許可する必要があります。
public:の部分がアクセス指定子です。これ以下の行に書かれたメンバ変数はpublicのアクセス権が設定されます。

# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
public:
	int32 x;
	int32 y;
};

void Main()
{
	Position pos;
	pos.x = 400; // アクセス権があるためエラーにならない

	while (System::Update()) {

	}

}

アクセス指定子には以下の種類があります

アクセス指定子 アクセスが許可される範囲
public: 全ての範囲
private: 同じクラスのメソッドからのみ
protected: 同じクラスのメソッドと継承したクラスのメソッドからのみ

クラスを宣言するとき、アクセス指定子を書かなかった場合はデフォルトでprivateになります。
そのため、デフォルトではpos.x = 400のようにしてメンバ変数にはアクセスできないというわけです。

ただし、privateなメンバ変数でも、メソッド内ではアクセスできるので外部からはメソッドの呼び出しを経由してメンバ変数を取得、書き換えをすることが出来ます。
このように、メンバ変数を直接書き換え、参照せずメソッドを通して使用する手法をオブジェクト指向の用語でカプセル化といいます。

# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
	int32 x;
	int32 y;

public:
	void set(int32 x, int32 y) {
		this->x = x;
		this->y = y;
	}

	int32 getX() {
		return x;
	}

	int32 getY() {
		return y;
	}

	// コンストラクタを経由してx, yを初期化
	Position(int32 initX, int32 initY) : x(initX), y(initY) {}
};

void Main()
{
	Position pos{ 400, 300 };

	Print << pos.getX(); // 400
	Print << pos.getY(); // 300

	pos.set(100, 200);
	Print << pos.getX(); // 100
	Print << pos.getY(); // 200

	while (System::Update()) {

	}

}

継承

継承とは、あるクラスを元にして新しいクラスを作ることです。
この元となるクラスを親クラス、親クラスを元にして作られた新しいクラスを子クラスといいます。
親クラスは基底クラススーパークラスと呼ばれることもあります。
子クラスは派生クラスサブクラスと呼ばれることもあります。

以下のコード例では、Positionクラスを継承したMyCircleというクラスを作成しています。
クラスを継承する際はclass 子クラス名 : public 親クラス名 {}というように書きます。

# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
protected: // アクセス指定子をprotectedにした場合は、子クラスからアクセス可能。外部からはアクセス不可。
	int32 x;
	int32 y;
public:
	Position(int32 x, int32 y) : x(x), y(y) {}

	void print() {
		Print << U"x: " << x << U", y: " << y;
	}
};

class MyCircle : public Position {
private:
	int32 r;
public:
	// Position(...)で親クラスのコンストラクタを呼び出し、親クラスを初期化する
	MyCircle(int32 x, int32 y, int32 r) : Position(x, y), r(r) {}

	void printCircle() {
		// アクセス指定子がprotectedのため、親クラスのメンバ変数であるx, yにアクセス可能
		Print << U"x: " << x << U", y: " << y << U", r: " << r;
	}

	void draw() {
		// アクセス指定子がprotectedのため、親クラスのメンバ変数であるx, yにアクセス可能
		Circle{ x, y, r }.draw();
	}
};

void Main()
{
	MyCircle circle{ 400, 300, 50 }; // ここでMyCircleのコンストラクタが呼び出される
	// circle.x = 100; // アクセス指定子がprotectedのため、外部からアクセス不可

	// Positionクラスのprintメソッドのアクセス指定子がpublicのため、親クラスのメソッドであるprint()にアクセス可能
	circle.print(); // x: 400, y: 300
	circle.printCircle(); // x: 400, y: 300, r: 50

	while (System::Update()) {
		circle.draw();
	}

}

オーバーライド

オーバーライドとは、小クラスで親クラスのメソッドを上書きすることです。
オーバーライドしたい場合は、親クラスのメソッドの先頭にvirtualをつけ、小クラスのメソッドにoverrideと書きます。

# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
protected: // アクセス指定子をprotectedにした場合は、子クラスからアクセス可能。外部からはアクセス不可。
	int32 x;
	int32 y;
public:
	Position(int32 x, int32 y) : x(x), y(y) {}

	// virtualを先頭につけることで、子クラスでオーバーライド(上書き)可能になる
	virtual void print() {
		Print << U"x: " << x << U", y: " << y;
	}
};

class MyCircle : private Position {
private:
	int32 r;
public:
	// Position(...)で親クラスのコンストラクタを呼び出し、親クラスを初期化する
	MyCircle(int32 x, int32 y, int32 r) : Position(x, y), r(r) {}

	// 親クラスであるPositionクラスのprintメソッドをオーバーライド(上書き)する
	void print() override {
		// アクセス指定子がprotectedのため、親クラスのメンバ変数であるx, yにアクセス可能
		Print << U"x: " << x << U", y: " << y << U", r: " << r;
	}
};

void Main()
{
	MyCircle circle{ 400, 300, 50 };

	// MyCircleクラスでオーバーライドされたprintメソッドが呼び出される
	circle.print(); // x: 400, y: 300, r: 50

	while (System::Update()) {
	}

}

オーバーロード
似た言葉にオーバーロードというものがありますが、オーバーライドとは全く別物です。
オーバーロードとは、同じ関数名で複数パターンの引数、戻り値の関数を定義できる機能の事をいいます。

# include <Siv3D.hpp> // Siv3D v0.6.14

void print(String name) {
	Print << U"Hello, " << name << U"!";
}

void print(String name, int32 age) {
	Print << U"Hello, " << name << U"!";
	Print << U"You are " << age << U" years old.";
}

void Main()
{
	print(U"PandD"); // void print(String name)が呼ばれる

	Print << U"=====================";

	print(U"ぱんどど", 20); // void print(String name, int32 age)が呼ばれる

	while (System::Update()) {
	}

}

コンポジション(合成)

クラスは、別のクラスのインスタンスをメンバ変数として持つことが出来ます。
以下のように、コンストラクタで別のクラスをインスタンス化し、そのインスタンスを自身のメンバ変数としてもつ手法をオブジェクト指向の用語でコンポジションもしくは合成といいます。

# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
private:
	int32 x;
	int32 y;

public:
	Position(int32 x, int32 y) : x(x), y(y) {}

	int32 getX() {
		return x;
	}

	int32 getY() {
		return y;
	}
};

class MyCircle {
private:
	Position pos; // Positionクラスのインスタンスをメンバ変数として持つ
	int32 r;

public:
	// コンストラクタで、Positionクラスをインスタンス化する
	MyCircle(int32 x, int32 y, int32 r) : pos(x, y), r(r) {}

	void draw() {
		// Positionクラスのメンバ変数x, yはprivateなので、メソッドを介して取得する
		Circle{ pos.getX(), pos.getY(), r }.draw();
	}
};

void Main()
{
	MyCircle circle(400, 300, 50);

	while (System::Update()) {
		circle.draw();
	}
}

集約
コンポジションと似たものに集約というものがあります。

練習問題1

問題1

第3回の練習問題1で作成し、練習問題3でコードを関数にまとめた、プレイヤーを方向キーで操作するコードを、クラスを使った書き方に変えてみよう。

第3回 練習問題3 問題1-3 の解答例である以下のコードを変更します。

# include <Siv3D.hpp> // Siv3D v0.6.14

void playerControl(int32& x, int32& y, int32 speed)
{
	if (KeyUp.pressed()) y -= speed;
	if (KeyDown.pressed()) y += speed;
	if (KeyLeft.pressed()) x -= speed;
	if (KeyRight.pressed()) x += speed;
}

int32 playerSpeed() {
	if (KeyShift.pressed()) return 10;
	return 3;
}

void playerMoveRestriction(int32& x, int32& y)
{
	if (x < 0) x = 0;
	if (x > 800) x = 800;
	if (y < 0) y = 0;
	if (y > 600) y = 600;
}

void Main()
{
	int32 x = 400;
	int32 y = 300;

	while (System::Update()) {
		playerControl(x, y, playerSpeed());
		playerMoveRestriction(x, y);

		Circle{ x, y, 50 }.draw();
	}

}
ヒント(テンプレート)
# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
public:
	// TODO: 以下にメンバ変数x, yを定義する
};

class Player {
private:
	// TODO: 以下にPositionクラスのインスタンスを持つメンバ変数posを定義する

public:
	// TODO: 以下にPlayerクラスのコンストラクタを定義する
	// 
	// プレイヤーの操作
	void control() {
		// TODO: 以下にキーボードの入力によってプレイヤーを移動させる処理を書く
	}

	// シフトキーが押されているかでスピードを変更
	int32 getSpeed() {
		// TODO: 以下にシフトキーが押されているかで返す値を変える処理を書く
	}

	// プレイヤーの移動制限
	void moveRestriction() {
		// TODO: 以下にプレイヤーの移動範囲を制限する処理を書く
	}

	// プレイヤーの描画
	void draw() {
		// TODO: posメンバ変数を使ってプレイヤーを描画する処理を書く
	}
};

void Main()
{
	Player player{ 400, 300 }; // プレイヤーの初期位置を(400, 300)に設定

	while (System::Update()) {
		player.control(); // プレイヤーの操作
		player.moveRestriction(); // プレイヤーの移動制限

		player.draw(); // プレイヤーの描画
	}

}
解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
public:
	int32 x;
	int32 y;

	Position(int32 x, int32 y) : x(x), y(y) {}
};

class Player {
private:
	Position position;
	int32 normalSpeed = 3; // 通常時のスピード
	int32 dashSpeed = 10; // ダッシュ時のスピード

public:
	Player(int32 x, int32 y) : position( x, y ) {}

	// プレイヤーの操作
	void control() {
		int32 speed = getSpeed();
		if (KeyUp.pressed()) position.y -= speed;
		if (KeyDown.pressed()) position.y += speed;
		if (KeyLeft.pressed()) position.x -= speed;
		if (KeyRight.pressed()) position.x += speed;
	}

	// シフトキーが押されているかでスピードを変更
	int32 getSpeed() {
		if (KeyShift.pressed()) return dashSpeed;
		return normalSpeed;
	}

	// プレイヤーの移動制限
	void moveRestriction() {
		if (position.x < 0) position.x = 0;
		if (position.x > 800) position.x = 800;
		if (position.y < 0) position.y = 0;
		if (position.y > 600) position.y = 600;
	}

	// プレイヤーの描画
	void draw() {
		Circle{ position.x, position.y, 50 }.draw();
	}
};

void Main()
{
	Player player{ 400, 300 };

	while (System::Update()) {
		player.control();
		player.moveRestriction();

		player.draw();
	}

}

問題2

第3回の練習問題2で作った、3つのキャラをSpaceキーで切り替えて操作できるようなコードを関数にまとめたものを以下に用意したので、これを参考にクラスを使った書き方に書き換えてみよう。

# include <Siv3D.hpp> // Siv3D v0.6.14

struct Position {
	int32 x;
	int32 y;
};

// プレイヤーの操作
void playerControl(Position& pos, int32 speed)
{
	if (KeyUp.pressed()) pos.y -= speed;
	if (KeyDown.pressed()) pos.y += speed;
	if (KeyLeft.pressed()) pos.x -= speed;
	if (KeyRight.pressed()) pos.x += speed;
}

// Shiftキーの押下状態によってスピードを変える
int32 getSpeed() {
	if (KeyShift.pressed()) return 10;
	else return 3;
}

// プレイヤーの移動範囲制限
void playerMovementRestriction(Position& pos)
{
	if (pos.x < 0) pos.x = 0;
	if (pos.x > 800) pos.x = 800;
	if (pos.y < 0) pos.y = 0;
	if (pos.y > 600) pos.y = 600;
}

// 円の描画
// isSelectedがtrueのときは黄色で描画
void draw(Position pos, bool isSelected)
{
	if (isSelected) {
		Circle{ pos.x, pos.y, 50 }.draw(Palette::Yellow);
	}
	else {
		Circle{ pos.x, pos.y, 50 }.draw();
	}
}

void Main()
{
	Array<Position> positions; // 3つのキャラの座標を格納する配列

	int32 selectedIndex = 0; // 現在操作しているキャラ(配列のインデックス)

	for (int i = 0; i < 3; i++) { // 3回ループする
		// 一旦全部真ん中に配置する
		positions << Position{ 400, 300 };
	}

	while (System::Update()) {
		// 選択中のキャラの移動
		playerControl(positions[selectedIndex], getSpeed());
		// 移動範囲制限
		playerMovementRestriction(positions[selectedIndex]);

		if (KeySpace.down()) { // Spaceキーを押した瞬間
			selectedIndex++;
			if (selectedIndex >= positions.size()) selectedIndex = 0;
		}

		// すべてのキャラの描画(円の描画)
		for (int32 i = 0; i < positions.size(); i++) {
			draw(positions[i], i == selectedIndex); // 選択中のキャラは黄色で描画(isSelectedがtrueになる)
		}
	}

}
ヒント(テンプレート)
# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
public:
	int32 x;
	int32 y;

	Position(int32 x, int32 y) : x(x), y(y) {}
};

class Player {
private:
	Position pos;
	int32 normalSpeed = 3; // 通常時のスピード
	int32 dashSpeed = 10; // Shiftキー押下時のスピード

public:
	Player(int32 x, int32 y) : pos( x, y ) {}

	// キャラの操作
	void control() {
		int32 speed = getSpeed();
		if (KeyUp.pressed()) pos.y -= speed;
		if (KeyDown.pressed()) pos.y += speed;
		if (KeyLeft.pressed()) pos.x -= speed;
		if (KeyRight.pressed()) pos.x += speed;
	}

	// Shiftキーが押されているかどうかでスピードを変える
	int32 getSpeed() {
		if (KeyShift.pressed()) return dashSpeed;
		return normalSpeed;
	}

	// 移動範囲制限
	void movementRestriction() {
		if (pos.x < 0) pos.x = 0;
		if (pos.x > 800) pos.x = 800;
		if (pos.y < 0) pos.y = 0;
		if (pos.y > 600) pos.y = 600;
	}

	// キャラの描画
	void draw(bool isSelected) {
		if (isSelected) {
			Circle{ pos.x, pos.y, 50 }.draw(Palette::Yellow);
		}
		else {
			Circle{ pos.x, pos.y, 50 }.draw();
		}
	}
};

void Main()
{
	Array<Position> positions; // TODO: ここを`Playerのインスタンスを格納する配列`に書き換える

	int32 selectedIndex = 0; // 現在操作しているキャラ(配列のインデックス)

	for (int i = 0; i < 3; i++) { // 3回ループする
		// 一旦全部真ん中に配置する
		// TODO: ここでPlayerのインスタンスを生成して配列に追加する
	}

	while (System::Update()) {
		// 選択中のキャラの移動
		playerControl(positions[selectedIndex], getSpeed()); // TODO: playerのメソッドを呼び出す方法に変更する
		// 移動範囲制限
		playerMovementRestriction(positions[selectedIndex]); // TODO: playerのメソッドを呼び出す方法に変更する

		if (KeySpace.down()) { // Spaceキーを押した瞬間
			selectedIndex++;
			if (selectedIndex >= positions.size()) selectedIndex = 0;
		}

		// すべてのキャラの描画(円の描画)
		for (int32 i = 0; i < positions.size(); i++) {
			// TODO: Playerのインスタンスの描画メソッドを呼び出す方法に変更する
			draw(positions[i], i == selectedIndex); // 選択中のキャラは黄色で描画(isSelectedがtrueになる)
		}
	}

}
解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

class Position {
public:
	int32 x;
	int32 y;

	Position(int32 x, int32 y) : x(x), y(y) {}
};

class Player {
private:
	Position pos;
	int32 normalSpeed = 3; // 通常時のスピード
	int32 dashSpeed = 10; // Shiftキー押下時のスピード

public:
	Player(int32 x, int32 y) : pos( x, y ) {}

	// キャラの操作
	void control() {
		int32 speed = getSpeed();
		if (KeyUp.pressed()) pos.y -= speed;
		if (KeyDown.pressed()) pos.y += speed;
		if (KeyLeft.pressed()) pos.x -= speed;
		if (KeyRight.pressed()) pos.x += speed;
	}

	// Shiftキーが押されているかどうかでスピードを変える
	int32 getSpeed() {
		if (KeyShift.pressed()) return dashSpeed;
		return normalSpeed;
	}

	// 移動範囲制限
	void movementRestriction() {
		if (pos.x < 0) pos.x = 0;
		if (pos.x > 800) pos.x = 800;
		if (pos.y < 0) pos.y = 0;
		if (pos.y > 600) pos.y = 600;
	}

	// キャラの描画
	void draw(bool isSelected) {
		if (isSelected) {
			Circle{ pos.x, pos.y, 50 }.draw(Palette::Yellow);
		}
		else {
			Circle{ pos.x, pos.y, 50 }.draw();
		}
	}
};

void Main()
{
	Array<Player> players; // Playerのインスタンスを格納する配列

	int32 selectedIndex = 0; // 現在操作しているキャラ(配列のインデックス)

	for (int i = 0; i < 3; i++) { // 3回ループする
		// 一旦全部真ん中に配置する
		players << Player{ 400, 300 };
	}

	while (System::Update()) {
		// 選択中のキャラの移動
		players[selectedIndex].control();
		// 移動範囲制限
		players[selectedIndex].movementRestriction();

		if (KeySpace.down()) { // Spaceキーを押した瞬間
			selectedIndex++;
			if (selectedIndex >= players.size()) selectedIndex = 0;
		}

		// すべてのキャラの描画(円の描画)
		for (int32 i = 0; i < players.size(); i++) {
			// TODO: Playerのインスタンスの描画メソッドを呼び出す方法に変更する
			players[i].draw(i == selectedIndex); // 選択中のキャラは黄色で描画(isSelectedがtrueになる)
		}
	}

}

メモリの動的確保

変数の値を保存するメモリには、スタック領域ヒープ領域の2種類あります。

スタック領域

int32 x = 400;や、Position pos{ 400, 300 };などの書き方で変数を作成した場合は、スタック領域に保存されます。
スタック領域に保存したデータは、その変数のスコープを抜けると破棄されます。
なので、以下のようにポインタを使用してスコープの外から変数の値にアクセスしようとすると、おかしな挙動をします。
これは、f()関数を抜けた時点で変数nが破棄されてしまうためです。

# include <Siv3D.hpp> // Siv3D v0.6.14

int32* f() {
	int32 n = 100;
	Print << n << U" (n)";
	Print << &n << U" (&n)";
	return &n;
}

void Main()
{
	int32* nAddress = f();

	Print << *nAddress << U" (*nAddress)"; // 不定な値が出力される
	Print << nAddress << U" (nAddress)";

	while (System::Update()) {
	}
}

ヒープ領域

これに対して、new int32{ 100 };new Position{ 400, 300 };などの書き方をした場合はヒープ領域に値が保存されます。
ヒープ領域に保存されたデータはプログラムが終了するまで破棄されないので、スコープを気にせず使用することが出来ます。

# include <Siv3D.hpp> // Siv3D v0.6.14

int32* f() {
	int32* n = new int32{ 100 };
	Print << *n << U" (*n)";
	Print << n << U" (n)";
	return n;
}

void Main()
{
	int32* nAddress = f();

	Print << *nAddress << U" (*nAddress)"; // 100
	Print << nAddress << U" (nAddress)";

	free(nAddress);

	while (System::Update()) {
	}
}

new 型名{ 初期値 };は、ヒープ領域(メモリ)にデータを保存する領域を確保し、そのアドレスを返すので、以下のようにポインタ型の変数で、確保したアドレスを受け取る必要があります。

int32* n = new int32{ 100 };

newを使って確保した領域はfree(アドレス)で開放することが出来ます。
ヒープ領域はプログラムが終了するまで破棄されないので、確保した領域をfreeで開放し忘れるとメモリにデータが残り続けメモリリークの原因になってしまいます。

free(nAddress);

多態性

親クラスを継承した子クラスは、親クラス型として扱うことが出来ます。
これを応用して、複数種類のクラスのインスタンスを一つの配列で扱う事ができます。

# include <Siv3D.hpp> // Siv3D v0.6.14

// 抽象クラス
// 抽象クラスはインスタンス化することができず、小クラスで抽象メソッドをオーバーライドすることで使うことができる
class Drawable {
public:
	// 抽象メソッド(純粋仮想関数)
	virtual void draw() = 0;
};

class MyCircle : public Drawable {
private:
	int32 x, y, r;

public:
	MyCircle(int32 x, int32 y, int32 r) : x(x), y(y), r(r) {}

	void draw() override {
		Circle(x, y, r).draw();
	}
};

class MyRect : public Drawable {
private:
	int32 x, y, width, height;

public:
	MyRect(int32 x, int32 y, int32 width, int32 height) : x(x), y(y), width(width), height(height) {}

	void draw() override {
		Rect(x, y, width, height).draw();
	}
};

void Main()
{
	Array<Drawable*> drawables;

	// Drawableを継承しているので、Drawable型として扱うことができる
	drawables << new MyCircle{ 200, 300, 50 };
	drawables << new MyRect{ 550, 250, 100, 100 };

	while (System::Update()) {
		for (Drawable* d : drawables) {
			d->draw();
		}
	}
}

補足

実は構造体とクラスはほぼ同じ
構造体でもメソッド、コンストラクタなどクラスの機能を使えます。
デフォルトのアクセス権がpublicprivateかの違いしか無い。
構造体:publicがデフォルト
クラス:privateがデフォルト

Circle{ 400, 300, 50 }.draw()は実は構造体を使っていた
Siv3Dでは色々な構造体(クラス)が用意されています。
Circleも用意されている構造体の一つです。
Circle{ 400, 300, 50 }Circle構造体をインスタンス化して、.draw()でメソッドを呼び出しているわけです。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?