4.2 クラス設計の例
4.2.2 長方形のクラス
{プログラミングI(2017)例題19.4}
4.2. クラス設計の例 77
例題 4.2 (長方形のクラス) 標準入力から3つの長方形のデータ(横幅と高さ)を読
み取り、その中から最大面積の長方形の情報を出力するC++プログラムを作成せよ。
(考え方) 1個の長方形の横幅と高さをバラバラに保持するのではなく1箇所に集めて操 作方法と共に1つのカプセルの中に入れて抽象的な1つのデータとして扱うことにより、
見通しが良く間違いが少ないプログラムを構築できると考えられる。そこで、プログラム 中で1個の長方形を表すオブジェクトの型枠となるクラスRectangle を定義することに する。その際、
• ソフトウェア部品としては汎用性のあるものが望ましいので、個々のRectangleイ ンスタンスには固有のid番号を保持させることにし、id番号の一意性を保証するた めに最新のid番号(=過去に生成したRectangleインスタンスの個数)を常に保持す
る領域をRectangleのメンバとして用意する。また、
• 十分に情報隠蔽も考慮すべきであるので、個々のデータメンバは外部から直接アクセ スできない様にし、一方では外部からの正当な利用ができる様に参照や値変更の関数 メンバを用意する。
(Rectangleクラスの定義) Rectangleクラスの定義例を次に示す。
[motoki@x205a]$ cat -n Rectangle.h
1 /* 長方形オブジェクトのクラス Rectangle (仕様部) */
2
3 #ifndef ___Class_Rectangle 4 #define ___Class_Rectangle 5
6 #include <string>
7
8 class Rectangle {
9 static int numOfInstances; // これまでに生成したインスタンスの個数 10 const int id; // 長方形インスタンスに付けるid番号
11 double width; // 長方形の横幅 12 double height; // 長方形の高さ 13 public:
14 Rectangle(double width = 0.0, double height = 0.0)
15 : id(numOfInstances++), width(width), height(height) {}
16 Rectangle(const Rectangle& rectangle); //コピーコンストラクタ 17 //~Rectangle() {}
18 // オブジェクト(もしくはRectangleクラス全体)の情報を提供するための関数群 19 static int getNumOfInstances() { return numOfInstances; }
20 //これまでに生成したRectangleインスタンスの個数を返す 21 double getId() const { return id; }
22 double getWidth() const { return width; } 23 double getHeight() const { return height; }
24 void setWidth(double width) { this->width = width; } 25 void setHeight(double height) { this->height = height; }
26 double getArea() const { return width*height; } //長方形の面積を返す 27 std::string toString() const; //内部の長方形情報をstringデータとして返す 28 };
29
30 #endif
78 4. 何をどうクラスとして定義すべきか?
[motoki@x205a]$ cat -n Rectangle.cpp
1 /* 長方形オブジェクトのクラス Rectangle (実装部) */
2
3 #include <sstream>
4 #include <string>
5 #include "Rectangle.h"
6 using namespace std;
7
8 // static変数の初期化 ---9 int Rectangle::numOfInstances = 0;
10
11 // 各種コンストラクタ
---12 Rectangle::Rectangle(const Rectangle& rectangle) // コピーコンストラクタ 13 : id(numOfInstances++),
14 width(rectangle.width), height(rectangle.height) {}
15
16 // Rectangle型オブジェクトを操作するための関数群
---17 // オブジェクト内部に保持している長方形情報をstring型データとして返す
18 string Rectangle::toString() const 19 {
20 ostringstream os;
21 os << "rectangle(id=" << id
22 << ",width=" << width << ",height=" << height << ")";
23 return os.str();
24 }
[motoki@x205a]$
(Rectangleクラスの利用) Rectangleクラスが上の様に定義されていれば、それを利
用して例題4.2で要求されている作業を例えば次のC++プログラムの様に表すことがで きる。
[motoki@x205a]$ cat -n findMaxAmong3Rectangles.cpp
1 /* Rectangle.h, Rectangle.cpp の利用例 */
2 /* 次の作業を順に行うC++プログラム */
3 /* (1)標準入力から得られたデータを基にRectangleインスタンスを3つ生成 */
4 /* (2)面積最大のインスタンスを見つけてその情報を出力 */
5
6 #include <iostream>
7 #include <string>
8 #include "Rectangle.h"
9 using namespace std;
10
11 int main() 12 {
13 Rectangle* rectangle[3];
14
15 //標準入力からデータを入力、それを基に長方形インスタンスを生成 16 for (int i=0; i<3; ++i) {
17 double width, height;
18 cout << "長方形の幅と高さを入力:";
19 cin >> width >>height;
20 rectangle[i] = new Rectangle(width, height);
4.2. クラス設計の例 79
21 } 22
23 //生成された長方形インスタンスの内部情報を出力 24 cout << "生成された長方形インスタンス:" << endl;
25 for (int i=0; i<3; ++i) {
26 cout << "*rectangle[" << i << "]=" << rectangle[i]->toString() << endl;
27 } 28
29 //面積最大の長方形インスタンスを見つけてその情報を出力 30 int indexOfMaxArea = 0;
31 double maxArea = rectangle[0]->getArea();
32 for (int i=1; i<3; ++i) {
33 double area = rectangle[i]->getArea();
34 if (area > maxArea) { 35 indexOfMaxArea = i;
36 maxArea = area;
37 }
38 }
39 cout << "==>面積最大のものは"
40 << rectangle[indexOfMaxArea]->toString() << endl;
41
42 //heap領域から確保したメモリを開放
43 for (int i=0; i<3; ++i) 44 delete rectangle[i];
45 }
[motoki@x205a]$ g++ findMaxAmong3Rectangles.cpp Rectangle.cpp [motoki@x205a]$ ./a.out
長方形の幅と高さを入力:3 17 長方形の幅と高さを入力:10 10 長方形の幅と高さを入力:15 5 生成された長方形インスタンス:
*rectangle[0]=rectangle(id=0,width=3,height=17)
*rectangle[1]=rectangle(id=1,width=10,height=10)
*rectangle[2]=rectangle(id=2,width=15,height=5)
==>面積最大のものはrectangle(id=1,width=10,height=10) [motoki@x205a]$
ここで、この利用例のプログラムでは、ブロック内の局所配列宣言によってRectangle型 配列要素をスタック領域上に予め確保するのを止め、代わりに、個々のRectangleイン スタンスの初期設定値が決まった時点で初めてヒープ領域からメモリ確保をしてコンスト ラクタを呼び出す様にしている。そのため、
• この利用例のプログラム13行目 では、Rectangleインスタンスへのポインタを入れ る配列が用意されているだけである。
• プログラム20行目 では、Rectangleインスタンスのための領域がヒープ領域から確 保され、コンストラクタRectangle(width, height)によって初期化される。(領域 確保と適切な初期化が同時に行われ分かり易い。)
• プログラム43∼44行目 では、ブロック終了に伴って、ヒープ領域から確保したメモ リの開放を行なっている。(このプログラムの場合は必要ないが、こういう習慣をつ けておいた方が無難。)
80 4. 何をどうクラスとして定義すべきか?