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

第13章 派生クラス

  1. 13.1 派生クラス
    1. (プログラム例 13.1 ) 派生クラス
    2. (プログラム例 13.2 ) フレームの表現
  2. 13.2 仮想関数
    1. (プログラム例 13.3 ) 仮想関数(フレームの表現)
  3. 13.3 抽象クラス
    1. (プログラム例 13.4 ) 抽象クラス
  4. 演習問題13

13.1 派生クラス

  Windows のプログラムを書くような場合について考えてみてください.多くの Windows アプリケーションが存在しますが,その基本的なレイアウトや機能はほとんど同じです.それらのすべてを一から記述するとすれば大変な作業になります.しかし,Windows の基本機能を実現したプログラムがすでに存在し,そのプログラムを利用しながら必要な箇所の修正,追加を行うことによって可能であるとすれば,作業量は非常に少なくなります.これを実現したのが継承です.

  あるクラスに宣言されているメンバー(変数,関数)をすべて受け継ぎ(継承inheritance ),受け継いだメンバーを修正,さらには,それに新たなメンバーを付加して新しいクラスを宣言することができます.このとき,基になったクラスを基底クラス基本クラスbase class ),また,派生したクラスを派生クラス導出クラスderived class )と呼びます.

  派生クラスの宣言方法は,
class クラス名 : [アクセス権] 基底クラス名 [,[アクセス権] 基底クラス・・・] {
	変更部分
}		
の方法によります.アクセス権を省略すると,基底クラスが struct ならば public,class ならば private とみなされます.private の場合は,基底クラスのすべてのメンバーは派生クラスのプライベート部に入れられますが,public の場合は,基底クラスのパブリックメンバーはそのまま派生クラスのパブリックメンバーになります.また,基底クラスが複数の場合を多重継承と呼びます.

  派生クラスは,基底クラスからすべてのメンバー変数とメンバー関数を継承します.ただし,コンストラクタ,デストラクタ,代入演算子は継承されません(後に説明しますように,C++11 からは,コンストラクタを継承させることが可能です).メンバー変数に対しては,派生クラスのオブジェクト内にメンバー変数を記憶する領域が確保されますが,メンバー関数に関しては,その利用権を受け継ぐだけです.

  protected の後ろで宣言されたプロテクティッドメンバーは,継承されても同じプロテクティッドメンバーとなり,基底クラス及び派生クラスのメンバー関数,フレンド関数,および,フレンドクラス(つまり,階層中にあるメンバー)からだけアクセスできます.

  public の後ろで宣言されたパブリックメンバーは,どこからでもアクセスできますが,private の後ろで宣言されたプライベートメンバーにアクセスできるのは,そのクラスのメンバー関数,フレンド関数,および,フレンドクラスだけです.従って,基底クラスのプライベートメンバーが継承された場合,継承されたメンバー関数からはアクセス可能ですが,派生クラスの中で定義されたメンバー関数やフレンド関数からはアクセスできません.

  基底クラスのパブリックメンバーが,派生クラスのプライベートメンバーに継承された場合は,次のようにして,指定したメンバーだけを派生クラスのパブリックメンバーに引き出すことができます.
class der : Base {
		 ・・・・・
	public:
		using Base::z;     // 基底クラスBaseのパブリックメンバー変数
		using Base::fun;   // 基底クラスBaseのパブリックメンバー関数
		 ・・・・・
};		
  派生クラスに対しては,コンストラクタの記述にも注意が必要です.基本的に,派生クラスの初期設定は,その基底クラスの初期設定が行われてから実行されます.従って,派生クラスのコンストラクタには,基底クラスの初期設定に必要なパラメータを記述してやる必要があります.もちろん,基底クラスがコンストラクタを持っていなかったり,または,持っていても引数がない場合は必要ありません.逆に,派生クラス自身には,コンストラクタやその引数を必要としなくても,基底クラスにパラメータを送るためにコンストラクタが必要となる場合があります.

  派生クラスのコンストラクタの定義方法を一般的に書けば以下のようになります.
クラス名 :: クラス名 (引数リスト) : 基底クラス名(パラメータリスト) [, 基底クラス名(パラメータリスト)・・・]
{
	このクラスに必要な初期設定
}		

(プログラム例 13.1 ) 派生クラス

  この例は,クラス Kodomo が 2 つの親 Oya1 と Oya2 を継承している例です.78 行目の定義において,Oya1 に対しては public を宣言しているため,Oya1 の public メンバーはそのまま Kodomo の public メンバーに継承されます.しかし,Oya2 に対しては何も宣言していないため,すべてのメンバーが,Kodomo の private メンバーに継承されます.しかし,106 から 108 行目の宣言により,pub2_1,set,および,output2 は,Kodomo の public メンバーに継承されます.

  クラス Kodomo 自体は,コンストラクタを必要としませんが,Oya1 が必要とするため,81 行目の宣言により,Oya1 のコンストラクタにパラメータを引き渡しています.

  親の public および private なメンバーがどのようにして子供に継承されるのかを,プログラムとその出力結果を見て十分理解してください.なお,? や ?? は,参照できないことを意味しています.

 01	/****************************/
 02	/* 派生クラス               */
 03	/*      coded by Y.Suganuma */
 04	/****************************/
 05	#include <iostream>
 06
 07	/********************/
 08	/* クラスOya1の定義 */
 09	/********************/
 10	class Oya1 {
 11			int priv1;
 12		protected:
 13			int prot1;
 14		public:
 15			int pub1;
 16			Oya1 (int n1, int n2, int n3)   // コンストラクタ
 17			{
 18				priv1 = n1;
 19				prot1 = n2;
 20				pub1  = n3;
 21			}
 22			void output1(int sw)   // 出力
 23			{
 24				switch (sw) {
 25					case 0:
 26						std::cout << "   priv1 " << priv1 << std::endl;
 27						break;
 28					case 1:
 29						std::cout << "   prot1 " << prot1 << std::endl;
 30						break;
 31					case 2:
 32						std::cout << "   pub1 " << pub1 << std::endl;
 33						break;
 34				}
 35			}
 36	};
 37
 38	/********************/
 39	/* クラスOya2の定義 */
 40	/********************/
 41	class Oya2 {
 42			int priv2;
 43		public:
 44			int pub2_1, pub2_2;
 45			void set(int sw, int n)   // 値の設定
 46			{
 47				switch (sw) {
 48					case 0:
 49						priv2 = n;
 50						break;
 51					case 1:
 52						pub2_1 = n;
 53						break;
 54					case 2:
 55						pub2_2 = n;
 56						break;
 57				}
 58			}
 59			void output2(int sw)   // 出力
 60			{
 61				switch (sw) {
 62					case 0:
 63						std::cout << "   priv2 " << priv2 << std::endl;
 64						break;
 65					case 1:
 66						std::cout << "   pub2_1 " << pub2_1 << std::endl;
 67						break;
 68					case 2:
 69						std::cout << "   pub2_2 " << pub2_2 << std::endl;
 70	 					break;
 71				}
 72			}
 73	};
 74
 75	/**********************/
 76	/* クラスkodomoの定義 */
 77	/**********************/
 78	class Kodomo : public Oya1, Oya2 {
 79		public:
 80						// コンストラクタ(基底クラスのコンストラクタのために必要)
 81			Kodomo (int n1, int n2, int n3) : Oya1(n1, n2, n3) {}
 82			void output_k(int sw)   // 出力
 83			{
 84				switch (sw) {
 85					case 0:   // 親のprivate変数にはアクセスできない
 86						std::cout << "   priv1 ?" << std::endl;
 87						break;
 88					case 1:
 89						std::cout << "   prot1 " << prot1 << std::endl;
 90						break;
 91					case 2:
 92						std::cout << "   pub1 " << pub1 << std::endl;
 93						break;
 94					case 3:   // 親のprivate変数にはアクセスできない
 95						std::cout << "   priv2 ?" << std::endl;
 96						break;
 97					case 4:
 98						std::cout << "   pub2_1 " << pub2_1 << std::endl;
 99						break;
100					case 5:
101						std::cout << "   pub2_2 " << pub2_2 << std::endl;
102						break;
103				}
104			}
105						// Oya2のpublic変数の一部と関数をpubulicに移動
106			Oya2::pub2_1;
107			Oya2::set;
108			Oya2::output2;
109	};
110
111	/******************/
112	/* mainプログラム */
113	/******************/
114	int main()
115	{
116				// 親1
117		Oya1 o1(10, 20, 30);
118
119		std::cout << "親1\n";
120		o1.output1(0);   // priv1 の出力
121		o1.output1(1);   // prot1 の出力
122		o1.output1(2);   // pub1 の出力
123		std::cout << "   (from main) priv1 ? prot1 ? pub1 " << o1.pub1 << std::endl;
124				// 親2
125		Oya2 o2;
126
127		std::cout << "親2\n";
128		o2.set(0, 50);
129		o2.set(1, 60);
130		o2.set(2, 70);
131		o2.output2(0);   // priv2 の出力
132		o2.output2(1);   // pub2_1 の出力
133		o2.output2(2);   // pub2_2 の出力
134		std::cout << "   (from main) priv2 ? pub2_1 " << o2.pub2_1 << " pub2_2 " << o2.pub2_2 << std::endl;
135				// 子供
136		Kodomo k(100, 200, 300);   // 親(Oya1)のコンストラクタのための引数
137
138		std::cout << "子供\n";
139					// Oya1から継承した部分
140		std::cout << "      Oya1から継承した関数を利用\n";
141		k.output1(0);   // Oya1から継承した priv1 の出力
142		k.output1(1);   // Oya1から継承した prot1 の出力
143		k.output1(2);   // Oya1から継承した pub1 の出力
144		std::cout << "      Kodomoの関数を利用\n";
145		k.output_k(0);   // Oya1から継承した priv1 の出力(アクセスできない)
146		k.output_k(1);   // Oya1から継承した prot1 の出力
147		k.output_k(2);   // Oya1から継承した pub1 の出力
148		std::cout << "   (from main) priv1 ? prot1 ? pub1 " << k.pub1 << std::endl;
								// pub1 はパブリック部分に継承されるためmainからアクセスできる
149					// Oya2から継承した部分
150		std::cout << "      Oya2から継承した関数を利用\n";
151		k.set(0, 500);
152		k.set(1, 600);
153		k.set(2, 700);
154		k.output2(0);   // Oya2から継承した priv2 の出力
155		k.output2(1);   // Oya2から継承した pub2_1 の出力
156		k.output2(2);   // Oya2から継承した pub2_2 の出力
157		std::cout << "      Kodomoの関数を利用\n";
158		k.output_k(3);   // Oya2から継承した priv2 の出力(アクセスできない)
159		k.output_k(4);   // Oya2から継承した pub2_1 の出力
160		k.output_k(5);   // Oya2から継承した pub2_2 の出力
161		std::cout << "   (from main) priv2 ? pub2_1 " << k.pub2_1 << " pub2_2  ?" << std::endl;
								// pub2_1 はパブリック部分に移動されたためmainからアクセスできる
162
163		return 0;
164	}
		
(出力)
親1
   priv1 10
   prot1 20
   pub1 30
   (from main) priv1 ? prot1 ? pub1 30
親2
   priv2 50
   pub2_1 60
   pub2_2 70
   (from main) priv2 ? pub2_1 60 pub2_2 70
子供
      Oya1から継承した関数を利用
   priv1 100
   prot1 200
   pub1 300
      Kodomoの関数を利用
   priv1 ?
   prot1 200
   pub1 300
   (from main) priv1 ? prot1 ? pub1 300
      Oya2から継承した関数を利用
   priv2 500
   pub2_1 600
   pub2_2 700
      Kodomoの関数を利用
   priv2 ?
   pub2_1 600
   pub2_2 700
   (from main) priv2 ? pub2_1 600 pub2_2  ?
			
  このプログラムを実行すると,次のような結果が得られます.A ~ F,A ~ F,及び,A ~ R の空白部分を埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.なお,値を出力できない場合に対しては,「 ? 」を入力してください.
  継承コンストラクタは,基底クラスで定義したコンストラクタ群を派生クラスでそのまま使用できるようにするための構文です.using に続いて,基底クラス名とそのコンストラクタ名を :: で区切りることによって記述します.以下に示すのは,その利用例です.
#include <iostream>

using namespace std;

class Base1 {
	public:
		int x;
		Base1() {}
		Base1(int x1) { x = x1; }
};

class Base2 {
	public:
		double y;
		Base2() {}
		Base2(double y1) { y = y1; }
};

class Test : public Base1, public Base2 {
	public:
		using Base1::Base1;
		using Base2::Base2;   // 下に示したコンストラクタのため,
		                      // Base2 のコンストラクタは利用されない
		                      // 従って,この行は必要ない
		Test(double y1) : Base1(10) {
			y = 10.0 * y1;
		}
};

int main()
{
	Test t1(1);   // 継承した Base1 のコンストラクタを利用
	cout << "t1.x: " << t1.x << ", t1.y: 未設定\n";
	Test t2(3.14);   // Test で定義されたコンストラクタを利用
	cout << "t2.x: " << t2.x << ", t2.y: " << t2.y << endl;

	return 0;
}
		
(出力)
t1.x: 1, t1.y: 未設定
t2.x: 10, t2.y: 31.4		
(プログラム例 13.2 ) フレームの表現

  ここでは,人工知能におけるフレーム表現による知識をクラスを使用して表現してみます.今,以下に述べるような 3 つのフレーム(知識)があるとし,それぞれをクラスとして定義します.

クラス Animal
  動物は生物である(生物は0,その他は1)
  動物は酸素呼吸する(酸素呼吸は0,その他は1)
クラス Human
  人間は動物である
  人間の身長は普通170㎝である
  人間の標準体重は,一般的に,(身長-100)x 0.9で算出される
クラス Taro
  太郎は人間である
  太郎の身長は175㎝である

  以下に示すのがその具体的なプログラムです.この場合,クラス Animal のコンストラクタは引数無しでも書けます( 2 つのメンバー変数に 0 を代入すればよい)が,派生クラスのコンストラクタとの関係を説明するためにあえて引数を持たせてあります.

  クラス Human は,派生クラスでなければコンストラクタを必要としませんが,その基底クラスである Animal のコンストラクタにパラメータを渡すためのコンストラクタが必要になります.もちろん,クラス Animal がコンストラクタを持たなかったり,コンストラクタを所有していても引数がない場合は,クラス Human にもコンストラクタは必要ありません.

  クラス Taro のコンストラクタは,クラス Human のコンストラクタに渡すためのパラメータを定数で与えています.このようにすれば,クラス Taro の宣言に必要な引数の数を減らすことができます.

/********************************/
/* 派生クラス(フレームの表現) */
/*      coded by Y.Suganuma     */
/********************************/
#include <stdio.h>

/**********************/
/* クラスAnimalの定義 */
/**********************/
class Animal {   /* 動物フレーム */
	protected:
		int ani;   // 生物:0,その他:1
		int ox;    // 酸素呼吸:0,その他:1
	public:
		Animal(int k1, int k2)   // コンストラクタ
		{
			ani = k1;
			ox  = k2;
		}
		void shu(int sw)   // 生物か否かに答える
		{
			if (ani == sw)
				printf("yes\n");
			else
				printf("no\n");
		}
};

/*********************/
/* クラスHumanの定義 */
/*********************/
class Human : public Animal {   /* 人間フレーム,クラスAnimalを継承 */
	protected:
		int height;      // 身長
		double weight;   // 体重
	public:
		Human(int x, int y) : Animal(x, y) {}   // コンストラクタ
		void taiju()   // 体重を返す関数
		{
			printf("体重は %f Kgです\n", weight);
		}
};

/********************/
/* クラスTaroの定義 */
/********************/
class Taro : public Human {   /* 太郎フレーム,クラスHumanを継承 */
	public:
		Taro(int hei = 0, double wei = 0.0) : Human(0, 0)   // コンストラクタ
		{
			height = (hei == 0) ? 170 : hei;
			weight = (wei == 0.0) ? 0.9 * (height - 100) : wei;
		}
};

/************/
/* main関数 */
/************/
int main()
{
	Human h1(0, 0);
	Taro t1(175);

	h1.shu(1);    // 人間は生物ではない?
	t1.shu(0);    // 太郎は生物?
	t1.taiju();   // 太郎の体重は?

	return 0;
}
		
(出力)
no
yes
体重は 67.500000 Kgです			
  このプログラムを実行すると,以下のような結果が得られます.例えば,太郎が生物か否かの問に対して,太郎自身の知識には,何ら記述してありませんが,上位概念(この場合は,クラス Animal )の知識(メンバー変数 ani とメンバー関数 shu )を継承して,正しい答えをだしています.A ~ C の空白部分を埋めてください(数値は,小数点以下 1 桁まで).その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.

13.2 仮想関数

(プログラム例 13.3 ) 仮想関数(フレームの表現)

  まず,次のプログラム(プログラム例 13.2 と階層構造は同じで,print という関数を各クラスで再定義)とその実行結果を見て下さい.

01	/****************************/
02	/* 仮想関数                 */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include <stdio.h>
06
07	/****************/
08	/* 動物フレーム */
09	/****************/
10	class Animal {
11		public:
12			void print()   // 出力
13	//		virtual void print()   // 出力,仮想関数
14			{
15				printf("動物\n");
16				printf("    動物は生物である\n");
17				printf("    動物は酸素呼吸する\n");
18			}
19	};
20	
21	/****************/
22	/* 人間フレーム */
23	/****************/
24	class Human : public Animal {
25		public:
26			void print()   // 出力
27			{
28				printf("人間\n");
29				printf("    人間は動物である\n");
30				printf("    身長は普通170㎝である\n");
31				printf("    体重は,一般的に,(身長-100)x 0.9で算出される\n");
32			}
33	};
34	
35	/****************/
36	/* 太郎フレーム */
37/****************/
38	class Taro : public Human {
39		public:
40			void print()   // 出力
41			{
42				printf("太郎\n");
43				printf("    太郎は人間である\n");
44				printf("    太郎の身長は175㎝である\n");
45			}
46	};
47	
48	/*************/
49	/* main 関数 */
50	/*************/
51	int main()
52	{
53		Animal a1;
54		Human h1;
55		Taro t1;
56	
57		a1.print();
58		h1.print();
59		t1.print();
60	
61		Animal *pt[3] = {&a1, &h1, &t1};
62	
63		pt[0]->print();
64		pt[1]->print();
65		pt[2]->print();
66	
67		return 0;
68	}
		
  実行結果は,以下のようになります.
動物
    動物は生物である
    動物は酸素呼吸する
人間
    人間は動物である
    身長は普通170㎝である
    体重は,一般的に,(身長-100)x 0.9で算出される
太郎
    太郎は人間である
    太郎の身長は175㎝である
動物
    動物は生物である
    動物は酸素呼吸する
動物
    動物は生物である
    動物は酸素呼吸する
動物
    動物は生物である
    動物は酸素呼吸する
		

  一般に,派生クラスのオブジェクトのアドレスを,基底クラス型のポインタ変数に代入することは許されます( 61 行目).なぜなら,派生クラスは,基底クラスに定義されたすべてのメンバーを含んでいるからです.しかし,そのポインタを通して,各オブジェクトのメンバーにアクセスしようとする( 63 行目~ 65 行目)と,そのポインタのタイプから判断され,基底クラスのメンバーをアクセスすることになってしまいます.そのため,上に示すような結果が出力されてしまいます.

  このような問題を解決するのが,仮想関数( virtual function )という考え方です.基底クラスに,キーワード virtual を付加して関数(コンストラクタを除くメンバー関数)を定義しておく( 12 行目を 13 行目のように記述する)と,派生クラスに同じ名前の関数が定義してあり,かつ,基底クラス型のポインタを介して呼び出しても,下に示すように,実行時に適切な関数と動的に結合してくれ,目的とする処理を行ってくれます.なお,仮想関数の型はすべて一致している必要があります.

動物
    動物は生物である
    動物は酸素呼吸する
人間
    人間は動物である
    身長は普通170㎝である
    体重は,一般的に,(身長-100)x 0.9で算出される
太郎
    太郎は人間である
    太郎の身長は175㎝である
動物
    動物は生物である
    動物は酸素呼吸する
人間
    人間は動物である
    身長は普通170㎝である
    体重は,一般的に,(身長-100)x 0.9で算出される
太郎
    太郎は人間である
    太郎の身長は175㎝である
		
  C++11 には,クラスの継承を禁止したり,または,仮想関数に対するオーバーライドを制御するためのキーワードが存在します.override は仮想メンバ関数のオーバーライドを明示的に宣言するキーワードです.また,final は,これ以上の継承をさせないことを明示的に宣言,または,派生クラスでの仮想メンバ関数のオーバーライドをさせないことを明示的に宣言するキーワードです.override や final を通常のメンバー関数に付加すればエラーになってしまいます.詳細については,以下に示すプログラム例を参照してください.
#include <iostream>

using namespace std;

class Base {
//class Base final {   派生クラスを生成できない
	public:
		Base() {}
		virtual void f_final() final { cout << "Base_final\n"; }
		virtual void f_over() { cout << "Base_over\n"; }
		void f_norm() { cout << "Base_norm\n"; }
};

class Test : public Base {
	public:
		Test() {}
//		void f_final() override { cout << "Test_final\n"; }   エラー
		void f_over() override { cout << "Test_over\n"; }
//		void f_norm() override { cout << "Test_norm\n"; }   エラー
		void f_norm() { cout << "Test_norm\n"; }
};

int main()
{
	Base b;
	cout << "Base: Base の f_final を使用 ";
	b.f_final();
	cout << "Base: Base の f_over を使用 ";
	b.f_over();
	cout << "Base: Base の f_norm を使用 ";
	b.f_norm();

	Test t;
	cout << "Test: Base の f_final を使用 ";
	t.f_final();
	cout << "Test: Test の f_over を使用 ";
	t.f_over();
	cout << "Test: Test の f_norm を使用 ";
	t.f_norm();

	return 0;
}
		
(出力)
Base: Base の f_final を使用 Base_final
Base: Base の f_over を使用 Base_over
Base: Base の f_norm を使用 Base_norm
Test: Base の f_final を使用 Base_final
Test: Test の f_over を使用 Test_over
Test: Test の f_norm を使用 Test_norm		

13.3 抽象クラス

  抽象クラスabstract class )は,基底クラスとしてだけ使えるクラスです.インスタンスを生成するためには使用できません.仕様の詳細を決めずに,抽象的な概念の定義だけを行います.抽象概念は,抽象クラスから派生する非抽象クラスによって表される具体的概念の基礎となります.

  クラスは,次の例のように,0 に初期化された仮想関数を含むと抽象クラスになります.0 に初期化された仮想関数を純粋仮想関数といいます.派生したクラスは,それが,非抽象クラスであれば,その基底クラスのすべての純粋仮想関数を定義してやる必要があります.

(プログラム例13.4) 抽象クラス

  次のプログラムは,クラス Figure という抽象クラスを定義し,その派生クラスとして,楕円の面積を計算するクラス Ellipse および多角形(左回りに与えた n 点からなる多角形)の面積を計算するクラス Polygon を定義したものです.
/****************************/
/* 抽象クラス               */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
#include <math.h>
#include <stdlib.h>

/****************/
/* クラスFigure */
/****************/
class Figure {
	public:
		double s;   // 図形の面積
		virtual void input() = 0;   // データの入力,純粋仮想関数
		virtual void menseki() = 0;   // 面積の計算,純粋仮想関数
		void print(int sw)   // 結果の出力
		{
			if (sw == 0)
				std::cout << "   与えられた楕円の面積は " << s << " です\n";
			else
				std::cout << "与えられた多角形の面積は " << s << " です\n";
		}
};

/*****************/
/* クラスEllipse */
/*****************/
class Ellipse : public Figure {
		double a, b;   // 楕円の長径と短径
	public:
		void input()   // データの入力
		{
			std::cout << "楕円の長径は? ";
			std::cin >> a;
			std::cout << "楕円の短径は? ";
			std::cin >> b;
		}
		void menseki()   // 楕円の面積
		{
			s = 4.0 * atan(1.0) * a * b;
		}
};

/*****************/
/* クラスPolygon */
/*****************/
class Polygon : public Figure {
		int n;   // 点の数
		int *m;   // 点の番号
		double **p;   // 各点の座標
		double men(int, double **, int *);
		double sankaku(int, int, int *, double **);
		int check1(int, int, int *, double **);
		int check2(int, int, int *, double **);
	public:
					// コンストラクタ
		Polygon (int n1)
		{
			if (n1 < 3) {
				std::cout << "3点以上与えてください\n";
				exit(1);
			}
			else {
				n = n1;
				m = new int [n];
				p = new double * [n];
				for (int i1 = 0; i1 < n; i1++)
					p[i1] = new double [2];
			}
		}
					// データの入力
		void input()
		{
			std::cout << "各点の座標の入力(反時計回り)\n";
			for (int i1 = 0; i1 < n; i1++) {
				std::cout << "   " << i1+1 << " 番目の点のx座標は? ";
				std::cin >> p[i1][0];
				std::cout << "      " << i1+1 << " 番目の点のy座標は? ";
				std::cin >> p[i1][1];
			}
		}
					// 多角形の面積
		void menseki()
		{
			for (int i1 = 0; i1 < n; i1++)
				m[i1] = i1;
			s = men(n, p, m);
		}
};

/******************************/
/* 多角形の面積を再帰的に計算 */
/*      n : 点の数            */
/*      p : 点の座標          */
/*      m : 点番号            */
/*      return : 面積         */
/******************************/
double Polygon::men(int n, double **p, int *m)
{
	double s = 0.0;
	if (n == 3)   // 三角形
		s = sankaku(n, 0, m, p);
	else {
		int sw1 = 0;
		for (int i1 = 0; i1 < n && sw1 == 0; i1++) {
			int sw = check1(n, i1, m, p);   // 多角形の中?
			if (sw == 0) {
				int sw = check2(n, i1, m, p);   // 中に他の点がある?
				if (sw == 0) {
					s = sankaku(n, i1, m, p);   // i1から始まる三角形
					for (int i2 = i1+1; i2 < n-1; i2++)   // 点を除く
						m[i2] = m[i2+1];
					s   += men(n-1, p, m);   // (n-1)この点からなる多角形
					sw1  = 1;
				}
			}
		}
	}
	for (int i1 = 0; i1 < n; i1++)
		delete [] p[i1];
	delete [] p;
	delete [] m;
	return s;
}

/***********************/
/* 三角形の面積の計算  */
/*      n : 点の数     */
/*      k : 開始点番号 */
/*      m : 点番号     */
/*      p : 点の座標   */
/*      return : 面積  */
/***********************/
double Polygon::sankaku(int n, int k, int *m, double **p)
{
	int k1 = m[k];
	int k2 = (k == n-1) ? m[0] :m[k+1];
	int k3;
	if (k < n-2)
		k3 = m[k+2];
	else
		k3 = (k == n-2) ? m[0] : m[1];
	double x1 = p[k2][0] - p[k1][0];
	double y1 = p[k2][1] - p[k1][1];
	double r1 = sqrt(x1 * x1 + y1 * y1);
	double t1 = atan2(y1, x1);
	x1 = p[k3][0] - p[k1][0];
	y1 = p[k3][1] - p[k1][1];
	double r2 = sqrt(x1 * x1 + y1 * y1);
	double t2 = atan2(y1, x1);
	double t3 = t2 - t1;
	double s  = fabs(0.5 * r1 * r2 * sin(t3));
	return s;
}

/****************************************/
/* 三角形が多角形の内部か否かのチェック */
/*       n : 点の数                     */
/*       k : 開始点番号                 */
/*       m : 点番号                     */
/*       p : 点の座標                   */
/*       return : =0 : 内部である       */
/*                =1 : 内部でない       */
/****************************************/
int Polygon::check1(int n, int k, int *m, double **p)
{
	int k1 = m[k];
	int k2 = (k == n-1) ? m[0] :m[k+1];
	int k3;
	if (k < n-2)
		k3 = m[k+2];
	else
		k3 = (k == n-2) ? m[0] : m[1];
	double x1 = p[k3][0] - p[k1][0];
	double y1 = p[k3][1] - p[k1][1];
	double t1 = atan2(y1, x1);
	x1 = p[k2][0] - p[k1][0];
	y1 = p[k2][1] - p[k1][1];
	double t2 = atan2(y1, x1);
	double t3 = t2 - t1;
	double pi = 4.0 * atan2(1.0, 1.0);
	if (t3 > pi)
		t3 -= 2.0 * pi;
	else {
		if (t3 < -pi)
			t3 += 2.0 * pi;
	}
	int sw;
	if (t3 > 0.0)
		sw = 1;
	else
		sw = 0;
	return sw;
}

/************************************************/
/* 三角形内に他の頂点が存在するか否かのチェック */
/*      n : 点の数                              */
/*      k : 開始点番号                          */
/*      m : 点番号                              */
/*      p : 点の座標                            */
/*      return : =0 : 存在しない                */
/*               =1 : 存在する                  */
/************************************************/
int Polygon::check2(int n, int k, int *m, double **p)
{
	int k1 = m[k];
	int k2 = (k == n-1) ? m[0] :m[k+1];
	int k3;
	if (k1 < n-2)
		k3 = m[k+2];
	else
		k3 = (k == n-2) ? m[0] : m[1];
	double x1  = p[k2][0] - p[k1][0];
	double y1  = p[k2][1] - p[k1][1];
	double t10 = atan2(y1, x1);
	x1 = p[k3][0] - p[k2][0];
	y1 = p[k3][1] - p[k2][1];
	double t11 = atan2(y1, x1);
	x1 = p[k1][0] - p[k3][0];
	y1 = p[k1][1] - p[k3][1];
	double t12 = atan2(y1, x1);
	double pi  = 4.0 * atan2(1.0, 1.0);
	int sw = 0;
	for (int i1 = 0; i1 < n && sw == 0; i1++) {
		if (i1 != k1 && i1 != k2 && i1 != k3) {
			x1 = p[m[i1]][0] - p[k1][0];
			y1 = p[m[i1]][1] - p[k1][1];
			double t2 = atan2(y1, x1);
			double t3 = t2 - t10;
			if (t3 > pi)
				t3 -= 2.0 * pi;
			else {
				if (t3 < -pi)
					t3 += 2.0 * pi;
			}
			if (t3 > 0.0) {
				x1 = p[m[i1]][0] - p[k2][0];
				y1 = p[m[i1]][1] - p[k2][1];
				t2 = atan2(y1, x1);
				t3 = t2 - t11;
				if (t3 > pi)
					t3 -= 2.0 * pi;
				else {
					if (t3 < -pi)
						t3 += 2.0 * pi;
				}
				if (t3 > 0.0) {
					x1 = p[m[i1]][0] - p[k3][0];
					y1 = p[m[i1]][1] - p[k3][1];
					t2 = atan2(y1, x1);
					t3 = t2 - t12;
					if (t3 > pi)
						t3 -= 2.0 * pi;
					else {
						if (t3 < -pi)
							t3 += 2.0 * pi;
					}
					if (t3 > 0.0)
						sw = 1;
				}
			}
		}
	}
	return sw;
}

/*************/
/* main 関数 */
/*************/
int main()
{
//	Figure f1;   エラー,抽象クラスからオブジェクトの生成はできない
/*
	 楕円
*/
	Ellipse e1;

	e1.input();
	e1.menseki();
	e1.print(0);

/*
	 多角形
*/
	int n;
	std::cout << "点の数は? ";
	std::cin >> n;

	Polygon p1(n);

	p1.input();
	p1.menseki();
	p1.print(1);

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
楕円の長径は? 5
楕円の短径は? 2
   与えられた楕円の面積は 31.4159 です
点の数は? 3
各点の座標の入力(反時計回り)
   1 番目の点のx座標は? 0
      1 番目の点のy座標は? 0
   2 番目の点のx座標は? 2
      2 番目の点のy座標は? 0
   3 番目の点のx座標は? 1
      3 番目の点のy座標は? 2
与えられた多角形の面積は 2 です
		

演習問題13

[問1]クラス Shape を基底クラスとして,正方形,円,正三角形を派生クラスとして定義せよ.また,それらの面積を表す関数を仮想関数として,基底クラスで定義せよ.

[問2]クラス Number を基底クラスとして定義し,整数に対し 8 進,16 進,及び,10 進でその値を出力する関数を派生クラスとして定義せよ.

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