[C++] クラスとオブジェクト (パート 1)

目次

まずはクラスの紹介から

次に、クラスの定義

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

  1. アクセス修飾子

  2. カプセル化

第四に、クラスの範囲

5. クラスのインスタンス化

6、クラスオブジェクトモデル

  1. クラスオブジェクトのサイズを計算する

  2. クラスオブジェクトの格納方法

  3. 構造/クラスのアライメント規則

セブン、このポインタ

  1. このポインタの導出

  2. このポインタの特徴


まず、C と C++ の初期の理解について話しましょう。

C 言語はプロセス指向でありプロセスに焦点を当て、問題を解決するためのステップを分析し、関数呼び出しを通じて問題を段階的に解決します。

C++ はオブジェクト指向に基づいておりオブジェクトに焦点を当て、1 つのものをさまざまなオブジェクトに分割し、オブジェクト間の相互作用に依存して完了します。

まずはクラスの紹介から

C言語では構造体に変数のみ定義できますが、 C++では変数だけでなく関数も構造体に定義できます例: データ構造の初期段階では、  C 言語で実装されたスタックは、struct 内で変数を定義することしかできませんでしたが、現在は C++ で実装されており、関数も struct 内で定義できることがわかります

以下のコードのように、C と C++ は次のように異なります。

typedef int DataType;
struct Stack
{
	//成员函数
	void Init(size_t capacity)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(const DataType& data)
	{
		// 扩容
		_array[_size] = data;
		++_size;
	}
	DataType Top()
	{
		return _array[_size - 1];
	}
	void Destroy()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
	//成员变量
	DataType* _array;
	size_t _capacity;
	size_t _size;
};
int main()
{
	Stack s;
	s.Init(10);
	s.Push(1);
	s.Push(2);
	s.Push(3);
	cout << s.Top() << endl;
	s.Destroy();
	return 0;
}

上記の構造体の定義は、C++ のクラスに置き換えることをお勧めします。

次に、クラスの定義

class className
{
	// 类体:由成员函数和成员变量组成

}; // 一定要注意后面的分号

classはクラスを定義するためのキーワードClassNameクラスの名前{} はクラスの本体ですクラス定義の最後のセミコロンは省略できないことに注意してください。

クラス本体の内容はクラスのメンバーと呼ばれ、クラス内の変数はメンバー変数と呼ばれ、クラス内の関数はクラスのメンバー関数と呼ばれます。

クラスを定義するには 2 つの方法があります。

  • 宣言と定義はすべてクラス本体に配置されます。注:メンバー関数がクラス内で定義されている場合、コンパイラはそれをインライン関数として扱うことがあります。

  • クラス宣言は .h ファイルに配置され、メンバー関数定義は .cpp ファイルに配置されます。注:クラス名はメンバー関数名の前に追加する必要があります::

ここでは 2 番目の方法を使用することを好みます。

メンバー変数の命名規則に関する提案:

// 我们看看这个函数,是不是很僵硬?
class Date
{
public:
	void Init(int year)
	{
		// 这里的year到底是成员变量,还是函数形参?
		year = year;
	}
private:
	int year;
};

上記のコードを読むと、誰がメンバー変数で誰が関数パラメータなのか少し混乱します。したがって、次の形式になります。

class Date
{
public:
	void Init(int year)
	{
		_year = year;
	}
private:
	int _year;
};

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

  1. アクセス修飾子

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

 

[アクセス修飾子の説明]

  1. パブリックに変更されたメンバーには、クラスの外部から直接アクセスできます。
  2. protected および private の変更されたメンバーには、クラスの外部から直接アクセスすることはできません (ここでは、protected と private は似ています)。
  3. アクセス権の範囲は、このアクセス修飾子の出現から次のアクセス修飾子の出現までとなります
  4. 背後にアクセス修飾子がない場合、スコープは }、つまりクラスの終わりに移動します。
  5. デフォルトのアクセス権限は、class は private、struct は public (struct は C と互換性があるため) です。

注: アクセス修飾子はコンパイル時にのみ役立ちます。データがメモリにマップされるときは、アクセス修飾子に違いはありません。

  2. カプセル化

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

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

  カプセル化は本質的に、ユーザーがクラスを使いやすくするための管理です。

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

第四に、クラスの範囲

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

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

5. クラスのインスタンス化

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

  1. クラスはオブジェクトの記述です。クラスが持つメンバーを定義し、実際のメモリ領域を割り当てずにクラスを定義するモデルのようなものです。たとえば、登録時に記入する学生情報フォーム、フォームは、特定の学生情報を記述するクラスと考えることができます。
  2. クラスは複数のオブジェクトをインスタンス化でき、インスタンス化されたオブジェクトは実際の物理空間を占有し、クラス メンバー変数を格納します。
  3. クラスからオブジェクトをインスタンス化することは、実際に建築設計図を使用して家を建てることに似ています。クラスは設計図のようなもので、必要なものだけが設計されますが、物理的な建物はありません。オブジェクトは実際にデータを保存し、物理的なスペースを占有することができます。

実際にデータを保存し、物理スペースを占有することができるのは、インスタンス化されたオブジェクトのみです。

class Person
{
public:
	void PrintInfo();
public:
	char* _name;
	char* _sex;
	char* _age;
};
void Test()
{
	Person man;
	man._name = "xxxx";
	man._sex = "男";
	man._age = 10;
	man.PrintInfo();
}

6、クラスオブジェクトモデル

  1. クラスオブジェクトのサイズを計算する

#include<iostream>
using namespace std;

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};
void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << endl << _age << endl;
}

int main()
{
	Person man;
	cout << sizeof(man) << endl;
	return 0;
}

操作結果:

  2. クラスオブジェクトの格納方法

#include<iostream>
using namespace std;

//1.类中既有成员变量,又有成员函数
class A1
{
public:
    void f1() {}
private:
    int _a;
};
//2.类中仅有成员函数
class A2
{
public:
    void f2() {}
};
//3.类中仅有成员变量
class A3
{
private:
    int _a;
};
//4.空类
class A4
{};
int main()
{
    cout << "A1: " << sizeof(A1) << " A2: " << sizeof(A2) << " A3: " << sizeof(A3) << " A4: " << sizeof(A4) << endl;
    return 0;
}

 操作結果:

  結論:クラスのサイズは実際にはクラス内の「メンバー変数」の合計です もちろん、メモリのアラインメントには注意を払う必要があります。

  注:空のクラスのサイズ、空のクラスは特別です。コンパイラは、このクラスのオブジェクトを一意に識別するためのバイトを空のクラスに与えます。

  3. 構造/クラスのアライメント規則

  クラスのメモリアライメントルールは構造体のメモリアライメントルールと同じです。以前に書いたブログを参照してください。

  カスタムタイプ(構造体、ビットフィールド)の詳細説明

セブン、このポインタ

  1. このポインタの導出

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;     // 年
	int _month;    // 月
	int _day;      // 日
};
int main()
{
	Date d1, d2;
	d1.Init(2022, 6, 19);
	d2.Init(2023, 6, 19);
	d1.Print();
	d2.Print();
	return 0;
}

  Date クラスには Init と Print という 2 つのメンバー関数があり、関数本体では異なるオブジェクト間の区別はありません。d1 が Init 関数を呼び出すとき、関数はどのようにして d2 オブジェクトの代わりに d1 オブジェクトを設定する必要があるかを認識しますか?物体?

  C++ では、この問題は this ポインターを導入することで解決されます。つまり、C++ コンパイラーは各「非静的メンバー関数」に隠しポインター パラメーターを追加し、ポインターが現在のオブジェクト (関数を呼び出すオブジェクト) を指すようにします。関数の実行中)、関数本体内の「メンバー変数」のすべての操作は、このポインターを通じてアクセスされます。すべての操作がユーザーに対して透過的であるというだけです。つまり、ユーザーが操作を渡す必要がなく、コンパイラーが自動的に操作を完了します。

  2. このポインタの特徴

  1. このポインターの型: class type * const。つまり、メンバー関数では、このポインターに値を割り当てることはできません。
  2. 「メンバー関数」内でのみ使用できます。
  3. this ポインタは本質的に「メンバ関数」の仮パラメータであり、オブジェクトがメンバ関数を呼び出すとき、オブジェクトのアドレスが実パラメータとしてこの仮パラメータに渡されます。したがって、 this ポインタはオブジェクトに格納されません。
  4. this ポインタは、「メンバー関数」の最初の暗黙的なポインタ パラメータです。通常、これはコンパイラによって ecx レジスタを通じて自動的に渡され、ユーザーが渡す必要はありません。

このポインタはどこにあるのでしょうか?

回答: これは仮パラメータとして存在し、通常はスタック上にあり、一部のコンパイラはそれをレジスタに置きます。

補足質問:

次のコードを分析します。

class A
{
public:
    void Print()
    {
        cout << "Print()" << endl;
    }
    void PrintA()
    {
        cout << _a << endl;
    }
private:
    int _a;
};
int main()
{
    A* p = nullptr;
    p->Print();    // 正常运行
    p->PrintA();   // 运行崩溃
    return 0;
}

なぜこのようなことが起こるのでしょうか?

回答: Print() メンバー関数を呼び出しますが、p が指すオブジェクトに Print() が存在しないため、コンパイラーは p を逆参照せず、同時に Print() は this ポインターを使用してアクセスしません。スペースが指すデータ このため、コンパイラはエラーを報告せず、Print() 関数を通常どおり実行します。 (*p).PrintfA() についても同じことが当てはまりますが、これが PrintfA( 内で使用される点が異なります) ) が指す空間内のデータにアクセスするため、結果として nullptr の逆参照が発生し、プログラムは当然クラッシュします。


この記事に不備がある場合は、以下にコメントしてください。できるだけ早く修正します。

   古いアイアン、好きになることと注目することを忘れないでください!  

おすすめ

転載: blog.csdn.net/m0_63198468/article/details/131284922