技術共有

C のクラスとオブジェクト (1)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

目次

プロセス指向とオブジェクト指向

手続き型プログラミング

オブジェクト指向プログラミング

1. クラスの定義

クラス定義形式

クラスドメイン

2. クラスアクセス修飾子とカプセル化

アクセス修飾子

カプセル化

3. インスタンス化

インスタンス化の概念

オブジェクトのサイズ

4. このポインタ

このポインターの特徴


プロセス指向とオブジェクト指向

  • C言語は典型的なプロセス指向プログラミング言語であり、プログラムの設計は主に関数とデータ構造を中心に行われ、関数の実装プロセスに重点が置かれます。
  • C++ は手続き型プログラミングとオブジェクト指向プログラミングの両方をサポートしています。オブジェクト指向プログラミングの中核となる概念には、クラス、オブジェクト、カプセル化、継承、ポリモーフィズムなどが含まれます。これらにより、プログラムの構成と設計が現実世界のモデルとより一貫性を持ち、コードの保守性とスケーラビリティが向上します。

手続き型プログラミング

プロセス指向プログラミングは、プロセス中心のプログラミングの考え方です。プロセス指向プログラミングでは、プログラムは、特定のタスクを完了するために特定の順序で実行される関数またはプロシージャの集合として見なされます。

アドバンテージ:

  • 効率的なパフォーマンス : プロセス指向プログラミングは、プロセスに従ってタスクを直接実行し、過剰なオブジェクトの作成と管理を必要としないため、基盤となるシステム プログラミング、組み込みプログラミングなど、より高いパフォーマンス要件が必要な一部のシナリオでは実行効率が高くなります。たとえば、オペレーティング システム カーネルでは、プロセス指向プログラミングを使用して、プロセス スケジューリングやメモリ管理などの機能の実装のパフォーマンスをより最適化できます。
  • 明確な論理 :単純なプログラムロジックの場合、プロセスに従って段階的に実装され、コードの論理構造は明確で理解しやすく、理解と保守が容易です。たとえば、2 つの数値の合計を計算する単純なプログラムは、プロセス指向プログラミングを使用して計算用の関数を直接定義できます。

欠点:

  • 保守性が悪い :プログラムのサイズが大きくなり、機能が複雑になると、手続き型プログラミング指向のコードの保守や拡張が困難になる場合があります。さまざまな機能間の結合が高いため、1 つの機能を変更すると、関連する他の機能に影響を与える可能性があります。
  • コードの再利用性が低い: コードの再利用は通常、関数呼び出しによって実現されますが、複雑な機能モジュールの場合、再利用はさらに難しく、関数を適切にカプセル化および抽象化することができません。

オブジェクト指向プログラミング

オブジェクト指向プログラミングは、オブジェクト中心のプログラミングの考え方です。オブジェクトは、データ (プロパティ) とそのデータを操作するメソッド (動作) を含むエンティティです。関連するデータとメソッドをオブジェクトにカプセル化することで、データと操作の統合が実現します。

アドバンテージ:

  • 高いメンテナンス性 : 関数をオブジェクトにカプセル化し、オブジェクトの内部実装を外部から隠し、モジュール間の結合を軽減します。関数を変更する必要がある場合、他の無関係な部分に影響を与えることなく、対応するオブジェクトの内部実装のみを変更する必要があります。たとえば、グラフィカル インターフェイス アプリケーションでボタンの機能を変更する場合、他のインターフェイス要素に影響を与えることなく、ボタン オブジェクトの対応するメソッドを変更するだけで済みます。
  • 強力なコードの再利用性 : コードの再利用と拡張は、継承、ポリモーフィズム、その他の機能を通じて簡単に実現できます。たとえば、基本クラスを作成します。Shape(形状) から導出Circle(ラウンド)、Rectangle(Rectangle) および他のサブクラスと同様に、サブクラスは基本クラスの属性とメソッドを再利用し、特定の拡張を作成できます。
  • 優れた柔軟性: オブジェクト指向プログラミングはポリモーフィズムをサポートしています。これにより、プログラムは実行時にオブジェクトの実際の型に応じて対応するメソッドを動的に選択して実行できるため、プログラムの柔軟性とスケーラビリティが向上します。

欠点:

  • パフォーマンスのオーバーヘッド注: オブジェクトの作成、メソッドの呼び出しなどの操作には一定のオーバーヘッドが必要なため、非常に高いパフォーマンス要件が要求される一部のシナリオでは、プログラムの実行効率に影響を与える可能性があります。
  • 学習コストが高い: オブジェクト指向プログラミングの概念と機能は比較的複雑なので、初心者が学習して理解するのは困難です。

1. クラスの定義

クラス定義形式

• class はクラスを定義するキーワード、Data はクラスの名前、{} はクラスの本体です。クラス定義の末尾のセミコロンは省略できないことに注意してください。クラス本体の内容はクラスのメンバーと呼ばれます。クラス内の変数はクラスの属性またはメンバー変数と呼ばれ、クラス内の関数はクラスのメソッドまたはメンバー関数と呼ばれます。

• メンバー変数を区別するために、メンバー変数の前後に _ または m で始まるなど、特別な識別子をメンバー変数に追加するのが通例です。これは C++ では必須ではなく、単なる規則であることに注意してください。 。

• C++ では、struct は C での struct の使用法と互換性があります。同時に、明らかな変更点は、関数を struct で定義できることです。クラスを定義するには class を使用することをお勧めします。

• クラスで定義されたメンバー関数は、デフォルトではインラインです。

  1. class Date
  2. {
  3. public:
  4. void Init(int year, int month, int day)
  5. {
  6. _year = year;
  7. _month = month;
  8. _day = day;
  9. }
  10. private:
  11. // 为了区分成员变量,⼀般习惯上成员变量
  12. // 会加⼀个特殊标识,如_ 或者 m开头
  13. int _year; // year_ m_year
  14. int _month;
  15. int _day;
  16. };
  17. int main()
  18. {
  19. Date d;
  20. d.Init(2024, 3, 31);
  21. return 0;
  22. }

クラスドメイン

• クラスは新しいスコープを定義します。クラスのすべてのメンバーはクラスのスコープ内にあります。クラス外のメンバーを定義する場合は、メンバーがどのクラス ドメインに属しているかを示すために使用する必要があります。

• クラスドメインはコンパイル時の検索規則に影響します。以下のプログラムの Init でクラスドメイン Stack が指定されていない場合、コンパイラは Init をグローバル関数として扱います。その場合、コンパイル中に配列などのメンバーの宣言/定義を行うことはできません。が見つかった場合、エラーが報告されます。クラス ドメイン Stack を指定するということは、Init がメンバー関数であることを意味し、配列などのメンバーが現在のドメインで見つからない場合は、クラス ドメインで検索されます。

  1. #include<iostream>
  2. using namespace std;
  3. class Stack
  4. {
  5. public:
  6. // 成员函数
  7. void Init(int n = 4);
  8. private:
  9. // 成员变量
  10. int* array;
  11. size_t capacity;
  12. size_t top;
  13. };
  14. // 声明和定义分离,需要指定类域
  15. void Stack::Init(int n)
  16. {
  17. array = (int*)malloc(sizeof(int) * n);
  18. if (nullptr == array)
  19. {
  20. perror("malloc申请空间失败");
  21. return;
  22. }
  23. capacity = n;
  24. top = 0;
  25. }
  26. int main()
  27. {
  28. Stack st;
  29. st.Init();
  30. return 0;
  31. }

2. クラスアクセス修飾子とカプセル化

アクセス修飾子

• C++ はカプセル化を実装する方法であり、クラスを使用してオブジェクトのプロパティとメソッドを結合してオブジェクトをより完全にし、アクセス許可を通じてそのインターフェイスを外部ユーザーに選択的に提供します。

• public によって変更されたメンバーはクラスの外部から直接アクセスできますが、protected および private によって変更されたメンバーはクラスの外部から直接アクセスできません。これらの違いは後の継承の章で反映されます。

• アクセス権の範囲は、アクセス修飾子が出現した位置から次のアクセス修飾子が出現するまでとなります。 後続のアクセス修飾子がない場合は、}、つまりクラスで終了します。

• クラス定義メンバーがアクセス修飾子によって変更されていない場合、デフォルトで private になります。

• 構造体のデフォルトは public です

• 通常、メンバ変数はプライベート/プロテクトに制限され、他の人が使用する必要があるメンバ関数はパブリックになります。

カプセル化

オブジェクト指向の 3 つの主要な特徴: カプセル化、継承、ポリモーフィズム。

クラスとオブジェクトの段階では、主にクラスのカプセル化の特性を研究します。

カプセル化: データとデータを操作するメソッドを有機的に組み合わせ、オブジェクトのプロパティと実装の詳細を非表示にし、オブジェクトと対話するインターフェイスのみを公開します。

カプセル化は本質的に、ユーザーがクラスを使いやすくする一種の管理です。例: コンピューターのような複雑なデバイスの場合、ユーザーに提供されるのは電源オン/オフ キー、キーボード入力、モニター、USB ジャックなどだけであり、ユーザーはコンピューターを操作して日常のタスクを完了できます。しかし実際には、コンピュータの実際の働きは、CPU、グラフィックス カード、メモリ、およびその他のハードウェア コンポーネントです。

コンピュータ ユーザーは、マザーボード上の回路がどのようにレイアウトされているか、CPU がどのように設計されているかなど、内部コア コンポーネントについて心配する必要はありません。ユーザーが知っていればよいのは、コンピュータの電源を入れる方法とその方法だけです。キーボードとマウスを介してコンピュータと対話します。そのため、コンピューター製造業者は工場から出荷されるとき、内部実装の詳細を隠すために外側にシェルを置き、ユーザーがコンピューターを操作できるように電源スイッチ、マウス、キーボード ジャックのみを外側に提供します。

C++ 言語でカプセル化を実装するには、データとデータを操作するためのメソッドをクラスを通じて有機的に組み合わせることができ、アクセス権を使用してオブジェクトの内部実装の詳細を非表示にし、どのメソッドをクラスの外部で直接使用できるかを制御できます。

3. インスタンス化

インスタンス化の概念

• クラス型を使用して物理メモリ内にオブジェクトを作成するプロセスは、クラスのインスタンス化と呼ばれます。

• クラスは、オブジェクトの抽象的な記述であり、クラスのメンバー変数を制限します。これらのメンバー変数は、オブジェクトがインスタンス化されるときに領域を割り当てません。クラス。

• クラスは複数のオブジェクトをインスタンス化でき、インスタンス化されたオブジェクトは実際の物理空間を占有し、クラス メンバー変数を格納します。たとえば、クラスからオブジェクトをインスタンス化することは、実際に家を建てるために建築設計図を使用することに似ています。設計図は、部屋の数、部屋のサイズ、機能などを計画します。建物は物ではありませんが、設計図があって初めて人が住むことができます。同じクラスは設計図面に似ており、データを保存できません。インスタンス化されたオブジェクトは、データを保存するために物理メモリを割り当てます。

オブジェクトのサイズ

クラスオブジェクトにどのようなメンバーが含まれているかを分析しますか?クラスによってインスタンス化された各オブジェクトには独立したデータ空間があるため、オブジェクトにはメンバー変数が含まれている必要があります。では、メンバー関数は含まれるのでしょうか。 まず、関数はコンパイルされた後、オブジェクトに格納できない命令のセクションになります。これらの命令は別の領域 (コード セグメント) に格納されるため、オブジェクトに格納する必要がある場合のみオブジェクトに格納できます。メンバー関数へのポインター。もう一度分析してみましょう。オブジェクトにポインターを格納する必要がありますか? Date は 2 つのオブジェクト d1 と d2 をインスタンス化します。d1 と d2 には、独自のデータを格納するための独自のメンバー変数 _year/_month/_day がありますが、メンバー関数 Init/Print は、 d1 と d2 のポインタは同じなので、オブジェクトに格納するのは無駄です。 Date を使用して 100 個のオブジェクトをインスタンス化すると、メンバー関数ポインターが 100 回保存されることになり、無駄が多すぎます。実際、関数ポインタはアドレスであり、コンパイラはコンパイルおよびリンク時に関数のアドレスを見つける必要があります。実行時ではなく、動的ポリモーフィズムのみが実行時に検出され、関数アドレスを保存する必要があります。

上記では、メンバー変数のみがオブジェクトに格納されることを分析しました。C++ では、クラスによってインスタンス化されたオブジェクトもメモリ アライメント ルールに準拠する必要があると規定されています。

メモリアライメントルール

メモリのアライメント規則はC言語の規則とまったく同じです参考記事:メモリアライメントを計算するC言語

最初のメンバーは、構造体からのアドレス オフセット 0 にあります。

• その他のメンバ変数は、特定の番号(アライメント番号)の整数倍のアドレスにマッピングする必要があります。

• 注: 対数 = コンパイラのデフォルトのアラインメント数とメンバーのサイズの小さい方。

•VS のデフォルトの対数は 8 です。

• 構造体の合計サイズは、最大アライメント数 (すべての変数タイプの最大値と最小のデフォルト アライメント パラメータ) の整数倍です。

• 構造がネストされており、ネストされた構造がその最大対数の整数倍にアライメントされている場合、構造全体のサイズは、すべての最大アライメント数 (ネストされた構造のアライメントを含む) になります。

メンバー変数がない場合は、1 バイトを指定する必要があります。1 バイトも指定しない場合、オブジェクトが存在したことをどうやって示すことができるでしょうか。したがって、ここでは純粋にオブジェクトの存在をプレースホルダーで識別するために 1 バイトが与えられています。

4. このポインタ

このポインターの特徴

Date クラスには、Init と Print という 2 つのメンバー関数があります。関数本体内の異なるオブジェクト間の区別はありません。そのため、d1 が Init 関数と Print 関数を呼び出すとき、関数は d1 オブジェクトにアクセスする必要があるかどうかをどのように判断するのでしょうか。 d2オブジェクト?次に、C++ がこの問題を解決するために暗黙的な this ポインターを提供することがわかります。

• コンパイラがコンパイルした後、クラスのメンバー関数は、デフォルトで、このポインタと呼ばれる現在のクラス型のポインタを仮パラメータの最初の位置に追加します。たとえば、Init of Date クラスの実際のプロトタイプは次のとおりです。void Init(Date* const this, int year, int month, int day) • クラスのメンバー関数でメンバー変数にアクセスする場合、基本的にこのポインターを介してアクセスされます。たとえば、Init 関数で _year に値を代入する場合は、this になります。 -&gt;_year = 年;

• C++ では、このポインタを実パラメータおよび仮パラメータの位置に明示的に記述することはできません (コンパイル時にコンパイラが処理します) が、関数本体内では明示的にこのポインタを使用できます。

  1. #include<iostream>
  2. using namespace std;
  3. class Date
  4. {
  5. public:
  6. // void Init(Date* const this, int year, int month, int day)
  7. void Init(int year, int month, int day)
  8. {
  9. // 编译报错:error C2106: “=”: 左操作数必须为左值
  10. // this = nullptr;
  11. // this->_year = year;
  12. _year = year;
  13. this->_month = month;
  14. this->_day = day;
  15. }
  16. void Print()
  17. {
  18. cout << _year << "/" << _month << "/" << _day << endl;
  19. }
  20. private:
  21. // 这⾥只是声明,没有开空间
  22. int _year;
  23. int _month;
  24. int _day;
  25. };
  26. int main()
  27. {
  28. // Date类实例化出对象d1和d2
  29. Date d1;
  30. Date d2;
  31. d1.Init(2024, 7, 1); // d1.Init(&d1, 2024, 7, 1);
  32. d1.Print(); // d1.Print(&d1);
  33. d2.Init(2024, 7, 10); // d2.Init(&d2, 2024, 7, 10);
  34. d2.Print(); // d2.Print(&d2);
  35. return 0;
  36. }