【C++言語】第7回「参照」new,delete,値渡し,ポインタ渡し,参照渡し


本アーティクルは「応用プログラミングA」の2013年5月28日の講義内容の補足です。
内容の正確さは保証しません。
これをそのままコピーしてレポートを書く人がいますが、全く勉強にならないのでやめましょう。

 

問題1「newとdelete」

前回、「C++でのポインタと配列の利用」ではクラスのオブジェクトをいきなり配列にとって、そのポインタを挿げ替えるような初期化を行っていたわけですが、ふつうは(少なくとも最近の言語は)こんな”野蛮な”オブジェクトの生成をしたりはしません。
(もちろんmallocやfreeなどもありますが…)

今回はnew演算子とdelete演算子を使った例を見てみましょう。

// 2013/5/28 Q1
/* new と delete 演算子
 * 三角形を表す Triangle クラスのオブジェクトを、new 演算子を使って作成する。
 * オブジェクトには初期値として、(3,4,5)を与える。
 * 各辺の長さと長さと面積を出力した後に delete でメモリを開放する。
 * なお、 delete 演算子によってデストラクタが呼び出されることを確認するために、
 * デストラクタを追加している。
*/
#include <iostream>
#include <cmath>
using namespace std;
class Triangle {
	double a, b, c; //辺の長さ
public:
	//コンストラクタ
	Triangle(double u, double v, double w) {
		a = u; b = v; c = w;
	}
	//デストラクタ
	~Triangle() {
		cout << "デストラクタが呼び出されました\n";
	}
	void set_a(double x) { a = x; }
	void set_b(double x) { b = x; }
	void set_c(double x) { c = x; }
	double get_a() {return a;}
	double get_b() {return b;}
	double get_c() {return c;}
	double area(); //面積を求める関数
	void show();  //結果を表示する
};
double Triangle::area() {
	double s;
	s = (a+b+c)/2.0f;
	return sqrt(s*(s-a)*(s-b)*(s-c));
}
void Triangle::show() {
	cout << "辺a = " << a << "\n";
	cout << "辺b = " << b << "\n";
	cout << "辺c = " << c << "\n";
	cout << "面積 = " << area() << "\n"; 
} 

int main() { 
	Triangle *tri; 
	tri = new Triangle(3,4,5); 	
/* new演算子はコンストラクタを指定して新しいオブジェクトの実体のポインタを取得する */ 
/* 1行で書くとこんな感じ 
	Triangle *tri = new Triangle(3,4,5); 	*/
	tri->show();
	delete tri;
	return 0;
}

new演算子を使うとメモリの動的確保が行えます。deleteはnewで確保したメモリを開放します。
newとdeleteは演算子でmalloc()やfree()は関数なので書式が異なります。
このサンプルでは

Triangle *tri;
tri = new Triangle(3,4,5);

という書式で、コンストラクタを呼んでクラスのインスタンスを取得しています。

なおnew/deleteを使った配列の場合も覚えておきましょう。たとえばint型10個分の配列を動的確保する場合

int *p;
p = new int[10];
for (int i =0; i<10; i++ ) {
    p[i] = i; 
} 
delete [] p;  //このカッコの付き方に注意 

new演算子を使ってクラスのインスタンスを取得した場合、そのメンバ変数やメンバ関数にアクセスするときはアロー演算子(->)を使用します。

tri->show();

 

問題2「値渡し、ポインタ渡し、参照渡し」

 
C++で関数にオブジェクトを渡す際に、「値渡し」、「ポインタ渡し」、「参照渡し(reference)」といった方法でオブジェクトを渡すことができます。問題2ではこれらを実験するプログラムを作成します。

// 2013/5/28 Q2
/* 値渡し、ポインタ渡し、参照渡し
*/
#include <iostream>
using namespace std;

class Coord {
	double x,y,z;
public:
	void set(double u,double v, double w) {
		x = u; y = v; z = w;
	}
	void show();
};
void Coord::show() {
	cout << "(" << x << "," << y << "," << z << ")";
}
//以下の関数はクラス内のメソッドではなく、オブジェクトの外でやり取りするオペレータ関数
//値渡し
void setCoord1(Coord ob) {
	double u,v,w;
	cout << "数字を3つ入力してください:";
	cin >> u >> v >> w;
	ob.set(u,v,w); //実際にはここで値は設定できないことに注意!
}  //ここでobのスコープが終了するため、設定した値は消滅する。
//ここで生成されたオブジェクトの実体obはobjとは別のメモリ空間にいることに注意

//ポインタ渡し
void setCoord2(Coord *ob) {
  //渡すときは"&obj"、受けるときは"*ob"と書く!
	double u,v,w;
	cout << "数字を3つ入力してください:";
	cin >> u >> v >> w;
	ob->set(u,v,w);  //アロー演算子"->"をつかってメソッドset()を呼ぶ 
}  //ここでobのスコープが終了するため、設定した値は消滅する。
// obは消滅するが、実際のメモリ上のポインタが渡されているため、
// 処理と処理結果はobjに残る

//参照渡し
void setCoord3(Coord &ob) {
  //参照渡しで受け取るときは"&ob"になる。なぜならポインタだから。
	double u,v,w;
	cout << "数字を3つ入力してください:";
	cin >> u >> v >> w;
	ob.set(u,v,w);  //オブジェクトそのものがきているので、"."ドット演算子になる 
}  
//ここでobのスコープが終了するが....
//ポインタ渡しと同様、処理結果はobjに残る

int main() {
	Coord obj;
	obj.set(1.1, 2.3, 5.5);
	cout << "オブジェクトの初期値:";	obj.show(); cout << "\n\n";

	cout << "値渡しで関数にオブジェクトを渡します\n";
	setCoord1(obj);
	cout << "オブジェクトの値:";	obj.show(); cout << "\n\n";

	cout << "ポインタ渡しで関数にオブジェクトを渡します\n";
	setCoord2(&obj);  //アンパサンドを使う scanf(&buf)と同じ! 
                              //関数の入力ではなく受け取り容器
	cout << "オブジェクトの値:";	obj.show(); cout << "\n\n";

	cout << "参照渡しで関数にオブジェクトを渡します\n";
	setCoord3(obj);  //&がつかないので値渡しにそっくりだが、
                             //関数の実装での受け方が違います
	cout << "オブジェクトの値:";	obj.show(); cout << "\n\n";

	return 0;
}

チャック・ノリス問題として出題してみたり。

わかりやすい解説
www.aerith.net/design/argument-j.html

問題3「参照渡しを用いた値の交換」トランプ問題

★レポート締切前なので未完です。

// 2013/5/28 Q3
/* トランプ問題
*/
#include
#include
using namespace std;

enum suit { SPADE, HEART, CLUB, DIAMOND } ;
class PCard {
//from the question
};
//参照渡し仮引数
void swap(PCard &ob1, PCard &ob2) {
	PCard ob;
	ob = ob1;	ob1 = ob2; ob2 = ob;
}

int main() {
	PCard deck[52];
	for ( int i=0; i< 4; i++ ){ //suit
		for (int j =1; j<= 13; j++ ){  //number
			switch(i) {
			case 0:
				deck[i*13+j-1].set(SPADE, j);
				break;
				//★以下すべてのsuitに実施(未実装)
			}
		}
	}
	//カードの初期化終了
	//内容一覧
	for (int i=0; i<52; i++) {
		deck[i].show();
	}

	//シャッフル
	for (int i =0; i<100; i++) {
		int a,b;
		a = rand() % 52;
		b = rand() % 52;
		swap(deck[a], deck[b]);
		//ここにAとBを入れ替えました、的な表示を入れておくとわかりやすい
	}
	//再度内容一覧
	for (int i=0; i<52; i++) {
		deck[i].show();
	}

	return 0;
}