少し前に、友人がTencentによって採用されたバックエンド開発ポストに会い、彼と話をしました。彼は、Tencentの側はさらに基本的であると述べました。ソーシャル採用であっても、C ++、オペレーティングシステム、ネットワーク、およびアルゴリズムについてはさらに質問があります。 。なかでも、仮想継承に関しては、C ++の構築と破棄の順序について質問がありますが、普段はあまり気にしないので、面接官の多くが恥ずかしい思いをします。友人もその一人なので、まとめる必要があります。 。
C ++では、クラスオブジェクトが作成されると、コンパイラは自動的にコンストラクタと呼ばれるものを呼び出します。C++のクラスとクラスは、継承、構成など、多くの場合に関連していることがわかっています。このトピックは、記事「UMLクラス図について」で説明されています(私を突いてください)。この記事では、主にさまざまな状況での構築と破壊のシーケンスを例を通して要約します。
継承
シナリオ:クラスBは2つの親クラスAとCを継承します。各クラスのコンストラクターとデストラクタは非常に単純です。対応する関数名を出力するだけで、コンストラクションとデストラクタの実行順序を確認できます。
#include <iostream>
using namespace std;
class A
{
public:
A(){cout << "A()" << endl;}
~A(){cout << "~A()" << endl;}
};
class C
{
public:
C(){cout << "C()" << endl;}
~C(){cout << "~C()" << endl;}
};
class B: public A, public C
{
public:
B(){cout << "B()" << endl;}
~B(){cout << "~B()" << endl;}
};
int main(int argc, char const *argv[])
{
B b;
return 0;
}
bogon:dataStructure lizhong$ ./t
A()
C()
B()
~B()
~C()
~A()
実行結果からわかるように、サブクラスオブジェクトを作成するときは、まず親クラスのコンストラクターを実行し、次に独自のコンストラクターを実行します。サブクラスが複数の親クラスを継承する場合、親クラスは継承順に左から右に呼び出されます。コンストラクター(この例では、最初にAを構築し、次にCを構築します)、破棄の順序は構築の順序と逆です。
また、仮想継承と呼ばれる別の種類の継承があることもわかっています。この場合の構築と破棄の順序を見てみましょう。
#include <iostream>
using namespace std;
class A
{
public:
A(){cout << "A()" << endl;}
~A(){cout << "~A()" << endl;}
};
class C
{
public:
C(){cout << "C()" << endl;}
~C(){cout << "~C()" << endl;}
};
class B: public A, public C
{
public:
B(){cout << "B()" << endl;}
~B(){cout << "~B()" << endl;}
};
int main(int argc, char const *argv[])
{
B b;
return 0;
}
bogon:dataStructure lizhong$ ./t
C()
A()
B()
~B()
~A()
~C()
仮想継承と一般継承の構築と破棄の順序がまだ少し異なっていることがわかります。親クラスの構築順序が変更されました。仮想継承のCコンストラクターが最初に実行され、次にAが実行されます。最後に、独自のコンストラクターが呼び出され、破棄の順序は構築の順序と逆になります。
メンバーには、他のクラスオブジェクトメンバーが含まれます
シナリオ:クラスBにはクラスAオブジェクトとクラスCオブジェクトのメンバーが含まれ、クラスBでは、そのメンバーの宣言の順序は、最初にcを宣言し、次にaを宣言することです。クラスBオブジェクトを作成するときは、コンストラクターとデストラクターの実行順序を確認してください。
#include <iostream>
using namespace std;
class A
{
public:
A(){cout << "A()" << endl;}
~A(){cout << "~A()" << endl;}
};
class C
{
public:
C(){cout << "C()" << endl;}
~C(){cout << "~C()" << endl;}
};
class B
{
public:
B():a(A()), c(C()) {cout << "B()" << endl;}
~B(){cout << "~B()" << endl;}
C c;
A a;
};
int main(int argc, char const *argv[])
{
B b;
return 0;
}
bogon:dataStructure lizhong$ ./t
C()
A()
B()
~B()
~A()
~C()
操作の結果を見ることができます:クラスBオブジェクトbを作成するとき、最初にそのメンバーオブジェクトが属するクラスのコンストラクターを実行し、次に独自のコンストラクターを実行します。複数のクラスオブジェクトメンバーがある場合は、宣言の順序で対応するクラス構造を呼び出します。関数(この例では、最初にCタイプのオブジェクトcを作成し、次にAタイプのオブジェクトaを作成します)、破棄の順序は作成の順序と逆です。
継承とクラスオブジェクトメンバーの両方
シナリオ:クラスBは2つの親クラスAとCを継承し、クラスBにはクラスXのオブジェクトメンバーがあります。構築関数とデストラクタ関数の実行順序を確認してください。
#include <iostream>
using namespace std;
class A
{
public:
A(){cout << "A()" << endl;}
~A(){cout << "~A()" << endl;}
};
class C
{
public:
C(){cout << "C()" << endl;}
~C(){cout << "~C()" << endl;}
};
class X
{
public:
X(){cout << "X()" << endl;}
~X(){cout << "~X()" << endl;}
};
class B: public A, public C
{
public:
B(){cout << "B()" << endl;}
~B(){cout << "~B()" << endl;}
X x;
};
int main(int argc, char const *argv[])
{
B b;
return 0;
}
bogon:dataStructure lizhong$ ./t
A()
C()
X()
B()
~B()
~X()
~C()
~A()
操作の結果を確認できます。クラスが構築されると、最初に親クラスのコンストラクターが左から右に呼び出され、次にクラスオブジェクトのメンバーコンストラクターが呼び出され、最後に独自のコンストラクターが呼び出されます。破壊の順序は、建設の順序と反対です。
推奨読書:
丁寧に整理|歴史的な乾物記事カタログ
[福祉]オンラインブティックコースのビデオ共有を集めました(パート1)
[データ構造とアルゴリズム]わかりやすいバイナリツリートラバーサル
[データ構造とアルゴリズム]わかりやすいバイナリ検索ツリー
サーバーのバックグラウンドテクノロジースタックの知識の概要の共有に焦点を当てる
コミュニケーションと共通の進歩に注意を払うことを歓迎します
コーディング
コードファーマーには、テクノロジーを簡単にするためのわかりやすい技術記事を提供する正しい方法があります。