新規記事の投稿を行うことで、非表示にすることが可能です。
2017年12月25日
《その205》 抽象クラス(3)
抽象クラス
下記のプログラムでは、抽象クラス Aaa は3つの純粋仮想関数 set, get, func を持っています。
純粋仮想関数は、関数 set, 関数 get のように、関数本体を与えずに記述するのが一般的ですが、
関数 func のように関数本体を定義することも可能です(★1.と ★2.)。
関数 Aaa::func は、関数 set と 関数 get を連続して呼び出します。
純粋指定子「 = 0 」★1. を付ける都合上、関数定義をクラス定義の外に記述してあります★2.。
また、抽象クラス Aaa のデストラクタ ~Aaa ★3. は virtual の付いた仮想デストラクタになっています。
virtual無しの場合は、プログラム終了時に、デストラクタ ~Bbb が呼ばれず、 動的に確保 ★4. した領域が解放されないままになってしまいます。
// ------------------------------------
#include <iostream>
class Aaa {
public:
virtual ~Aaa() { // ★3.
std::cout << "~Aaa が呼ばれました。\n";
}
virtual void set() = 0;
virtual void get() const = 0;
virtual void func() = 0; // ★1.
};
void Aaa::func() { // ★2.
set(); get();
}
class Bbb : public Aaa{
int n;
int* p;
public:
Bbb(int x) : n(x) {
p = new int[n]; // ★4.
}
~Bbb() {
delete[] p;
std::cout << "~Bbb が呼ばれました。\n";
}
void set() {
for (int i = 0; i < n; i++)
p[i] = i;
}
void get() const {
for (int i = 0; i < n; i++)
std::cout << p[i] << " ";
std::cout << '\n';
}
void func() { Aaa::func(); }
};
int main() {
Aaa* p = new Bbb(5);
p->func();
delete p;
}
// ------------------------------------
もし、★3.の箇所で、デストラクタ ~Aaa に virtual が付いていない場合は、下のような結果になります。
デストラクタ ~Bbb が呼ばれていないことが確認できます。
《その204》 抽象クラス(2)
抽象クラス
前回《203》、仮想関数からの派生関数 Rectangle を作成しました。
Rectangl関数は四角形クラスでしたが、今回は、横線クラス HorzLine と縦線クラス VertLine を作成します。
※ '-' で描く横線クラス … HorzLine
※ '|' で描く縦線クラス … VertLine
この2つの関数には、"線"という共通点があります。"線"ならば、その線の長さをデータメンバとして持っていなければなりません。
そこで、まず、直線クラス Line を考えます。線を描く方法は、横線と縦線で異なるので関数 draw は、ここではまだ定義できません。
class Shape {
public:
virtual void draw() = 0;
};
class Line : public Shape {
int length; // 線の長さ
public:
};
この Lineクラスは、Shapeクラスの public派生クラスですが、
基底クラス Shape の純粋仮想関数 draw をオーバーライドしていません。
そのため、Lineクラスにおいても、draw は純粋仮想関数のままです。
純粋仮想関数を有するクラスは、抽象クラスです。
したがって、Lineクラスは、まだ抽象クラスです。
次のプログラムでは、Lineクラスの public派生クラスとして、
横線クラス HorzLine,縦線クラス VertLine を定義しています。
// ------------------------------------
#include <iostream>
// 図形クラス(抽象クラス) Shape
class Shape {
public:
virtual ~Shape() = 0;
// 純粋仮想デストラクタ
virtual void draw() const = 0;
// 描画
// 純粋仮想関数
};
inline Shape::~Shape() { }
// 長方形クラス Rectangle
class Rectangle : public Shape {
int width; // 横幅
int height; // 高さ
public:
Rectangle(int w, int h)
: width(w), height(h) { }
void draw() const {
for (int i = 1; i <= height; i++) {
for (int j = 1; j <= width; j++)
std::cout << '*';
std::cout << '\n';
}
}
};
// 直線クラス(抽象クラス) Line
class Line : public Shape {
protected:
int length; // 線の長さ
public:
Line(int len) : length(len) { }
};
// 横線クラス HorzLine
class HorzLine : public Line {
public:
HorzLine(int len) : Line(len) { }
void draw() const {
for (int i = 1; i <= length; i++)
std::cout << '-';
std::cout << '\n';
}
};
// 縦線クラス VertLine
class VertLine : public Line {
public:
VertLine(int len) : Line(len) { }
void draw() const {
for (int i = 1; i <= length; i++)
std::cout << "|\n";
}
};
int main() {
Rectangle a(5, 3);
HorzLine c(20);
VertLine d(3);
a.draw(); std::cout << '\n';
c.draw(); std::cout << '\n';
d.draw();
}
// ------------------------------------
《その203》 抽象クラス(1)
抽象クラス
次のような、簡単なクラスを作るものとします。
※ '-' で描く横線クラス … HorzLine
※ '|' で描く縦線クラス … VertLine
※ '*' で描く四角形クラス … Rectangle
これらはすべて、図形ですから、まず具体的な個々の形には踏み込まずに、抽象的な
図形クラス … Shape
を作成することにします。
とは言っても、図形というだけでは、形に関してはまだ何もわかりません。ひとつ確かなことは、図形なら、"描く"作業が必ず必要なはずです。
そこで、次のようなクラスを作ってみます。draw関数は、"描く"ための関数(関数の中身は空っぽですが)です。
class Shape {
public:
virtual void draw() = 0;
// " = 0 " は純粋指定子
// この純粋指定子を付けて宣言された
// 仮想関数は、純粋仮想関数となりま
// す。
};
【 抽象クラス 】
クラス Shape には具体的な情報が含まれていないので、Shape型のオブジェクトを作ることは不可能です。
この Shape のようなクラスは抽象クラスと呼ばれ、具体的な図形クラスは、このクラスからの派生で実現するようにします。
次のプログラムでは、抽象クラス Shape から派生した Rectangleクラスの長方形オブジェクト a, b を生成して、それぞれのオブジェクトの draw関数で長方形を描いています。
// ------------------------------------
#include <iostream>
// 抽象クラス Shape
class Shape {
public:
virtual ~Shape() = 0;
// 純粋仮想デストラクタ
virtual void draw() const = 0;
// 描画
// 純粋仮想関数
};
inline Shape::~Shape() { }
// 長方形クラス Rectangle
// (抽象クラス Shape から派生)
class Rectangle : public Shape {
int width; // 横幅
int height; // 高さ
public:
Rectangle(int w, int h)
: width(w), height(h) { }
void draw() const {
for (int i = 1; i <= height; i++) {
for (int j = 1; j <= width; j++)
std::cout << '*';
std::cout << '\n';
}
}
};
int main() {
Rectangle a(5, 3);
Rectangle b(3, 4);
Shape& ref = b;
// Rectangle& ref = b; としても結果は同じで
// すが、図形の種類が増えてきて、様々な
// 処理をしなけらばならなくなったときの
// 効率を考えれば、Shape& とすべきです。
a.draw();
std::cout << '\n';
ref.draw();
}
// ------------------------------------
《その202》 仮想関数のいちばん基礎的な部分 のおさらい
仮想関数に関する、いちばん基礎的な事項を、
下記の2つのプログラムと プログラム中の注記で おさらいしてみました •̀д•́;)/
@【仮想関数の利用無し】の場合
// ------------------------------------
#include <iostream>
using namespace std;
/* 基底クラス */
class Aaa {
int a;
public:
Aaa(int x = 10) : a(x) { }
void get() { cout << a << '\n'; }
};
/* 派生クラス(クラス Aaa を public継承) */
class Bbb : public Aaa {
int b;
public:
Bbb(int x = 20, int y = 99)
: Aaa(x), b(y) { }
void get() { cout << b << '\n'; }
// 基底クラスの get と関数名が同じなので、
// 基底クラスの get は隠蔽される。
};
int main()
{
Bbb bbb;
// Bbb型オブジェクト bbb を生成。
Aaa& ref = bbb;
// クラス Bbb は多相的クラスではないので、
// ref は bbb への静的な Aaa&型参照。
ref.get(); // 20 が表示される。
bbb.get(); // 99 が表示される。
// 基底クラスの get は隠蔽されているので、
// 派生クラス内で定義された get
// が呼び出される。
bbb.Aaa::get(); // 20 が表示される。
// 基底クラス Aaa から継承した get関数に
// アクセス。
}
// ------------------------------------
A【仮想関数の利用有り】の場合
// ------------------------------------
#include <iostream>
using namespace std;
/* 基底クラス */
class Aaa {
int a;
public:
Aaa(int x = 10) : a(x) { }
virtual void get() { cout << a << '\n'; }
// virtual を付けて仮想関数にする。
// クラス Bbb の 関数 get も自動的
// に仮想関数になる。
// クラス Aaa, Bbb は、仮想関数をメンバ
// として持つので、多相的クラスです。
};
/* 派生クラス(クラス Aaa を public継承) */
class Bbb : public Aaa {
int b;
public:
Bbb(int x = 20, int y = 99)
: Aaa(x), b(y) { }
void get() { cout << b << '\n'; }
// 基底クラスの get と関数名・仮引数が同
// じ関数を、派生クラス内で定義。
// このことを、「オーバライドする」と
// 表現します。
// オーバライドした関数は、
// オーバライダと呼ばれます。
};
int main()
{
Bbb bbb;
// Bbb型オブジェクト bbb を生成。
Aaa& ref = bbb;
// クラス Bbb は多相的クラスなので、
// Aaa&型参照 ref の動的な型は Bbb&型。
ref.get(); // 99 が表示される。
bbb.get(); // 99 が表示される。
// 基底クラスの get は隠蔽されているので、
// 派生クラス内で定義された get
// が呼び出される。
bbb.Aaa::get(); // 20 が表示される。
// 基底クラス Aaa から継承した get関数に
// アクセス。
}
// ------------------------------------