静岡理工科大学 菅沼ホーム 全体目次 演習解答例 付録 索引

第Ⅲ部

第10章 クラス

    10.1 クラス
    1. 10.1.1 クラスの宣言
      1. (プログラム例 10.1 ) クラス宣言
    2. 10.1.2 前送りのクラス宣言(Javaを除く)
    10.2 メンバー関数とフレンド関数
    1. (プログラム例 10.2 ) メンバー関数とフレンド関数
    10.3 コンストラクタとデストラクタ
    1. (プログラム例 10.3 ) 時間データ
    2. (プログラム例 10.4 ) プラントモデル
    3. (プログラム例 10.5 ) ベクトルの内積と絶対値
    4. (プログラム例 10.6 ) リスト構造
    5. (プログラム例 10.7 ) 様々なデータ型の引き渡し
    演習問題10

10.1 クラス

10.1.1 クラスの宣言 

  大きなプログラムの場合,その部分的な機能の修正のため,他の機能に対応する部分も理解し,かつ,場合によってはそれらの一部も修正するなど,プログラム全体にわたって理解し,かつ,修正をしなければならないとしたら,大変なことになります.そこで,プログラムのモジュール化が非常に重要になります.各機能毎にモジュール化し,

  1. そのモジュール内部における詳細な処理を知らなくても,適当なインターフェースを介してデータを渡し,また,インターフェースを介して希望する結果が得られる.

  2. モジュールとのやりとりは,インターフェースを介してのみ可能であり,モジュールの外部から,そのモジュール内の処理を直接コントロールことはできないし,また,コントロールする必要がない.

ようにしておけば,各モジュールは他のモジュールの影響を受けにくくなり,対応する機能の修正はそのモジュールの修正だけで済みます.ある意味では,各モジュールをブラックボックス化するわけです.

  このモジュール化の一つの実現方法が関数です.引数を付けて関数を呼ぶというインターフェースによって,関数内部の処理の詳細を知らなくても結果を得ることができます.また,ローカル変数の存在により,関数外部から関数内部の処理を直接コントロールすることは基本的に不可能です.

  このように,関数もモジュール化の強力な手段ですが,それは,アルゴリズムに重点を置いたブラックボックス化です.しかし,場合によっては,データに重点を置いたブラックボックス化が必要になる場合があります.それが,まさに,C++ のクラスです.クラスでは,データとそれを扱う関数を一つにまとめ,特定のインターフェース(クラス内で宣言された関数)を介してのみ,その内部にアクセスできるようにしています.このような処理をデータの抽象化と呼び,抽象データ型の変数(つまり,あるクラス型として宣言された変数)をオブジェクト(データとそれを操作する手続き-関数-をひとまとめにしたもの,object )と呼びます.

  また,先に説明しましたように,クラスとは,ある「もの」に対し,その「もの」に共通する特徴等をもとにして,形式的な定義を与えたものであるといえます.もう少しプログラミング的な感覚でいえば,「もの」に共通するデータと,それらのデータを処理する方法を記述した手続きの集まりです.例えば,「車」というクラスを定義したとすれば,車の構造を記述するデータと車を動かしたりするのに必要な手続きからなっているはずです.また,クラスは「もの」に対する抽象的な定義ですが,そのインスタンス(オブジェクトとなる)はクラスを具体化したものに相当します.例えば,「車」クラスの場合であれば,そのインスタンスは,ある特定の人が所有する特定の車になります.

  同じプログラムであっても,アルゴリズムに重点を置いたモジュール化を行うのか,または,データに重点を置いたモジュール化を行うのかによって,書き方はかなり異なってきます.問題によって,より適した方法があるはずです.以下の節で述べるクラスに関する詳細説明やプログラム例を見ながら検討してみて下さい.

  クラスは,その定義の方法から見て,ユーザーが新たに定義する変数の型と考えても良いと思います.新しい変数の型を定義すれば,その型の操作方法も必要になります.例えば,実部と虚部という 2 つのデータからなる複素数に対応する型をクラスによって定義したとします.すると,単純な加算ですら,既存の方法,つまり,int 型や double 型と同じような方法を使用することができません.従って,定義した変数の加算をどのようにして実行するかについても定義してやる必要が出てきます.そこで,クラスの定義には,単にデータだけでなく,そのデータを取り扱う方法を記述した関数が必要になってくるわけです.

  クラスclass )は,C++ の最も重要な概念です.基本的には構造体の拡張であり,キーワードの違いを除けば,その宣言方法も構造体と同じです.一般的な表現方法をすると,例えばクラス Example ( Example: クラス識別子)を,
	class Example {
			int x;                // プライベートメンバー変数
			 ・・・
		protected:
			int y;                // 派生クラスだけが使用可能なメンバー変数
			 ・・・
		public:
			int z;                // パブリックメンバー変数
			 ・・・
		private:
			double fun1(int);     // プライベートメンバー関数
			 ・・・
		protected:
			double fun2(int);     // 派生クラスだけが使用可能なメンバー関数
			 ・・・
		public:
			double fun3(int);     // パブリックメンバー関数
			 ・・・
		friend double fun4(int); // フレンド関数
		 ・・・
		friend class Example2;   // フレンドクラス
		 ・・・
	};
		
のように宣言します.ただし,上の例における public,private 等に対応するすべてのメンバーmember )を所有する必要はありません.public 等を記述しないと,クラス宣言のメンバーがプライベートとみなされる点以外,記述する順番も一般的には決まっていません.なお,クラス識別子のあとに記述される項目(派生クラスの宣言)も存在しますが,それらについては後ほど説明します.

  private,及び,protected の後に記述されたメンバーは,定義されたクラスのメンバー関数,フレンド関数,および,フレンドクラスだけから参照可能です.ただし,派生クラスの場合は,多少複雑になります.詳細については 13 章を参照してください.また,public の後に記述されたメンバーは,どこからでも参照可能です.C/C++ の通常の関数や別のクラスに対して,メンバー関数と同様のアクセス権を与えようとしたものが,フレンド関数やフレンドクラスの宣言です.なお,メンバー関数の本体は,クラス定義の中に記述することも,クラス定義の本体では関数の宣言だけを行いクラス宣言の外側(別のファイルでも構わない)に記述することも可能です.

  上の定義からも明らかなように,構造体との大きな違いは,メンバーとして関数を持てる点です.さらに,構造体ではそのメンバーをどの関数からでもアクセスできましたが,クラスでは,メンバーを private,public,及び,protected で分類し,各メンバーをアクセスできる範囲を限定しています.C++ は,構造体を,パブリックメンバー変数だけを持つ特殊なクラスと見なしていますので,後に述べるコンストラクタ,派生クラス等,クラスに特有な機能を構造体に対してもそのまま適用できます.

  上のようにして宣言されたクラスのオブジェクトを生成(クラス Example のインスタンスを生成,Example 型の変数の定義)し,そのメンバー変数やメンバー関数を参照するには,構造体と同様,基本的に以下のようにして行います.
	Example ex;   // Example 型オブジェクトの生成
	EXample *p_ex = new Example;   // p_ex は Example 型オブジェクトへのポインタ
	ex.x = 20;   // メンバー変数 x の参照
	y    = p_ex->func(10);   // ポインターによるメンバー関数 func の参照
		

(プログラム例 10.1 ) クラス宣言 

  クラスの宣言とメンバー変数やメンバー関数の参照方法に関する例です.

/****************************/
/* クラスの宣言             */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>

/***********************/
/* クラスExampleの宣言 */
/***********************/
class Example {

		int x;   // private メンバー変数

	public:

		int y;   // public メンバー変数

		void v_set1()   // public メンバー関数
		{
			x = 10;   // メンバー関数からは参照可能
			y = 20;
		}

		void v_set2()   // public メンバー関数
		{
			x = 30;   // メンバー関数からは参照可能
			y = 40;
		}

		void output()   // public メンバー関数
		{
			std::cout << "   x = " << x << ",  y = " << y << std::endl;
		}
};   // 「;」が必要なことに注意

/*************/
/* main 関数 */
/*************/
int main()
{
	Example t1;   // Example 型オブジェクト
	Example *t2 = new Example;   // Example 型オブジェクトへのポインタ

	t1.v_set1();   // メンバー関数の呼び出し
	t2->v_set2();   // メンバー関数の呼び出し

	std::cout << "オブジェクト t1\n";
	std::cout << "   y = " << t1.y << std::endl;   // 変数xへのアクセスはできない
	t1.output();
	std::cout << "オブジェクト t2\n";
	std::cout << "   y = " << t2->y << std::endl;   // 変数xへのアクセスはできない
	t2->output();

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
オブジェクト t1
   y = 20
   x = 10,  y = 20
オブジェクト t2
   y = 40
   x = 30,  y = 40
		

10.1.2 前送りのクラス宣言(Javaを除く)

  あるクラス C1 で別のクラス C2 を参照し,かつ,クラス C2 側でもクラス C1 を参照しているように,クラスどうしが相互参照するような場合は,どちらを先に宣言してもエラーになります.この場合,次のように前送りのクラス宣言forward class declaration ),または,不完全なクラス宣言imcomplete class declaration )をする事によって解決できます.
	class C2;
	class C1 {C2 *p; ・・・};
	class C2 {C1 *q; ・・・};
		
ただし,各クラスで他のクラスを参照できるのは,ポインタと参照の定義に限られます.

10.2 メンバー関数とフレンド関数 

  クラスのメンバーとして宣言された関数をメンバー関数member function )と言います.メンバー関数は,その関数が定義されたクラス型の変数(オブジェクト)のプライベートメンバーにアクセスでき,そのオブジェクトと外部とのインターフェースの役割を果たします.下に,メンバー関数の定義の方法を示します.なお,以下においては,クラス名を Example として説明を行っていきます.
	関数の型 クラス名::関数名 (引数のリスト)
	{
		・・・・・
	}
		
  上の方法は,クラス宣言の外側で定義する方法ですが,クラスの宣言の中に,関数の宣言だけでなくその本体も記述することができます.その場合,その関数はインライン関数とみなされます.

  クラス宣言の中で,
	friend int func(Example &,・・・);
		
のように friend を付加して宣言された関数をフレンド関数friend function )と言います.フレンド関数は,メンバー関数のように,オブジェクトのプライベートメンバーにアクセス可能な点を除けば,普通の関数と同じです.また,他のクラス(例えば,Example1 )のメンバー関数を,
	friend int Example1::func(Example &,・・・);
		
のように宣言し,フレンド関数とすることも可能です.さらに,
	friend class List;
		
のように宣言し,クラス List のすべてをフレンドとし,そのクラス内のすべてのメンバー関数等からプライベートメンバーを参照できるようにすることも可能です.

  メンバー関数とフレンド関数の違いは,次のプログラム例に見るように,関数の呼び出しやメンバーに対するアクセス方法の違いにあります.メンバー関数の場合はメンバー名だけでアクセスできますが,フレンド関数の場合は,クラス名(アドレス)が必要になります.

(プログラム例 10.2 ) メンバー関数とフレンド関数

  次のプログラムは,メンバー関数,フレンド関数,フレンドクラスの違いを示したものです.関数の呼び出し方,及び,メンバーの参照方法に注意して下さい.また,fun2 は普通の関数ですので,クラス Example のパブリック変数 y にアクセスすることは可能ですが,プライベート変数 x を参照しようとするとコンパイルエラーになります.

  なお,メンバー関数の中で使用されている this というキーワードは,メンバー関数の中だけで使用でき,起動されたオブジェクト(この場合は,data )へのポインタを表しています.従って,「 this->y 」( Java の場合は,this.y )と「 y 」は同じ意味になります.このように,メンバー関数の中ではメンバー名でメンバーを直接アクセスできますので必要ありませんが,例えば,

   return *this;

のように,自分自身への参照を返したいとき等に使用されます.

  関数 fun1,fun2,fun3 に対して,Example 型オブジェクトを参照で渡している点に注意してください.この例の場合,関数内で,メンバー変数の値を変えているわけではないので,

   void fun1(Example data)

のような一般的な引数の渡し方で正常に動作します.しかし,その場合は,関数の説明の時に述べましたように,オブジェクトのコピーが渡されます.単純な変数の場合は特に問題ありませんが,大きなオブジェクトの場合は,そのコピーを生成するために余分な時間とメモリを必要とします.このコピー生成の無駄を省くために参照渡しを行っています.もちろん,アドレス(ポインタ)で渡しても構いませんが,オブジェクト内の変数を参照するときに,ピリオドではなく,「->」を使用する必要があり,多少プログラムが読みにくくなります.

/******************************/
/* メンバー関数とフレンド関数 */
/*      coded by Y.Suganuma   */
/******************************/
#include <iostream>

/***********************/
/* クラスExampleの定義 */
/***********************/
class Example {
		int x;
	public:
		int y;
		void set(int, int);
	friend void fun1(Example &);
	friend class Example1;
};

/************************/
/* クラスExample1の定義 */
/************************/
class Example1 {
	public:
		void fun3(Example &data)
		{
			std::cout << "フレンドクラス x " << data.x << " y " << data.y << std::endl;
		}
};

void fun2(Example &);

/************/
/* main関数 */
/************/
int main()
{
	Example data;   // dataがExample型のオブジェクトであることを宣言
	Example1 data1;   // data1がExample1型のオブジェクトであることを宣言

	data.set(10, 20);   // メンバー関数の呼び出し.ピリオドに注意
	fun1(data);         // フレンド関数の呼び出し
	fun2(data);         // 普通の関数の呼び出し
	data1.fun3(data);   // クラスExample1のメンバー関数の呼び出し

	return 0;
}

/*********************************************/
/* メンバー関数                              */
/*      プログラム例10.1のようにクラス定義の */
/*      内部に記述することもできる           */
/*********************************************/
void Example::set(int a, int b)
{
	x = a;
	this->y = b;        // y = b と同じ
	std::cout << "メンバー関数 x " << x << " y " << y << std::endl;
}

/****************/
/* フレンド関数 */
/****************/
void fun1(Example &data)
{
	std::cout << "フレンド関数 x " << data.x << " y " << data.y << std::endl;
}

/**************/
/* 普通の関数 */
/**************/
void fun2(Example &data)
{
	std::cout << "通常の関数 x ??" << " y " << data.y << std::endl;   // xにはアクセスできない
}
		

このプログラムを実行すると,以下に示すような結果が得られます.
メンバー関数 x 10 y 20
フレンド関数 x 10 y 20
通常の関数 x ?? y 20
フレンドクラス x 10 y 20
		

10.3 コンストラクタとデストラクタ 

  コンストラクタ構築子constructor )は,オブジェクトを初期化する目的で作成するクラスと同じ名前を持った特別なメンバー関数です.コンストラクタを持つクラスのインスタンスを生成すると,コンストラクタが自動的に呼び出され,オブジェクトを初期化します.初期化は,代入文やメンバー初期設定リスト(プログラム例 10.3 参照)を使用して行われます(プログラム例 10.3 参照).

  コンストラクタは関数ですから,通常,引数を持っています.従って,インスタンスの生成の際に,必ず,引数も与えてやる必要があります.ただし,コンストラクタが定義されていない場合や引数のないコンストラクタが定義されている場合は別です(今まで述べてきた例は,これに相当します).

  プログラム例 10.1 においては,Example 型オブジェクト t1,および,*t2 を定義した後,
	t1.v_set1();
	t2->v_set2();
		
とうい方法で,メンバー関数 v_set1,および,v_set2 を呼び出し,その値を設定していました.しかし,関数 v_set1,および,v_set2 の代わりに,
	Example(int x1, int y1)
	{
		x = x1;
		y = y1;
	}
		
のようなコンストラクタを用意しておけば,
	Example t1(10, 20);   // Example 型オブジェクト
	Example *t2 = new Example (30, 40);   // Example 型オブジェクトへのポインタ
		
という記述だけで,オブジェクトの定義と初期設定が済んでしまいます.

  デストラクタ消滅子destructor )は,オブジェクトを消滅させる目的で作成する特別なメンバー関数であり,「~クラス名」という関数名に決まっています.デストラクタに引数を渡したり,デストラクタから値を返したり,また,デストラクタの多重定義をするなどのことはできません.

  大きさの決まっていないデータを扱うためには,オブジェクト内で new 演算子によりメモリを確保する方法が一般的です.このメモリの確保をコンストラクタの中で行うと,プログラムが非常に書きやすくなります.さらに,確保したメモリは,必要が無くなれば解放してやらなければなりませんが,この処理もデストラクタを使用すれば自動的に行ってくれます.デストラクタに対する具体的な使用方法に関しては,プログラム例 10.5 を参照してください.

  通常,コンストラクタやデストラクタは,public より後ろで宣言します.

(プログラム例 10.3 ) 時間データ 

/****************************/
/* 時間データの処理         */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

/********************/
/* クラスTimeの宣言 */
/********************/
class Time {
		int hour, min, sec;
	public:
					// コンストラクタ,2つの引数はデフォルト
		Time(int h, int m = 0, int s = 0)
		{
			hour = h;
			min  = m;
			sec  = s;
		}
					// 以下のように,メンバー初期設定リストを使用しても良い
//		Time(int h, int m = 0, int s = 0) : hour(h), min(m), sec(s) {}
					// コンストラクタ.引数無しも許可.このように引数の異なる
					// 宣言を許す場合は,デフォルト引数又は関数名のオーバーロードが必要
		Time() {}
					// 出力
		void print()
		{
			printf("%2d:%2d:%2d\n", hour, min, sec);
		}
};

/************/
/* main関数 */
/************/
int main()
{
	Time time1(10, 20, 23);      // 10:20:23
	Time time2 = Time(12, 30);   // 12:30:00
	Time time3;                  // 初期設定されない(内容は不定)

	time1.print();
	time2.print();
	time3.print();

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
10:20:23
12:30: 0
6749984:1970558921:6750032
		

(プログラム例 10.4 ) プラントモデル 

  例えば,化学プラントでは,パイプを通して原料が供給され,それが反応炉等に入り,その成分構成が変化して次の反応炉等へ進むということが繰り返されます.反応炉内の処理やパイプ内の成分構成は変化しても,各反応炉等はパイプを通して入力が入り,パイプに出力されるという点ではほとんど同じです.そこで,様々な反応炉等に対応するクラスを用意しておき,その内部処理をコンストラクタや関数で行う(この例の場合は,繰り返しがないため,すべてコンストラクタで行っている)ようにしておけば,それらを適当に組み合わせることによってプラントを実現できるはずです.

  ここでは,
	feed  : 原料の供給
	add  : 2 つの原料を混ぜる
	product: 反応を行う
		
の 3 つのクラスを用意し,次のようなプラント(?)をシミュレーションするプログラムを書いてみました.

/****************************/
/* プラントモデル           */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

/********************/
/* クラスFeedの定義 */
/********************/
class Feed {         /* 原料の供給 */
	public:
		double x, y, z;
						  // コンストラクタ
		Feed(double a, double b, double c)
		{
			x = a;
			y = b;
			z = c;
		}
						  // 出力
		void print()
		{
			printf("x %f y %f z %f (feed)\n", x, y, z);
		}
};

/*******************/
/* クラスAddの定義 */
/*******************/
class Add {		  /* 混合 */
	public:
		double x, y, z;
						  // コンストラクタ
		Add(Feed &a, Feed &b)
		{
			x = a.x + b.x;
			y = a.y + b.y;
			z = a.z + b.z;
		}
						  // 出力
		void print()
		{
			printf("x %f y %f z %f (add)\n", x, y, z);
		}
};

/***********************/
/* クラスProductの定義 */
/***********************/
class Product {	  /* 製品 */
	public:
		double x, y, z;
						  // コンストラクタ
		Product(Add &a)
		{
			x = 0.1 * a.x;
			y = 0.2 * a.y;
			z = 0.9 * a.x + 0.8 * a.y;
		}
						  // 出力
		void print()
		{
			printf("x %f y %f z %f (product)\n", x, y, z);
		}
};

/************/
/* main関数 */
/************/
int main()
{
	Feed feed1(10.0, 20.0, 0.0);
	feed1.print();

	Feed feed2(5.0, 10.0, 0.0);
	feed2.print();

	Add add1(feed1, feed2);
	add1.print();

	Product pro1(add1);
	pro1.print();

	return 0;
}
		

  このプログラムの実行により,以下のような結果が得られます.
	x 10.000000 y 20.000000 z 0.000000 (feed)
	x 5.000000 y 10.000000 z 0.000000 (feed)
	x 15.000000 y 30.000000 z 0.000000 (add)
	x 1.500000 y 6.000000 z 37.500000 (product)
		

(プログラム例 10.5 ) ベクトルの内積と絶対値 

  次は,n 次元ベクトルの内積と絶対値を計算するプログラムです.任意の次元に対応するため,new 演算子を使用しています.このような場合,new 演算子で確保した領域を開放するためにデストラクタが必要になります.

/****************************/
/* ベクトルの内積と絶対値   */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
#include <math.h>

/**********************/
/* Vectorクラスの定義 */
/**********************/
class Vector {
	public:
		int n;
		double *v;
		Vector (int n1)   // コンストラクタ
		{
			n = n1;
			v = new double [n];
		}
		~Vector()   // デストラクタ
		{
			if (n > 0)
				delete [] v;
		}
		double zettai();   // 絶対値
		double naiseki(Vector &);   // 内積
		void input();   // 要素の入力
};

/**********/
/* 絶対値 */
/**********/
double Vector::zettai()
{
	double x = 0.0;
	for (int i1 = 0; i1 < n; i1++)
		x += v[i1] * v[i1];
	return sqrt(x);
}

/*********************/
/* 内積              */
/*      b : ベクトル */
/*********************/
double Vector::naiseki(Vector &b)
{
	double x = 0.0;
	for (int i1 = 0; i1 < n; i1++)
		x += v[i1] * b.v[i1];
	return x;
}

/********/
/* 入力 */
/********/
void Vector::input()
{
	for (int i1 = 0; i1 < n; i1++) {
		std::cout << "   " << i1+1 << "番目の要素は? ";
		std::cin >> v[i1];
	}
}

/******************/
/* mainプログラム */
/******************/
int main()
{
	Vector a(2), b(2);

	std::cout << "a\n";
	a.input();
	std::cout << "b\n";
	b.input();

	std::cout << "内積 " << a.naiseki(b) << std::endl;
	std::cout << "絶対値 " << a.zettai() << " " << b.zettai() << std::endl;

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
a
   1番目の要素は? 1
   2番目の要素は? 2
b
   1番目の要素は? 2
   2番目の要素は? 5
内積 12
絶対値 2.23607 5.38516
		

(プログラム例 10.6 ) リスト構造 

  プログラム例 8.5 をクラスを用いて書いた例です.

/****************************/
/* リスト構造               */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>
#include <string.h>

/********************/
/* クラスListの定義 */
/********************/
class List
{
		char *st;
		List *next;

	public :
				// コンストラクタ
		List () { next = NULL; }

		List (char *s)
		{
			next = NULL;
			st   = new char [strlen(s)+1];
			strcpy(st, s);
		}

		void add(List *);   // データの追加
		void del(char *);   // データの削除
		void output();   // 出力
};

/**************************************/
/* データの追加                       */
/*      dt : Listクラスのオブジェクト */
/**************************************/
void List::add(List *dt)
{
	List *lt1, *lt2 = this;
	int k, sw = 1;

	while (sw > 0) {
					// 最後に追加
		if (lt2->next == NULL) {
			lt2->next = dt;
			sw        = 0;
		}
					// 比較し,途中に追加
		else {
			lt1 = lt2;
			lt2 = lt2->next;
			k   = strcmp(dt->st, lt2->st);   // 比較
			if (k < 0) {                     // 追加
				dt->next  = lt2;
				lt1->next = dt;
				sw        = 0;
			}
		}
	}
}

/*********************/
/* データの削除      */
/*      st1 : 文字列 */
/*********************/
void List::del(char *st1)
{
	List *lt1, *lt2 = this;
	int k, sw = 1;

	while (sw > 0) {
					// データが存在しない場合
		if (lt2->next == NULL) {
			printf("      指定されたデータがありません!\n");
			sw = 0;
		}
					// 比較し,削除
		else {
			lt1 = lt2;
			lt2 = lt2->next;
			k   = strcmp(st1, lt2->st);   // 比較
			if (k == 0) {                 // 削除
				lt1->next = lt2->next;
				sw        = 0;
			}
		}
	}
}

/**********************/
/* リストデータの出力 */
/**********************/
void List::output()
{
	List *nt = this->next;

	while (nt != NULL) {
		printf("   data = %s\n", nt->st);
		nt = nt->next;
	}
}

/****************/
/* main program */
/****************/
int main ()
{
	int sw = 1;
	char st[100];
	List base, *lt;

	while (sw > 0) {
		printf("1:追加,2:削除,3:出力,0:終了? ");
		scanf("%d", &sw);
		switch (sw) {
			case 1:   // 追加
				printf("   データを入力してください ");
				scanf("%s", st);
				lt = new List(st);
				base.add(lt);
				break;
			case 2:   // 削除
				printf("   データを入力してください ");
				scanf("%s", st);
				base.del(st);
				break;
			case 3:   // 出力
				base.output();
				break;
			default :
				sw = 0;
				break;
		}
	}

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
1:追加,2:削除,3:出力,0:終了? 1
   データを入力してください xyz
1:追加,2:削除,3:出力,0:終了? 1
   データを入力してください sdd
1:追加,2:削除,3:出力,0:終了? 1
   データを入力してください abc
1:追加,2:削除,3:出力,0:終了? 2
   データを入力してください sdd
1:追加,2:削除,3:出力,0:終了? 3
   data = abc
   data = xyz
1:追加,2:削除,3:出力,0:終了? 0
		

  先にも示しましたように,以下に示すのは,STL を使用して記述した場合の例です.

/****************************/
/* リスト処理               */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
#include <set>
#include <string>

using namespace std;

/**********************/
/* リストデータの出力 */
/*      lt : リスト   */
/**********************/
void output(set<string> &s) {
	set<string>::iterator it;
	cout << "要素数: " << s.size() << "\n";
	for (it = s.begin(); it != s.end(); it++)
		cout << "  " << *it;
	cout << "\n";
}

/****************/
/* main program */
/****************/
int main ()
{
	int sw = 1;
	string str;
	set <string> s;

	while (sw > 0) {
		cout << "1:追加,2:削除,3:出力,0:終了? ";
		cin >> sw;
		switch (sw) {
			case 1:   // 追加
				cout << "   データを入力してください ";
				cin >> str;
				s.insert(str);
				break;
			case 2:   // 削除
				cout << "   データを入力してください ";
				cin >> str;
				s.erase(str);
				break;
			case 3:   // 出力
				output(s);
				break;
			default :
				sw = 0;
				break;
		}
	}

	return 0;
}
		

(プログラム例 10.7 ) 様々なデータ型の引き渡し 

  この例では,102 行目に見るように,11 種類の方法でデータを関数 method に引数として渡しています.
001	/****************************/
002	/* 様々なデータ型の引き渡し */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	#include <iostream>
006	using namespace std;
007	
008	/***********************/
009	/* クラスComplexの定義 */
010	/***********************/
011	class Complex {
012		public:
013			double re, im;
014							 // コンストラクタ
015			Complex(double re, double im)
016			{
017				this->re = re;
018				this->im = im;
019			}
020	};
021	
022	/*****************************************************/
023	/* 関数の例                                          */
024	/*      a, b, c : int 型                             */
025	/*      ar_11, ar_12 : int 型 1 次元配列             */
026	/*      ar_21, ar_22, ar_23 : int 型 2 次元配列      */
027	/*      cx1, cx2, cx3 : Complex クラスのオブジェクト */
028	/*****************************************************/
029	void method(int a, int *b, int &c, int ar_11[], int *ar_12, int ar_21[][3], int **ar_22,
            int *ar_23, Complex cx1, Complex *cx2, Complex &cx3)
030	{
031		a  = 9;
032		*b = 9;
033		c  = 9;
034		ar_11[0] = 99;
035		ar_12[0] = 99;   // *ar_12 = 99;
036		ar_21[1][0] = 999;
037		ar_22[1][0] = 999;
038		ar_23[3] = 999;
039		cx1.im  = 9999;
040		cx2->im = 9999;
041		cx3.im  = 9999;
042	}
043	
044	/*************/
045	/* main 関数 */
046	/*************/
047	int main()
048	{
049		int a = 1, b = 1, c = 1;
050		int ar_11 [] = {10, 20};
051		int *ar_12 = new int [2];
052		ar_12[0] = 10;
053		ar_12[1] = 20;
054		int ar_21 [][3] = {{100, 200, 300}, {400, 500, 600}};
055		int **ar_22 = new int * [2];
056		for (int i1 = 0; i1 < 2; i1++) {
057			ar_22[i1] = new int [3];
058			for (int i2 = 0; i2 < 3; i2++) {
059				if (i1 == 0)
060					ar_22[i1][i2] = 100 * (i2 + 1);
061				else
062					ar_22[i1][i2] = 100 * (i2 + 4);
063			}
064		}
065		int ar_23 [][3] = {{100, 200, 300}, {400, 500, 600}};
066		Complex cx1(1000, 2000);
067		Complex cx2(1000, 2000);
068		Complex cx3(1000, 2000);
069						// メソッドを呼ぶ前の状態
070		cout << "   ***メソッドを呼ぶ前の状態***\n";
071		cout << "a = " << a << " b = " << b << " c = " << c << endl;
072		cout << "ar_11[0] = " << ar_11[0] << ", ar_11[1] = " << ar_11[1] << endl;
073		cout << "ar_12[0] = " << ar_12[0] << ", ar_12[1] = " << ar_12[1] << endl;
074		for (int i1 = 0; i1 < 2; i1++) {
075			for (int i2 = 0; i2 < 3; i2++) {
076				if (i2  < 2)
077					cout << "ar_21[" << i1 << "][" << i2 << "] = " << ar_21[i1][i2] << ", ";
078				else
079					cout << "ar_21[" << i1 << "][" << i2 << "] = " << ar_21[i1][i2] << endl;
080			}
081		}
082		for (int i1 = 0; i1 < 2; i1++) {
083			for (int i2 = 0; i2 < 3; i2++) {
084				if (i2  < 2)
085					cout << "ar_22[" << i1 << "][" << i2 << "] = " << ar_22[i1][i2] << ", ";
086				else
087					cout << "ar_22[" << i1 << "][" << i2 << "] = " << ar_22[i1][i2] << endl;
088			}
089		}
090		for (int i1 = 0; i1 < 2; i1++) {
091			for (int i2 = 0; i2 < 3; i2++) {
092				if (i2  < 2)
093					cout << "ar_23[" << i1 << "][" << i2 << "] = " << ar_23[i1][i2] << ", ";
094				else
095					cout << "ar_23[" << i1 << "][" << i2 << "] = " << ar_23[i1][i2] << endl;
096			}
097		}
098		cout << "cx1.re = " << cx1.re << ", cx1.im = " << cx1.im << endl;
099		cout << "cx2.re = " << cx2.re << ", cx2.im = " << cx2.im << endl;
100		cout << "cx3.re = " << cx3.re << ", cx3.im = " << cx3.im << endl;
101						// メソッドを呼ぶ
102		method(a, &b, c, ar_11, ar_12, ar_21, ar_22, &ar_23[0][0], cx1, &cx2, cx3);
103						// メソッドを呼んだ後の状態
104		cout << "   ***メソッドを呼んだ後の状態***\n";
105		cout << "a = " << a << " b = " << b << " c = " << c << endl;
106		cout << "ar_11[0] = " << ar_11[0] << ", ar_11[1] = " << ar_11[1] << endl;
107		cout << "ar_12[0] = " << ar_12[0] << ", ar_12[1] = " << ar_12[1] << endl;
108		for (int i1 = 0; i1 < 2; i1++) {
109			for (int i2 = 0; i2 < 3; i2++) {
110				if (i2  < 2)
111					cout << "ar_21[" << i1 << "][" << i2 << "] = " << ar_21[i1][i2] << ", ";
112				else
113					cout << "ar_21[" << i1 << "][" << i2 << "] = " << ar_21[i1][i2] << endl;
114			}
115		}
116		for (int i1 = 0; i1 < 2; i1++) {
117			for (int i2 = 0; i2 < 3; i2++) {
118				if (i2  < 2)
119					cout << "ar_22[" << i1 << "][" << i2 << "] = " << ar_22[i1][i2] << ", ";
120				else
121					cout << "ar_22[" << i1 << "][" << i2 << "] = " << ar_22[i1][i2] << endl;
122			}
123		}
124		for (int i1 = 0; i1 < 2; i1++) {
125			for (int i2 = 0; i2 < 3; i2++) {
126				if (i2  < 2)
127					cout << "ar_23[" << i1 << "][" << i2 << "] = " << ar_23[i1][i2] << ", ";
128				else
129					cout << "ar_23[" << i1 << "][" << i2 << "] = " << ar_23[i1][i2] << endl;
130			}
131		}
132		cout << "cx1.re = " << cx1.re << ", cx1.im = " << cx1.im << endl;
133		cout << "cx2.re = " << cx2.re << ", cx2.im = " << cx2.im << endl;
134		cout << "cx3.re = " << cx3.re << ", cx3.im = " << cx3.im << endl;
135		return 0;
136	}
		
1 番目の引数 a

  049 行目で設定された a の値が,029 行目の a にコピーされ,関数 method に渡されます.031 行目において a の値を変更しても,コピーが変更されただけですから,main プログラム内の a の値はそのままです(出力結果の 015 行目参照).

2 番目の引数 &b

  変数 b のアドレスのコピーが関数 method に渡されます.アドレスが指す場所は main プログラムの b そのものですから,032 行目の実行によって,main プログラムにおける b の値も変更されます(出力結果の 015 行目参照).

3 番目の引数 c

  102 行目だけを見る限り,変数 a と同じ渡し方に見えますが,029 行目を見て下さい.このような渡し方を参照渡しと呼びます.033 行目のように,変数の参照方法は異なりますが,実際的な効果はアドレスを渡す場合と同様です(出力結果の 015 行目参照).なお,029 行目において,int &c の代わりに const int &c と記述しておけば,値の変更は不可能になります.

4 番目の引数 ar_11

  1 次元配列を渡しています.C/C++ に対する配列とポインタにおいて説明しましたように,配列は,ポインタが記憶領域の先頭アドレスを指しているとみなすことが出来ます.この場合も,記憶領域を指すアドレスが関数 method に渡されます.従って,関数内で記憶領域の値を変更すれば,main プログラム内の配列の値も変化します(出力結果の 016 行目参照).なお,1 次元配列の場合は,029 行目において,int ar_11[] の代わりに int *ar_11 と記述しても構いません.

5 番目の引数 ar_12

  new 演算子で生成した 1 次元配列を渡しています.基本的に,通常の 1 次元配列と同じです(出力結果の 017 行目参照).

6 番目の引数 ar_21

  2 次元配列を渡しています.1 次元配列の場合と同様,記憶領域の先頭を指すアドレスを渡していることになりますが,029 行目の記述に注意して下さい.このプログラムの場合,int ar_21[2][3] と記述すべきところを,行の大きさ 2 を省略し,int ar_21[][3] と記述しています.3 次元以上の配列の場合も同様ですが,省略可能なのは最も左側に相当する要素数だけです.この場合も,当然,main プログラムにおける配列の値が変化しています(出力結果の 018,019 行目参照).

7 番目の引数 ar_22

  new 演算子で生成した 2 次元配列を渡しています.基本的に,通常の 2 次元配列と同じです(出力結果の 020,021 行目参照).ただし,029 行目の記述に注意して下さい.

8 番目の引数 &ar_23[0][0]

  C/C++ に対する配列とポインタにおいて説明しましたように,多次元配列の各要素は連続した領域に確保されますので,それを 1 次元配列として扱うことが可能です.この例では,2 次元配列の先頭のアドレスを渡し,関数側では 1 次元配列として処理しています(038 行目,出力結果の 022,023 行目参照).

9 番目の引数 cx1

  Complex クラスのオブジェクトを渡しています.オブジェクト全体がコピーされ関数に渡されます.従って,関数内で値を変更しても,main プログラム内のオブジェクトはその影響を受けません(出力結果の 024 行目参照).

10 番目の引数 &cx2

  Complex クラスのオブジェクトのアドレスを渡しています.従って,関数内で値を変更すると,main プログラム内のオブジェクトの値も変更されます(出力結果の 025 行目参照).

11 番目の引数 cx3

  Complex クラスのオブジェクトに対する参照渡しです.9 番目の引数のような渡し方をすると,オブジェクトが大きい場合,コピーを作成するために多くの時間や領域が必要になります.10 番目の引数のように,アドレスを渡せばその問題は解決しますが,040 行目のように,変数の参照方法を変えなくてはなりません.従って,オブジェクトを引数とする場合は,参照渡しが多く使用されます.勿論,期待した結果も得られますし(出力結果の 026 行目参照),値を変更しない場合は,3 番目の引数の項で説明したような const を使用すれば可能です.
  このプログラムによって,以下に示すような結果が得られます(行番号は説明用に追加).
01	   ***メソッドを呼ぶ前の状態***
02	a = 1 b = 1 c = 1
03	ar_11[0] = 10, ar_11[1] = 20
04	ar_12[0] = 10, ar_12[1] = 20
05	ar_21[0][0] = 100, ar_21[0][1] = 200, ar_21[0][2] = 300
06	ar_21[1][0] = 400, ar_21[1][1] = 500, ar_21[1][2] = 600
07	ar_22[0][0] = 100, ar_22[0][1] = 200, ar_22[0][2] = 300
08	ar_22[1][0] = 400, ar_22[1][1] = 500, ar_22[1][2] = 600
09	ar_23[0][0] = 100, ar_23[0][1] = 200, ar_23[0][2] = 300
10	ar_23[1][0] = 400, ar_23[1][1] = 500, ar_23[1][2] = 600
11	cx1.re = 1000, cx1.im = 2000
12	cx2.re = 1000, cx2.im = 2000
13	cx3.re = 1000, cx3.im = 2000
14	   ***メソッドを呼んだ後の状態***
15	a = 1 b = 9 c = 9
16	ar_11[0] = 99, ar_11[1] = 20
17	ar_12[0] = 99, ar_12[1] = 20
18	ar_21[0][0] = 100, ar_21[0][1] = 200, ar_21[0][2] = 300
19	ar_21[1][0] = 999, ar_21[1][1] = 500, ar_21[1][2] = 600
20	ar_22[0][0] = 100, ar_22[0][1] = 200, ar_22[0][2] = 300
21	ar_22[1][0] = 999, ar_22[1][1] = 500, ar_22[1][2] = 600
22	ar_23[0][0] = 100, ar_23[0][1] = 200, ar_23[0][2] = 300
23	ar_23[1][0] = 999, ar_23[1][1] = 500, ar_23[1][2] = 600
24	cx1.re = 1000, cx1.im = 2000
25	cx2.re = 1000, cx2.im = 9999
26	cx3.re = 1000, cx3.im = 9999
		

演習問題10

[問1]名前( char ),給与( int ),及び,年齢( int )をクラスで表現し,そこへ入出力を行うプログラムを書け.

[問2] n (入力)個の 3 次元空間の点の座標を入力した後,原点からの平均距離を計算し,平均距離以上離れた点の個数を出力するプログラムを,クラスを使用して書け.

[問3]名前,住所,電話番号をクラスを使用して記述し,名前を入力すると電話番号を出力するプログラムを書け.

[問4]最大待ち行列長を 10 として,待ち行列をクラスで表現するプログラムを書け.ただし,待ち行列は,配列に待ち行列に入っている要素-数字-を格納することによって表すものとする.メンバー関数としては,待ち行列に要素を入れる関数 put(int num),待ち行列から要素を取り出す関数 get(),及び,待ち行列の状態を出力する関数 qprint() を用意せよ.

[問5]レコードの格納と取り出しを文字列キーで行うハッシュ表のクラスを書け.その際,レコードの挿入,探索,及び,削除を行うメンバー関数も用意せよ.

[問6]英語の単語を入力し,それを 2 進木として記憶するプログラムをクラスを利用して書け.ただし,新しい単語が入力されたとき,記憶されている単語よりアルファベット順で前にあるなら左,そうでなければ右の枝をたどるものとする.例えば,11 個の単語が,「 network parameters are optimized from empirical data and the optimal number 」という順に入力された場合は以下のようになる.

静岡理工科大学 菅沼ホーム 全体目次 演習解答例 付録 索引