C++ インタビュー体験の概要: オブジェクト指向プログラミング
序文
この記事の目的は、就職面接の過程で学んだことの理解不足を補うことです。学習過程では、多くのオンライン記事を参考にしています。不適切な理解や漏れがあった場合は、ご指摘いただければ幸いです。アドバイスをいただけますか。
オブジェクト指向プログラミングとは
オブジェクト指向プログラミングの定義
オブジェクト指向プログラミング (OOP) は、多くのプログラミング言語 (Java、C++ を含む) の基本的なプログラミング パラダイムであり、関数やロジックではなくデータまたはオブジェクトを中心にソフトウェア設計を組織します。オブジェクトは、固有のプロパティと動作を持つデータ フィールドになります。
オブジェクト指向プログラミングは、オブジェクトの操作に必要なロジックではなく、開発者が操作したいオブジェクトに焦点を当てます。このプログラミングの考え方は、積極的に更新または保守される大規模で複雑なプログラムに最適です。
プロセス指向とオブジェクト指向
-
プロセス指向はイベント中心のプログラミングの考え方であり、機能的な動作を強調し、一般に機能を単位として捉え、問題を解決するための動作ステップとして主題を分析します。
-
オブジェクト指向はオブジェクト中心のプログラミングの考え方であり、関数を備えたオブジェクトに重点を置き、一般にオブジェクト/クラスを単位として扱い、分析の対象は問題の実行者と実行者です。
-
プロセス指向はオブジェクト指向よりもパフォーマンスが高く、オブジェクト指向はクラスを呼び出すときにインスタンスを生成する必要があり、このプロセスにより多くのリソースが消費されます。
-
オブジェクト指向はプロセス指向に比べて保守、拡張、再利用が容易であり、オブジェクト指向の継承、カプセル化、ポリモーフィズムにより低結合システムの設計が可能
オブジェクト指向プログラミングの構造
- クラス個々のオブジェクト、プロパティ、メソッドの設計図として機能するユーザー定義のデータ型
- クラスはそれ自体では何も行いません。クラスは、そのタイプの具体的なオブジェクトを作成するための一種のテンプレートです。
- オブジェクト特別に定義されたデータを使用して作成されたクラスのインスタンス
- メソッドオブジェクトの動作を記述するクラスで定義された関数
- クラス定義に含まれるすべてのメソッドは、インスタンス オブジェクトへの参照で始まります。
- オブジェクトに含まれるサブルーチン(メソッド)をインスタンスメソッドといいます。
- プロパティはクラス テンプレートで定義され、オブジェクトの状態を表します。
- クラス属性はクラス自体に属します
オブジェクト指向の主な原則
- カプセル化によりオブジェクトの内部状態と機能が隠蔽され、一連のパブリック関数を介してのみアクセスが許可されます。
- 抽象化 エンティティの関連プロパティと相互作用をクラスとしてモデル化し、システムの抽象表現を定義します。
- 継承既存の抽象化に基づいて新しい抽象化を作成する機能
- ポリモーフィズム 継承されたプロパティまたはメソッドを複数の抽象化にわたって異なる方法で実装する機能
オブジェクト指向プログラミングの利点
- モジュール性のカプセル化によりオブジェクトを自己完結型にできるため、トラブルシューティングや共同開発が容易になります。
- 再利用可能なコードは継承を通じて再利用できるため、チームは同じコードを何度も記述する必要がありません。
- 効率の向上プログラマーは、複数のライブラリと再利用可能なコードを使用することで、新しいプログラムをより速く構築できます。
- アップグレードと拡張が容易プログラマーが独自にシステム機能を実現可能
- インタフェース記述オブジェクト通信をベースとしたメッセージパッシング技術により、外部インタフェースの記述が非常に簡単
- セキュリティカプセル化と抽象化を使用して複雑なコードを隠し、ソフトウェアのメンテナンスを容易にします。
- 柔軟性ポリモーフィズムにより、単一の関数が複数のクラスと互換性を持つことができ、異なるオブジェクトが同じインターフェイスを呼び出すことで関数を実装できます。
カプセル化
カプセル化とは、抽象データ型を使用して、データとデータベースの操作を一緒にカプセル化し、分離不可能な独立したエンティティを形成することを指します。データは抽象データ型内で保護され、詳細は可能な限り隠蔽され、一部の外部インターフェイスのみが使用のために保持されます。 . 外の世界と通信すること。ユーザーはオブジェクトの内部詳細を知る必要はなく、提供された外部インターフェイスを介してのみオブジェクトにアクセスできます。
カプセル化を使用する利点
- 適切なパッケージングによりカップリングが減少
- クラス内の構造は自由に変更可能
- メンバーをより正確に制御できるようになります
- 情報の非表示、実装の詳細
アクセス修飾子
アクセス修飾子は、クラス メンバーへのアクセスを制限するためにクラス本体内で使用されます。アクセス修飾子には主に、public、private、protected があります (C# では、internal と protected external が追加されます)。クラスには複数のパブリック、プライベート、または保護されたマーク付けされた領域を持つことができ、各マーク付けされた領域は、次のマーク付けされた領域が始まるまで、またはクラス本体の右括弧に遭遇するまで有効です。
- public には、このクラスの関数、サブクラスの関数、およびそのフレンド関数からアクセスでき、またこのクラスのオブジェクトからもアクセスできます。
- privateには、このクラスの関数とそのフレンド関数からのみアクセスでき、他のオブジェクトやこのクラスのオブジェクトからはアクセスできません。
- protectedには、このクラスの関数、サブクラスの関数、およびそのフレンド関数からアクセスできますが、このクラスのオブジェクトからはアクセスできません。
- 同じアセンブリ内のコードは型またはメンバーにアクセスできますが、他のアセンブリ内のコードはアクセスできません。
- protected external型またはメンバーは、それが宣言されているアセンブリ内の任意のコード、または別のアセンブリ内の派生クラスからアクセスできます。
- private protected型またはメンバーは、それを含むアセンブリで宣言されているクラスから派生した型によってアクセスできます。
組み立て
1 つ以上のタイプ定義ファイルとリソース ファイルのコレクション
組み立てには以下が含まれます
- リソース
- 型メタデータ (コード内で定義されているすべての型とメンバーをバイナリ形式で説明します)
- ILコード(exeまたはdllにカプセル化)
結果として得られるアセンブリは、実行可能アプリケーションまたは DLL のいずれかになります。
アセンブリを使用する利点
- プログラム内で必要なアセンブリのみが参照されるため、プログラムのサイズが削減されます。
- アセンブリは一部のコードをカプセル化し、必要なアクセス インターフェイスのみを提供できます。
- 拡張が簡単
フレンド機能
- フレンド関数を使用する目的: 一部のセット関数がクラス内のプライベートまたは保護されたデータにアクセスし、操作を実行できるようにすること
- フレンド機能の 3 つの実装
- グローバルは友達として機能する
- 友達としてクラス
- メンバーが友達として機能する
- フレンド関数の欠点: クラスのカプセル化特性が破壊されます。外部に友達として宣言されると、クラスのすべての詳細が友達に公開されます
//全局函数做友元函数
class House
{
//告诉编译器全局函数visit()是house类的友元函数,可以访问house对象的私有成员
friend void visit1(House *house);
friend void visit2(House &house);
friend void visit3(House house);
public:
house()
{
bedroom = "卧室";
kitchen = "厨房";
}
string bedroom;
private:
string kitchen;
};
void visit1(House *house) //地址传递
{
cout<<"visiting(地址传递)"<<house->bedroom<<endl;
cout<<"visiting(地址传递)"<<house->kitchen<<endl;
}
void visit2(House &house) //引用传递
{
cout<<"visiting(引用传递)"<<house.bedroom<<endl;
cout<<"visiting(引用传递)"<<house.kitchen<<endl;
}
void visit3(House house) //值传递
{
cout<<"visiting(值传递)"<<house.bedroom<<endl;
cout<<"visiting(值传递)"<<house.kitchen<<endl;
}
void test()
{
House house;
visit1(House &house);
visit2(House house);
visit3(House house);
}
int main()
{
test();
}
//输出结果
visiting(地址传递)卧室
visiting(地址传递)厨房
visiting(引用传递)卧室
visiting(引用传递)厨房
visiting(值传递)卧室
visiting(值传递)厨房
継承する
継承とは、既存のクラスの定義をベースにして新しいクラスを作成する技術のことで、新しいクラスの定義では新しいデータや新しい関数を追加したり、親クラスの関数を利用したりすることはできますが、それはできません。親クラスを選択的に継承します。
- 派生クラスには、基本クラスの非プライベート プロパティとメソッドがあります (実際、基本クラスのプライベート メンバーも継承され、派生クラス オブジェクトのメモリを占有しますが、それらは非表示であり、派生クラスでは使用できません)クラス)
- 派生クラスは独自のプロパティとメソッドを持つことができ、基本クラスを拡張できます。
- 派生クラスは独自の方法で基本クラスのメソッドを実装できます。
派生クラスは、次の例外を除いて、基本クラスのすべてのメソッドを継承できます。
- 基本クラスのコンストラクター、デストラクター、およびコピー デストラクター
- 基本クラスのオーバーロードされた演算子
- 基本クラスのフレンド関数
3つの継承方法
- パブリック継承メソッド
- 基本クラスのすべてのパブリック メンバーは、派生クラスのパブリック プロパティです。
- 基本クラスのすべての保護されたメンバーは、派生クラスの保護されたプロパティです
- 基本クラス内のすべてのプライベート メンバーは派生クラスでは使用できません
- 保護された継承
- 基本クラスのすべてのパブリック メンバーは、派生クラスの保護されたプロパティです。
- 基本クラスのすべての保護されたメンバーは、派生クラスの保護されたプロパティです
- 基本クラス内のすべてのプライベート メンバーは派生クラスでは使用できません
- プライベート継承メソッド
- 基本クラスのすべてのパブリック メンバーは、派生クラスのプライベート プロパティです。
- 基本クラスのすべての保護されたメンバーは、派生クラスのプライベート プロパティです。
- 基本クラス内のすべてのプライベート メンバーは派生クラスでは使用できません
上記の 3 つの継承メソッドに従って、派生クラスの基底クラス メンバーのアクセス権が継承メソッドで指定されたアクセス権を超えてはいけないことを取得できます。using キーワードを使用すると、基底クラスのアクセス権を変更できます
。派生クラスのメンバー (基本クラスのみ変更可能) のパブリックおよび保護されたメンバーへのアクセス
//基类People
class People {
public:
void show();
protected:
char *m_name;
int m_age;
};
//派生类Student
class Student : public People {
public:
void learning();
public:
using People::m_name; //将protected改为public
using People::m_age; //将protected改为public
float m_score;
private:
using People::show; //将public改为private
};
コンストラクターとデストラクター
コンストラクタ
コンストラクターは、クラス オブジェクトのデータ メンバーを初期化するために使用されます。つまり、クラスのインスタンス (オブジェクト) が作成されると、コンパイル システムがオブジェクトにスペースを割り当て、自動的にコンストラクターを呼び出してクラスの初期化を完了します。メンバー
コンストラクターの機能
- 戻り値がないため、void を書き込まないでください
- 関数名はクラス名と同じです
- パラメータを持つことができ、再現することができます(クラスは複数のコンストラクタを持つことができます)
- 手動で電話をかける必要はありません。システムが自動的に電話をかけ、電話をかけるのは 1 回だけです。
- 使用するにはパブリックで定義する必要があります
よく使用されるコンストラクター
- 引数のないコンストラクター
- クラス内でコンストラクターが宣言されていない場合、コンパイラーは暗黙的にデフォルトのデフォルト コンストラクターをインライン化します。これには通常パラメーターがありませんが、デフォルト値を持つパラメーターを持つことができます。
- コンストラクターがクラスで宣言されている場合、デフォルトのコンストラクターは自動的に生成されず、明示的に宣言されたコンストラクターにはパラメーターがないこともあります。
class Student {
public:
int m_age;
int m_score;
//无参构造函数
Student() {
m_age = 18;
m_score = 99;
}
};
- システムの暗黙的コンストラクターに依存する場合は、メンバーがクラス定義で初期化されていることを確認する必要があります。そうしないと、呼び出しによってガベージ値などが生成される状況が発生する可能性があります。
- 暗黙的なコンストラクターを削除済みとして定義することで、コンパイラーによる生成を防ぐことができます。デフォルトで構築できないクラス メンバーがある場合、コンパイラによって生成されたデフォルト コンストラクターは削除されたものとして定義されます。
- コピーコンストラクター
- コピー コンストラクターは、同じ型のオブジェクトからメンバー値をコピーすることでオブジェクトを初期化します。
- コピー コンストラクターを宣言しない場合、コンパイラーはメンバーのコピー コンストラクターを生成します。コピー代入演算子を宣言しない場合、コンパイラーはメンバーのコピー代入演算子を生成します。
class Student {
public:
int m_age;
int m_score;
//复制构造函数
Student(Student& s) {
m_age = s.m_age;
m_score = s.m_score;
}
};
クラスにポインター メンバーが存在する場合、システムによってデフォルトで作成されるコピー コンストラクターには「浅いコピー」のリスクがあるため、コピー コンストラクターを明示的に宣言する必要があります。
- 移動コンストラクター
- 移動コンストラクターはポインターの移動を実現し、あるオブジェクト内のポインター メンバーを別のメンバーに転送できます。ポインタ メンバが転送された後、元のオブジェクトのポインタは通常、再使用されないように NULL に設定されます。
- 移動コンストラクターは、移動セマンティクスの具体的な実装です。
移動セマンティクス。これは、ポインター メンバーを含むクラス オブジェクトが、ディープ コピーではなく移動によって初期化されることを意味します。
class A {
public:
int x;
//构造函数
A(int x) : x(x)
{
cout << "Constructor" << endl;
}
//拷贝构造函数
A(A& a) : x(a.x)
{
cout << "Copy Constructor" << endl;
}
//移动构造函数
A(A&& a) : x(a.x)
{
cout << "Move Constructor" << endl;
}
};
デストラクター
デストラクターは、オブジェクトがスコープ外になったとき、または delete の呼び出しによって明示的に破棄されたときに自動的に呼び出されるメンバー関数です。デストラクターが定義されていない場合、コンパイラはデフォルトのデストラクターを提供します。通常、カスタム デストラクターは、クラスが解放する必要があるシステム リソースへのハンドルを格納している場合、またはクラスが指すメモリへのポインターを所有している場合にのみ必要です。
デストラクターの特徴
- 戻り値がないため、void を書き込まないでください
- 関数名は「~」+クラス名です
- パラメーターを持つことはできず、オーバーロードすることもできません
- プログラムはオブジェクトが破棄される前に自動的にデストラクターを呼び出します。
- 使用するにはパブリックで定義する必要があります
- const、volatile、または static として宣言することはできませんが、そのように宣言されたオブジェクトを破棄するために呼び出すことはできます。
- 仮想として宣言できます。仮想デストラクターを使用すると、クラス オブジェクトの型を知らなくてもオブジェクトを破棄できます。
多態性
ポリモーフィズムとは、同じインターフェイスを使用して異なる実装を表すことを指します。
たとえば、bicycle (自転車)、car (car)、truck (トラック) という 3 つのクラスがあり、3 つのクラスのそれぞれに 3 つの実装があるとします。 bike::ride()、car::run()、 3 つの実装には、truck:: launch() を起動するための同じ機能があります。ポリモーフィズムがない場合は、これら 3 つの実装を個別に使用する必要があります
// 实现
Bicycle bicyle = new Bicycle();
Car car = new Car();
Truck truck = new Truck();
// 使用
bicyle.Ride();
car.Run();
truck.Launch();
いずれかのクラスのインターフェイスが変更されると、たとえば車のインターフェイスが変更されると、対応する使用コードも変更する必要があり、作業効率に大きな影響を与えます。作業効率を向上させるために、次のように設計できます。
- Bicycle、car、truck はすべて車両であるため、車両の基本クラスを作成できます。bicycle、car、truck はその派生クラスです。
- 起動関数 run() は車両内で宣言され、このメソッドは 3 つの派生クラスで書き換えられます。
class Vehicle {
// 新增抽象类
virtual void Run() {
}
};
class Bicycle: Vehicle {
virtual void Run() {
......}
};
class Car: Vehicle{
virtual voie Run() {
......}
};
class Truck: Vehicle {
virtual void Run() {
......}
};
// 实现部分
List<Vehicle> vehicles = {
new Bicycle(),
new Car(),
new Truck() };
// 使用部分
for (v : vechicles)
v.Run();
このように、実装部分のコード変更は使用部分のコードに影響を与えません。
別の例を挙げると、例えばATMでお金を入出金する際にはキャッシュカードに対応する口座が存在するかどうかを確認する必要がありますが、入金時にはパスワードを確認する必要はありませんが、パスワードを確認する必要があります。出金時のパスワード. 同じ確認手順には、1つの関数 check_in() を使用できます。
class ATM
{
void check_in(userid){
......} //存款检查账户
void check_in(userid,password){
......} //取款检查账户
};
ポリモーフィックな実装
オーバーロード (コンパイル時のポリモーフィズム)
静的多態性とも呼ばれるコンパイル時多態性は、テンプレート プログラミング (C++11 の新機能) の実現と関数のオーバーロード解決に基づいています。この多態性はコンパイル時に実行されるため、コンパイルと呼ばれます。時間多態性
オーバーロードとは、関数名は同じですが、関数のパラメーターの数、パラメーターの型、またはパラメーターの順序の少なくとも 1 つが異なることを意味します。関数の戻り値は同じでも異なっていても構いません。関数のオーバーロードはクラス内で発生し、スコープを越えることはできません
class Animal
{
public:
void self_introduction(int tmp)
{
cout << "I'm an animal -" << tmp << endl;
}
void self_introduction(const char *s)//函数的重载
{
cout << "(overload)I'm an animal -" << s << endl;
}
};
リライト
動的ポリモーフィズムとも呼ばれる実行時ポリモーフィズムは、仮想関数メカニズムに基づいてポリモーフィックな関数を実装し、継承関係のある異なるクラスで同じ関数名を持ちます。この実装方法は書き換えとも呼ばれます。
オーバーライドとも呼ばれる書き換えは、通常、派生クラスと基本クラスの継承関係の間で行われ、派生クラスは、基本クラス内の同じ名前とパラメーターを持つ仮想関数を再定義します。
C++ が書き換えを実装する方法はコンパイラに依存します。コンパイラが仮想関数を使用してクラスをインスタンス化すると、仮想関数テーブルの先頭を指す vptr ポインタが生成され、仮想関数の関数ポインタは宣言順に仮想関数テーブルに格納されます。派生クラスでオーバーライドされた場合、派生クラスのメモリ空間で
書き換えには注意が必要です。
- 書き換えられた関数は静的であってはならず、仮想的でなければなりません
- オーバーライドされた関数は、同じ型、名前、パラメータ リストを持つ必要があります
- オーバーライドされた関数のアクセス修飾子は異なる場合があります
class Animal
{
public:
void self_introduction(int tmp)
{
cout << "I'm an animal -" << tmp << endl;
}
};
class Fish :public Animal
{
public:
void self_introduction(int tmp) //函数的重写
{
cout << "(override)I'm an fish -" << tmp << endl;
}
};
再定義する
再定義 (隠蔽とも呼ばれます) は、サブクラスが親クラス内の同じ名前の非仮想関数を再定義し (パラメーター リストは異なる場合があります)、派生クラスに割り当てられた関数が同じ名前の基本クラス関数をシールドします。継承関係と同様に理解できます オーバーロードが発生しました
派生クラスに再定義された関数がある場合、クラスは親クラスのメソッドを非表示にするため、呼び出し時に親クラスにキャストしない限り、サブクラスや親クラスへの同様のオーバーロード呼び出しは成功しません。
再定義の実装原理は継承ツリーの検索メソッドに関係しており、現在のオブジェクトのクラススコープから同名の関数を検索し、関数がない場合は基底クラスまで検索します。パラメータリストが同じかどうかは判断しません
再定義には注意が必要です。
- 派生クラスの関数が基底クラスの関数と同じ名前でパラメータが異なる場合、このときvirtualの有無に関わらず基底クラスの関数は隠蔽されます。
- 派生クラスの関数が基底クラスの関数と同じ名前および同じパラメーターを持っているが、基底クラスの関数に vitual キーワードがない場合、この時点では基底クラスの関数は非表示になります (存在する場合)。仮想の場合は書き換えられます)
class Animal
{
public:
void self_introduction(int tmp)
{
cout << "I'm an animal -" << tmp << endl;
}
};
class Fish :public Animal
{
public:
void self_introduction(char *s) //函数的重定义
{
cout << "(override)I'm an fish -" << s << endl;
}
};