隠ぺい化の次のステップとして、派生・継承を説明する。
派生を使わずに書くと…
元となるデータ構造(例えばPersonが名前と年齢)でプログラムを作っていて、 途中でその特殊パターンとして、所属と学年を加えた学生(Student)という データ構造を作るとする。
// 元となる構造体(Person) struct Person {
char name[ 20 ] ; // 名前
int age ; // 年齢
} ;
// 初期化関数
void set_Person( struct Person* p ,
char s[] , int x ) {
strcpy( p->name , s ) ;
p->age = x ;
}
// 表示関数
void print_Person( struct Person* p ) {
printf( "%s %d\n" , p->name , p->age ) ;
}
void main() {
struct Person saitoh ;
set_Person( &saitoh , "t-saitoh" , 50 ) ;
print_Person( &saitoh ) ;
}
パターン1(そのまんま…)
上記のPersonに、所属と学年を加えるのであれば、以下の方法がある。 しかし以下パターン1は、要素名がname,ageという共通な部分があるようにみえるが、 プログラム上は、PersonとPersonStudent1は、まるっきり関係のない別の型にすぎない。
このため、元データと共通部分があっても、同じ処理を改めて書き直しになる。
// 元のデータに追加要素(パターン1)
struct PersonStudent1 {
char name[ 20 ] ; // 名前
int age ; // 年齢
char dep[ 20 ] ; // 所属
int grade ; // 学年
} ;
void set_PersonStudent1( struct PersonStudent1* p ,
char s[] , int x ,
char d[] , int g ) {
strcpy( p->name , s ) ; // 同じことを書いてる
p->age = x ;
strcpy( p->dep , d ) ; // 追加分はしかたない
p->grade = g ;
}
// 名前と年齢だけ表示
void print_PersonStudent1( struct PersonStudent1* p ) {
// また同じ処理を書いてる
printf( "%s %d\n" , p->name , p->age ) ;
}
void main() {
struct PersonStudent1 naka1 ;
set_PersonStudent1( &naka1 ,
"naka" , 22 , "PS" , 2 ) ;
print_PersonStudent1( &naka1 ) ;
}
パターン2(元データの処理を少し使って…)
パターン1では、同じような処理を何度も書くことになり、面倒なので、 元データ用の関数をうまく使うように書いてみる。
// 元のデータに追加要素(パターン2)
struct PersonStudent2 {
struct Person person ;
char dep[ 20 ] ;
int grade ;
} ;
void set_PersonStudent2( struct PersonStudent2* p ,
char s[] , int x ,
char d[] , int g ) {
// Personの関数を部分的に使う
set_Person( &(p->person) , s , x ) ;
// 追加分はしかたない
strcpy( p->dep , d ) ;
p->grade = g ;
}
void print_PersonStudent2( struct PersonStudent2* p ) {
// Personの関数を使う。
print_Person( &p->person ) ;
}
void main() {
struct PersonStudent2 naka2 ;
set_PersonStudent2( &naka2 ,
"naka" , 22 , "PS" , 2 ) ;
print_PersonStudent2( &naka2 ) ;
}
このパターン2であれば、元データ Person の処理をうまく使っているので、 プログラムの記述量を減らすことはできるようになった。
しかし、print_PersonStudent2() のような処理は、元データ構造が同じなのに、 いちいちプログラムを記述するのは面倒ではないか?
そこで、元データの処理を拡張し、処理の流用ができないであろうか?
基底クラスから派生クラスを作る
オブジェクト指向では、元データ(基底クラス)に新たな要素を加えたクラス(派生クラス)を 作ることを「派生」と呼ぶ。派生クラスを定義するときは、クラス名の後ろに、 「:」「public/protected/private」基底クラス名を書く。
// 基底クラス
class Person {
private:
char name[ 20 ] ;
int age ;
public:
Person( const char s[] , int x ) {
strcpy( name , s ) ;
age = x ;
}
void print() {
printf( "%s %d\n" , name , age ) ;
}
} ;
// 派生クラス
class Student : public Person {
private:
char dep[ 20 ] ;
int grade ;
public:
Student( const char s[] , int x ,
const char d[] , int g )
: Person( s , x ) // 基底クラスのコンストラクタ
{
strcpy( dep , d ) ;
grade = g ;
}
} ;
void main() {
Person saitoh( "t-saitoh" , 50 ) ;
saitoh.print() ;
Student naka( "naka" , 22 , "PS" , 2 ) ;
naka.print() ;
}
ここで注目すべき点は、main()の中で、Studentクラス"naka"に対し、naka.print() を呼び出しているが、パターン2であれば、print_PersonStudent2()に相当するプログラムを 記述していない。 しかし、この派生を使うと Person の print() が自動的に流用することができる。 これは、基底クラスのメソッドを「継承」しているから、 このように書け、名前と年齢「naka 22」が表示される。
さらに、Student の中に、以下のような Student 専用の新しい print()を記述してもよい。
class Student ...略... {
...略...
void print() {
Person::print() ;
printf( "%s %d\n" , dep , grade ) ;
}
} ;
void main() {
...略...
Student naka( "naka" , 22 , "PS" , 2 ) ;
naka.print() ;
}
この場合は、継承ではなく機能が上書き(オーバーライト)されるので、 「naka 22 / PS 2」が表示される。
派生クラスを作る際の後ろに記述した、public は、他にも protected , private を 記述できる。
public だれもがアクセス可能。 protected であれば、派生クラスからアクセスが可能。 派生クラスであれば、通常は protected で使うのが一般的。 private 派生クラスでもアクセス不可。