ジャンル雑多なゲーム・ゲーム制作関連の色々な情報を取り扱っているブログ。最近はBlenderについてが中心。
[C, C++学習]C, C++言語再学習ノート-11日目- –オーバーロード、クラス、コンストラクタ、デストラクタ、const

[C, C++学習]C, C++言語再学習ノート-11日目- –オーバーロード、クラス、コンストラクタ、デストラクタ、const

オーバーロード、クラス、コンストラクタ、デストラクタ、const —勉強しなおしの記録というか学習ノート-11日目-

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

※2019年6月12日 加筆修正しました。

1つ前の記事で、『名前空間』の「変数や関数、クラスの名前被り」というのを書いてて引っ掛かってたんですが、そういえば『オーバーロード』っていうのあったなーって思い出したので、その話についてを冒頭で書かせていただきます。それからは、クラスについてを書けるところまで書いていきますよ!

目次

オーバーロード –同名の関数の扱い

Cの場合、関数が同じ名前であれば、二重定義になってエラーしてコンパイルできません。

ですが、C++の場合、 『オーバーロード』(なんかかっこいい)と言い、同じ名前の関数であっても、型か引数が違えば、別の関数として扱われます。
※流石に型も引数も全く同じでは只の二重定義になります(^_^;)

#include <iostream>

using namespace std;

int func1(int);
int func1(double);

int main() {
	int a = 11;
	double d = 5.25;
	cout << "int型func1関数: " << func1(a) << endl;
	cout << "double型func1関数: " << func1(d) << endl;
	
	return 0;
}

int func1(int a) {
	return a * a;
}

int func1(double d) {
	return (int)d;
}

実行結果↓

int型func1関数: 121
double型func1関数: 5

Cの場合 – 二重定義になりエラー

#include <stdio.h>
#include <windows.h>

int func1(int);
double func1(double); //エラーします

void main(int argc, char* argv[]) {
	printf(func1(11));
}

int func1(int a) {
	return a * a;
}

double func1(double d) {
	return d;
}

ちなみに、名前空間と組み合わせて使った場合

名前空間の自作の仕方もさらっとご紹介

namespace 名前空間名{
   // 関数や変数、クラスを定義
}

自作した名前空間の中の関数などへのアクセス方法

デフォルトで定義されている”std”の場合と変わりません。

名前空間名::関数名

もちろん”using”で省略できます。

using namespace 名前空間名;

int main(){
     名前空間名内で定義済みの関数名 
/*
 略
 */
#include <iostream>

namespace A {
	int func1(int a) {
		return a * a;
	}
}
namespace B {
	int func1(double d) {
		return (int)d;
	}
}

using namespace std;
using namespace A;
using namespace B;

int main() {
	int a = 11;
	double d = 5.25;
	cout << "int型func1関数: " << func1(a) << endl;
	cout << "double型func1関数: " << func1(d) << endl;
	
	return 0;
}

実行結果↓

int型func1関数: 121
double型func1関数: 5

クラス

前回の記事でも、考え方だけお伝えしました、クラスとクラスの扱い方や性質などについて、今回は詳しく書いていきます。

クラスの一番簡単な作り方編

ひとまず、例をご覧ください。

#include <iostream>

using namespace std;

/*********************
*四角形クラス
*/
class Square {
private:
	// メンバ変数の宣言
	int width; // 横幅
	int height; // 高さ
public:
	// メンバ関数の宣言
	void set_width(int _w);
	void set_height(int _h);
	int get_width();
	int get_height();
	void info_str(); 
};


int main() {
	// クラスのインスタンスを生成
	Square square;
	// インスタンスのメンバ関数にアクセス
	square.set_width(10); 
	square.set_height(10);
	square.info_str();
	cout << "面積は、" << (square.get_width() * square.get_height()) << endl;

	return 0;
}

// Squareクラスのメンバ関数の定義
void Square::set_width(int _w) {
	width = _w;
}
void Square::set_height(int _h) {
	height = _h;
}
int  Square::get_width() {
	return width;
}
int  Square::get_height() {
	return height;
}
void  Square::info_str() {
	if (width && height) {
		string str = "四角形";
		if (width == height) {
			str = "正方形";
		}
		cout << "横幅:" << width << ", 高さ:" << height 
			<< "の" + str + "です。" << endl;

	}
}

実行結果↓

横幅:10, 高さ:10の正方形です。
面積は、100

クラスの宣言

/*********************
*四角形クラス
*/
class Square {
private:
	// メンバ変数の宣言
	int width; // 横幅
	int height; // 高さ
public:
	// メンバ関数の宣言
	void set_width(int _w);
	void set_height(int _h);
	int get_width();
	int get_height();
	void info_str(); 
};

ここでクラスの宣言をしています。

最後に”;”が付くところとか、メンバ変数(メンバ関数もいるけど)が並んでいるところとか、何となく全体の雰囲気的に構造体の面影を感じるのではないかと思われます。

ですが、他の言語(Javaなど)で見たことがない方は、 “private”“public”というキーワードに疑問符を浮かべられておられるかもしれません。

これらは、『アクセス修飾子』といって、 そのクラスのメンバのスコープ範囲を制御するのに使います。

class クラス名 {
private:
	/*
        privateが指定されている領域
        */
public:
	/*
        publicが指定されている領域
        */
};

カプセル化』というのですが、オブジェクト指向ではよく使われる手法で、メンバ変数は”private”(外部からのアクセス禁止)に設定し、メンバ関数は”public”(外部からのアクセス可)して、外部から隠ぺいするというものです。

なので、メンバ変数の値を参照する場合は、メンバ関数を呼び出して、間接的に操作する形になります。

こうすることで、「明示的にメンバ関数から呼び出しして値にアクセス」、というワンクッションが出来ます。メンバ変数の値を不用意に変更されるのを防ぐことができるかとおもいます。

ちなみに、クラスのメンバは、デフォルトでは”private”キーワードで指定したのと同じなので、アクセスを許可するところだけ”public”で明示すればいいのですが、様式美として、”private”も明示しておきます。

アクセス修飾子の種類

修飾子説明
public外部からのアクセスに制限はなく、全範囲からアクセス可能。
private同じクラス・同じインスタンス内でのみ、アクセス可能。
protected同じクラス・同じインスタンス、そして派生クラス・そのインスタンス内でのみアクセス可能。

Cでの『記憶クラス指定子』(auto, extern, staticなど)に似ていますね。

記憶クラス指定子に関しましては、もしよろしければ、過去記事で書かせていただいたものがありますので、分からない方は、参考にどうぞ。

クラスの関数メンバの定義

// Squareクラスのメンバ関数の定義
void Square::set_width(int _w) {
	width = _w;
}
void Square::set_height(int _h) {
	height = _h;
}
int  Square::get_width() {
	return width;
}
int  Square::get_height() {
	return height;
}
void  Square::info_str() {
	if (width && height) {
		string str = "四角形";
		if (width == height) {
			str = "正方形";
		}
		cout << "横幅:" << width << ", 高さ:" << height 
			<< "の" + str + "です。" << endl;

	}
}

前項の「クラスの宣言」では、宣言しかしていなかったので、こちらで個別にメンバ関数の定義をしています。

外部からクラスのメンバ関数を定義する時には、

クラス名::メンバ関数名

と言った形でアクセスします。

この時、メンバ関数内で、同クラスのメンバ変数にアクセスする時には、”::”で再びクラス名と共に記述しなくても、そのままメンバ変数名だけで参照できます。

名前空間にアクセスする際にも使いましたが、”::”は、『スコープ解決演算子』といいます。

アクセスメソッド –セッターとゲッター

先ほど話題に出た、『カプセル化』ですが、それと併せて、「外部から直接アクセスができないため、メンバ関数を呼び出して、そこからメンバ変数へアクセスする」といったことを説明したと思います。

なので、それ専用のメンバ関数、『アクセスメソッド』を作っているわけなのですが、上のソースコードで、一番最後の”info_str()”関数以外、すべて、

set_〇〇〇()

あるいは

get_〇〇〇()

と言った名前になっているのにお気づきでしょうか?

これがその『アクセスメソッド』で、”set_〇〇〇()”の方を『セッター』。”get_〇〇〇()”の方を『ゲッター』と言います。

『セッター』 は、メンバ変数に値をセットするのに使い、『ゲッター』は、メンバ変数の値をゲットするのに使います。(そのまんま)

クラスのインスタンスを生成、インスタンスのメンバ関数にアクセス

int main() {
	// クラスのインスタンスを生成
	Square square;
	// インスタンスのメンバ関数にアクセス
	square.set_width(10); 
	square.set_height(10);
	square.info_str();
	cout << "面積は、" << (square.get_width() * square.get_height()) << endl;

	return 0;
}

定義済みのクラスを、メイン関数で生成、活用していきます。

まずは、生成ですが、ここは構造体でも同じ感じだったので、多分そういうものだと思っていただけるんでしょうが、一応解説します。

// クラスのインスタンスを生成
Square square;    // 書式:クラス名 インスタンス名

としているように、これには、定義したクラス名の次に、生成するインスタンス名を続けて、インスタンスを生成しています。

複数生成する場合は1個目のインスタンス名の後に”,”を付けて、その後に続けて書いていきます。

その次に、生成されたインスタンスのメンバ関数にアクセスする訳なのですが、これも、構造体と同じく、インスタンス名の次に”.”をつけ、そのあとに続けて、アクセスしたいメンバ関数名を続けるわけです。

// インスタンスのメンバ関数にアクセス
	square.set_width(10); // 書式:インスタンス名.メンバ関数
	square.set_height(10);
	square.info_str();
	cout << "面積は、" << (square.get_width() * square.get_height()) << endl;

ちなみに、これも構造体と同じく、インスタンスのポインタへアクセスする時は、”->”を使います。

コンストラクタ、デストラクタ

「クラスがインスタンスで、オブジェクトはオブジェクトで、今度はコンストラクタ?デストラクタ?」と混乱しなくても、大丈夫です!

この2つはそういう存在だって、分かっちゃえば心強い味方になってくれる子達なので、気楽に構えてて大丈夫ですよ。

まずは使用例としてのソースコードを出してしまいます。

#include <iostream>

using namespace std;

// 定数
const float PI = 3.14;

/**************************
* 円柱クラス
*/
class Column {
private:
	/* メンバ変数の宣言 */
	int x; // x座標
	int y; // y座標
	int z; // z座標
	double radius; // 半径
	double height; // 高さ
	string color; // 色
	double volume; // 体積

	void set_volume();//勝手に弄られたら困るのでprivateに
public:
	/* メンバ関数の宣言 */
	//コンストラクタ
	Column();
	// デストラクタ
	~Column();
	void set_position(int _x, int _y, int _z);
	void set_radius(double _radius);
	void set_height(double _height);
	void set_color(string _color);
	int get_x();
	int get_y();
	int get_z();
	double get_radius();
	double get_height();
	string get_color();
	void info_status();
};

int main() {
	// クラスのインスタンスを生成
	Column clm;
	int x, y, z;
	// インスタンスのメンバ関数にアクセス
	cout << "初期位置 = (" << clm.get_x() << ", " << clm.get_y() << ", " << clm.get_z() << ")" << endl;
	clm.info_status();
	cout << "変更するX軸の位置を入力してください。:";
	cin >> x;
	cout << "変更するY軸の位置を入力してください。:";
	cin >> y; 
	cout << "変更するZ軸の位置を入力してください。:";
	cin >> z;
	clm.set_position(x, y, z);
	clm.info_status();
	return 0;
}

/*Columnクラスのメンバ関数の定義********/
// コンストラクタの定義
Column::Column() {
	cout << "円柱を一つ建設しました。" << endl;
	// インスタンスの初期化
	x = 0; // x座標
	y = 0; // y座標
	z = 0; // z座標
	radius = 3.5; // 半径
	height = 10.0; // 高さ
	color = "red"; // 色
	volume = radius * radius * PI * height;// 体積
}
// デストラクタの定義
Column::~Column() {
	cout << "円柱を一つ破壊しました。" << endl;
	cout << "鮮烈な" + color + "が一瞬の閃光と、凄まじい轟音を伴い、\n跡形もなく消え失せた。" << endl;
}
// X,Y,Z座標を変更するセッター
void Column::set_position(int _x, int _y, int _z) {
	x = _x;
	y = _y;
	z = _z;
}
// 半径を変更するセッター
void Column::set_radius(double _radius) {
	radius = _radius;
	set_volume();//一緒に体積も変更
}
// 高さを変更するセッター
void Column::set_height(double _height) {
	height = _height;
	set_volume();//一緒に体積も変更
}
// 色を変更するセッター
void Column::set_color(string _color) {
	color = _color;
}
// Xのゲッター
int Column::get_x() {
	return x;
}
// Yのゲッター
int Column::get_y() {
	return y;
}
// Zのゲッター
int Column::get_z() {
	return z;
}
// 半径の値のゲッター
double Column::get_radius() {
	return radius;
}
// 高さの値のゲッター
double Column::get_height() {
	return height;
}
// 色の値のゲッター
string Column::get_color() {
	return color;
}
// 半径か高さが変更された時に呼び出される体積のセッター
void Column::set_volume() {
	volume = radius * radius * PI * height;// 体積
}
void Column::info_status() {
	cout << "この" << color << "の円柱は(" << x << ", " << y << ", " << z << ")の位置にあり、\n"
		<< "半径:" << radius << ", 高さ:" << height << ", なので、体積:" << volume << endl;
}

実行結果↓

円柱を一つ建設しました。
初期位置 = (0, 0, 0)
このredの円柱は(0, 0, 0)の位置にあり、
半径:3.5, 高さ:10, なので、体積:384.65
変更するX軸の位置を入力してください。:5
変更するY軸の位置を入力してください。:4
変更するZ軸の位置を入力してください。:7
このredの円柱は(5, 4, 7)の位置にあり、
半径:3.5, 高さ:10, なので、体積:384.65
円柱を一つ破壊しました。
鮮烈なredが一瞬の閃光と、凄まじい轟音を伴い、
跡形もなく消え失せた。

メンバ変数とメンバ関数増やしたから、ちょっとソースコードが長くなってしまいましたが、分解すれば、難しいことはやっていないので、大丈夫です。一つ一つ解説していきますね!

おまけ:const修飾子 –定数を定義する

冒頭にあるこの部分。

// 定数
const float PI = 3.14;

この、”const”というのは、Cの方で詳しい説明しなかったので、ここで少し説明します。

書式:const 型 変数名 = 値;

定数と言えば、Cにはマクロ定義の”#define”がありましたが、これは、それとは違い、コンパイル後なので、変数もちゃんと認識してくれます。

double d = 0.14;
const float PI = 3 + d;

無理やりな分け方ですが、こうしても、ちゃんと3.14として認識してくれるわけです。

また、Cでは、constで修飾した変数はポインタ経由で書き換えることが可能であったりしたのですが、C++では、constで修飾されたオブジェクトは書き換えられません。

C++での定数では、”#define”よりも有用であると思います。

おまけのおまけ:色んなconst修飾子の使い方

“const”修飾子は、付ける場所によって、少しだけ違う働きをします。

付ける場所説明
変数の前定数の定義。const int n = 16;
メンバ関数の引数の前メンバ関数内で付けた引数の値が変化しない。void set_num(const int num) {…}
メンバ関数の()の後メンバ関数内で、メンバ変数の値が変化しない。private:
str = “あいう”;
public:
string get_str()const {…}

コンストラクタとは –インスタンスの生成時に自動的に呼び出される特別な関数

class Column {
/**略**/
public:
	/* メンバ関数の宣言 */
	//コンストラクタ
	Column();
/**略****************/

まずは、ここで宣言しています。

コンストラクタ』とは、クラスからインスタンスを生成する時に、自動的に呼び出される特別な関数です。

インスタンスを初期化をするために使われます。

コンストラクタが、クラス名と同じ名前で宣言されているのにお気づきでしょうか? コンストラクタ名は、そのクラスと同じ名前で定義します。

省略可能で、その場合は初期化せずにインスタンスを生成します。

コンストラクタ関数の本体は、メンバ関数同様の方法で行いますが、コンストラクタに戻り値は存在しないため、コンストラクタ名の前に”データ型”は付きません。

ただし、コンストラクタ関数も引数は受け取ることができます。

引数は、クラスからインスタンスを生成する際に渡し、その引数を使ってインスタンスを初期化します。

先ほどのソースコードの該当部分を直すとこんな感じ。

/*******略*******************/
/**************************
* 円柱クラス
*/
class Column {
/*
*略
*/
public:
	/* メンバ関数の宣言 */
	//コンストラクタ
	Column(int _x,int _y, int _z,double _radius, double _height,string _color);
/*
*略
*/
};
/*
*略
*/
int main() {
	// クラスのインスタンスを生成
	Column clm(0, 0, 0, 3.5, 10, "red");
/*
*略
*/
        return 0;
}

/*Columnクラスのメンバ関数の定義********/
// コンストラクタの定義
Column::Column(int _x, int _y, int _z, double _radius, double _height, string _color) {
	cout << "円柱を一つ建設しました。" << endl;
	// インスタンスの初期化
	x = _x; // x座標
	y = _y; // y座標
	z = _z; // z座標
	radius = _radius; // 半径
	height = _height; // 高さ
	color = _color; // 色
	volume = radius * radius * PI * height;// 体積
}
/*******略*******************/

デストラクタ –インスタンスの消滅時に自動的に呼び出される特別な関数

class Column {
/**略**/
public:
	/* メンバ関数の宣言 */
	//コンストラクタ
	Column();
	// デストラクタ
	~Column();
/**略****************/

名前がなんとなく『コンストラクタ』の反対な感じ…という印象を持たれている方、Exactly(そのとおりでございます)

デストラクタ』は、コンストラクタの逆の役割を持つ存在です。

つまり、『コンストラクタ』が初期化時に呼び出されるのに対し、その逆、『デストラクタ』は、そのインスタンスが消滅するときに、自動的に呼び出される関数です。

クラス名と同じ名前の関数名の先頭に、”~”が付いていればデストラクタ関数になります。

そして、戻り値がない点はコンストラクタ関数と同じなのですが、引数は、コンストラクタ関数と違い受け取りません。

あとがき

あー!やっと、ちょっと定数紹介できた!!!

けど、new演算子とdelete演算子を紹介できなかった(´・ω・`)

クラス、なんかちょっと間違ったこと書いちゃわないか不安で、慎重になり気味です(^_^;)

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

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

Pocket