ジャンル雑多なゲーム・ゲーム制作関連の色々な情報を取り扱っているブログ。最近はBlenderについてが中心。
[C, C++学習]C, C++言語再学習ノート-13日目- –bool型、値渡し、ポインタ渡し、参照渡し、ポインタと参照の違い比較、インスタンスメンバと静的メンバ

[C, C++学習]C, C++言語再学習ノート-13日目- –bool型、値渡し、ポインタ渡し、参照渡し、ポインタと参照の違い比較、インスタンスメンバと静的メンバ

bool型、値渡し、ポインタ渡し、参照渡し、ポインタと参照の違い比較、インスタンスメンバと静的メンバ —勉強しなおしの記録というか学習ノート-13日目-

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

目次

bool型

他の言語でも出てくるので、他言語習得されている方に説明は不要かと思いますが、一応、書かせていただきます。

bool型とは、真偽値を格納するためのデータ型で、「”true”、”false”(または、それを表す数値”0以外”か、”0″)の2パターンの値を格納する」ための専用の型です。

#include <iostream>

using namespace std;

int main() {
        // bool型の宣言
	bool b = true;

	cout << "bool型で'true': " << b << endl;
	b = false;
	cout << "bool型で'false': " << b << endl;
	b = 1;
	cout << "bool型で'1': " << b << endl;
	b = 0;
	cout << "bool型で'0': " << b << endl;
	b = -1;
	cout << "bool型で'-1': " << b << endl;
	
	return 0;
}

実行結果↓

bool型で'true': 1
bool型で'false': 0
bool型で'1': 1
bool型で'0': 0
bool型で'-1': 1

実はC言語でも使えたbool型

C99から標準ライブラリ”stdbool.h”で、使えるらしいのでおまけ的に載せておきます。

#include <stdio.h>
#include <stdbool.h>


void main(int argc, char* argv[]) {
	bool b = true;
	printf("%d", b);
}

実行結果↓

1

値渡し、ポインタ渡し、参照渡し

※少しややこしいのですが、C言語で行っていた参照渡しは、C++言語ではポインタ渡しという扱いになるようです。

『値渡し』、『ポインタ渡し(Cでの参照渡し)』、『参照渡し』の三種類の値の渡し方を書いてみたいと思います。

値渡し

まず、『値渡し』。これは普通に値を渡すだけの、他2種類と違いアドレスを弄ったりしないものです。

値渡しは、いわばその値をコピーして他の変数へ格納している状態。なので、関数の引数へ値渡しして、値の変更を行っても、元々の値には影響しません。

#include <iostream>

using namespace std;

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

int main() {
	int i = 11;
	int j = func1(i);// 値渡し
	cout << "元々の値: " << i << endl;
	cout << "値渡しして帰ってきた値: " << j << endl;
	
	return 0;
}

int func1(int i) {
	i *= 2;
	cout << "func1関数内でiを2倍にした値:" << i << endl;
	return i;
}

実行結果↓

func1関数内でiを2倍にした値:22
元々の値: 11
値渡しして帰ってきた値: 22

これは、Cでも普通に行っていた上、一番理解がしやすい方法だと思います。

ポインタ渡し

※Cの方では、『参照渡し』と呼んでいましたが、C++では、新たな方法が出てきて、そちらが『参照渡し』と呼ばれているので、分かりやすく、C++の方では、こちらのことを今後、『ポインタ渡し』呼びに統一します。

Cの方でも登場した『ポインタ渡し』です。こちらは『値渡し』と違い、値の実体(そもそも変数に実体はありませんがイメージとして)そのものを渡すのではなく、「値を格納している変数のアドレスを渡して、アドレスを格納したポインタ変数からアドレスを辿って参照する」というものになります。(ちょっと文章がわかりにくいかも)

『ポインタ』についてはCの方で説明させていただきましたので割愛いたします。もし、詳しく知りたい方は、よろしければ以前の記事をご覧ください。

#include <iostream>

using namespace std;

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

int main() {
	int i = 11;
	cout << "元々の値: " << i << endl;
	func1(&i);// ポインタ渡し
	cout << "ポインタ渡し後の値: " << i << endl;

	return 0;
}

int func1(int* i) {
	*i *= 2;
	cout << "func1関数内でiを2倍にした値:" << *i << endl;
	return false;
}

実行結果↓

元々の値: 11
func1関数内でiを2倍にした値:22
ポインタ渡し後の値: 22

『参照渡し』の場合、”return”で変更後の値を返さなくても、元の変数の値が既に変わっているので、「”return”使いたくない時」や、「複数の値を変更したい時」などに便利だと思います。

参照渡し

さて、問題なのがこちらの『参照渡し』ですね。問題とは言っても、Cになかった見慣れなさを言っているだけで、そこまで難しいものではありませんのでご安心を!

『参照渡し』とは、『値渡し』とは違い、”アドレス”が絡むという点が『ポインタ渡し』と同じですが、『ポインタ渡し』とは違う点がもちろんある訳です。

『参照渡し』はざっくり言うと「元の変数に別名を付ける*」というものだそうです。

*引用元:C++ 値渡し、ポインタ渡し、参照渡しを使い分けよう – Qiita

『ポインタ渡し』が、「”アドレス”から辿るための渡し方」だとすれば、『参照渡し』は、「そのままの”アドレス”を渡す渡し方」と言えるのではないかと思います。このニュアンスの違いわかりますかね?(^_^;)

それと、『ポインタ』とは違い、『参照』の配列は作れないというのも、違いの1つです。

参照の書式↓

型& 変数名 = 変数;

何はともあれ、百聞は一見に如かず!

ソースコードから読み取ってくださいm(_ _;)m

#include <iostream>

using namespace std;

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

int main() {
	int i = 11;
	cout << "元々の値: " << i << endl;
	func1(i);// 参照渡し
	cout << "参照渡し後の値: " << i << endl;
	
	return 0;
}

int func1(int& i) {
	i *= 2;
	cout << "func1関数内でiを2倍にした値:" << i << endl;
	return 0;
}

実行結果↓

元々の値: 11
func1関数内でiを2倍にした値:22
参照渡し後の値: 22

私自身、この参照渡しとポインタ渡しの違いがこんがらがりそうで怖いので、メモとして比較みたいなものを書いておきます。

ポインタと参照の違い

#include <iostream>

using namespace std;

int main() {
	int i = 100;
	int j = 100;
	int& ref = i;
	int* poi = 0;
	poi = &j;
	
	ref++;
	(*poi)++;
	
	cout << i << " : " << ref << endl;
	cout << &i << " : " << &ref << endl;
	cout << "------------------------" << endl;
	cout << j << " : " << *poi << endl;
	cout << &j << " : " << poi << endl;

	return 0;
}

実行結果↓

101 : 101
010FFBA0 : 010FFBA0
------------------------
101 : 101
010FFB94 : 010FFB94

ちなみに、関数の引数へ渡すのではなく、参照それ自体を引数として独立して扱うやり方を、『独立参照』というようです。(※こういうやり方をするのは稀なようです。)

名称格納する対象格納時の記述値にアクセス値の格納されているアドレスにアクセス
ポインタアドレス、ポインタポインタ変数 = &変数名;*ポインタ変数ポインタ変数
参照アドレス&参照 = 変数;参照&参照

『ポインタ』の方は、明確に普通の変数と扱いが違うのに対し、『参照』の方は、(宣言や値の格納時以外)普通の変数と全く同じ扱いなのがわかるかと思われます。

インスタンスメンバと静的メンバ

前回までのクラスでご紹介したメンバ変数・メンバ関数は、生成されたインスタンスに紐づく形で存在していたので、インスタンスを生成してから消滅するまでの間、尚且つインスタンスを通してしか利用できませんでした。こういった、今まで使っていたインスタンスの生成が必須のメンバを『インタスタンスメンバ』と言ったりします。

Q: じゃあ、それ以外のメンバもあるの?
A: あります!

それ以外のメンバに『静的メンバ』というものがあります。

静的メンバ』とは、『インスタンスメンバ』とは違い、インスタンスを生成せずに扱うことのできるメンバ変数・メンバ関数のことを言います。(アクセスを受け付けてくれる時間が、24時間営業か、営業時間帯が〇時~〇〇時みたいに決まっているか、みたいな感じでというほろほろり個人のイメージです。)

『静的』というので、ピンとくる方もいらっしゃるかもしれませんね。

以前、Cの方の記事で『記憶領域』についてお話ししたと思います。その中の『静的領域(スタティック領域)』にメモリを確保する『記憶クラス指定子』で”static”というものがありました

この”static“を使って、『静的メンバ』を生成する訳です。

静的メンバ変数

  • 静的メンバ変数の宣言
static 型 変数名;
  • 静的メンバ変数の定義と初期化
型 クラス名::変数名;

※静的メンバ変数の初期値の設定は、宣言時以外で行われるのが一般的なようです。

  • 静的メンバ変数への(クラス外からの)アクセス
クラス名::変数名;

静的メンバ変数は、インスタンスから呼ぶのではなく、クラスからアクセスするので、こういう形になるようです。

静的メンバ関数

静的メンバ変数の場合とほとんど変わりませんが、一応書き出しておきます。

  • 静的メンバ関数の宣言
static 型 関数名(引数1, 引数2...);
  • 静的メンバ関数の定義
クラス名::関数名(型 引数1, 型 引数2...){
    //処理を記述
}
  • 静的メンバ関数の呼び出し
クラス名::関数名();

静的メンバ変数・関数共に、「宣言時に”static”が付く」という点と、「呼び出し元がインスタンスではなく、クラスから」という点を認識していれば、特に戸惑うこともないと思います。

静的メンバの利用の際の注意点

静的なメンバ変数・関数から、普通のメンバ変数・関数は扱えないという点にご注意ください。

通常のメンバ変数・関数の場合、インスタンスを生成しなければ利用できないため、インスタンスを生成する前から存在する静的なメンバからは利用できないのです。

ただし、その逆、通常のメンバから、静的なメンバへのアクセスは問題なく行えます。

ちなみに、静的なメンバ同士であっても問題なくアクセスできます。

実際に静的メンバを使ってプログラムを書いてみる

またまた以前作った”円柱クラス”を記述したコードを使いまわしつつ、今回はついでにクラスでファイルを分けてソースコード書いてみました。

※J〇JOネタ(伏せられていない伏字)突っ込んでます。こういうネタがお嫌いな方は、すみませんがブラウザバックか、我慢しつつお付き合い下さいm(_ _)m

column.h

#pragma once // インクルードガード
#include <string>

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

	void set_volume(); // 体積をセットしなおす関数

	static int column_count; // 新たに追加した静的メンバ変数****************************************
public:
	/* メンバ関数の宣言 */
	//コンストラクタ
	Column(int _x, int _y, int _z, double _radius, double _height, string _color);
	// デストラクタ
	~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();

	static int show_column_count(); // 新たに追加した静的メンバ関数********************************
};

column.cpp

#include "Column.h"
#include <iostream>
#include <string>

using namespace std;

// 定数
const double PI = 3.14;

/*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;// 体積

	// 柱の合計数をカウントする静的メンバ変数をインクリメントする****************************************************
	Column::column_count++;
	// 柱の合計数を出力する静的メンバ関数を呼び出す****************************************************************
	Column::show_column_count();
}
// デストラクタの定義
Column::~Column() {
	cout << "円柱を一つ破壊しました。--------------------------" << endl;
	// 柱の合計数をカウントする静的メンバ変数をデクリメントする*****************************************************
	Column::column_count--;
	// 柱の合計数を出力する静的メンバ関数を呼び出す****************************************************************
	Column::show_column_count();
}
// 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;
}

// 柱の合計数をカウントする静的メンバ変数の初期化**************************************************************
int Column::column_count = 0;

// 柱の合計数を出力する静的メンバ関数の定義********************************************************************
int Column::show_column_count() {
	cout << "現在の円柱の数は:"<< column_count << endl;
	switch (column_count) { // 呼び出された時の円柱の数で分岐
	case 0:
		cout << "-そのうちカーズは 考えるのをやめた。" << endl;
			break;
	case 1:
		cout << "このワムウにとって強者だけが真理!勝者だけが正義であり友情" << endl;
			break;
	case 2:
		cout << "う~ううう、あんまりだ…HEEEEYYYY!あァァァんまりだアアアア" << endl;
		break;
	case 3:
		cout << "いいかげんにするんだな この原始人がァ" << endl;
		break;
	}
	return column_count;
}

main.cpp

#include "Column.h"
#include <iostream>

using namespace std;
int main() {
	Column::show_column_count();  // 柱の合計数を出力する静的メンバ関数の定義*********************************************
	Column* pC = 0;  // Columnクラス用ポインタの初期化
	
	cout << "////new演算子で動的にメモリを確保してColmunクラスのインスタンスを生成します。///////////" << endl;
	pC = new Column[3]{
		{0, 0, 0, 15.3, 34.6, "blue"},
		{0, 0, 0, 15.3, 34.6, "red"},
		{0, 0, 0, 15.3, 34.6, "green"}
	};
	
	delete[] pC; // インスタンス消去
	cout << "////delete演算子で動的にメモリを解放してColmunクラスのインスタンスを消滅させました。///////////" << endl;
	
	Column::show_column_count(); // 柱の合計数を出力する静的メンバ関数の定義*********************************************
	
	return 0;
}

実行結果↓

現在の円柱の数は:0
-そのうちカーズは 考えるのをやめた。
////new演算子で動的にメモリを確保してColmunクラスのインスタンスを生成します。///////////
円柱を一つ建設しました。-----------------------
現在の円柱の数は:1
このワムウにとって強者だけが真理!勝者だけが正義であり友情
円柱を一つ建設しました。-----------------------
現在の円柱の数は:2
う~ううう、あんまりだ…HEEEEYYYY!あァァァんまりだアアアア
円柱を一つ建設しました。-----------------------
現在の円柱の数は:3
いいかげんにするんだな この原始人がァ
円柱を一つ破壊しました。--------------------------
現在の円柱の数は:2
う~ううう、あんまりだ…HEEEEYYYY!あァァァんまりだアアアア
円柱を一つ破壊しました。--------------------------
現在の円柱の数は:1
このワムウにとって強者だけが真理!勝者だけが正義であり友情
円柱を一つ破壊しました。--------------------------
現在の円柱の数は:0
-そのうちカーズは 考えるのをやめた。
////delete演算子で動的にメモリを解放してColmunクラスのインスタンスを消滅させました。///////////
現在の円柱の数は:0
-そのうちカーズは 考えるのをやめた。

新たに追加した部分を目立つようにコメントの”*”で示してみました。

ほんの少しコードの解説・説明

まず最初に、いきなり書いてしまったんですが、”column.h”ファイルの一番上の行に書いてある「#pragma once // インクルードガード」という記述について説明します。

これは、今までCの記事の方でも書いていた↓のような記述の二重インクルードされるのをガードするものの代わりに記述する、大抵のコンパイラで提供されている仕組みのコードだそうです。

#ifndef _SHOW_ANSWER_H_
#define _SHOW_ANSWER_H_

#endif // _SHOW_ANSWER_H_

今回、Visual Studio 2019でも動きそうなので使ってみました!

ちなみに、以前、C言語での「ファイル分け」や「プリプロセッサ指令」、「インクルードガード」について書いた記事がありますので、よろしければこちらも一緒にご覧ください。

さて、本編になりますが、特に注目してほしいところを抜粋してみます。

    /*
    略
    */
// コンストラクタの定義
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;// 体積

	// 柱の合計数をカウントする静的メンバ変数をインクリメントする****************************************************
	Column::column_count++;
	// 柱の合計数を出力する静的メンバ関数を呼び出す****************************************************************
	Column::show_column_count();
}
// デストラクタの定義
Column::~Column() {
	cout << "円柱を一つ破壊しました。--------------------------" << endl;
	// 柱の合計数をカウントする静的メンバ変数をデクリメントする*****************************************************
	Column::column_count--;
	// 柱の合計数を出力する静的メンバ関数を呼び出す****************************************************************
	Column::show_column_count();
}
    /*
    略
    */
// 柱の合計数をカウントする静的メンバ変数の初期化**************************************************************
int Column::column_count = 0;

// 柱の合計数を出力する静的メンバ関数の定義********************************************************************
int Column::show_column_count() {
	cout << "現在の円柱の数は:"<< column_count << endl;
	switch (column_count) { // 呼び出された時の円柱の数で分岐
	case 0:
		cout << "-そのうちカーズは 考えるのをやめた。" << endl;
			break;
	case 1:
		cout << "このワムウにとって強者だけが真理!勝者だけが正義であり友情" << endl;
			break;
	case 2:
		cout << "う~ううう、あんまりだ…HEEEEYYYY!あァァァんまりだアアアア" << endl;
		break;
	case 3:
		cout << "いいかげんにするんだな この原始人がァ" << endl;
		break;
	}
	return column_count;
}

『コンストラクタ』、『デストラクタ』が呼ばれるたびに、円柱の数をカウントする静的変数”column_count”を操作して、円柱の数を出力する(数によって変化するセリフ付き)”show_column_count()”関数を呼び出しています。

そして、main関数の方からは2度、↓の静的メンバ関数が呼び出されています。

Column::show_column_count();  // 柱の合計数を出力する静的メンバ関数の定義*********************************************

ですが、どちらもColumnクラスのインスタンスが生成前、あるいは消滅後の、存在しないタイミングで呼び出されているのがお分かりになるでしょうか?

にも拘わらず、動作に問題がないことから、静的メンバがどういった存在か、また、その有用性について、何となくでも理解を深められたのではないかと期待します。

あとがき

やたらめったらソースコードの使いまわしとかしない方がいいんだろうか?と思ったりする今日この頃です。

でも、ちょっと冗長なコードな割に愛着湧いちゃって…でも、創造って、破壊することも時には大切なのですよね…( ◠‿◠ )

また次、何かしらの長めのコード書くってなった時には、違うクラスを作ると思います!

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

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

Pocket

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください