情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引

第12章 代入と初期化

  1. 12.1 代入
    1. (プログラム例 12.1 ) 代入( new を使用しない場合)
    2. (プログラム例 12.2 ) 代入( new を使用する場合)
    3. (プログラム例 12.3 ) 代入問題の解決( = のオーバーロード)
  2. 12.2 初期化
    1. (プログラム例 12.4 ) 初期化( new を使用しない場合)
    2. (プログラム例 12.5 ) 初期化( new を使用する場合)
    3. (プログラム例 12.6 ) 初期化問題の解決(初期化用のコンストラクタ)
    4. (プログラム例 12.7 ) 行列の乗算

12.1 代入

  初期化とは,まだ何も値の入っていないオブジェクトに値を設定することであり,また,代入とは,オブジェクトが記憶している値を捨てて新しい値と置き換えることです.new 演算子によってメモリを確保していないようなオブジェクトにおいては,いずれの場合も期待したとおりに働き,特に気にかける必要はありません.

  ここで,「期待したとおり」というのは以下のような意味です.例えば,a を b に代入したとします( a,b: クラスや基本型の変数).当然,a の値と b の値は等しくなり,かつ,代入後,a( b )の値を変化させたても,b( a )の値に影響を与えないといった意味です.プログラム例 12.1 とその実行結果を見てください.Complex 型および Vector1 型の変数に対しては,期待通りの結果が得られています.代入の際,a の値のコピーが b に代入されるわけですから,当然の結果です.

(プログラム例 12.1 ) 代入( new を使用しない場合)

/*******************************/
/* 代入(newを使用しない場合) */
/*      coded by Y.Suganuma    */
/*******************************/
#include <iostream>
using namespace std;

/***********************/
/* クラスComplexの定義 */
/***********************/
class Complex {
	public:
		double r, i;
		Complex (double r1, double i1)     // コンストラクタ
		{
			r = r1;
			i = i1;
		}
};

/***********************/
/* クラスVector1の定義 */
/***********************/
class Vector1 {
	public:
		int n;
		double x[2];
		Vector1 (int n1, double x1, double x2)     // コンストラクタ
		{
			n    = n1;
			x[0] = x1;
			x[1] = x2;
		}
};

/************/
/* main関数 */
/************/
int main()
{
	Complex c1(1, 2), c2(3, 4);
	Vector1 v1(1, 0.0, 0.0), v2(2, -1.0, -2.0);
						// 代入前
	cout << endl;
	cout << "      c1(代入前) " << c1.r << " " << c1.i << endl;
	cout << "      c2(代入前) " << c2.r << " " << c2.i << endl;
	cout << "      v1(代入前) " << v1.n << " " << v1.x[0] << " " << v1.x[1] << endl;
	cout << "      v2(代入前) " << v2.n << " " << v2.x[0] << " " << v2.x[1] << endl;
						// 代入
	c1 = c2;
	v1 = v2;
						// 代入後
	cout << endl;
	cout << "      c1(c2をc1に代入後) " << c1.r << " " << c1.i << endl;
	cout << "      c2(c2をc1に代入後) " << c2.r << " " << c2.i << endl;
	cout << "      v1(v2をv1に代入後) " << v1.n << " " << v1.x[0] << " " << v1.x[1] << endl;
	cout << "      v2(v2をv1に代入後) " << v2.n << " " << v2.x[0] << " " << v2.x[1] << endl;
						// 値の変更
	c2.r = -1.5;
	v2.x[0] = 1.5;
						// 変更後の値
	cout << endl;
	cout << "      c1(c2.rを-1.5に変更後) " << c1.r << " " << c1.i << endl;
	cout << "      c2(c2.rを-1.5に変更後) " << c2.r << " " << c2.i << endl;
	cout << "      v1(v2.x[0]を1.5に変更後) " << v1.n << " " << v1.x[0] << " " << v1.x[1] << endl;
	cout << "      v2(v2.x[0]を1.5に変更後) " << v2.n << " " << v2.x[0] << " " << v2.x[1] << endl;

	return 0;
}
		
(出力)
      c1(代入前) 1 2
      c2(代入前) 3 4
      v1(代入前) 1 0 0
      v2(代入前) 2 -1 -2

      c1(c2をc1に代入後) 3 4
      c2(c2をc1に代入後) 3 4
      v1(v2をv1に代入後) 2 -1 -2
      v2(v2をv1に代入後) 2 -1 -2

      c1(c2.rを-1.5に変更後) 3 4
      c2(c2.rを-1.5に変更後) -1.5 4
      v1(v2.x[0]を1.5に変更後) 2 -1 -2
      v2(v2.x[0]を1.5に変更後) 2 1.5 -2
			
  このプログラムを実行すると,以下のような結果が得られます.A ~ J の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.また.小数点以下が 0 の場合は,小数点以下を入力しないでください.

  ただし,ポインタを使用している場合(例えば,new 演算子を使用しているような場合)は,次節で述べる初期化の場合と同様,注意が必要です.プログラム例 12.2 において,Vector2 型の変数 v4 を v3 に代入しています.代入後は,結果からも明らかなように,v3 と v4 は全く同じ値になっています.ところが,v4 の n を 5 に,また,v4 の x[0] を 1.5 に変化させた場合の結果を見てください.n については期待通りの結果となっています.しかし,x[0] の値は,v4 だけでなく v3 においても変化しています.

(プログラム例 12.2 ) 代入( new を使用する場合)

/*****************************/
/* 代入(newを使用する場合) */
/*      coded by Y.Suganuma  */
/*****************************/
#include <iostream>
using namespace std;

/***********************/
/* クラスVector2の定義 */
/***********************/
class Vector2 {
	public:
		int n;
		double *x;
		Vector2 (int n1, double x1, double x2)     // コンストラクタ
		{
			n    = n1;
			x    = new double [2];
			x[0] = x1;
			x[1] = x2;
		}
//		~Vector2 () {delete [] x;}   // デストラクタ
};

/************/
/* main関数 */
/************/
int main()
{
	Vector2 v3(3, 0.0, 0.0), v4(4, -1.0, -2.0);
						// 代入前
	cout << endl;
	cout << "      v3(代入前) " << v3.n << " " << v3.x[0] << " " << v3.x[1] << endl;
 	cout << "      v4(代入前) " << v4.n << " " << v4.x[0] << " " << v4.x[1] << endl;
						// 代入
	v3 = v4;
						// 代入後
	cout << endl;
	cout << "      v3(v4をv3に代入後) " << v3.n << " " << v3.x[0] << " " << v3.x[1] << endl;
	cout << "      v4(v4をv3に代入後) " << v4.n << " " << v4.x[0] << " " << v4.x[1] << endl;
						// 値の変更
	v4.n    = 5;
	v4.x[0] = 1.5;
						// 変更後の値(v3.x[0]も変化してしまう)
						// (デストラクタが定義されていると実行時にエラー)
	cout << endl;
	cout << "      v3(v4.nを5,v4.x[0]を1.5に変更後) " << v3.n << " " << v3.x[0] << " " << v3.x[1] << endl;
	cout << "      v4(v4.nを5,v4.x[0]を1.5に変更後) " << v4.n << " " << v4.x[0] << " " << v4.x[1] << endl;

	return 0;
}
		
(出力)
      v3(代入前) 3 0 0
      v4(代入前) 4 -1 -2

      v3(v4をv3に代入後) 4 -1 -2
      v4(v4をv3に代入後) 4 -1 -2

      v3(v4.nを5,v4.x[0]を1.5に変更後) 4 1.5 -2
      v4(v4.nを5,v4.x[0]を1.5に変更後) 5 1.5 -2
			
  このプログラムを実行すると,以下のような結果が得られます.A ~ F の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.また.小数点以下が 0 の場合は,小数点以下を入力しないでください.

  これは,v3 と v4 の x が同じ領域を指しているためです.もちろん,このままの状態でも,同じ領域を指していることを認識し,かつ,v3 および v4 の使用上,不都合がなければ特に問題ありません.しかし,使用上不都合である場合も多く存在します.さらに,プログラム例 12.2 ではコメントになっており実行されていませんが,このコメントをはずし,デストラクタを実行するようにすると実行時のエラーが出てしまいます.デストラクタは,v3 と v4 が不必要になった時点で起動します.まず,最初に,v3 に対してデストラクタが起動されたとします.この際は,特に問題は起こりません.しかし,次に v4 に対してデストラクタが起動されたとき,既に v3 に対する起動によって削除された x に対する領域を再度削除しようとします.これが,実行時におけるエラーの原因です.

  上に述べた代入の問題を解決するための手段の一つが,プログラム例 12.3 のように,代入演算子をオーバーロードする方法です.代入演算子のオーバーロードにおいては,既にある領域を開放し,新しい領域を確保し,かつ,そこに代入するデータをコピーしています.

(プログラム例 12.3 ) 代入問題の解決( = のオーバーロード)

/****************************/
/* 代入問題の解決           */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
using namespace std;

/***********************/
/* クラスVector2の定義 */
/***********************/
class Vector2 {
	public:
		int n;
		double *x;
		Vector2 (int n1, double x1, double x2)     // コンストラクタ
		{
			n    = n1;
			x    = new double [2];
			x[0] = x1;
			x[1] = x2;
		}
		~Vector2 () {delete [] x;}   // デストラクタ
		Vector2& operator= (const Vector2 &);   // =のオーバーロード
};

/*******************************/
/* =のオーバーロード          */
/*      a = b (a.operator=(b)) */
/*******************************/
Vector2& Vector2::operator= (const Vector2 &b)
{
	if (&b == this)                 // 自分自身への代入を防ぐ
		return *this;
	else {
		delete [] x;                // 代入する前のメモリを解放
		x    = new double [2];      // メモリの確保
		n    = b.n;                 // 値の代入
		x[0] = b.x[0];
		x[1] = b.x[1];
		return *this;
	}
}

/************/
/* main関数 */
/************/
int main()
{
	Vector2 v3(3, 0.0, 0.0), v4(4, -1.0, -2.0);
						// 代入前
	cout << endl;
	cout << "      v3(代入前) " << v3.n << " " << v3.x[0] << " " << v3.x[1] << endl;
	cout << "      v4(代入前) " << v4.n << " " << v4.x[0] << " " << v4.x[1] << endl;
						// 代入
	v3 = v4;
						// 代入後
	cout << endl;
	cout << "      v3(v4をv3に代入後) " << v3.n << " " << v3.x[0] << " " << v3.x[1] << endl;
	cout << "      v4(v4をv3に代入後) " << v4.n << " " << v4.x[0] << " " << v4.x[1] << endl;
						// 値の変更
	v4.n    = 5;
	v4.x[0] = 1.5;
						// 変更後の値
	cout << endl;
	cout << "      v3(v4.nを5,v4.x[0]を1.5に変更後) " << v3.n << " " << v3.x[0] << " " << v3.x[1] << endl;
	cout << "      v4(v4.nを5,v4.x[0]を1.5に変更後) " << v4.n << " " << v4.x[0] << " " << v4.x[1] << endl;

	return 0;
}
		
(出力)
      v3(代入前) 3 0 0
      v4(代入前) 4 -1 -2

      v3(v4をv3に代入後) 4 -1 -2
      v4(v4をv3に代入後) 4 -1 -2

      v3(v4.nを5,v4.x[0]を1.5に変更後) 4 -1 -2
      v4(v4.nを5,v4.x[0]を1.5に変更後) 5 1.5 -2
			
  このプログラムを実行すると,以下のような結果が得られます.A ~ F の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.また.小数点以下が 0 の場合は,小数点以下を入力しないでください.

12.2 初期化

  プログラム例 12.4 を見てください.代入の場合と同様,Complex 型や Vector1 型の変数に対しては,
Complex c3 = c4;
Vector1 v5 = v6;		
の初期化によって,特に問題となるようなことは発生しません.

(プログラム例 12.4 ) 初期化( new を使用しない場合)

/*********************************/
/* 初期化(newを使用しない場合) */
/*      coded by Y.Suganuma      */
/*********************************/
#include <iostream>
using namespace std;

/***********************/
/* クラスComplexの定義 */
/***********************/
class Complex {
	public:
		double r, i;
		Complex (double r1, double i1)     // コンストラクタ
		{
			r = r1;
			i = i1;
		}
};

/***********************/
/* クラスVector1の定義 */
/***********************/
class Vector1 {
	public:
		int n;
		double x[2];
		Vector1 (int n1, double x1, double x2)     // コンストラクタ
		{
			n    = n1;
			x[0] = x1;
			x[1] = x2;
		}
};

/************/
/* main関数 */
/************/
int main()
{
	Complex c4(3, 4);
	Complex c3 = c4;
	Vector1 v6(2, -1.0, -2.0);
	Vector1 v5 = v6;
					// 変更前
	cout << endl;
	cout << "      c3(変更前) " << c3.r << " " << c3.i << endl;
	cout << "      c4(変更前) " << c4.r << " " << c4.i << endl;
	cout << "      v5(変更前) " << v5.n << " " << v5.x[0] << " " << v5.x[1] << endl;
	cout << "      v6(変更前) " << v6.n << " " << v6.x[0] << " " << v6.x[1] << endl;
					// 値の変更
	c4.r    = -1.5;
	v6.x[0] = 1.5;
					// 変更後の値
	cout << endl;
	cout << "      c3(c4.rを-1.5に変更後) " << c3.r << " " << c3.i << endl;
	cout << "      c4(c4.rを-1.5に変更後) " << c4.r << " " << c4.i << endl;
	cout << "      v5(v6.x[0]を1.5に変更後) " << v5.n << " " << v5.x[0] << " " << v5.x[1] << endl;
	cout << "      v6(v6.x[0]を1.5に変更後) " << v6.n << " " << v6.x[0] << " " << v6.x[1] << endl;

	return 0;
}
		
(出力)
      c3(変更前) 3 4
      c4(変更前) 3 4
      v5(変更前) 2 -1 -2
      v6(変更前) 2 -1 -2

      c3(c4.rを-1.5に変更後) 3 4
      c4(c4.rを-1.5に変更後) -1.5 4
      v5(v6.x[0]を1.5に変更後) 2 -1 -2
      v6(v6.x[0]を1.5に変更後) 2 1.5 -2
			
  このプログラムを実行すると,以下のような結果が得られます.A ~ J の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.また.小数点以下が 0 の場合は,小数点以下を入力しないでください.

  ところが,new を使用する場合,プログラム例 12.5 の Vector2 型の変数に対して,代入の場合と同様の問題が発生してしまいます.

(プログラム例 12.5 ) 初期化( new を使用する場合)

/*******************************/
/* 初期化(newを使用する場合) */
/*      coded by Y.Suganuma    */
/*******************************/
#include <iostream>
using namespace std;

/***********************/
/* クラスVector2の定義 */
/***********************/
class Vector2 {
	public:
		int n;
		double *x;
		Vector2 (int n1, double x1, double x2)     // コンストラクタ
		{
			n    = n1;
			x    = new double [2];
			x[0] = x1;
			x[1] = x2;
		}
//		~Vector2 () {delete [] x;}   // デストラクタ
};

/************/
/* main関数 */
/************/
int main()
{
	Vector2 v8(4, -1.0, -2.0);
	Vector2 v7 = v8;
					// 変更前
	cout << endl;
	cout << "      v7(変更前) " << v7.n << " " << v7.x[0] << " " << v7.x[1] << endl;
	cout << "      v8(変更前) " << v8.n << " " << v8.x[0] << " " << v8.x[1] << endl;
					// 値の変更
	v8.n    = 5;
	v8.x[0] = 1.5;
					// 変更後の値(v7.x[0]も変化してしまう)
					// (デストラクタが定義されていると実行時にエラー)
	cout << endl;
	cout << "      v7(v8.nを5,v8.x[0]を1.5に変更後) " << v7.n << " " << v7.x[0] << " " << v7.x[1] << endl;
	cout << "      v8(v8.nを5,v8.x[0]を1.5に変更後) " << v8.n << " " << v8.x[0] << " " << v8.x[1] << endl;

	return 0;
}
		
(出力)
      v7(変更前) 4 -1 -2
      v8(変更前) 4 -1 -2

      v7(v8.nを5,v8.x[0]を1.5に変更後) 4 1.5 -2
      v8(v8.nを5,v8.x[0]を1.5に変更後) 5 1.5 -2			
  このプログラムを実行すると,以下のような結果が得られます.A ~ F の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.また.小数点以下が 0 の場合は,小数点以下を入力しないでください.

  この問題は,プログラム例 12.6 に示すように,初期化用のコンストラクタを定義することによって解決できます.

(プログラム例 12.6 ) 初期化問題の解決(初期化用のコンストラクタ)
/****************************/
/* 初期化問題の解決         */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
using namespace std;

/***********************/
/* クラスVector2の定義 */
/***********************/
class Vector2 {
	public:
		int n;
		double *x;
		Vector2 (int n1, double x1, double x2)     // コンストラクタ
		{
			n    = n1;
			x    = new double [2];
			x[0] = x1;
			x[1] = x2;
		}
		Vector2 (const Vector2 &b)   // 初期化のためのコンストラクタ
		{
			x    = new double [2];      // メモリの確保
			n    = b.n;                 // 値の代入
			x[0] = b.x[0];
			x[1] = b.x[1];
		}
		~Vector2 () {delete [] x;}   // デストラクタ
		Vector2& operator= (const Vector2 &);   // =のオーバーロード
};

/************/
/* main関数 */
/************/
int main()
{
	Vector2 v8(4, -1.0, -2.0);
	Vector2 v7 = v8;
					// 変更前
	cout << endl;
	cout << "      v7(変更前) " << v7.n << " " << v7.x[0] << " " << v7.x[1] << endl;
	cout << "      v8(変更前) " << v8.n << " " << v8.x[0] << " " << v8.x[1] << endl;
					// 値の変更
	v8.n    = 5;
	v8.x[0] = 1.5;
					// 変更後の値
	cout << endl;
	cout << "      v7(v8.nを5,v8.x[0]を1.5に変更後) " << v7.n << " " << v7.x[0] << " " << v7.x[1] << endl;
	cout << "      v8(v8.nを5,v8.x[0]を1.5に変更後) " << v8.n << " " << v8.x[0] << " " << v8.x[1] << endl;

	return 0;
}
		
(出力)
      v7(変更前) 4 -1 -2
      v8(変更前) 4 -1 -2

      v7(v8.nを5,v8.x[0]を1.5に変更後) 4 -1 -2
      v8(v8.nを5,v8.x[0]を1.5に変更後) 5 1.5 -2			
  このプログラムを実行すると,以下のような結果が得られます.A ~ F の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.また.小数点以下が 0 の場合は,小数点以下を入力しないでください.

  代入,初期化の適用例として,行列に対する演算の例を下に挙げておきます.

(プログラム例 12.7 ) 行列の乗算

  この例では,n 行 m 列行列と m 行 l 列行列( n,m,l は任意)の乗算を,演算子のオーバーロード機能を利用して実現しています.その際,初期化や代入の問題も考慮しています.

/****************************/
/* 行列の乗算               */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>
#include <stdlib.h>

/**********************/
/* クラスMatrixの定義 */
/**********************/
class Matrix {       /* 2次元行列 */
		int n;              // 行の数
		int m;              // 列の数
		double **mat;       // 行列本体
	public:
		Matrix(int, int);   // コンストラクタ(引数あり)
		Matrix(const Matrix &);   // 初期化のためのコンストラクタ
		Matrix() {n = 0;}   // コンストラクタ(引数無し)
		~Matrix()
		{
			if (n > 0) {
				for (int i1 = 0; i1 < n; i1++)
					delete [] mat[i1];
				delete [] mat;
			}
		}
		void input();       // 入力
		void output();      // 出力
		Matrix& operator= (const Matrix &);   // =のオーバーロード
	friend Matrix operator* (const Matrix &, const Matrix &);   // *のオーバーロード
};

/******************************/
/* コンストラクタ(引数あり) */
/******************************/
Matrix::Matrix(int n1, int m1)
{
	n    = n1;
	m    = m1;
	mat  = new double * [n];
	for (int i1 = 0; i1 < n; i1++)
		mat[i1] = new double [m];
}

/********************************/
/* 初期化のためのコンストラクタ */
/********************************/
Matrix::Matrix(const Matrix &A)
{
	n   = A.n;
	m   = A.m;
	mat = new double * [n];
	for (int i1 = 0; i1 < n; i1++) {
		mat[i1] = new double [m];
		for (int i2 = 0; i2 < m; i2++)
			mat[i1][i2] = A.mat[i1][i2];
	}
}

/********/
/* 入力 */
/********/
void Matrix::input()
{
	for (int i1 = 0; i1 < n; i1++) {
		printf("%d行目のデータ\n", i1+1);
		for (int i2 = 0; i2 < m; i2++) {
			printf("   %d列目の値は?", i2+1);
			scanf("%lf", &mat[i1][i2]);
		}
	}
}

/********/
/* 出力 */
/********/
void Matrix::output()
{
	for (int i1 = 0; i1 < n; i1++) {
		for (int i2 = 0; i2 < m; i2++)
			printf("%f ", mat[i1][i2]);
		printf("\n");
	}
}

/**********************/
/* *のオーバーロード */
/**********************/
Matrix operator* (const Matrix &A, const Matrix &B)
{
	Matrix C;
	if (A.m != B.n) {
		printf("***error  invalid data\n");
		exit(1);
	}
	else {
		C = Matrix(A.n, B.m);
		for (int i1 = 0; i1 < A.n; i1++) {
			for (int i2 = 0; i2 < B.m; i2++) {
				C.mat[i1][i2] = 0.0;
				for (int i3 = 0; i3 < A.m; i3++)
					C.mat[i1][i2] += A.mat[i1][i3] * B.mat[i3][i2];
			}
		}
	}
	return C;
}

/*******************************/
/* =のオーバーロード          */
/*      a = b (a.operator=(b)) */
/*******************************/
Matrix& Matrix::operator= (const Matrix &B)
{
	if (&B != this) {   // 自分自身への代入を防ぐ
		if (n > 0) {   // 代入する前のメモリを解放
			for (int i1 = 0; i1 < n; i1++)
				delete [] mat[i1];
			delete [] mat;
		}
		n   = B.n;
		m   = B.m;
		mat = new double * [n];   // メモリの確保と代入
		for (int i1 = 0; i1 < n; i1++) {
			mat[i1] = new double [m];
			for (int i2 = 0; i2 < m; i2++)
				mat[i1][i2] = B.mat[i1][i2];
		}
	}
	return *this;
}

/************/
/* main関数 */
/************/
int main()
{
	int n, m, l;

	printf("行列Aの行と列の数は? ");
	scanf("%d %d", &n, &m);
	printf("行列Bの列の数は? ");
	scanf("%d", &l);

	Matrix A(n, m);
	Matrix B(m, l);
	Matrix C;

	printf("行列A\n");
	A.input();
	printf("行列B\n");
	B.input();

	C = A * B;

	C.output();

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
行列Aの行と列の数は? 3 2
行列Bの列の数は? 2
行列A
1行目のデータ
   1列目の値は?1
   2列目の値は?2
2行目のデータ
   1列目の値は?3
   2列目の値は?4
3行目のデータ
   1列目の値は?5
   2列目の値は?6
行列B
1行目のデータ
   1列目の値は?1
   2列目の値は?0
2行目のデータ
   1列目の値は?0
   2列目の値は?1
1.000000 2.000000
3.000000 4.000000
5.000000 6.000000
		

情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引