派生と継承

隠ぺい化の次のステップとして、派生・継承を説明する。

派生を使わずに書くと...

元となるデータ構造(例えば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   派生クラスでもアクセス不可。
 

2015年12月

    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

アーカイブ

Google

このブログ記事について

このページは、T-Saitohが2015年5月24日 14:20に書いたブログ記事です。

ひとつ前のブログ記事は「数値の範囲とトラブル事例」です。

次のブログ記事は「6/1よりOffice365ProPlusが使えます」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。