技術共有

クラスとオブジェクト (パート 1)

2024-07-12

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

前の章では、クラスとオブジェクトのいくつかの概念について簡単に説明しました。ここで、それらを一緒に確認しましょう。

クラス定義

クラス定義形式

class は定義クラスのキーワードで、その後に定義クラスの名前が続きます。{} はクラスの本体で、その後に「;」が続きます。クラス内で定義されたメンバー関数はデフォルトで展開されます (インライン)。

アクセス修飾子

カプセル化形式はパブリック、プライベート、プロテクトの 3 種類に分かれており、アクセス修飾子が出現した位置から次のアクセス修飾子が出現するまでがアクセス許可の範囲となります。 }、つまりクラスで終わります。

クラスドメイン

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

以下は長編映画です///

インスタンス化する

コンセプト

• クラス型を使用して物理メモリ内にオブジェクトを作成するプロセスは、クラスのインスタンス化と呼ばれます。
• クラスはオブジェクトの抽象的な記述であり、クラスのメンバー変数のみを制限します。
これは宣言であり、スペースは割り当てられません。クラスを使用してオブジェクトがインスタンス化される場合にのみスペースが割り当てられます。
• クラスは複数のオブジェクトをインスタンス化でき、インスタンス化されたオブジェクトは実際の物理空間を占有し、クラス メンバー変数を格納します。

一言でまとめると、オブジェクトがクラスでインスタンス化された場合にのみスペースが割り当てられます (図面で家を建てるとスペースが占有されるのと同じです)。

  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. void Print()
  11. {
  12. cout << _year << "/" << _month << "/" << _day << endl;
  13. }
  14. private:
  15. // 这⾥只是声明,没有开空间
  16. int _year;
  17. int _month;
  18. int _day;
  19. };

上記のコードでは、プライベートのメンバー変数が定義されるだけで、新しいスペースは開かれません。

  1. // Date类实例化出对象d1和d2
  2. Date d1;
  3. Date d2;

次に、2 つの新しいオブジェクトがインスタンス化されるため、新しいスペースが開きます。

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

意味

• 最初のメンバーは、構造体からのアドレス オフセット 0 にあります。
• その他のメンバ変数は、特定の数(対数)の整数倍のアドレスにマッピングする必要があります。
• 注: 対数 = コンパイラのデフォルトの対数とメンバーのサイズの小さい方。
• VS のデフォルトの対数は 8 です。
• 構造体の合計サイズは、最大ペア数 (すべての変数タイプの最大値と最小のデフォルト ペアリング パラメータ) の整数倍です。
• 構造がネストされている場合、ネストされた構造はその最大対数の整数倍と構造全体のサイズに合わせて調整されます。
これは、すべての最大対数 (入れ子構造の対数を含む) の整数倍です。

これは C 言語でわかっていることです。次の質問を例として考えてみましょう。

  1. // 计算⼀下A实例化的对象是多⼤?
  2. class A
  3. {
  4. public:
  5. void Print()
  6. {
  7. cout << _ch << endl;
  8. }
  9. private:
  10. char _ch;
  11. int _i;
  12. };

呼び出される関数はないので、メンバー変数を見てください。_ch は 1 バイトの char 文字型で、_i は 4 バイトの int 型です。添字 0 の位置に ch を置き、空白を 3 つ残し、最大アライメント番号の整数倍の 0 ~ 7 の合計 8 バイトのスペースを占めます。条件を満たしているので、この質問の答えは 8 です。

この 2 つの質問だったらどうでしょうか?

  1. class B
  2. {
  3. public:
  4. void Print()
  5. {
  6. //...
  7. }
  8. };
  9. class C
  10. {
  11. };

クラス B には空の関数があり、クラス C にはメンバーが存在しないことがわかります。C 言語では、両方のクラスが 1 バイトのスペースを占有するようにします。

このポインタ

コンセプト

ate クラスには Init と Print という 2 つのメンバー関数があり、d1 が Init と Print を呼び出すと、異なるオブジェクト間の区別はありません。
関数を出力するとき、関数は d1 オブジェクトにアクセスすべきか d2 オブジェクトにアクセスすべきかをどのようにして知るのでしょうか?次に、ここで C++ が次のことを行うことがわかります。
暗黙的な this ポインタがここでの問題を解決します
• コンパイラがコンパイルした後、クラスのメンバー関数は、デフォルトで、このポインタと呼ばれる現在のクラス型のポインタを仮パラメータの最初の位置に追加します。 たとえば、Date クラスの Init の実際のプロトタイプは、 void Init(Date* const this, int year,
int 月、int 日)
• クラスのメンバー関数のメンバー変数にアクセスする場合、基本的には this ポインターを介してアクセスされます。たとえば、Init 関数の _year に値を割り当てる場合は、this- となります。
&gt;_year = 年;
• C++ では、このポインタを実パラメータおよび仮パラメータの位置に明示的に記述することはできません (コンパイル時にコンパイラがこれを処理します) と規定されていますが、関数本体内ではこのポインタを明示的に使用できます。

例えば

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

Init 関数で実際のパラメータを記述する場合、関数内のコメントに示されているように、C++ は非表示の定数 this ポインター (変更できません) を関数の最初の変数に追加します。

知らせ:

Null ポインタが逆参照されない限り、エラーは報告されません。

  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << "A::Print()" << endl;
  7. }
  8. private:
  9. int _a;
  10. };
  11. int main()
  12. {
  13. A* p = nullptr;
  14. p->Print();
  15. return 0;
  16. }

p には null ポインターが割り当てられ、Print 関数を指しますが、逆参照されないため、ここではエラーは報告されません。

  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << "A::Print()" << endl;
  7. cout << _a << endl;//这个地方多了一句
  8. }
  9. private:
  10. int _a;
  11. };
  12. int main()
  13. {
  14. A* p = nullptr;
  15. p->Print();
  16. return 0;
  17. }

前のプログラムと比較すると、cout &lt;&lt; _a &lt;&lt; endl という文が 1 つだけ増えていますが、この時点では null ポインタがメンバ変数を指しているため、null ポインタが逆参照されるため、プログラムはエラーを報告します。

クラスのデフォルトのメンバー関数

コンセプト

デフォルトのメンバー関数は、ユーザーが明示的に実装せず、コンパイラによって自動的に生成されるメンバー関数です。 クラスの場合、それを記述しない場合、コンパイラはデフォルトで次の 6 つのデフォルトのメンバー関数を生成します。

コンストラクタ

意味

コンストラクターは特別なメンバー関数です。コンストラクターの名前はコンストラクターと呼ばれますが、コンストラクターの主なタスクはオブジェクトを作成するためにスペースを開くことではないことに注意してください (私たちがよく使用するローカル オブジェクトはスタック時のスペースです)。フレームが作成されます))、代わりに、オブジェクトがインスタンス化されるときにオブジェクトが初期化されます。本質は、初期化 Init 関数を置き換えることです。

特徴

上位 5 つの機能

1. 関数名はクラス名と同じです。
2. 戻り値はありません。 (voidを書く必要はありません)
3. オブジェクトがインスタンス化されると、システムは対応するコンストラクターを自動的に呼び出します。
4. コンストラクターはオーバーロードできます。
5. クラス内に明示的に定義されたコンストラクターがない場合、C++ コンパイラーはパラメーターのないデフォルト コンストラクターを自動的に生成します。ユーザーが明示的に定義すると、コンパイラーはそれを生成しなくなります。

最初の 5 つの項目は比較的単純です。例を示します。

  1. class Date
  2. {
  3. public:
  4. //1.⽆参构造函数,无需写void
  5. Date()//若不写,则构造函数会写出这一中无参构造函数
  6. {
  7. _year = 1;
  8. _month = 1;
  9. _day = 1;
  10. }
  11. //2.全缺省构造函数
  12. Date(int year = 1, int month = 1, int day = 1)
  13. {
  14. _year = year;
  15. _month = month;
  16. _day = day;
  17. }
  18. private:
  19. int _year;
  20. int _month;
  21. int _day;
  22. }
  23. int main()
  24. {
  25. Date d1;//此时相当于已经自动调用了Init函数
  26. Date d2;
  27. return 0;
  28. }

プログラムには 2 つのコンストラクターが記述されます。1 つはパラメーターなしのコンストラクターで、もう 1 つは完全なデフォルトのコンストラクターです。関数を記述しない場合、プログラムはパラメーターなしのコンストラクターを 1 つまで記述します。 2 つの関数はオーバーロードされた関数です。呼び出しのあいまいさのため、2 つの関数は同時に存在できません。

後の2つの特徴

6. パラメーターなしのコンストラクター、完全なデフォルトのコンストラクター、およびコンストラクターを作成しないときにデフォルトでコンパイラーによって生成されるコンストラクターはすべて、デフォルトのコンストラクターと呼ばれます。ただし、これら 3 つの機能はいずれか 1 つだけが存在し、同時に存在することはできません。 パラメーターなしのコンストラクターと完全なデフォルトのコンストラクターは関数のオーバーロードを構成しますが、それらを呼び出すときにあいまいさが生じます。多くの学生は、デフォルト コンストラクターがコンパイラによって生成されたデフォルト コンストラクターであると考えていることに注意してください。実際、パラメータなしのコンストラクターと完全なデフォルト コンストラクターも、実際のコンストラクターを渡さずに呼び出すことができます。これはデフォルト構造と呼ばれます。

6

6 番目の機能はさらに複雑です。パラメーターなしのコンストラクター、すべてデフォルトのコンストラクター、およびコンストラクターを作成しないときにデフォルトでコンパイラーによって生成されるコンストラクターはすべてデフォルト コンストラクターと呼ばれ、デフォルト コンストラクターを理解してはなりません。デフォルトでコンパイラによって生成されるコンストラクターとして使用します。

7


7. これを書かないと、コンパイラがデフォルトで生成するコンストラクタには、組み込み型メンバ変数の初期化の要件がありません。つまり、初期化されるかどうかはコンパイラに依存します。 。カスタム型のメンバー変数の場合、このメンバー変数のデフォルトのコンストラクターを呼び出して初期化する必要があります。このメンバー変数にデフォルトのコンストラクターがない場合、エラーが報告されます。このメンバー変数を初期化するには、初期化リストを使用する必要があります。初期化リストについては、次の章で詳しく説明します。 。

デストラクター

コンセプト

デストラクタの機能はコンストラクタの逆です。たとえば、ローカル オブジェクトにはスタック フレームがあり、関数が終了すると、そのオブジェクトは破棄されます。 C++ では、オブジェクトが破棄されると、オブジェクト内のリソースのクリーンアップと解放を完了するためにデストラクターが自動的に呼び出されることが規定されています。デストラクターの機能は、以前 Stack で実装した Destroy 関数と似ています。実際には、Date には解放されるリソースがありません。デストラクターは、Destroy 関数と同様に、スタックやキューなどの非ローカル オブジェクトをリソースに適用する必要があるデータ構造に使用する必要があります。

特徴

1. デストラクター名には、クラス名の前に文字 ~ が付きます。 (C言語のビットごとの否定に似ています)
2. パラメータも戻り値もありません。 (構造的には同様なのでvoidを追加する必要はありません)
3. クラスにはデストラクターを 1 つだけ含めることができます。明示的に定義されていない場合、システムはデフォルトのデストラクターを自動的に生成します。
4. オブジェクトのライフサイクルが終了すると、システムは自動的にデストラクターを呼び出します。
5. コンストラクターと同様に、コンパイラーによって自動的に生成されるデストラクターを作成しない場合、カスタム型メンバーがデストラクターを呼び出す組み込み型メンバーは処理されません。
6. デストラクターを明示的に記述すると、カスタム型メンバーのデストラクターも呼び出されることにも注意してください。つまり、状況に関係なく、カスタム型メンバーのデストラクターが自動的に呼び出されます。
7. クラスにリソースが適用されていない場合は、デストラクタを記述する必要はなく、コンパイラによって生成される Date などのデフォルトのデストラクタを直接使用できます。デフォルトで生成されるデストラクタが使用できる場合は、 MyQueue などのデストラクタを記述する必要はありませんが、リソース アプリケーションがある場合は、自分でデストラクタを記述する必要があります。そうしないと、Stack などのリソース リークが発生します。
8. ローカル ドメイン内の複数のオブジェクトの場合、C++ では、後で定義されたオブジェクトが最初に破棄されると規定しています。

デストラクターの使用

上記のコンストラクターと同様に、C++ によって自動的に生成されるデフォルトのデストラクターは、通常はあまり役に立ちませんが、2 つのスタックを使用してキューを実装するなど、まれなケースでは便利です。

  1. public:
  2. Stack(int n = 4)//相当于Init
  3. {
  4. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  5. if (nullptr == _a)
  6. {
  7. perror("malloc fail!");
  8. return;
  9. }
  10. _capacity = n;
  11. _top = 0;
  12. }
  13. ~Stack()//相当于Destroy
  14. {
  15. cout << "~Stack()" << endl;
  16. free(_a);
  17. _a = nullptr;
  18. _top = _capacity = 0;
  19. }
  20. private:
  21. STDataType* _a;
  22. size_t _capacity;
  23. size_t _top;
  24. };
  25. class MyQueue
  26. {
  27. public:
  28. //编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
  29. // 显⽰写析构,也会⾃动调⽤Stack的析构
  30. private:
  31. Stack pushst;
  32. Stack popst;
  33. };

このとき、MyQueue 内のコンパイラはスタックのコンストラクタ(初期化)とデストラクタ(破壊)を自動的に呼び出します。