================================================= =======================
クリックすると個人のホームページに直接移動します:Xiaobi はプログラム元ではありません
C++ シリーズのコラム:C++ 乾物店
代码仓库:Gitee
================================================= =======================
目次
継承の概念と定義
継承の概念
継承メカニズムは、コードを再利用可能にするオブジェクト指向プログラミングの最も重要な手段です。これにより、プログラマは元のクラスの特性を維持しながら機能を拡張および追加し、派生クラスと呼ばれる新しいクラスを生成できます。継承はオブジェクト指向プログラミングの階層構造を表し、単純なものから複雑なものまでの認知プロセスを具体化します。以前は、私たちが接してきた再利用は関数の再利用であり、継承はクラス設計レベルでの再利用でした。
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18;
};
class Student : public Person
{
protected:
int _stuid; // 学号
};
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
int main()
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
継承の定義
フォーマットを定義する
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类
継承とアクセス修飾子
継承された基本クラスのメンバーのアクセス方法の変更
クラスメンバー/継承メソッド | パブリック継承 | 保護された継承 | 私的継承 |
基本クラスのパブリックメンバー | 派生クラスのパブリック メンバー | 派生クラスの保護されたメンバー メンバー |
プライベート 派生クラスのメンバー |
基本クラスの保護された メンバー |
派生クラスの保護されたメンバー メンバー |
派生クラスの保護されたメンバー メンバー |
プライベート 派生クラスのメンバー |
基本クラスのプライベート メンバー メンバー |
派生クラスでは表示されません | 派生クラスでは表示されません | 派生クラスでは表示されません |
要約:
1. 基本クラスのプライベート メンバーは、どのように継承されたとしても、派生クラスには表示されません。ここで非表示とは、基本クラスのプライベート メンバーが引き続き派生クラス オブジェクトに継承されますが、構文により、クラスの内部または外部にかかわらず、派生クラス オブジェクトによるアクセスが制限されていることを意味します。
2. 基本クラスのプライベート メンバーには、派生クラスではアクセスできません。基本クラスのメンバーがクラスの外部から直接アクセスされることを望まないが、派生クラスではアクセスできるようにする必要がある場合、そのメンバーは保護されたものとして定義されます。継承により、保護されたメンバー修飾子が表示されていることがわかります。
3. 実際、上記の表を要約すると、基本クラスのプライベート メンバーがサブクラスでは表示されないことがわかります。サブクラス内の基本クラスの他のメンバーのアクセス メソッド == Min (基本クラスのメンバーのアクセス修飾子、継承メソッド)、public > protected
> private。4. キーワード クラスを使用する場合、デフォルトの継承メソッドはプライベートであり、構造体を使用する場合、デフォルトの継承メソッドはパブリックです。ただし、
明確に記述することをお勧めします。継承メソッド。5. 実際のアプリケーションでは、一般にパブリック継承が使用され、プロテクト/プライベート継承はほとんど使用されません。また、プロテクト/プライベート継承の使用は推奨されません
。継承は推奨されません。残りのメンバーは派生クラスでのみ使用できます。実際には、拡張と保守は強力ではありません。
class Person
{
public:
void Print()
{
cout << _name << endl;
}
protected:
string _name; // 姓名
private:
int _age; // 年龄
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
public:
void Pinrt()
{
cout << _name << _age << endl;
cout << _stunum << endl;
}
protected:
int _stunum; // 学号
};
基底クラスと派生クラスの代入変換
- 派生クラス オブジェクトは基本クラス オブジェクト/基本クラス ポインター/基本クラス参照に割り当てることができます。 。ここにはスライスまたはカッティングと呼ばれる鮮やかな用語があります。派生クラスの親クラス部分を切り取って代入するということです。
- 基本クラスのオブジェクトを派生クラスのオブジェクトに割り当てることはできません。
- 基本クラスのポインターまたは参照は、キャストを通じて派生クラスのポインターまたは参照に割り当てることができます。ただし、基本クラス ポインターは、派生クラス オブジェクトを指している場合にのみ安全である必要があります。ここでの基本クラスがポリモーフィック型の場合、RTTI (Run-Time Type Information) の Dynamic_cast を使用して識別し、安全な変換を実行できます。
class Person
{
protected:
string _name;
string _sex;
int _age;
};
class Student : public Person
{
protected:
int _No;
};
int main()
{
Student S;
Person P = S;
Person* PP = &S;
Person& ppp = S;
//基类通过强制转化赋值给派生类
Student* PS = (Student*)PP;
//
return 0;
}
継承の範囲
1. 継承システムでは、基本クラスと派生クラスは独立したスコープを持ちます。
2. サブクラスと親クラスに同じ名前のメンバーが存在する場合、サブクラスのメンバーは、親クラスが同じ名前のメンバーに直接アクセスすることをブロックします。この状況は と呼ばれます。 非表示。再定義とも呼ばれます。 (サブクラスのメンバー関数では、Base Class::Base Class Member を使用して明示的にアクセスできます)
3. 必須メンバー関数の非表示の場合、非表示を構成するには関数名が同じである必要があるだけであることに注意してください。
4. 実際には、継承システムで同じ名前のメンバーを定義しないことが最善であることに注意してください
//继承中的作用域
class Person
{
protected :
int _num = 1;
};
class Student : public Person
{
public:
void Print()
{
cout << _num << endl;
cout << Person::_num << endl;
}
protected:
int _num = 2;
};
int main()
{
Person P;
Student S;
S.Print();
return 0;
}
class Person
{
public:
void func()
{}
};
class Student : public Person
{
public:
void func(int)
{
}
};
派生クラスのデフォルトのメンバー関数
6 つのデフォルトのメンバー関数。「デフォルト」とは、関数を記述しない場合、コンパイラが自動的に関数を生成することを意味します。その後、派生クラス
では、これらのメンバーがどのように関数は生成されますか?
1. 派生クラスのコンストラクターは、基本クラスのコンストラクターを呼び出して、基本クラスのメンバーのその部分を初期化する必要があります。基本クラスにデフォルト コンストラクターがない場合は、派生クラス コンストラクターの初期化リスト フェーズ中に明示的に呼び出す必要があります。
2. 派生クラスのコピー コンストラクターは、基本クラスのコピー コンストラクターを呼び出して、基本クラスのコピーの初期化を完了する必要があります。
3. 派生クラスの Operator= は、基本クラスの Operator= を呼び出して、基本クラスのコピーを完了する必要があります。
4. 派生クラスのデストラクターは、呼び出された後に基本クラスのメンバーをクリーンアップするために、基本クラスのデストラクターを自動的に呼び出します。これにより、派生クラス オブジェクトが最初に派生クラス メンバーをクリーンアップし、次に基本クラス メンバーを順番にクリーンアップすることが保証されるためです。
5. 派生クラス オブジェクトを初期化するときは、最初に基本クラスのコンストラクターを呼び出し、次に派生クラスのコンストラクターを呼び出します。
6. 派生クラスのオブジェクトを破棄してクリーンアップする場合は、最初に派生クラスのデストラクターを呼び出してから、基本クラスのデストラクターを呼び出します。
7 後続のシナリオではデストラクターを書き換える必要があるため、書き換えの条件の 1 つは関数名が同じであることです (これについては後で説明します)。その後、コンパイラはデストラクタ名に特別な処理を実行して destructor() に変更するため、親クラスのデストラクタが virtual を追加しない場合、サブクラスのデストラクタと親クラスのデストラクタは隠された関係を形成します。
コンストラクタ
class person
{
public:
person(const char * name="")
:_name(name)
{}
protected:
string _name;
};
class student : public person
{
public:
student(const char* name,int a)
:person(name)
,_stuid(a)
{}
protected:
int _stuid;
};
int main()
{
student s("张三", 18);
return 0;
}
コピーコンストラクター
class person
{
public:
person(const char * name="")
:_name(name)
{}
person(const person& p)
:_name(p._name)
{}
protected:
string _name;
};
class student : public person
{
public:
student(const char* name,int a)
:person(name)
,_stuid(a)
{}
student(student& s)
:person(s)
, _stuid(s._stuid)
{}
protected:
int _stuid;
};
int main()
{
student s("张三", 18);
return 0;
}
代入のオーバーロード
class person
{
public:
person(const char * name="")
:_name(name)
{}
person(const person& p)
:_name(p._name)
{}
person& operator=(const person& p)
{
if (this != &p)
{
_name = p._name;
}
return *this;
}
protected:
string _name;
};
class student : public person
{
public:
student(const char* name,int a)
:person(name)
,_stuid(a)
{}
student(student& s)
:person(s)
, _stuid(s._stuid)
{}
student& operator=(const student& s)
{
if (this != &s)
{
person::operator=(s);
_stuid = s._stuid;
}
return *this;
}
デストラクター
class person
{
public:
person(const char * name="")
:_name(name)
{}
person(const person& p)
:_name(p._name)
{}
person& operator=(const person& p)
{
if (this != &p)
{
_name = p._name;
}
return *this;
}
~person()
{
cout << "~person()" << endl;
}
protected:
string _name;
};
class student : public person
{
public:
student(const char* name,int a)
:person(name)
,_stuid(a)
{}
student(student& s)
:person(s)
, _stuid(s._stuid)
{}
student& operator=(const student& s)
{
if (this != &s)
{
person::operator=(s);
_stuid = s._stuid;
}
return *this;
}
~student()
{
cout << "~student()" << endl;
}
protected:
int _stuid;
};
相続と友人
フレンド関係は継承できません。つまり、基本クラスのフレンドはサブクラスのプライベート メンバーや保護されたメンバーにアクセスできません
class student;
class person
{
public:
friend void Print(const person&p,const student&);
protected:
int _age;
private:
string _name;
};
class student : public person
{
public:
//friend void Print(const person& p, const student&);
protected:
int _stuid;
};
void Print(const person&p,const student& s)
{
cout << p._age << p._name << s._stuid << endl;
}
継承と静的メンバー
基本クラスは静的メンバーを定義しており、そのようなメンバーは継承システム全体で 1 つだけ存在します。サブクラス
がいくつ派生しても、 存在する静的メンバー インスタンスは 1 つだけです 。
複雑なダイヤモンド継承とダイヤモンド仮想継承
単一継承:サブクラスに直接の親クラスが 1 つだけある場合、この継承関係は単一継承と呼ばれます
多重継承:サブクラスに 2 つ以上の直接の親クラスがある場合、この継承関係は多重継承と呼ばれます。
ダイヤモンドの継承:ダイヤモンドの継承は、多重継承の特殊なケースです。
ダイヤモンド継承の問題:以下のオブジェクト メンバー モデルの構築から、ダイヤモンド継承にはデータの冗長性と曖昧さの問題があることがわかります。
Assistant オブジェクトには Person メンバーのコピーが 2 つあります。
//多继承及解决多继承的虚继承
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //学号
};
class Teacher : public Person
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
void Test()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
仮想継承は、ダイヤモンド継承のあいまいさとデータの冗長性の問題を解決できます。上記の継承関係に示されているように、Student と
Teacher が Person から継承するときに仮想継承を使用すると、問題を解決できます。仮想継承は他の場所では使用しないでください。
class Person
{
public:
string _name; // 姓名
};
class Student : virtual public Person
{
protected:
int _num; //学号
};
class Teacher : virtual public Person
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
void Test()
{
Assistant a;
a._name = "peter";
}
データの冗長性と曖昧性を解決するための仮想継承の原理
仮想継承の原理を研究するために、単純化されたダイヤモンド継承システムを与え、メモリ ウィンドウを使用してオブジェクト メンバーのモデルを観察しました。
class A
{
public:
int _a;
};
// class B : public A
class B : virtual public A
{
public:
int _b;
};
// class C : public A
class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
次の図は、ダイヤモンド継承のメモリ オブジェクト メンバー モデルです。ここでデータの冗長性がわかります。
次の図は、ダイヤモンド仮想継承のメモリ オブジェクト メンバー モデルです。ここでは、D オブジェクト内で、A がオブジェクト構成の最下位に配置されていることを分析できます
。 A は B と C にも属します。では、B と C はどのようにして共通の A を見つけますか? B と C の 2 つのポインタを介して
が指すテーブルを次に示します。この 2 つのポインタを仮想実表ポインタと呼び、これら 2 つのテーブルを仮想実表と呼びます。仮想ベーステーブルに格納されているオフセット。次の A は、 offset
によって見つけることができます。
以下は、上記の PERSON 関係のダイヤモンド仮想継承の原理の説明です。
継承の概要
1. C++ の構文は複雑だと多くの人が言いますが、実際には多重継承がその現れです。多重継承にはダイヤモンド継承があり、ダイヤモンド
継承にはダイヤモンド仮想継承があり、基礎となる実装は非常に複雑です。したがって、通常は多重継承を設計することはお勧めできません。また、ダイヤモンド継承を設計しないように注意してください。そうしないと、複雑さとパフォーマンスに問題が発生します。2. 多重継承は C++ の欠点の 1 つと考えられますが、Java などの最近の OO 言語の多くには多重継承がありません。
3. 継承と合成
- パブリック継承は is-a 関係です。つまり、すべての派生クラス オブジェクトは基本クラス オブジェクトです。
- 組み合わせは「有」と「一」の関係です。 B が A を結合し、すべての B オブジェクトの中に A オブジェクトが存在すると仮定します。
- クラスの継承よりもオブジェクトの合成を優先します。
- 継承を使用すると、基本クラスの実装に基づいて派生クラスの実装を定義できます。派生クラスの生成によるこの種の再利用は、多くの場合、ホワイトボックス再利用と呼ばれます。 「ホワイト ボックス」という用語は可視性を指します。継承では、基本クラスの内部詳細がサブクラスから見えます。継承により基底クラスのカプセル化がある程度破壊され、基底クラスの変更は派生クラスに大きな影響を与えます。派生クラスと基底クラス間の依存関係は非常に強く、結合度は高くなります。
- オブジェクトの合成は、再利用のためのクラス継承の代替手段です。オブジェクトを組み立てたり組み合わせたりすることで、より複雑な新しい機能を得ることができます。オブジェクトを合成するには、合成されるオブジェクトに明確に定義されたインターフェイスが必要です。このスタイルの再利用は、オブジェクトの内部の詳細が表示されないため、ブラックボックス再利用と呼ばれます。オブジェクトは「ブラック ボックス」としてのみ表示されます。結合されたクラス間には強い依存関係はなく、結合度は低いです。オブジェクトの構成を優先すると、各クラスをカプセル化した状態に保つことができます。
- 実際にできるだけ多くの組み合わせを試してみてください。この組み合わせは結合度が低く、コードの保守性が優れています。ただし、継承にも役割があり、一部のリレーションシップは継承に適しているため、継承を使用します。また、ポリモーフィズムを実現するには継承も必要です。クラス間の関係は継承したり結合したりすることができるので、そのまま結合してください。
本日の C++ における継承の紹介と共有はこれで終わりです。読んで多くのことを得ることができれば幸いです。また、記事の内容についてコメントしたり、コメント エリアでご自身の意見を共有したりすることもできます。皆様のご支援が私にとって前進の原動力となりますので、どうぞよろしくお願いいたします! ! !