目次
1. プロセス指向とオブジェクト指向
C 言語はプロセス指向言語であり、問題を解決するプロセスを指向しており、関数呼び出しを通じて順番に問題を解決します。
たとえば、衣服を洗う: 洗面器を用意します - 水を入れます - 衣服を入れます - 洗剤を入れます...
C++ はオブジェクト指向に基づいていますが、オブジェクトに焦点を当てています。洗濯物を例にとると、C++ は人、衣服、洗剤などに焦点を当てています。
2. 授業の紹介
C 言語では構造体は変数のみを定義できますが、C++ では構造体がクラスにアップグレードされ、変数だけでなく関数も定義できるようになります。
struct Date
{
void addDate(int x) {}
int _year;
int _month;
int _day;
};
しかし、上記の構造は、C++ で定義するためにクラスを使用することをより積極的に行っています。
3. クラス定義
class はクラスを定義するキーワード、{} 内の内容はクラスのクラス本体、内部で定義された内容はメンバーと呼ばれ、クラス内の変数はクラスの属性またはクラスのメンバー変数と呼ばれます, ClassName はクラス名であり、中括弧の後のセミコロン「;」は省略できません。
class ClassName
{ //类体};
クラスは次の 2 つの方法で定義できます。
- 宣言と定義はすべてクラス本体に配置されます (メンバー関数はクラス本体に定義され、コンパイラーはそれらをインライン関数として扱う場合があります)。
class Date { public: void addDate(int x) { // } private: int _year; int _month; int _day; };
- クラス宣言は .h ファイルに配置され、メンバー関数は .cpp ファイルに定義されます (クラス名::をメンバー関数の前に追加する必要があります)。
#pragma once class Date { public: void addDate(int x); private: int _year; int _month; int _day; }; #include<iostream> #include"date.h" void Date::addDate(int x) { std::cout << "void addDate(int x);"; }
4. クラスアクセス修飾子とカプセル化
4.1 クラスアクセス修飾子
また、 クラス のデフォルトのアクセス権は privateであるのに対し、structのデフォルトのアクセス権は public (C 言語と互換)であることに注意してください。
4.2 梱包
オブジェクト指向には、カプセル化、継承、ポリモーフィズムという3 つの特徴があります。他の 2 つについては、以降の記事で説明します。今日お話しする主な内容はカプセル化です。カプセル化とは:データとデータを操作するメソッドを有機的に組み合わせ、オブジェクトの属性と実装の詳細を非表示にします。オブジェクトと対話するためのインターフェイスのみを公開します。
カプセル化は本質的に一種のデータ管理であり、銀行が銀行の内部処理資金の詳細を私たちに公開せず、ユーザーと通信するための 1 つ以上のウィンドウを開くだけであるのと同じで、これにより使用コストが削減されます。梱包も同様です。
5. クラスの範囲
クラスは新しいスコープを再定義します。クラスのすべてのメンバーはクラスのスコープ内にあります。クラス外のメンバーを定義するには、「::」スコープ演算子を使用して、メンバーがどのクラス ドメインに属しているかを示す必要があります。
class Date
{
public:
void addDate();
private:
int _year;
int _month;
int _day;
};
void Date::addDate()
{
cout << "void addDate()";
}
6. クラスのインスタンス化
クラスのインスタンス化は、クラスを使用してオブジェクトを作成するプロセスであり、クラスは描画に相当し、インスタンス化は描画を実現するプロセスです。クラスは複数のオブジェクトをインスタンス化でき、インスタンス化されたオブジェクトは実際の物理空間を占有し、クラス メンバー変数を格納します。
7. クラスオブジェクトモデル
7.1 クラスオブジェクトの格納方法
クラスがインスタンス化されると、メンバー変数のみが保存され、メンバー関数はパブリック コード セグメントに格納されます。
オブジェクトのサイズを取得することで、上記の保存方法を検証することもできます。
class Date
{
public:
void addDate()
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
cout << sizeof(d1);
}
出力:
12
コードを見ると、クラスのサイズは int 型変数 3 つ分のサイズだけであることがわかり、ストレージ モデルが正しいことが証明できます。また、クラスのサイズもメモリ アライメント ルールに従います。
7.2 クラスの規模
7.2.1 空のクラスのサイズ
空のクラスを定義してこのクラスをインスタンス化すると、インスタンス化されたオブジェクトのサイズは 0 ではなく 1 になります。このバイトは場所を占めるために使用され、オブジェクトが存在することを示します。空ではないクラスのサイズは、メモリ アライメントのルールに従って計算する必要があります。
class MyClass
{};
int main()
{
MyClass mc;
cout << sizeof(mc);
}
出力:
1
7.2.2 構造体メモリのアライメント規則
C++ の構造体とクラスはどちらもメモリ アライメントの原則に従っていますが、ここでのメモリ アライメントは C 言語の構造体のメモリ アライメントとまったく同じであるため、詳しい説明は省略します。
- 最初のメンバーは構造体からのオフセット 0 にあります。
- その他のメンバ変数は、ある数値(アライメント番号)の整数倍のアドレスにアライメントされている必要があります。注: アライメント = コンパイラのデフォルトのアライメントとメンバー サイズの間の小さい値。VS のデフォルトのアライメント番号は 8 です。
- 構造体の合計サイズは、最大アライメント数 (すべての変数タイプの最大値と最小のデフォルト アライメント パラメータ) の整数倍です。
- 構造がネストされている場合、ネストされた構造はその最大アライメントの整数倍にアライメントされ、構造全体のサイズはすべての最大アライメント (ネストされた構造のアライメントを含む) の整数倍になります。
8. このキーワードの詳しい説明
8.1 このポインタの導出
上記の説明では、メンバー関数がパブリック コード領域に格納されていることがわかっていますが、インスタンス化されたオブジェクトはどのようにしてメンバー関数内の独自のメンバー変数を正確に呼び出すことができるのでしょうか? これが私たちが話している this キーワードであり、実際には this ポインタです。this ポインタは実際にはメンバ関数の最初の暗黙的なパラメータであり、インスタンス化したオブジェクトがこの関数を呼び出すと、それ自体のアドレスが自動的に渡されます。
表示されるコードと出力は次のとおりです。
class Date
{
public:
void print()
{
cout << _year << "年" << _month << "月" << _day << "日";
}
private:
int _year = 2023; //缺省值,用于默认构造时成员变量的默认值,会在后续构造函数中讲到
int _month = 8; //暂时不用太过注意
int _day = 6;
};
int main()
{
Date d1;
d1.print();
}
出力:
2023 年 8 月 6 日
実際のコード (コンパイラによって処理される):
8.2 このポインタの特徴
- このポインターの型: type * const、つまりメンバー関数では、 this ポインターに値を割り当てることはできません。
- 「メンバー関数」内でのみ使用できます
- this ポインタは本質的に「メンバ関数」の仮パラメータであり、オブジェクトがメンバ関数を呼び出すとき、オブジェクトのアドレスが実パラメータとしてこの仮パラメータに渡されます。したがって、 this ポインタはオブジェクトに格納されません。
- this ポインタは、「メンバー関数」の最初の暗黙的なポインタ パラメータです。通常、これはコンパイラによって ecx レジスタを通じて自動的に渡され、ユーザーが渡す必要はありません。
//相当于下面的代码
class Date
{
public:
void print(Date* const this)
{
cout << this->_year << "年" << this->_month << "月" << this->_day << "日";
}
private:
int _year = 2023;
int _month = 8;
int _day = 6;
};
int main()
{
Date d1;
d1.print(&d1);
}
ただし、print 関数の呼び出しに失敗する別の特殊な状況があります。つまり、次のように、渡すオブジェクト d1 が const 型であるということです。
9. NULLポインタによるメンバ関数呼び出しの問題
ここで誰もが疑問に思うかもしれませんが、null ポインターはどうやってメンバー関数を呼び出すことができるのでしょうか? 答えは、関数内のメンバー変数に対する逆参照操作がない場合に呼び出すことができるということです。次のように:
ここでの print 関数はパブリック コード領域に保存されており、null ポインターへのアクセス動作がないため、正常に呼び出すことができます。
class Date
{
public:
void print()
{
//这里没有在函数体内对成员函数进行操作,
//而我们的成员函数是统一放在公共代码区的,
//所以这里也就没有发生任何的空指针行为,所以是可以被调用成功的。
cout << "void print()" << endl;
}
private:
int _year = 2023;
int _month = 8;
int _day = 6;
};
int main()
{
Date* d1 = nullptr;
d1->print();
}
出力:
void print()
次の print 関数は null ポインターを逆参照するため、プログラムがクラッシュします。
class Date
{
public:
void print()
{
//这里对空指针进行了解引用的操作,
//引发了空指针的非法访问,所以代码就会直接崩溃
cout << _year << "年" << _month << "月" << _day << "日" << endl;
//引发了异常: 读取访问权限冲突。this 是 nullptr。
}
private:
int _year = 2023;
int _month = 8;
int _day = 6;
};
int main()
{
Date* d1 = nullptr;
d1->print();
}
出力: 出力なし
例外: 例外がスローされました: 読み取りアクセス違反。これはnullptrです。
要約: クラスに関する基本的な知識はほぼカバーしました。この後、クラスの 6 つのデフォルトのメンバー関数について説明します。これは理解するのが難しいため、楽しみにしていてください。コードテキストは簡単ではありません。3 回連続のオリンピックを思い出してください。