l VisualC クラスの派生 '97.00.00
l.お知らせ 内容 LINK FILE   HTML Win PC Unix MS-DOS C C++ Mfc Java 
目次.C++言語_ 1 クラス 2 派生クラス 5 演算子のオーバーロード クラス(1)
 準備 3 オブジェクト 6 テンプレート 8 etc クラス(2)
 CC++ 4 フレンド・多重継承 7 メモリ確保・例外処理 未使用
 (別ページ)...  以上のまとめ  
( ページ内 bih-p_22.htm )
基本クラスと派生クラス   クラスのコピーを作る   実際のコピーと違う点   基本クラスを区別する   private メンバにアクセスする   基本クラスには影響がありません   アクセスできる範囲   クラスは設計図   オブジェクトのポインタ   仮想関数   純粋仮想関数と抽象クラス  

T 基本クラスと派生クラス super-class , sub-class
類似したクラスが複数必要な場合は、基本的なクラスを書いてそれを必要なだけコピーし、そのコピーを目的に応じて修正すればプログラミング作業が楽になります。
VisualC++ はそのような機能をあらかじめ持っているので、手作業でコピーする必要はありません。
コピーの元になるクラスを基本クラスと呼び、コピーして作ったクラスは派生クラスと呼びます。

T クラスのコピーを作るには
クラス名の後に、: とコピー元のクラス名を付けてクラスを宣言します。
class C2 :public C1 // 派生クラス C2を宣言 (基本クラスは C1)
{ /* 新しく追加するメンバ*/ }
コピーしてきたメンバのアクセス指定子は、新しく追加するメンバとは別に書きます。
アクセス指定子を省略すると private を指定したことになり、main() や他のクラスからはアクセス禁止になります。

C1 と C2 のふたつクラスを宣言し、C1 から C2 に派生する例を示します。
#include <iostream.h> /* 派生クラス */
class C1            // 基本クラス C1
{ public: int a; };
class C2 :public C1 // 派生クラス C2 (基本クラスは C1)
{ public: int b; };
void main()
{ C2 OB2; // C2 のオブジェクト OB2 を宣言
  OB2.a =1; // a は、C1 から C2 にコピーされたメンバ
  OB2.b =2; // b は、C2 で追加したメンバ
  cout << OB2.a << " " << OB2.b << endl;
  cout << "文字を入力して下さい"; char x; cin >> x;
}
上記の派生クラス C2 は、次のように宣言したのとほぼ等価です。
class C2  // クラス C2
{ public: int a;
  public: int b;
};

T 実際のコピーと違う点
1.基本クラスからコピーしてきたメンバと、新しく追加したメンバは区別されます。
基本クラスのメンバと同じ名前のメンバを追加した場合は、新しく追加したメンバが優先的に使用されます。
2.基本クラスで private のアクセス指定がされているメンバは、派生クラスのメンバ関数で操作できません。
基本クラスの public に指定されているメンバ関数を呼び出して操作します。

T 派生クラスの中の基本クラスを区別するには
メンバ名の前に、記号 :: と共に基本クラス名を記述します。

派生クラスで、基本クラスのメンバ a と同じ名前のメンバ a を追加して、両方を区別する例を示します。
#include <iostream.h> /* メンバの区別 */
class C1            // 基本クラス C1
{ public: int a; };
class C2 :public C1 // 派生クラス C2 (基本クラスは C1)
{ public: int b; // C2 で追加したメンバ b
          int a; // C2 で追加したメンバ a
//public: int a; ← C1 からのコピー
  void func() // C2 で追加したメンバ関数 func
  {     a=2; // C2 で追加した a に 2 を代入
    C2::a=2; //   〃
    C1::a=1; // C1 からコピーした a に 1 を代入
  }
};
void main()
{ C2 OB2; // C2 のオブジェクト OB2 を宣言
      OB2.a =2; // C2 で追加した a に 2 を代入
  OB2.C2::a =2; //   〃
  OB2.C1::a =1; // C1 からコピーした a に 1 を代入
  cout << OB2.a << endl; // 表示する例
  cout << "文字を入力して下さい"; char x; cin >> x;
}
クラスを指定する記号 ::スコープ解決演算子と呼ばれます。

普通は基本クラスのメンバと同じ名前のメンバは追加しません。
基本クラスのメンバ関数が後述の仮想関数である場合だけ、基本クラスのメンバ関数と同じ名前のメンバ関数を追加することになっています。

T private メンバにアクセスするには
そのクラスの、public メンバ関数を使って間接的にアクセスします。
プライベートメンバを宣言したら、それを扱うパブリックメンバ関数を必ず書きます。
プライベートメンバだけのクラスでは、全く利用することができません。

クラス C1 の private メンバを、public メンバ関数を使って、派生クラスで操作する例を示します。
(代入用の関数 func()だけしか設けていないので、何も表示されません。)
#include <iostream.h> /* プライベートメンバへのアクセス */
class C1            // 基本クラス C1
{ private: int a;    // プライベート メンバデータ a
  public:  void func() { a=1; } // パブリック メンバ関数
};
class C2 :public C1 // 派生クラス C2 (基本クラスは C1)
{ public: int b; // C2 で追加したメンバ
//private: int a; ← C1 からのコピー
//public: void func() { a=1; } ← C1 からのコピー
  void func2() // C2 で追加したメンバ関数
  {     func(); // C1 からコピーした func を呼び出して代入する。
    C1::func(); //   〃 (どちらの書式でもよい。)
  } // func2() は a にアクセスできないので、func() にアクセスしてもらいます。
};
void main()
{ C2 OB2; // C2 のオブジェクト OB2 を宣言
      OB2.func2(); // C2 で追加した関数を呼び出して代入する
  OB2.C2::func2(); //   〃 (どちらの書式でもよい。)
      OB2.func(); // C1 からコピーした関数を呼び出して代入する
  OB2.C1::func(); //   〃
  cout << "文字を入力して下さい"; char x; cin >> x;
}

派生クラスのメンバ関数で、基本クラスからコピーしたメンバを扱うには
基本クラスであらかじめ protected のアクセス指定をしておきます。

プロテクテッドのアクセス指定をしたメンバは、基本クラスのメンバ関数と、基本クラスから派生したクラスのメンバ関数だけがアクセスできます。
class C1 { private: int a; }; // メンバ a はクラス C1 のメンバ関数だけがアクセスできます。
class C1 { protected: int a; }; // メンバ a は派生クラスのメンバ関数もアクセスできます。
class C1 { public: int a; }; // メンバ a はどこからでもアクセスできます。

T 派生クラスを作っても基本クラスには影響がありません。
基本クラスと派生クラスのオブジェクトを作る例を示します。
#include <iostream.h> /* 基本クラスはそのまま */
class C1            // 基本クラス C1
{ public: int a; };
class C2 :public C1 // 派生クラス C2 (基本クラスは C1)
{ public: int b; // C2 で追加したメンバ
//public: int a; ← C1 からのコピー
};
void main()
{ C2 OB2; // C2 のオブジェクト OB2 を宣言
      OB2.a =2; // C2 で追加した a に 2 を代入
  OB2.C1::a =1; // C1 からコピーした a に 1 を代入
  C1 OB1; // C1 のオブジェクト OB1 を宣言
  OB1.a =11; // C1 の a に 11 を代入
  cout << OB1.a << endl; // 表示する例
  cout << "文字を入力して下さい"; char x; cin >> x;
}

T 基本クラスからコピーしたメンバにアクセスできる範囲は
アクセス(access : 接近、出入口、発作) :
変数に代入したり関数を呼び出すなど、何かを働きかけること。

次のようなクラスがあるとします。
class C1      // 基本クラス
{ public : int a; };
class C2 :public : C1 // 派生クラス   
{ private: int b;   // C2 で追加したメンバ
  public: func() { a=1; b=2;} // C2 で追加したメンバ関数
//public: int a; ← C1 からコピーされたメンバ
};

派生クラスにコピーされたメンバは、アクセス指定子の指定のされ方によって、アクセスが許される範囲が次のようになります。
基本クラスの指定
public
public
private
private
派生クラスの指定
public
private
public
private
(メンバ関数は)
アクセス可
アクセス可
アクセス不可
アクセス不可
クラスの外からは
アクセス可
アクセス不可
アクセス不可
アクセス不可

T クラスは設計図で、オブジェクトは製品
クラスはオブジェクトの設計図に相当し、オブジェクトはその設計図を元に作られた実物に相当します。
この点はストラクチャと同じです。
変数の型やクラスなどに対して、それに合わせて実際に作られた変数やオブジェクトはインスタンス(instance:例 実例)と呼ばれます。

ひとつのクラスから作られたオブジェクトのメンバ関数は全て同じですが、 メンバ関数は自分が含まれているオブジェクトのメンバデータを処理しますから、 メンバデータの値が異なればそれぞれに別な働きをすることになります。

T オブジェクトのポインタ
オブジェクトのポインタを代入するためのポインタ変数はクラス名を指定して宣言します。
このとき指定するクラス名は、変数型やストラクチャの型と同様に、クラス型と呼びます。
class C1       // クラス C1
{ public : int a; }

void main()
{ C1 *p; // C1 型のポインタ変数を宣言
 C1 OB; // C1 のオブジェクトを宣言
 p=&OB; // p にオブジェクトのポインタを代入
 cout << p->a << endl; // ポインタの使用例
}
   
緑色の縁取りがないサンプルソースには、左の例のように、全角スペース(コンパイルできない)が含まれている場合があります。
 

もし派生クラスで、基本クラスのメンバと同じ名前のメンバを追加したときは、派生クラスで新しく追加したメンバの方が優先的に使用されます。
#include <iostream.h> /* 同じ名前のメンバ */
class C1             // 基本クラス C1
{ public :
   void func() { cout << "基本クラスのメンバ関数" << endl; }
};
class C2 : public C1 // 派生クラス C2 (基本クラスは C1)
{ public :
   void func() { cout << "派生クラスのメンバ関数" << endl; }
};
void main()
{ C2 *p; // C2 型のポインタ変数を宣言
  C2 OB; // C2 のオブジェクトを宣言
  p=&OB; // p2 にオブジェクトのポインタを代入
  p->func();     // C2 で追加したメンバ関数が呼び出されます
  p->C1::func(); // C1 からコピーしたメンバ関数を呼び出します
  char s; cin >> s;
}

基本クラス型のポインタには派生クラスのポインタを代入できます
基本クラス型のポインタに派生クラスのオブジェクトを代入しても、 そのポインタを使って上記の例のように派生クラスのオブジェクトを操作できます。
class C1       // 基本クラス C1
{ public : int a; }
class C2 : public C1 // 派生クラス C2 (基本クラスは C1)
{ public : int b; }
void main()
{ C1 *p;  // C1 型のポインタ変数を宣言
 C1 OB1; // C1 のオブジェクトを宣言
 p=&OB1; // p にオブジェクトのポインタを代入
 // ポインタ変数 p を使って C1 のオブジェクト OB1 にアクセスできます。
 C2 OB2; // C2 のオブジェクト OB2 を宣言
 p=&OB2; // p にオブジェクト OB2 のポインタを代入
 p->b=5; // ポインタを使った代入の例
 // ポインタ変数 p1 を使って C2 のオブジェクト OB2 にアクセスできます。
}
派生クラスで、基本クラスと同じ名前のメンバを追加した場合、
派生クラス型のポインタと基本クラス型のポインタは、優先的に使用するメンバが逆になります。
派生クラス型のポインタ : 派生クラスで新しく追加したメンバを優先的に使用します。
基本クラス型のポインタ : 基本クラスからコピーされたメンバを優先的に使用します。

T 仮想関数
派生クラスでは、基本クラスのメンバと同じ名前のメンバを追加できます。
基本クラスからコピーしたメンバと派生クラスで新しく追加したメンバは区別されるため、 どちらか一方には OB.C1::a や P->C1::a のように、クラスを記述して操作する必要があります。
仮想関数は、この記述を省略するために使われます。

仮想関数は、virtual を付けて宣言します。
派生クラスで、仮想関数と同じ名前のメンバ関数を追加することはオーバーライドといいます。

基本クラス型のポインタ
基本クラスからコピーされたメンバを優先的に使用します。
ただし仮想関数は、派生クラスで新しく追加したものが優先的に使用されます。

class C1       // 基本クラス C1
{ public :
  virtual void func() { cout << "基本クラスの仮想関数"; }
};
class C2 : public C1 // 派生クラス C2 (基本クラスは C1)
{ public :
  void func() { cout << "派生クラスで追加したメンバ関数"; }
};
void main()
{ C1 *p;  // C1 型のポインタ変数を宣言
 C2 OB2; // C2 のオブジェクト OB2 を宣言
 p=&OB2; // p にオブジェクト OB2 のポインタを代入
 // ポインタ変数 p を使って C2 のオブジェクト OB2 にアクセスできます。
 p->func();   // C2 で追加した func() が呼び出されます
 p->C1::func(); // C1 からコピーされた func() を呼出します。
 // 仮想関数でない場合とは、優先関係が逆になります。
}

仮想関数を使うと
例えばひとつのメンバ関数だけが違うクラスが複数必要なとき
まず、異なる定義内容のメンバ関数を追加した派生クラスを必要なだけ宣言します。
そのメンバ関数を仮想関数にすれば、それらのクラスのオブジェクトは全て基本クラス型のポインタを使って直接( P->C1::a のように指定しなくても)アクセスできます。

次の例は、基本クラスの仮想関数を、クラス C2 と C3でオーバーライドしています。
なお、この例では基本クラスで仮想関数 func() を宣言しているだけで、内容を定義していません。
このように、内容を定義しない仮想関数は純粋仮想関数と呼びます。
#include <iostream.h> /* 仮想関数 */
class C1             // 基本クラス C1
{ public:         void func1() { cout << "果物="; } // 普通のメンバ関数
          virtual void func()=0;                      // 純粋仮想関数
};
class C2 : public C1 // 派生クラス C2 (基本クラスは C1)
{ public : void func(){ cout << "りんご" << endl; } // オーバーライド
};
class C3 : public C1 // 派生クラス C3 (基本クラスは C1)
{ public : void func(){ cout << "ミカン" << endl; } // オーバーライド
};
void main()
{ C1 *p2, *p3; // C1 型のポインタ変数 p2 と p3 を宣言
  C2 OB2; // C2 のオブジェクト OB2 を宣言
  p2=&OB2; // p2 にオブジェクト OB2 のポインタを代入
  p2->func1(); // C2 にコピーされた func1() の呼出し  果物=
  p2->func();  // C2 で追加した func() の呼出し      りんご

  C3 OB3; // C3 のオブジェクト OB3 を宣言
  p3=&OB3; // p3 にオブジェクト OB3 のポインタを代入
  p3->func1(); // C3 にコピーされた func1() の呼出し  果物=
  p3->func();  // C3 で追加した func() の呼出し      ミカン
  char s; cin >> s;
}
もし仮想関数を使用しなければ、コピーされた func1() か、追加した func() のどちらかに C1:: のようなクラスの指定をして呼出す必要があります。

なお、オーバーライドしたメンバ関数は派生クラスで追加したのですから、基本クラスでプライベートに指定されているかどうかは関係ないと考えられます。
しかし、基本クラス型のポインタを使って呼び出す場合に限り、基本クラスでパブリックに指定しておく必要があるようです。

T 純粋仮想関数と抽象クラス
内容を定義しない仮想関数です。
純粋仮想関数を宣言するときは、{定義内容}の部分を =0 と書きます。

純粋仮想関数は内容が空ですから、純粋仮想関数を含むクラスはオブジェクトの設計図としては未完成です。
従って、純粋仮想関数を含むクラス(上記の例ではクラス C1)は、オブジェクトを宣言できません。

純粋仮想関数を含むクラスを基本クラスとする派生クラスも、未完成の設計図をコピーするわけですから、純粋仮想関数をオーバーライドして設計図を完成しなければ、オブジェクトを宣言できません。

純粋仮想関数を含むクラスは抽象クラスと呼ばれます。

T

C++言語 1 クラス 2 派生クラス 3 オブジェクト
 
  mtoga@sannet.ne.jp   登録日 '96. 6.15
URL : http://www.page.sannet.ne.jp/mtoga/index.html