目次
前文
この記事では、オブジェクト指向の三大特徴のうち、継承について主に詳しく解説します。
ここでは継成の意味と諸性質を中心に説明し、皆様に継成を理解していただきます。
1.チェンチェンとは何ですか?
1.1 継承の定義
小さな例を見てみましょう
上の絵を観察すると、ピカソの牛は牛の特徴を抽象化したものであることがわかり、下の水牛、牛、牛には基本的に上の絵の牛の特徴がすべて含まれており、これは継承とみなされます。ピカソの牛は親カテゴリとみなすことができ、水牛、乳牛、ダフ屋はサブカテゴリとみなすことができます。
継承(継承) メカニズムは、オブジェクト指向プログラミングでコードを再利用可能にするための最も重要な方法であり、プログラマが元のクラスの特性を維持したまま機能を拡張および追加することで、派生クラスと呼ばれる新しいクラスを生成できます。継承はオブジェクト指向プログラミングの階層構造を表し、単純なものから複雑なものまでの認知プロセスを反映します。これまでに経験した再利用は関数の再利用であり、継承はクラス設計レベルの再利用です。
ここでは、最初に派生クラスを作成するだけで、誰もがそれを体験できます。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
class person
{
public:
void print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
private:
string _name = "张三";
int _age = 18;
};
class student :public person
{
private:
int _stuNumber;
};
int main()
{
student aa;
aa.print();
return 0;
}
上記のように実行すると、継承後、親クラスのメンバーがサブクラスの一部になることがわかります。また、サブクラスを通じて親クラスのメンバー関数を呼び出すこともできます。どのような機能があるのでしょうか? 順番に相続の形式を説明していきます。
1.2 継承的格式
1.2.1 継承的使用格式
上の図に示すように、人は基本/親クラスと呼ばれ、学生はサブクラス/派生クラスと呼ばれます。
1.2.2 継承とアクセス修飾子
継承モードはアクセス修飾子とペアで対応しており、具体的な変更点は以下の通りです。
要約:
1. 基本クラスのプライベート メンバーは、派生クラスでどのように継承されても表示されません。ここでの非可視性とは、基本クラスのプライベート メンバーは引き続き派生クラス オブジェクトに継承されますが、派生クラス オブジェクトはクラスの内外に関係なくアクセスできないように文法的に制限されていることを意味します。
2. 基本クラスのプライベート メンバーは、派生クラスではアクセスできません。基本クラスのメンバーがクラスの外部から直接アクセスされることを望まないが、派生クラスではアクセスできるようにする必要がある場合、そのメンバーは保護済みとして定義されます。protected メンバー修飾子は継承によるものであることがわかります。
3. 実際、上記の表を要約すると、基本クラスのプライベート メンバーがサブクラスでは表示されないことがわかります。public > protected > private はパーミッションとみなされ、パーミッションは縮小することのみが可能であり、拡大することはできません。
4. キーワード class を使用する場合、デフォルトの継承メソッドは private になり、struct を使用する場合、デフォルトの継承メソッドは public になりますが、継承メソッドは明示的に記述するのが最善です。
5.実際には、パブリック継承が一般に使用され、プロテクト/プライベート継承はめったに使用されません。また、プロテクト/プライベート継承メンバーは派生クラスでのみ使用できるため、プロテクト/プライベート継承の使用は推奨されません。拡張保守性は強くありません。
class person
{
public:
void print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "张三";
private:
int _age = 18;
};
class student :private person
{
private:
int _stuNumber;
};
int main()
{
student aa;
//私有継承的话父类我们办法直接访问但是可以间接访问
aa.print();
return 0;
}
図にあるように、プライベート継承の場合、親クラスのすべてのメンバー関数や変数に直接アクセスすることはできないので、本当にアクセスする方法がないのでしょうか?サブクラスのメンバー関数を通じて親クラスのメンバーにアクセスできます。
実はこれは家族に似ていて、公的なものは部外者に門戸を開くもの、保護的なものは部外者を防ぐもの、私的なものは部外者や自分の家族を防ぐものです。
2 番目、基本クラスと派生クラスのオブジェクトのコピー変換
- 派生クラスのオブジェクトは、基本クラスのオブジェクト/基本クラスのポインター/基本クラスの参照に割り当てることができます。ここにはスライスまたはカットと呼ばれる比喩的な用語があります
。派生クラス内の親クラスの部分を切り取って過去のものに代入するという意味です -
基本クラスのオブジェクトを派生クラスのオブジェクトに割り当てることはできません。
-
基本クラスのポインタまたは参照は、キャストを通じて派生クラスのポインタまたは参照に代入できます。ただし、基本クラス
のポインタが派生クラス オブジェクトを指している場合は安全でなければなりません。ここで、基本クラスがポリモーフィック型の場合、RTTI (RunTime Type Information) の Dynamic_cast を使用して識別し、安全な変換を実行できます。(追記:これについては後ほど説明しますので、まずは知っておきましょう)class person { public: void print() { cout << "name:" << _name << endl; cout << "age:" << _age << endl; } protected: string _name = "张三"; int _age = 18; }; class student :public person { private: int _stuNumber; }; int main() { student aa; //1.子类对象可以赋值给基类的对象,指针,引用 person p = aa; person* ptr = &aa; person& pp = aa; //2.基类对象不能赋值给派生类 //aa = p;会报错 //3.基类的指针可以通过强转赋值给子类的指针 ptr = &aa; student* ps1 = (student*)ptr;//这种情况可以 //这个ptr指向的空间和aa指向的空间是相同的,所以不会越界 ptr = &p; student* ps2 = (student*)ptr;//这种情况可能会存在越界访问 //因为ptr只有父类的内容没有子类的内容 return 0; }
では、後で説明する基底クラスと派生クラスの間の代入変換はどのように実現されるのか。
三,継承中的作用域
1. 継承システムでは、基底クラスと派生クラスは独立したスコープを持ちます。
class person
{
public:
void print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "张三";
int _age = 18;
};
class student :public person
{
public:
void print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
cout << "stunumber:" << _stuNumber << endl;
}
private:
int _stuNumber=1111111;
};
int main()
{
student aa;
aa.print();
return 0;
}
上記のコードを実行すると何が起こるでしょうか?
上の図に示すように、print を呼び出すと、サブクラスと親クラスの両方に print があるにもかかわらず、サブクラスで最後に print を呼び出すことは何でしょうか? 状況はどうなるのでしょうか? これは実際には非表示と呼ばれます。
2. サブクラスと親クラスに同じ名前のメンバーが存在し、サブクラスのメンバーは、親クラスから同じ名前のメンバーへの直接アクセスをブロックします。この状況は、隠蔽または再定義と呼ばれます。この場合、同じ名前を持つサブクラスのメンバーが最初にアクセスされます。(サブクラスのメンバー関数では、基底 class::base クラスのメンバーを使用してアクセスを表示できます)
3. メンバー関数が非表示の場合、非表示を構成するには同じ関数名のみが必要であることに注意してください。
4. 実際には、継承システムで同じ名前のメンバーを定義しないことが最善であることに注意してください。
図のように、アクセス修飾子を介して親クラスの同名のメンバにもアクセスできますが、実際の継承では同名のメンバ関数は意味が少ないので定義しない方がよいでしょう。意義。
アイロニーに多肢選択問題を出してみましょう
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << "func(int i)->" <<i<<endl;
}
};
void Test()
{
B b;
b.fun(10);
};
对于上面程序的运行,下面选项那个正确?
A:A中fun和B中fun构成函数重载
B:A中fun和B中fun构成隐藏
C:程序错误
D:上面三个选项均不正确
この質問では B を選択しますが、まず、A の fun と B の fun は同じスコープ内にないため、関数のオーバーロードにはなりません。A の fun は B の fun と同じ名前なので、非表示になります。
4 番目、派生クラス/サブクラスのデフォルトのメンバー関数
6 つのデフォルトのメンバー関数。「デフォルト」とは、記述しなければコンパイラが自動的に生成することを意味します。では、
これらのメンバー関数は派生クラスでどのように生成されるのでしょうか?
1. 派生クラスのコンストラクターが呼び出されるとき、基本クラスのメンバーのその部分は、構築用の基本クラスのデフォルト コンストラクターを呼び出す必要があります。基本クラスにデフォルトのコンストラクターがない場合、派生クラスはコンストラクターの初期化シーケンスで明示的に呼び出す必要があります。
2. 派生クラスのコピー コンストラクターは、基本クラスのコピー コンストラクターを呼び出して、基本クラスのコピー構築を完了する必要があります。
3. 派生クラスの演算子= は、基本クラスの演算子= を呼び出して、基本クラスの割り当てを完了する必要があります。
4. 派生クラスのデストラクターが呼び出されるとき、派生クラスのデストラクターが呼び出された後に基本クラスのデストラクターが呼び出されます。これにより、派生クラスのメンバーが最初にクリーンアップされ、次に派生クラスのメンバーがクリーンアップされることが保証されるためです。基底クラスは回避するためにクリーンアップされます。まず基底クラスのメンバーをクリーンアップすると、ワイルド ポインターの状況が現れます。
5. 派生クラスの初期化構造。最初に基本クラスのコンストラクターを呼び出して基本クラス部分を初期化し、次に派生クラスを初期化します。
6. 派生クラスのオブジェクトが破棄され、最初に派生クラスが破棄され、次に基本クラスが破棄されます。
7. 後続のシナリオではデストラクターを書き換える必要があるため、関数名が同じであることが書き換えの条件の 1 つです (これについては後で説明します)。その後、コンパイラはデストラクタ名に対して特別な処理を実行し、destructor() として処理します。そのため、親クラスのデストラクタが virtual を追加しない場合、サブクラスのデストラクタと親クラスのデストラクタは隠された関係を形成します。
一般に、6 つのデフォルト メンバー関数の場合、派生クラスと基本クラスはそれぞれのデフォルト メンバー関数を呼び出します。
class Person
{
public:
Person(const char* name = "peter")
: _name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
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 num)
: Person(name)
, _num(num)
{
cout << "Student()" << endl;
}
Student(const Student& s)
: Person(s)
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator = (const Student& s)
{
cout << "Student& operator= (const Student& s)" << endl;
if (this != &s)
{
Person::operator =(s);
_num = s._num;
}
return *this;
}
~Student()
{
cout << "~Student()" << endl;
}
protected:
int _num; //学号
};
void Test()
{
Student s1("jack", 18);
Student s2(s1);
Student s3("rose", 17);
s1 = s3;
}
int main()
{
Test();
return 0;
}
これを見れば、先の基底クラスと派生クラス間の代入変換がどのように実現されているかも理解できるはずですが、実際には、親クラスのサブクラスの一部を 取り出してコピー構築し、新しいクラスを作成することになります。サブクラスには親クラスのメンバー変数が含まれている必要があるためです。
6位、チェンチェンとユーユアン
フレンド関係は継承できません。つまり、基本クラスのフレンドはサブクラスのプライベートおよび保護されたメンバーにアクセスできません。
class student;
class person
{
public:
friend void Test(const person& p, const student& s);
protected:
string _name = "张三";
int _age = 18;
};
class student :public person
{
protected:
int _stuNumber=1111111;
};
void Test(const person& p,const student& s)
{
cout << p._name << endl;
cout << p._age << endl;
cout << s._stuNumber << endl;
}
int main()
{
person pp;
student aa;
Test(pp,aa);
return 0;
}
6、継承と静的メンバー
基本クラスが静的な静的メンバーを定義している場合、そのようなメンバーは継承システム全体で 1 つだけ存在します。サブクラスがいくつ派生しても、静的メンバー インスタンスは 1 つだけです
要約する
以上がこの記事の内容です。主に派生クラスと基底クラスの定義、形式、代入変換、継承範囲、サブクラスのデフォルトコンストラクタ6種類、フレンドは継承できないこと、静的メンバの継承について説明します。鉄人たちが何かを得ることができれば幸いです。
次回は相続における偽装相続についてお話しますが、ベテランの皆様には引き続きご支援を賜りますようお願い申し上げます。