ジャンル雑多なゲーム・ゲーム制作関連の色々な情報を取り扱っているブログ。最近はBlenderについてが中心。
[C, C++学習]C, C++言語再学習ノート-14日目- –列挙型、関数の返りインスタンス、インライン関数、仕様書を確認する大切さ

[C, C++学習]C, C++言語再学習ノート-14日目- –列挙型、関数の返りインスタンス、インライン関数、仕様書を確認する大切さ

列挙型、関数の返りインスタンス、インライン関数、仕様書を確認する大切さ —勉強しなおしの記録というか学習ノート-14日目-

Unreal Engin4すごい!(語彙力)ってなったので、少しいじりつつ、むかーしかじった事あるC,C++の学びなおしの学習ノート。この連載記事の詳しい趣旨と注意事項は1日目をご覧ください。

目次

列挙型

(実はCの方でも既にあったんですがちょっと飛ばしちゃってましたm(_ _;)m)

『列挙型』とは、定数のリストを定義できる型です。『列挙型』は定数のリストで定義した『列挙子』しか受け付けない型です。

  • 列挙型の定義をする書式
enum 列挙型タグ名{ 列挙子1, 列挙子2, ...}; 

ここで『列挙型』を定義しています。『構造体』や『クラス』と同じように、このタグ名に指定したものが『型』のように扱えます。なので、扱う時は、そのタグ名から変数を生成する訳です。

ちなみに、列挙子は、”=”で繋いで、初期値を指定できます。初期値をしていない場合、最初の『列挙子』を”0″として、以降の『列挙子』に順番に前の「『列挙子』+1」の値が指定されたのと同じ扱いになります。

enum タグ名{
    列挙子1 = 0,
    列挙子2 = 1,
.
.
.
};

ちなみに、こういった定義もできます。

enum タグ名{
    列挙子1 = 7,
    列挙子2,      // 8
    列挙子3,      // 9
    列挙子4 = 0,
    列挙子5       // 1
};

この場合、”cout”で出力してみると、”列挙子1″は”7″、”列挙子2″は”8″、”列挙子3″は”9″、”列挙子4″は”0″となって、その次の”列挙子5″は”1″と表示されます。

  • 列挙型変数の生成
タグ名 変数名;

ちなみに、C言語の場合は変数生成時、↓のように先頭の”enum”を書く必要があります。

enum タグ名 変数名;

構造体と同じで”typedef”を使えば省略可能です。

この変数には、先ほど設定した『列挙子』しか格納できません。

  • 変数への値の代入
変数名 = 列挙子のどれか;
  • 活用例
#include <iostream>

using namespace std;

// 列挙型
// クラムボンの状態を表す
enum Status {
	GIGGLE,
	LAUGH,
	JUMP,
	LAUGHED,
	DEAD,
	KILLED
};

/******************
* クラムボンクラスの宣言
*/
class Kuramubon
{
private:
	Status status; // 状態
public:
	Kuramubon();//コンストラクタ
	~Kuramubon();//デストラクタ
	void set_status(Status _sta); // 状態の値をセット
	Status get_status(); // 状態の値を返す
	void show_status(); // 状態を告げる関数
};

/*************************************:
* メイン関数
*/
int main() {
        // クラムボンクラスのインスタンス生成
	Kuramubon kura;

	kura.show_status();
	kura.set_status(JUMP);
	kura.show_status();

	return 0;
}

/**クラムボンクラスのメンバの初期化・定義***********************************/
// コンストラクタ
Kuramubon::Kuramubon() :status(GIGGLE) {// ここでメンバ変数statusの初期化
	cout << "小さな谷川の底を写した二枚の青い幻燈です。" << endl;
}
// デストラクタ
Kuramubon::~Kuramubon() {
	cout << "私の幻燈はこれでおしまいであります。" << endl;
}
// メンバ変数statusのセッター
void Kuramubon::set_status(Status _sta) {
	status = _sta;
}
// メンバ変数statusのゲッター
Status Kuramubon::get_status() {
	return status;
}

// クラムボンの状態を表す文章を出力するメンバ関数
void Kuramubon::show_status() {
	//statusの数値によってよって分岐
	switch (status) {
		case GIGGLE:
			cout << "『クラムボンはわらったよ。』" << endl;
			break;
		case LAUGH:
			cout << "『クラムボンはかぷかぷわらったよ。』" << endl;
			break;
		case JUMP:
			cout << "『クラムボンは跳ねてわらったよ。』" << endl;
			break;
		case LAUGHED:
			cout << "『クラムボンはわらっていたよ。』" << endl;
			break;
		case DEAD:
			cout << "『クラムボンは死んだよ。』" << endl;
			break;
		case KILLED:
			cout << "『クラムボンは殺されたよ。』" << endl;
			break;
		default:  // 実行されないはずのケース
			cout << "『わからない。』" << endl;
			break;
	}
}

※一部文章を、青空文庫様『宮沢賢治 やまなし 』よりお借りしました。…懐かしいですよね。

実行結果↓

小さな谷川の底を写した二枚の青い幻燈です。
『クラムボンはわらったよ。』
『クラムボンは跳ねてわらったよ。』
私の幻燈はこれでおしまいであります。

今回の「クラムボンクラス」のメンバ変数”status”の型を、「列挙型”Status”」で作っているところにご注目ください。

単純に『int型』でメンバ変数を作っておいて、数値で条件分岐することも出来ますが、上記の例では『列挙型』を活用して、条件分岐をしてみました。「定数のリストで指定する方が分かりやすい場合もある」というのが伝わりましたでしょうか?

『switch文』で分岐する際には特に便利さがわかりやすくなるかと思います。

値渡し、ポインタ渡し、参照渡し –関数の返りオブジェクトで、インスタンスを扱う時

前回も取り扱った3種類の値の渡し方なのですが、インスタンスを扱う時に注意すべきかな、と思ったことがあったので、そこを書いておこうと思います。

先ほどの「クラムボンクラス」を記述したプログラムを使いまわします。

値渡しの場合 –我が滅びようとも第二第三の我が…

#include <iostream>

using namespace std;

// 列挙型
// クラムボンの状態を表す
enum Status {
	GIGGLE,
	LAUGH,
	JUMP,
	LAUGHED,
	DEAD,
	KILLED
};

/******************
* クラムボンクラスの宣言
*/
class Kuramubon
{
private:
	Status status; // 状態
public:
	Kuramubon();//コンストラクタ
	~Kuramubon();//デストラクタ
	void set_status(Status _sta); // 状態の値をセット
	Status get_status(); // 状態の値を返す
	void show_status(); // 状態を告げる関数
};

// プロトタイプ宣言
Kuramubon func1();

/*************************************:
* メイン関数
*/
int main() {
	cout << "//main関数が始まりました。///////////////////////" << endl;
	Kuramubon kura;
	Kuramubon kura2 = func1();
	cout << "//main関数に戻りました。////////////////////////" << endl;
	kura.show_status();
	kura.set_status(JUMP);
	kura2.show_status(); // 実はやっちゃいけないことその1
	cout << kura2.get_status() << endl; //実はやっちゃいけないことその2
	kura.show_status();
	cout << "//main関数を抜けます。//////////////////////////" << endl;
	return 0;
}

/**クラムボンクラスのメンバの初期化・定義***********************************/
// コンストラクタ
Kuramubon::Kuramubon() :status(GIGGLE) {// ここでメンバ変数statusの初期化
	cout << "小さな谷川の底を写した二枚の青い幻燈です。" << endl;
}
// デストラクタ
Kuramubon::~Kuramubon() {
	cout << "私の幻燈はこれでおしまいであります。" << endl;
}
// メンバ変数statusのセッター
void Kuramubon::set_status(Status _sta) {
	status = _sta;
}
// メンバ変数statusのゲッター
Status Kuramubon::get_status() {
	return status;
}

// クラムボンの状態を表す文章を出力するメンバ関数
void Kuramubon::show_status() {
	//statusの数値によってよって分岐
	switch (status) {
	case GIGGLE:
		cout << "『クラムボンはわらったよ。』" << endl;
		break;
	case LAUGH:
		cout << "『クラムボンはかぷかぷわらったよ。』" << endl;
		break;
	case JUMP:
		cout << "『クラムボンは跳ねてわらったよ。』" << endl;
		break;
	case LAUGHED:
		cout << "『クラムボンはわらっていたよ。』" << endl;
		break;
	case DEAD:
		cout << "『クラムボンは死んだよ。』" << endl;
		break;
	case KILLED:
		cout << "『クラムボンは殺されたよ。』" << endl;
		break;
	default:
		cout << "『わからない。』" << endl;
		break;
	}
}

// クラムボンクラスを生成して返す関数
Kuramubon func1() {
	cout << "--関数func1に入りました。-----------------------" << endl;
	Kuramubon k;
	k.set_status(LAUGHED);
	cout << "--関数func1を抜けます。-----------------------" << endl;
	return k;
}

実行結果↓

//main関数が始まりました。///////////////////////
小さな谷川の底を写した二枚の青い幻燈です。
--関数func1に入りました。-----------------------
小さな谷川の底を写した二枚の青い幻燈です。
--関数func1を抜けます。-----------------------
私の幻燈はこれでおしまいであります。
//main関数に戻りました。////////////////////////
『クラムボンはわらったよ。』
『クラムボンはわらっていたよ。』
3
『クラムボンは跳ねてわらったよ。』
//main関数を抜けます。//////////////////////////
私の幻燈はこれでおしまいであります。
私の幻燈はこれでおしまいであります。

“変数”でも”インスタンス”でも、『オブジェクト』の『スコープ』の基本ですが、生成された関数を抜ければ消失するという原則があります。

値渡しである場合、「インスタンス生成元である関数内に存在する“元のインスタンス”」は関数を抜けた時点で消失しています

しかし、”return”でその消失前に外の関数に“インスタンスのコピー”を渡しているので、その“オブジェクト”のデータのコピーが関数を抜けても保持できるわけです。

ポインタ渡しの場合 –アドレスだけ渡されても(^_^;)

#include <iostream>

using namespace std;

// 列挙型
// クラムボンの状態を表す
enum Status {
	GIGGLE,
	LAUGH,
	JUMP,
	LAUGHED,
	DEAD,
	KILLED
};

/******************
* クラムボンクラスの宣言
*/
class Kuramubon
{
private:
	Status status; // 状態
public:
	Kuramubon();//コンストラクタ
	~Kuramubon();//デストラクタ
	void set_status(Status _sta); // 状態の値をセット
	Status get_status(); // 状態の値を返す
	void show_status(); // 状態を告げる関数
};

// プロトタイプ宣言
Kuramubon* func1();

/*************************************:
* メイン関数
*/
int main() {
	cout << "//main関数が始まりました。///////////////////////" << endl;
	Kuramubon kura, *kura2;
	kura2 = func1();
	cout << "//main関数に戻りました。////////////////////////" << endl;
	kura.show_status();
	kura.set_status(JUMP);
	kura2->show_status(); // 実はやっちゃいけないことその1
	cout << kura2->get_status() << endl; //実はやっちゃいけないことその2
	kura.show_status();
	cout << "//main関数を抜けます。//////////////////////////" << endl;
	return 0;
}

/**クラムボンクラスのメンバの初期化・定義***********************************/
// コンストラクタ
Kuramubon::Kuramubon() :status(GIGGLE) {// ここでメンバ変数statusの初期化
	cout << "小さな谷川の底を写した二枚の青い幻燈です。" << endl;
}
// デストラクタ
Kuramubon::~Kuramubon() {
	cout << "私の幻燈はこれでおしまいであります。" << endl;
}
// メンバ変数statusのセッター
void Kuramubon::set_status(Status _sta) {
	status = _sta;
}
// メンバ変数statusのゲッター
Status Kuramubon::get_status() {
	return status;
}

// クラムボンの状態を表す文章を出力するメンバ関数
void Kuramubon::show_status() {
	//statusの数値によってよって分岐
	switch (status) {
	case GIGGLE:
		cout << "『クラムボンはわらったよ。』" << endl;
		break;
	case LAUGH:
		cout << "『クラムボンはかぷかぷわらったよ。』" << endl;
		break;
	case JUMP:
		cout << "『クラムボンは跳ねてわらったよ。』" << endl;
		break;
	case LAUGHED:
		cout << "『クラムボンはわらっていたよ。』" << endl;
		break;
	case DEAD:
		cout << "『クラムボンは死んだよ。』" << endl;
		break;
	case KILLED:
		cout << "『クラムボンは殺されたよ。』" << endl;
		break;
	default:
		cout << "『わからない。』" << endl;
		break;
	}
}

// クラムボンクラスを生成して返す関数
Kuramubon* func1() {
	cout << "--関数func1に入りました。-----------------------" << endl;
	Kuramubon obj;
	Kuramubon* k = &obj;
	k->set_status(LAUGHED);
	cout << "--関数func1を抜けます。-----------------------" << endl;
	return k;
}

実行結果↓

//main関数が始まりました。///////////////////////
小さな谷川の底を写した二枚の青い幻燈です。
--関数func1に入りました。-----------------------
小さな谷川の底を写した二枚の青い幻燈です。
--関数func1を抜けます。-----------------------
私の幻燈はこれでおしまいであります。
//main関数に戻りました。////////////////////////
『クラムボンはわらったよ。』
『わからない。』
-858993460
『クラムボンは跳ねてわらったよ。』
//main関数を抜けます。//////////////////////////
私の幻燈はこれでおしまいであります。

再三言うようですが、『オブジェクト』は、生成された場所である関数を抜ければ消失するという原則があります。

そのため、上記のプログラムでは、「生成元の関数」からポインタ渡しで「”オブジェクト”のアドレス」を外の関数(この場合main関数)へ返しても、その肝心の「”オブジェクト”が関数を抜けたことで同時に消失」してしまっています。

「アドレス元の”オブジェクト”」が残っていないので、オブジェクトのメンバ関数にアクセスしても”予期せぬ値”しか返されていないわけなのです。文字通り「元も子もない」わけですので。

参照渡しの場合 –返りオブジェクトの霊圧が…消えた…?

#include <iostream>

using namespace std;

// 列挙型
// クラムボンの状態を表す
enum Status {
	GIGGLE,
	LAUGH,
	JUMP,
	LAUGHED,
	DEAD,
	KILLED
};

/******************
* クラムボンクラスの宣言
*/
class Kuramubon
{
private:
	Status status; // 状態
public:
	Kuramubon();//コンストラクタ
	~Kuramubon();//デストラクタ
	void set_status(Status _sta); // 状態の値をセット
	Status get_status(); // 状態の値を返す
	void show_status(); // 状態を告げる関数
};

// プロトタイプ宣言
Kuramubon& func1();

/*************************************:
* メイン関数
*/
int main() {
	cout << "//main関数が始まりました。///////////////////////" << endl;
	Kuramubon kura, *kura2;
	kura2 = &func1();
	cout << "//main関数に戻りました。////////////////////////" << endl;
	kura.show_status();
	kura.set_status(JUMP);
	kura2->show_status(); // 実はやっちゃいけないことその1
	cout << kura2->get_status() << endl; //実はやっちゃいけないことその2
	kura.show_status();
	cout << "//main関数を抜けます。//////////////////////////" << endl;
	return 0;
}

/**クラムボンクラスのメンバの初期化・定義***********************************/
// コンストラクタ
Kuramubon::Kuramubon() :status(GIGGLE) {// ここでメンバ変数statusの初期化
	cout << "小さな谷川の底を写した二枚の青い幻燈です。" << endl;
}
// デストラクタ
Kuramubon::~Kuramubon() {
	cout << "私の幻燈はこれでおしまいであります。" << endl;
}
// メンバ変数statusのセッター
void Kuramubon::set_status(Status _sta) {
	status = _sta;
}
// メンバ変数statusのゲッター
Status Kuramubon::get_status() {
	return status;
}

// クラムボンの状態を表す文章を出力するメンバ関数
void Kuramubon::show_status() {
	//statusの数値によってよって分岐
	switch (status) {
	case GIGGLE:
		cout << "『クラムボンはわらったよ。』" << endl;
		break;
	case LAUGH:
		cout << "『クラムボンはかぷかぷわらったよ。』" << endl;
		break;
	case JUMP:
		cout << "『クラムボンは跳ねてわらったよ。』" << endl;
		break;
	case LAUGHED:
		cout << "『クラムボンはわらっていたよ。』" << endl;
		break;
	case DEAD:
		cout << "『クラムボンは死んだよ。』" << endl;
		break;
	case KILLED:
		cout << "『クラムボンは殺されたよ。』" << endl;
		break;
	default:
		cout << "『わからない。』" << endl;
		break;
	}
}

// クラムボンクラスを生成して返す関数
Kuramubon& func1() {
	cout << "--関数func1に入りました。-----------------------" << endl;
	Kuramubon obj;
	Kuramubon& k = obj;
	k.set_status(LAUGHED);
	cout << "--関数func1を抜けます。-----------------------" << endl;
	return k;
}

実行結果↓

//main関数が始まりました。///////////////////////
小さな谷川の底を写した二枚の青い幻燈です。
--関数func1に入りました。-----------------------
小さな谷川の底を写した二枚の青い幻燈です。
--関数func1を抜けます。-----------------------
私の幻燈はこれでおしまいであります。
//main関数に戻りました。////////////////////////
『クラムボンはわらったよ。』
『わからない。』
-858993460
『クラムボンは跳ねてわらったよ。』
//main関数を抜けます。//////////////////////////
私の幻燈はこれでおしまいであります。

当たり前ですが、こちらも”アドレスだけ”渡されても、元のオブジェクトが消失しています。なので、ポインタ渡しと同じような結果になります。

インライン関数

以前、Cの方の記事で「関数形式のマクロ」は説明させていただいたと思います。

今回のこのC++で新たに登場した『インライン関数』は、「関数形式マクロ」と同じようなことができるものです。
※全く同じというわけではなく、例外もあります。

「呼び出し元で短い処理の定義された関数名が記述」がされているところに「元々の関数で定義されている処理のコードをそのままハメ込む」ということをするためのものです。

つまりは、「関数の呼び出しに掛かる時間を短縮するための方法」で、短い行数で済むような処理が定義された関数の場合、呼び出しに掛かる時間は微々たるものとは言え、それを「少しでも短縮したい」という時、使用します。

また、『インライン関数』を記述しても、実際にそのコードがその呼び出し元の場所にハメ込まれるかは、『コンパイラ』さんに依存します。

長い処理の場合や、プログラム全体のサイズが大きくなると『コンパイラ』さんが判断した場合は、『インライン関数』とはならず、普通に呼び出すタイプの『関数』となります。

(『コンパイラ』さんに下手に逆らったら『コンピュータ』とまともにおしゃべり出来なくなっちゃうんだもん仕方ないね)

  • インライン関数の(宣言と)定義
inline 型 関数名(){
    //処理;
}

上記のように、型の前に”inline”を付けるだけです。

ちなみに、宣言と定義を別々にする場合(プロトタイプ宣言など)、宣言の方は普通に記述します。

// プロトタイプ宣言
型 関数名();

/*
略
*/
inline 型 関数名(){
    //処理
}
  • クラスでもインラインメンバ関数(?)

またまた、先ほどの「クラムボンクラス」のプログラムを引っ張り出してきて、例として挙げてみると、

/******************
* クラムボンクラスの宣言
*/
class Kuramubon
{
private:
	Status status; // 状態
public:
	Kuramubon();//コンストラクタ
	~Kuramubon();//デストラクタ
	void set_status(Status _sta); // 状態の値をセット
	Status get_status(); // 状態の値を返す
	void show_status(); // 状態を告げる関数
};
    /*
    略
    */

/**クラムボンクラスのメンバの初期化・定義***********************************/
// コンストラクタ
inline Kuramubon::Kuramubon() :status(GIGGLE) {// ここでメンバ変数statusの初期化
	cout << "小さな谷川の底を写した二枚の青い幻燈です。" << endl;
}
// デストラクタ
inline Kuramubon::~Kuramubon() {
	cout << "私の幻燈はこれでおしまいであります。" << endl;
}
// メンバ変数statusのセッター
inline void Kuramubon::set_status(Status _sta) {
	status = _sta;
}
// メンバ変数statusのゲッター
inline Status Kuramubon::get_status() {
	return status;
}

// クラムボンの状態を表す文章を出力するメンバ関数
inline void Kuramubon::show_status() {
	//statusの数値によってよって分岐
	switch (status) {
	case GIGGLE:
		cout << "『クラムボンはわらったよ。』" << endl;
		break;
	case LAUGH:
		cout << "『クラムボンはかぷかぷわらったよ。』" << endl;
		break;
	case JUMP:
		cout << "『クラムボンは跳ねてわらったよ。』" << endl;
		break;
	case LAUGHED:
		cout << "『クラムボンはわらっていたよ。』" << endl;
		break;
	case DEAD:
		cout << "『クラムボンは死んだよ。』" << endl;
		break;
	case KILLED:
		cout << "『クラムボンは殺されたよ。』" << endl;
		break;
	default:
		cout << "『わからない。』" << endl;
		break;
	}
}

という感じに定義時に”inline”と先頭につけるだけで『インライン』化出来ます。

↓と同義ですね。

/******************
* クラムボンクラスの宣言
*/
class Kuramubon
{
private:
	Status status; // 状態
public:
	Kuramubon() :status(GIGGLE) {// ここでメンバ変数statusの初期化
		cout << "小さな谷川の底を写した二枚の青い幻燈です。" << endl;
	}
	~Kuramubon() {
		cout << "私の幻燈はこれでおしまいであります。" << endl;
	}
	void set_status(Status _sta) {
		status = _sta;
	}
	Status get_status() {
		return status;
	}
	void show_status() {
		//statusの数値によってよって分岐
		switch (status) {
		case GIGGLE:
			cout << "『クラムボンはわらったよ。』" << endl;
			break;
		case LAUGH:
			cout << "『クラムボンはかぷかぷわらったよ。』" << endl;
			break;
		case JUMP:
			cout << "『クラムボンは跳ねてわらったよ。』" << endl;
			break;
		case LAUGHED:
			cout << "『クラムボンはわらっていたよ。』" << endl;
			break;
		case DEAD:
			cout << "『クラムボンは死んだよ。』" << endl;
			break;
		case KILLED:
			cout << "『クラムボンは殺されたよ。』" << endl;
			break;
		default:
			cout << "『わからない。』" << endl;
			break;
		}
	}
};

ちなみに、ファイル分けして、クラスの宣言と定義をヘッダー(.h)ファイルとクラス用のソース(.cpp)ファイルとで分けてinlineにしても、二重定義にはなりません。

また、ヘッダーファイルで↑のように宣言と定義をまとめてやってしまってもエラーしたりしません。(柔軟性すごい)

おまけ:なんやかんややってた時に偶然できたものと得た教訓メモ –仕様書確認しよう

#include <iostream>

using namespace std;

int main() {
	int n(10);
	cout << n << endl;

	return 0;
}

実行結果↓

10

!?!?!(^ω^ )

なんなら、↓も通るよ!

#include <iostream>

using namespace std;

int main() {
	cout << int(10) << endl;

	return 0;
}

実行結果↓

10

!?!?!?!?!(^ω^ )

ということで、少し調べてみました。

詳しくまとめてくださっていた方がいたので、以下引用

追記

  • int x(); はそもそも関数宣言になってしまう
    • 関数内でも関数宣言は出来る、いわゆる前方宣言になる
  • int x(1) では direct-initialize というものが発生し、 1 で初期化された int 型の変数 x が宣言される
  • int x = 1 では copy-initialize というものが発生し、 int 型の変数 x として宣言された物が 1 で初期化される
  • int x = int(1) では direct-initialize で 1 で初期化したものが copy-initialize で x を初期化する
    • とは言え処理系の実装によっては int x = 1 or int x(1) と同じ処理になるだろう
  • この話は POD かつ Scalar Type の場合のみである

プログラマコンパイラの気持ちを考えよう。コンパイラは仕様書の気持ちを考えよう。

追記 2

  • C++ では変数を宣言した瞬間に初期化が走るように設計されている
    • MyClass x; とやった時点で MyClass のコンストラクタは実行される
    • しかしプリミティブ型では未初期化変数の利用を許容している
      • int x; とやっても x は初期化されない
      • これは C 言語との後方互換のため

引用元:C++での変数の初期化について – おともだちティータイム

引用ここまで。

結論:コンパイラと仕様書の確認大事。

ある程度遊べるようになって、色々と試してみると「これ何?どういうこと!?」みたいなことが出てくると思うので、そう言った場合は、やっぱり本家本元(仕様書)を確認するのが大事だと再認識しました。

あとがき

ご意見・ご感想・ご質問、また、ここ間違ってるよとか、もっといい方法あるよといったご指摘などございましたら、お手数ですがコメント欄やtwitterアカウントほろほろり(@_horo_horori)へお願いしますm(_ _)m

参考にさせていただいたサイト・ページ一覧

Pocket