史上最も完全なクラスとオブジェクト。C++ のクラスとオブジェクトを注意深く読めば、面接官に毎分勝つことができます【C++】

記事ディレクトリ

基本

ここに画像の説明を挿入
ここに画像の説明を挿入

手続き型とオブジェクト指向

C 言語はプロセス指向であり、プロセスに焦点を当て、問題を解決するためのステップを分析し、関数呼び出しを通じて問題を段階的に解決します。
C++ はオブジェクト指向に基づいており、オブジェクトに焦点を当て、1 つのものをさまざまなオブジェクトに分割し、オブジェクト間の相互作用に依存して完了します。
例えば、フードデリバリーシステムを構築する場合、
プロセス指向では発注・受注・配送の3つのプロセスに着目し、
オブジェクト指向では顧客・加盟店・乗客の関係に着目します。

クラス紹介

C言語以前は構造体に変数しか定義できませんでしたが、C++では構造体に変数だけでなく関数も定義できるようになりました。

struct Test
{
    
    
    //成员函数
	int Add(int x, int y)
	{
    
    
		return x + y;
	}
	//成员变量
	int a;
	double b;
	
};

ただし、上記の構造の定義では、C++ では代わりにクラスを使用することをお勧めします。

class Test
{
    
    
    //成员函数
	int Add(int x, int y)
	{
    
    
		return x + y;
	}
	//成员变量
	int a;
	double b;
	
};

#include<iostream>
//类域
struct Queue
{
    
    
	//成员函数 
	void Init()
	{
    
    

	}
};
struct Stack
{
    
    
	//成员函数 
	void Init(int defaultcapacity =4)
	{
    
    
		a = (int * )malloc(sizeof(int)*capacity);
		if (a == nullptr)
		{
    
    
			printf("malloc fail");
			exit(-1);
		}
		capacity = defaultcapacity;
		top = 0;
	}
	void Push(int x)
	{
    
    
		a[top++] = x;
	}
	void Destory()
	{
    
    
		free(a);
		a = nullptr;
		top = capacity;
	}
	//成员变量 
	//类域是一个整体 ,写在成员函数前面或者后面都行 
	int* a;
	int top;
	int capacity;
};
int main()
{
    
    
	struct Stack st1;
	st1.Init();
	Stack st2;//cpp的类
	st2.Init();
	st2.Push(1);
	st2.Push(2);
	st2.Push(3);
	st2.Push(4);
	st2.Destory();
	return 0;
}

クラス定義

class className
{
    
    
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号

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

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

  1. 宣言と定義はすべてクラス本体に配置されます 注: メンバー関数がクラスで定義されている場合、コンパイラーはそれをインライン関数として扱うことがあります。
class  Stack
{
    
    
public :
	//成员函数 
	//类里面定义的函数默认就是inline 
	//一般长的函数声明和定义分离,短的函数直接定义的类里面
	void Init(int defaultcapacity = 4)
	{
    
    
		a = (int*)malloc(sizeof(int) * capacity);
		if (a == nullptr)
		{
    
    
			printf("malloc fail");
			exit(-1);
		}
		capacity = defaultcapacity;
		top = 0;
	}
	void Push(int x)
	{
    
    
		a[top++] = x;
	}

	void Destory()
	{
    
    
		free(a);
		a = nullptr;
		top = capacity;
	}
private:
	//成员变量 
	//类域是一个整体 ,写在成员函数前面或者后面都行 
	int* a;
	int top;
	int capacity;


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

機能h

#include<iostream>

using namespace std;

class  Stack
{
    
    

public:

	//成员函数 
	void Init(int defaultcapacity = 4);
	void Push(int x);
	void Destory();
private:
	//成员变量 
	//类域是一个整体 ,写在成员函数前面或者后面都行 
	int* a;
	int top;
	int capacity;
};

機能.cpp


#include"Func.h"

//指定类域
void Stack::Init(int defaultcapacity = 4)
{
    
    
	a = (int*)malloc(sizeof(int) * capacity);
	if (a == nullptr)
	{
    
    
		printf("malloc fail");
		exit(-1);
	}
	capacity = defaultcapacity;
	top = 0;
}

void Stack::Push(int x)
{
    
    
	a[top++] = x;
}

void Stack::Destory()
{
    
    
	free(a);
	a = nullptr;
	top = capacity;
}

一般的には 2 番目の方法が使用されます。
メンバー変数の命名規則に関する提案:

class Data
{
    
    
public :
	//成员函数
	void Init(int year)
	{
    
    
		_year = year;
	}
private:
	//成员变量 
	int _year;
	int _month;
	int day;
};

クラスアクセス修飾子

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

ここに画像の説明を挿入
アクセス修飾子の説明:

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

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

C++ の構造体とクラスの違いは何ですか?

C++ は C 言語と互換性がある必要があるため、C++ の struct を構造体として使用できます。さらに、C++ の struct を使用してクラスを定義することもできます。class がクラスを定義するのと同じですが、struct のメンバーのデフォルトのアクセス モードが public で、class のメンバーのデフォルトのアクセス モードが private である点が異なります。

クラスのカプセル化

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

C++ 言語のカプセル化では、クラスを通じてデータとデータを操作するメソッドを有機的に結合し、アクセス権を通じてオブジェクトの内部実装の詳細を隠し、どのメソッドをクラスの外部で直接使用できるかを制御できます。クラスを使用してデータとメソッドの両方をカプセル化します。外部の世界に公開したくないメンバーは protected/private でカプセル化する必要があり、public でカプセル化されたメンバーは外部の世界がそれらに適切にアクセスできるようにします。したがって、カプセル化は本質的に管理です

クラススコープ

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

ローカルおよびグローバル ドメインはライフ サイクルに影響しますが、クラス ドメインと名前空間ドメインはライフ サイクルに影響しません。

クラスのインスタンス化

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

1 クラスはオブジェクトを記述します。クラスは、クラスが持つメンバーを定義する単なるモデルです。クラスは、それを格納するための実際のメモリ領域を割り当てずに定義されます。

C 言語で構造体を定義する場合と同様、カスタム型で変数を作成していない場合、構造体型を定義するプロセスでは、それを格納するための実際のメモリ領域が割り当てられません。

2 クラスは複数のオブジェクトをインスタンス化でき、インスタンス化されたオブジェクトは実際の物理空間を占有し、クラス メンバー変数を格納します。

C 言語で構造体を定義し、このカスタム型で変数を作成するのと同じように、この変数はメンバー変数を格納するために実際の物理スペースを占有します。

3 オブジェクトのクラスのインスタンス化は、実際に建築設計図を使用して家を建てることに似ています。クラスは設計図のようなものです。必要なものだけを設計しますが、物理的な建物はありません。同じクラスは単なる設計です。インスタンス化 オブジェクトは実際に作成できます。データを保存し、物理スペースを占有する

ここに画像の説明を挿入

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

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

class A
{
    
    
public:
	void PrintA()
	{
    
    
		cout << _a << endl;
	}
private:
	char _a;
};

クラスにはメンバー変数とメンバー関数の両方を含めることができますが、クラスのオブジェクトには何が含まれるのでしょうか? クラスのサイズを計算するにはどうすればよいですか?

class Person
{
    
    
public:
	//显示基本信息
	void ShowInfo()
	{
    
    
		cout << _name << "-" << _sex << "-" << _age << endl;
	}
public:
	char* _name;  //姓名
	char* _sex;   //性别
	int _age;     //年龄
};

クラスオブジェクトの格納方法を推測する

オブジェクトにはクラスの個々のメンバーが含まれます

ここに画像の説明を挿入

欠点: 各オブジェクトのメンバ変数は異なるが、同じ関数が呼び出される このように格納すると、クラスが複数のオブジェクトを作成する際に、各オブジェクトにコードが保存され、同じコードが複数回保存されてしまいます。スペースの無駄遣い。では、どうすれば解決できるでしょうか?

コードのコピーは 1 つだけ保存され、コードが保存されているアドレスはオブジェクトに保存されます。

ここに画像の説明を挿入

メンバー変数のみが保存され、メンバー関数はパブリック コード セグメントに格納されます。

上記の保存方法では、コンピュータが保存するためにどの方法を使用するかについて、次のさまざまなオブジェクトのサイズを分析できます。
ここに画像の説明を挿入

以下のさまざまなオブジェクトのサイズを取得して分析してみましょう

// 类中既有成员变量,又有成员函数
class A1 
{
    
    
public:
	//成员函数
	void f1() 
	{
    
    

	}
private:
	//成员变量 
	int _a;
};
// 类中仅有成员函数
class A2 
{
    
    
public:
	//成员函数 
	void f2() 
	{
    
    

	}
};
// 类中什么都没有---空类
//没有成员变量的类对象 ,需要1byte,是为了占位,表示对象存在 
//但是不存储有效数据
class A3
{
    
    

};
int main()
{
    
    
//对象中只存储了成员变量,没有存储成员函数 
	cout << sizeof(A1) << endl;//4
	cout << sizeof(A2) << endl;//1
	cout << sizeof(A3) << endl;//1

	return 0;
}

これら 3 つのオブジェクトのサイズは、単項演算子 sizeof によって取得され、その結果、A1 のサイズは 4 バイト、A2 のサイズは 1 バイト、A3 のサイズも 1 バイトになります。

結論は:クラスのサイズは、実際にはクラス内の「メンバー変数」の合計です。もちろん、メモリのアラインメントには注意が必要です。空のクラスのサイズに注意してください。空のクラスは特別です。コンパイラは空のクラスを与えます
。 class このクラスのオブジェクトを一意に識別するバイト

構造体のメモリ配置がよくわからない場合は、ここをクリックしてください。

このポインタ

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	void Init(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//编译器对成员函数的处理 
	//void Print(Date* const this )
	//{
    
    
	//	cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	//}
private:
	int _year; // 年 声明 
	int _month; // 月
	int _day; // 日
	int a;
};
int main()
{
    
    
	Date d1, d2;
	d1.Init(2023, 5, 11);
	d2.Init(2023, 5, 11);
	d1.Print();
	//编译器的处理 
	//d1.Print(&d1);
	//d2.Print(&d2);

	d2.Print();
	return 0;
}

ここに画像の説明を挿入

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

これを仮パラメータと実パラメータの間で明示的に渡すことはできませんが、関数内で明示的に使用できます。

using namespace std;
class Date
{
    
    
public:
	void Init(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << this << endl;
		cout << this->_year << "-" << _month << "-" << _day << endl;
	}
	//编译器对成员函数的处理 
	//void Print(Date* const this )
	//{
    
    
	//	cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	//}
private:
	int _year; // 年  声明
	int _month; // 月
	int _day; // 日
	int a;
};
int main()
{
    
    
	Date d1, d2;
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print();
	//编译器的处理 
	//d1.Print(&d1);
	//d2.Print(&d2);

	d2.Print();
	return 0;
}

このポインターの特徴

  1. このポインタの型: クラスの型 * const、つまりメンバー関数内、this ポインタに値を割り当てることはできません。

  2. 「メンバー関数」内でのみ使用できます

  3. this ポインタは本質的に「メンバ関数」の仮パラメータであり、オブジェクトがメンバ関数を呼び出すとき、オブジェクトのアドレスが実パラメータとしてこの仮パラメータに渡されます。したがって、 this ポインタはオブジェクトに格納されません。

this ポインタはどこにありますか?

これは仮パラメータであるため、this ポインタは通常のパラメータと同様に関数呼び出しのスタック フレームに存在します。

  1. this ポインタは、「メンバー関数」の最初の暗黙的なポインタ パラメータです。通常、これはコンパイラによって ecx レジスタを通じて自動的に渡され、ユーザーが渡す必要はありません。

ここに画像の説明を挿入
vS では、this ポインタが渡されて最適化され、オブジェクトのアドレスが ecx に配置され、ecx はこのポインタの値を格納します。

このポインタは null であってもよいでしょうか?

次のコードを通じて this ポインターをより深く理解します。


class A
{
    
    
public:

	void Print()//this指针是空的,但是函数内没有对this指针解引用
	{
    
    
		cout << "Print()" << endl;
	}
	//编译器对成员函数的处理 
	//	void Print(A * const this)//this指针是空的,但是函数内没有对this指针解引用
	//{
    
    
	//	cout << "Print()" << endl;
	//}
private:

	int _a;
};

int main()
{
    
    
	A* p = nullptr;
	p->Print();//并且p调用Print,不会发生解引用,因为Print的地址不在对象中,Print的地址在公共代码段。但是p会作为实参传递给this指针,p是一个nullptr ,传递一个nullptr并不会报错
	return 0;
}

this ポインタは空ですが、this ポインタは関数内で逆参照されず、
p が Print を呼び出しても、Print のアドレスはオブジェクト内になく、Print のアドレスはパブリック コード セグメント内にあるため、逆参照は発生しません。ただし、p は実パラメータとしてこのポインタに渡され、p は nullptr です。nullptr を渡してもエラーは報告されません。

class A
{
    
    
public:
	void PrintA()
	{
    
    
		cout << _a << endl;//this指针是空的,但是函数内访问_a,本质是this->_a
	}
	//编译器对成员函数的处理 
	//	void PrintA(A * const this )
	//{
    
    
	//	cout << this->_a << endl;//this指针是空的,但是函数内访问_a,本质是this->_a
	//}
private:
	int _a;
};
int main()
{
    
    
	A* p = nullptr;
	p->PrintA();
	return 0;
}

this ポインタは空ですが、関数内の _a へのアクセスは基本的に this->_a であり
、p は Print を呼び出します。Print のアドレスがオブジェクト内にないため、逆参照は発生しません。p は実パラメータとして this ポインタに渡されます。
ここに画像の説明を挿入

記事を改善する

ここに画像の説明を挿入

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

クラスにメンバーが存在しない場合、それを単に空のクラスと呼びます。しかし、空のクラスには本当に何もないのでしょうか? 実際、どのクラスでも、何も書かなくても、デフォルトのメンバー関数が 6 つクラス内に自動的に生成されます。

class Date 
{
    
    

}; //空类

ここに画像の説明を挿入

コンストラクタ

コンストラクター: 名前はクラス名と同じで、クラス型オブジェクトの作成時にコンパイラーによって自動的に呼び出され、各データ メンバーが適切な初期値を持つようにし、オブジェクトのライフ サイクル中に 1 回だけ呼び出されます。

たとえば、次の Date クラスのメンバー関数 Date はコンストラクターです。date クラスを使用してオブジェクトを作成すると、コンパイラは自動的にコンストラクターを呼び出し、新しく作成された変数を初期化します。

特性

コンストラクターは特別なメンバー関数です。コンストラクターの名前はコンストラクターと呼ばれていますが、コンストラクターの主なタスクはオブジェクトを作成するためのスペースを開くことではなく、オブジェクトを初期化することであることに注意してください。

その特徴は次のとおりです。

  1. コンストラクター関数名はクラス名と同じです。

  2. コンストラクターには戻り値がありません。
    戻り値がないことは、戻り値が void であることを意味するわけではありませんが、関数には戻り値がありません。

  3. コンパイラは、オブジェクトがインスタンス化されるときに、対応するコンストラクターを自動的に呼び出します。

class Stack
{
    
    
public:
	//构造函数 :初始化 
	Stack(  int capacity =4 )
	{
    
    
		printf("Stack(  int capacity =4 )");
		_array = (int*)malloc(sizeof(int) * capacity);
		if (_array == NULL)
		{
    
    
			printf("malloc fail");
			exit(-1);
		}
		_capacity = capacity;
		_size = 0;

	}
	void Push(int x)
	{
    
    
		_array[_size++] = x;
	}
	//析构函数 : 完成对象中资源的清理工作
	~Stack( )
	{
    
    
		printf("~Stack( )");
		if (_array)
		{
    
    
			free(_array);
			_array = nullptr;
			_size = 0;
			_capacity = 0;
		}
   }

private:
	int* _array;
	int _capacity;
	int _size;

};
int main()
{
    
    
	Stack s;
	s.Push(1);
	s.Push(2);
	s.Push(3);

	return 0;
}
  1. コンストラクターはオーバーロードできる

コンストラクターは関数のオーバーロードをサポートします

class Stack
{
    
    
public:
	//函数重载 
	Stack(int* a, int n)
	{
    
    
		cout << "Stack(int* a, int n)" << endl;
		_array = (int*)malloc(sizeof(int) * n);
		if (_array == NULL)
		{
    
    
			printf("malloc fail");
			exit(-1);
		}
		memcpy(_array, a, sizeof(int) * n);
		_capacity = n;
		_size = n;

	}
	//构造函数 :初始化 
	Stack(int capacity = 4)
	{
    
    
		printf("Stack(  int capacity =4 )");
		_array = (int*)malloc(sizeof(int) * capacity);
		if (_array == NULL)
		{
    
    
			printf("malloc fail");
			exit(-1);
		}
		_capacity = capacity;
		_size = 0;

	}
	void Push(int x)
	{
    
    
		_array[_size++] = x;
	}
	//析构函数 : 完成对象中资源的清理工作
	~Stack()
	{
    
    
		printf("~Stack( )");
		if (_array)
		{
    
    
			free(_array);
			_array = nullptr;
			_size = 0;
			_capacity = 0;
		}
	}

private:
	int* _array;
	int _capacity;
	int _size;

};
int main()
{
    
    
	Stack s;
	s.Push(1);
	s.Push(2);
	s.Push(3);

	return 0;
}

5コンパイラによって自動生成されるデストラクタについては、何かを実現しますか? 次のプログラムでは、コンパイラーによって生成されたデフォルトのデストラクターがカスタム型メンバーのデストラクターを呼び出すことがわかります。

class Date
{
    
    
public:
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1;
	Date d2;
	d1.Print();
	return 0;
}

ここに画像の説明を挿入

補足:任意の型の
int/char/double/pointers などの

カスタム型、struct/class で定義された型などの組み込み型/基本型 (言語自体で定義された基本型)。

コンストラクターを作成しない場合、コンパイラーはデフォルトでコンストラクターを生成します。組み込み型はこれを処理しません (一部のコンパイラーはこれを処理します。これはコンパイラーの個人化された動作であり、C++ はこれを処理しません)。カスタム型はそのデフォルトのコンストラクターを呼び出します。

一般に、組み込み型メンバーがある場合は、コンストラクターを自分で記述する必要があります。また、ほとんどのコンパイラーは組み込み型を処理しないため、コンパイラーを使用して自分でコンストラクターを記述することはできません。

すべてがカスタム型メンバーである場合は、コンパイラーが独自にそれを生成できるようにすることを検討できます。


C++11標準リリース時にパッチが適用され、メンバ宣言にデフォルト値を与えられるようになりました

class Stack
{
    
    

};
class Date
{
    
    
public:
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	//内置类型
	//c++11支持 ,这里不是初始化 ,这里是声明,并没有开空间 
	//这里是给的默认缺省值,是给编译器生成的默认构造函数用的
	int _year = 1;
	int _month = 1;
	int _day = 1;
	//自定义类型 
	//Stack _st;
};
int main()
{
    
    
	Date d1;
	Date d2;
	d1.Print();
	return 0;
}
  1. クラス内でコンストラクターが明示的に定義されていない場合、C++ コンパイラーはパラメーターのないデフォルトのコンストラクターを自動的に生成します。ユーザーが明示的に定義すると、コンパイラーは自動的に生成されなくなります。

コンパイラによって自動生成されるコンストラクタのメカニズム:
 1.コンパイラによって自動生成されるコンストラクタは、組み込み型を扱いません。
 2.カスタム型の場合、コンパイラは独自のデフォルト コンストラクターを呼び出します。

コンストラクターを記述しないとコンパイラーが自動的にコンストラクターを生成しますが、コンパイラーが自動生成したコンストラクターでは期待した効果が得られない可能性があるため、ほとんどの場合、コンストラクターを自分で記述する必要があります。

コンストラクター呼び出し

class Date
{
    
    
public:
	//构造函数支持函数重载 
	Date()
	{
    
    
		_year = 1;
	   _month = 1;
	   _day = 1;
	}
	 Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	//内置类型
	//c++11支持 ,这里不是初始化 ,这里是声明,并没有开空间 
	//这里是给的默认缺省值,是给编译器生成的默认构造函数用的
	int _year = 1;
	int _month = 1;
	int _day = 1;
	//自定义类型 
	//Stack _st;
};


int main()
{
    
    

	//构造函数的调用跟普通函数也不一样
	Date d1;
	//Date d1(); ,是错的
	Date d2(2023,5,12);
	Date d3(2023);
	d2.Print();
	d3.Print();

	return 0;
}

デフォルトのコンストラクター

次の 3 つのタイプはデフォルト コンストラクターと呼ばれます。
1. コンパイラーによって自動的に生成されるコンストラクターは作成しません。
 2. 独自に作成したパラメータなしのコンストラクター。
 3. 私たち自身が作成したデフォルトのコンストラクター。
全体として、パラメーターを渡さずに呼び出すことができるコンストラクターがデフォルトのコンストラクターです。

引数なしのコンストラクターと完全なデフォルトのコンストラクター、コンパイラによって生成されたデフォルトのコンストラクターを作成しませんでした。これはデフォルトのコンストラクターと見なすことができます。しかし、これら 3 つは 1 つしか出現できません。
つまり、パラメータを渡さずにデフォルトのコンストラクタを呼び出すことができます。

引数なしのコンストラクターと完全なデフォルトのコンストラクターの両方が同時に存在するとどうなりますか?

コンストラクターは完全なデフォルト値を与えますが、コンストラクターはパラメーターを渡しません。まとめると、関数のオーバーロードは満たしますが、パラメーターなしで呼び出す場合にはあいまいさが生じます。一般に、完全なデフォルト値を与えるにはコンストラクターを使用することを選択します。

ここに画像の説明を挿入

デストラクタ

コンストラクターの機能とは対照的に、デストラクターはオブジェクトの破棄を完了する責任があります。オブジェクトが破棄されると、デストラクターはクラスのリソース クリーニング作業を完了するために自動的に呼び出されます。

デストラクタの特徴

1 デストラクタの関数名は、クラス名の前に文字「~」を追加します。

class Date
{
    
    
public:
	Date()// 构造函数
	{
    
    
	
	}
	~Date()// 析构函数
	{
    
    
	
	}
private:
	int _year;
	int _month;
	int _day;
};

2 デストラクタにはパラメータも戻り値もありません。

戻り値がないことは、戻り値が void であることを意味するわけではありませんが、関数には戻り値がありません。

3 クラスにはデストラクターを 1 つだけ含めることができます。明示的に定義されていない場合、システムはデフォルトのデストラクターを自動的に生成します。注: デストラクターはオーバーロードできません

コンパイラによって自動的に生成されるデストラクタ メカニズム:

1. コンパイラによって自動的に生成されるデストラクタは、組み込み型を扱いません。
2. カスタム型の場合、コンパイラーは独自のデフォルトのデストラクターを呼び出します。

4 オブジェクトのライフサイクルが終了すると、C++ コンパイラは自動的にデストラクタを呼び出します。

以前は、C 言語の破棄が忘れられることが多く、メモリ リークが発生していましたが、デストラクタを使用することで、C 言語の領域解放を忘れる問題が大幅に減少しました
デストラクタは 1 つだけです定義が表示されない場合、システムはデフォルトのデストラクターを自動的に生成します。

6. 最初に構築されたものが次に破壊され、後で構築されたものが最初に破壊されます。

オブジェクトは関数内で定義され、関数呼び出しによってスタック フレームが作成されます。スタック フレーム内のオブジェクトの構築と破棄も先入れ後出しの原則に従う必要があります。

ここに画像の説明を挿入

**概要:
1. 一般に、リソースの動的なアプリケーションがある場合、リソースを解放するデストラクターを明示的に記述する必要があります。


2. 動的アプリケーション用のリソースがないため、デストラクターを作成する必要がありません。


3. リソースを解放する必要があるメンバーはすべて自己定義型であり、デストラクターを記述する必要はありません。


コピーコンストラクター

コピー コンストラクター: このクラス型のオブジェクトへの参照である仮パラメーターが 1 つだけあり (通常は const 装飾が一般的に使用されます)、それは次の中で使用されます。既存のクラス型オブジェクトが新しいオブジェクトを作成するときに、コンパイラによって自動的に呼び出されます。

コピーコンストラクターの特徴

1. コピー コンストラクターは、コンストラクターのオーバーロードされた形式です。

コピーコンストラクターの関数名もクラス名と同じであるためです。

2. コピー コンストラクターにはパラメーターが 1 つだけあり、参照によって渡される必要があります。値ごとのメソッドを使用すると、無限の再帰呼び出しが発生します。

まずコードの一部を見てください。

#include<iostream>
using namespace std;

class Date
{
    
    
public:
	//构造函数 
	Date(int year = 2023 ,int month = 5,int day =12)
	 {
    
    
		_year = year;
		_month = month;
		_day = day;
	 }
	//拷贝构造函数
    Date ( Date d)//err
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;

	}
private:
	int _year;
	int _month;
	int _day;

};

int main()
{
    
    
	Date d1;
	Date d2(d1);//用已存在的对象d1创建对象d2

	return 0;
}

コピー コンストラクターを呼び出すには、最初にパラメータを渡す必要があります。パラメータが値によって渡される場合は、パラメータを渡すプロセスでオブジェクトのコピー構築を実行する必要があり、これにより最終的には無限の再帰呼び出しに。ここに画像の説明を挿入

C++ では、
組み込み型は直接コピーされ、
カスタム型はコピー構築を呼び出してコピーを完了する必要があると規定しています。

コピー コンストラクターを呼び出すには、最初にパラメータを渡す必要があります。パラメータが値によって渡される場合は、パラメータを渡すプロセスでオブジェクトのコピー構築を実行する必要があり、これにより最終的には無限の再帰呼び出しに。
ここに画像の説明を挿入
上記の問題を解決するために、文法的な観点からスペースを開かない参照形式を採用し、d は d1 の別名です。

#include<iostream>
using namespace std;

class Date
{
    
    
public:
	//构造函数 
	Date(int year = 2023 ,int month = 5,int day =12)
	 {
    
    
		_year = year;
		_month = month;
		_day = day;
	 }
	//拷贝构造函数
    Date ( const Date & d)//引用
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;

	}
private:
	int _year;
	int _month;
	int _day;

};

int main()
{
    
    
	Date d1;
	Date d2(d1);//用已存在的对象d1创建对象d2

	return 0;
}

組み込みタイプのコピーとカスタムタイプのコピー

内蔵タイプのコピー

void func(int i)
{
    
    

}
void func(Date d)
{
    
    

}
int main()
{
    
    
	func(10);//C++内置类型直接拷贝
	func(d1);//C++内置类型直接拷贝
	return 0;
}

カスタム タイプは、コピー コンストラクターを呼び出してコピーを完了します。

#include<iostream>
using namespace std;

class Date
{
    
    
public:
	//构造函数 
	Date(int year = 2023 ,int month = 5,int day =12)
	 {
    
    
		_year = year;
		_month = month;
		_day = day;
	 }
	//拷贝构造函数
    Date (const Date &d)
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;

	}
private:
	int _year;
	int _month;
	int _day;

};

int main()
{
    
    
	Date d1;
	Date d2(d1);//用已存在的对象d1创建对象d2

	return 0;
}

3. 明示的に定義されていない場合、コンパイラはデフォルトのコピー コンストラクターを生成します。デフォルトのコピー コンストラクター オブジェクトは、メモリ ストレージに従ってバイト オーダーでコピーされます。この種のコピーは、浅いコピー、または値コピーと呼ばれます。

コンパイラーによって自動的に生成されるコピー コンストラクターのメカニズム:
 1. コンパイラーによって自動的に生成されるコピー コンストラクターは、組み込み型の浅いコピー (値のコピー) を実行します
 2. カスタム型の場合、コンパイラーは独自のデフォルトのコピー コンストラクターを呼び出します関数

#include<iostream>
using namespace std;

class Date
{
    
    
public:
	//构造函数 
	Date(int year = 2023 ,int month = 5,int day =12)
	 {
    
    
		_year = year;
		_month = month;
		_day = day;
	 }
private:
	int _year;
	int _month;
	int _day;

};

int main()
{
    
    
	Date d1(2023,5,12);
	Date d2(d1);//用已存在的对象d1创建对象d2,我们不写,编译器会生成默认的拷贝构造函数
	return 0;
}

ここに画像の説明を挿入

上記のコードでは、コピー コンストラクターを自分で記述していませんが、コンパイラーによって自動的に生成されたコピー コンストラクターによってオブジェクトのコピー構築が完了します。

それで、ここで質問がありますか?
コピー コンストラクターを作成できない人は多いので、コンパイラによって自動的に生成されたコピー コンストラクターを使用してください。
次のコードを参照してください。

class Stack
{
    
    
public:
   //构造函数 
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack()" << endl;

		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
    
    
			perror("malloc申请空间失败");
			return;
		}

		_capacity = capacity;
		_top = 0;
	}

	// st2(st1) ,拷贝构造函数 
	Stack(const Stack& st)
	{
    
    
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (nullptr == _a)
		{
    
    
			perror("malloc申请空间失败");
			return;
		}

		memcpy(_a, st._a, sizeof(int) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}
	//析构函数 
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_capacity = _top = 0;
	}

private:
	int* _a = nullptr;
	int _top = 0;
	int _capacity;
};

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}

	Date(const Date& d)
	{
    
    
		cout << "Date(Date& d)" << endl;
		this->_year = d._year;
		_month = d._month;
		_day = d._day;
	
	}
private:
	int _year;
	int _month;
	int _day;
};

class MyQueue
{
    
    
private:
    //自定义类型
	Stack _pushst;
	Stack _popst;
};
int main()
{
    
    


	// 可以不写,默认生成的拷贝构造就可以用
	Date d1(2023, 5, 12);
	Date d2(d1);


	// 必须自己实现,实现深拷贝
	Stack st1;
	Stack st2(st1);

	return 0;
}

ここに画像の説明を挿入
上記のコードの問題は、コピー コンストラクターが記述されていないことと、コンパイラーによって自動的に生成されたコピー コンストラクター (上図の st1 の _a と st2 の _a ) が同じ空間を指しており、コンパイラーがデストラクターを呼び出し、 2回目、1回目のデストラクタ呼び出しで_aの指す空間が破壊され、2回目のデストラクタ呼び出しでメモリの不正アクセスが発生

要約:


Date 関数は、コンパイラーによって自動的に生成されたコピー コンストラクターが組み込み型の浅いコピー (値のコピー) を完了するため、コピー コンストラクターを作成する必要はありません。MyQueue 関数は、コピー コンストラクターを作成する必要はありません。カスタム型の場合、コンパイラは独自のデフォルトのコピー コンストラクターを呼び出します。スタック
のようなクラスの場合、浅いコピーは二重破壊やプログラムのクラッシュなどの問題を引き起こすため、対応するコピー コンストラクターを自分で記述する必要があります。

4. コンパイラによって自動生成されるコピー コンストラクターはディープ コピーを実装できません。

代入演算子のオーバーロード

コードの可読性を高めるために、C++ では演算子のオーバーロードが導入されています。演算子のオーバーロードは特別な関数名を持つ関数です。その目的は、カスタム型を組み込み型と同様に演算子で直接操作できるようにすることです。

関数名は、キーワード演算子の後に、オーバーロードする必要がある演算子記号が続きます。

関数プロトタイプ:戻り値型演算子演算子(パラメータリスト)

知らせ:

1. 新しい演算子は、operator@ などの他のシンボルを接続して作成することはできません。
2. オーバーロードされた演算子にはクラス型パラメーターが必要です。
3. 組み込み型に使用される演算子の意味は変更できません。例: 組み込み整数型+、その意味を変更することはできません。
4. クラスのメンバー関数としてオーバーロードされると、メンバー関数の最初のパラメーターが非表示のパラメーターであるため、その仮パラメーターはオペランドの数より 1 少ないように見えます。

5. .* :: sizeof ?: . 上記の 5 つの演算子はオーバーロードできないことに注意してください。これは記述式の多肢選択問題でよく出てきます。

例えば:

class Date
{
    
    
public :
	//构造函数 
	Date(int year =2023,int month =5,int day =13)
	{
    
    
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	//拷贝构造 
	//用已存在的对象d1创建对象d2 
	Date( const Date & d)
	{
    
    
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
//private:
	int _year;
	int _month;
	int _day;
};
bool operator< (const Date & x1, const Date & x2)
{
    
    
	if (x1._year < x2._year)
	{
    
    
		return true;
	}
	else if (x1._year == x2._year && x1._month < x2._month)
	{
    
    
		return true;
	}
	else if (x1._year == x2._year && x1._month == x2._month && x1._month < x2._day)
	{
    
    
		return true;
	}
	else
	{
    
    
		return false;
	}
}
int main()
{
    
    
	Date d1(2023, 5, 12);
	Date d2(2023, 2, 12);
	cout << (d1 < d2) << endl;
	cout << ( operator< (d1,d2)) << endl;
	return 0;
}

演算子のオーバーロード関数をクラス外に置きましたが、このとき問題が発生します。クラス内のメンバ変数には外部からアクセスできないため、クラス内のメンバ変数をプライベートからパブリックに設定し、メンバ変数がなくなり、クラスの
外には this ポインタが存在しないため、この時点では関数の仮パラメータを明示的に 2 に設定する必要があります。

class Date
{
    
    
public:
	//构造函数 
	Date(int year = 2023, int month = 5, int day = 13)
	{
    
    
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	//拷贝构造 
	//用已存在的对象d1创建对象d2 
	Date(const Date& d)
	{
    
    
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	bool operator< (const Date& x)
	{
    
    
		if (_year < x._year)
		{
    
    
			return true;
		}
		else if (_year == x._year && _month < x._month)
		{
    
    
			return true;
		}
		else if (_year == x._year && _month == x._month && _month < x._day)
		{
    
    
			return true;
		}
		else
		{
    
    
			return false;
		}
	}
//	bool operator< (Date* const  this ,const Date& x)
//此时该函数的第一个形参默认为this指针。
	//{
    
    
	//	if (this->_year < x._year)
	//	{
    
    
	//		return true;
	//	}
	//	else if (this->_year == x._year && this->_month < x._month)
	//	{
    
    
	//		return true;
	//	}
	//	else if (this->_year == x._year && this->_month == x._month && this->_month < x._day)
	//	{
    
    
	//		return true;
	//	}
	//	else
	//	{
    
    
	//		return false;
	//	}
	//}
	private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1(2023, 5, 12);
	Date d2(2023, 2, 12);//是否要重载运算符,取决于运算符对这个类是否有意义
	d1 < d2;	//转换成d1.operator<(d2);

	return 0;
}

ここに画像の説明を挿入


アセンブリの観点から見ると、d1 < d2 は d1.operator<(d2) と同じですが、通常は d1<d2 と書きます。

クラスのメンバー関数として演算子オーバーロード関数を使用します。この時点では、関数の仮パラメーターはまだ 2 に設定されていますが、関数の最初の仮パラメーターはデフォルトで this ポインターです。

要約:

演算子には複数のオペランドがあり、オーバーロードされた関数には複数のパラメータがあります

演算子のオーバーロード

演算子オーバーロード関数には、独自の戻り値の型、関数名、パラメーター リストもあります。戻り値の型とパラメータのリストは通常​​の関数と同様です。演算子のオーバーロード関数名: キーワード演算子の後に、オーバーロードする必要がある演算子記号が続きます。

概要:
演算子をオーバーロードするかどうかは、演算子がこのクラスにとって意味があるかどうかによって決まります。

代入演算子のオーバーロード

= 演算子のオーバーロードの例を次に示します。

class Date
{
    
    
public:
	//构造函数 
	Date(int year = 2021, int month = 1, int day = 4)
	{
    
    
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	//拷贝构造 
	//用已存在的对象d1创建对象d2 
	Date(const Date& d)
	{
    
    
		cout<< "	Date(const Date& d)" << endl;
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	//赋值运算符重载
	//d4 = d1
	// Date & operator= (Date* const this,  const Date & d ) 
   Date & operator= (const Date & d ) //出了对象this生命周期还在 
	{
    
    
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;

		return *this;  // 返回d4,this就是d4的地址 ,所以返回*this
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1(2023, 5, 12);
	Date d2(2022, 2, 12);//是否要重载运算符,取决于运算符对这个类是否有意义
	d1 = d2;//已经存在的两个对象之间的复制拷贝 (运算符重载函数)

	/*Date d2(d1);*///用一个已经存在的对象初始化另一个对象(本质是一个构造函数 )
	Date d5, d4;
	//d4 =d1 其实就是 d4.operator=(d1)
	d5 = d4 = d1;
	return 0;
}

1. 代入演算子をオーバーロードする場合は、いくつかの注意点があります。

1. パラメータの型: const T&、参照渡しによりパラメータの受け渡しの効率が向上します。

代入演算子オーバーロード関数の最初の仮パラメータはデフォルトで this ポインタになり、2 番目の仮パラメータは代入演算子の右オペランドです。
 これはカスタム タイプのパラメーター受け渡しであるため、パラメーターが値によって渡される場合、コピー コンストラクターがもう一度呼び出されるため、関数の 2 番目のパラメーターは参照パラメーターを使用するのが最適です (最初のパラメーターはデフォルトです)。ポインター、私たちはそれを制御できません)。
 次に、2 番目のパラメータ、つまり代入演算子の右側のオペランドは、関数本体の 2 番目のパラメータを変更しないため、const を追加するのが最善です。

2. 戻り値の型: T&、戻り参照により戻りの効率が向上します。戻り値の目的は、継続的な代入をサポートすることです。

実際、代入演算子を d2 = d1 の方法でのみ使用する場合、d2 は関数本体の this ポインターを通じて変更されているため、代入演算子のオーバーロード関数には戻り値が必要ありません。しかし、連続代入、つまり d3 = d2 = d1 をサポートするには、関数の戻り値を設定する必要があります。戻り値は、代入演算子の左オペランド、つまり this ポインタが指すオブジェクトである必要があります。

パラメータの参照渡しと同様に、パラメータの参照渡しも不必要なコピーを避けるためです。この時点で関数のスコープがアウトであれば、このポインタが指すオブジェクトは破棄されていません。このとき、参照渡しで返すのが最善です。不要なコピーを減らし、効率を向上させます

3. 自分自身に値を割り当てているかどうかを確認します

d1 = d1 となった場合、代入演算を行う必要はありませんので、代入演算を行う前に、自分に値を代入するかどうかを判断した方がよいでしょう。

4. *this: を返して、連続代入の意味をさらに複雑にします。

代入演算が完了したら、代入演算子の左オペランドを返す必要がありますが、関数本体では this ポインタを介して左オペランドにのみアクセスできるため、左オペランドを返すには *this を返すことしかできません。

代入演算子でオーバーロードされた関数とコピー コンストラクターを区別する

これを見ると、コピー コンストラクターと代入演算子のオーバーロード関数についてより明確に理解できたと思いますが、
コピー コンストラクターと代入演算子のオーバーロード関数の違いに注意してください。

ここに画像の説明を挿入

コピー コンストラクター: 既存のオブジェクトを使用して、作成する別のオブジェクトを構築および初期化します。
代入演算子のオーバーロード機能: 両方のオブジェクトがすでに存在する場合に、1 つのオブジェクトを別のオブジェクトに代入します。

class Date
{
    
    
public:
	//构造函数 
	Date(int year = 2021, int month = 1, int day = 4)
	{
    
    
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	//拷贝构造 
	//用已存在的对象d1创建对象d2 
	Date(const Date& d)
	{
    
    
		cout << "	Date(const Date& d)" << endl;
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	//赋值运算符重载
	//d4 = d1
	Date& operator= (const Date& d) //出了对象this生命周期还在 
	{
    
    
		//d1 =d1 
		if (this != &d)
		{
    
    
			this->_year = d._year;
			this->_month = d._month;
			this->_day = d._day;
	  }
		 
		return *this;  //返回*this 这个对象的别名
	}


	//Date& operator= (Date * const  this ,const Date& d) //出了对象this生命周期还在 
	//{
    
    
	//	//d1 =d1 
	//	if (this != &d) //比较的是地址 ,&d是取地址,防止自己给自己赋值
	//	{
    
    
	//		this->_year = d._year;
	//		this->_month = d._month;
	//		this->_day = d._day;
	//	}

	//	return *this;  //返回*this 这个对象的别名
	//}
private:
	int _year;
	int _month;
	int _day;
};

	int main()	
	{
    
    
	d1 = d2;//已经存在的两个对象之间的复制拷贝 (运算符重载函数)
	Date d2(d1);//用一个已经存在的对象d1初始化另一个对象d2(本质是一个构造函数 )
	Date d3 = d1;//调用的也是拷贝构造函数
	return 0 ;
	}	

2. 代入演算子はクラスのメンバー関数としてのみオーバーロードでき、グローバル関数としてオーバーロードすることはできません。
次のコードを見てください。

class Date
 
{
    
    
 
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
 
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
 
Date& operator=(Date& left, const Date& right)
{
    
    
	if (&left != &right)
	{
    
    
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}

理由: 代入演算子が明示的に実装されていない場合、コンパイラはデフォルトの代入演算子を生成します。このとき、ユーザーがグローバル代入演算子オーバーロードをクラス外で実装すると、クラス内のコンパイラによって生成されるデフォルトの代入演算子オーバーロードと競合するため、代入演算子オーバーロードはクラスのメンバー関数のみにすることができます。
ここに画像の説明を挿入

3. ユーザーが明示的に実装しない場合、コンパイラはデフォルトの代入演算子のオーバーロードを生成します。これは値ごとにバイトごとにコピーされます。

知らせ:組み込み型のメンバー変数は直接割り当てられますが、カスタム型のメンバー変数は、対応するクラスの代入演算子オーバーロードを呼び出して代入を完了する必要があります。

つまり、デフォルトで生成される代入オーバーロードの動作は、コピー構築の動作と同じです:
1. コンパイラによって自動的に生成される代入オーバーロード関数は、組み込み型の浅いコピー (値のコピー) を実行します
。カスタム型の場合、コンパイラはそれらを再度呼び出します 独自の代入オーバーロード関数

class Time
{
    
    
public:
	Time()
	{
    
    
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
    
    
		if (this != &t)
		{
    
    
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
 
class Date
 
{
    
    
 
private:
	// 基本类型(内置类型)
 
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
 
	Time _t;
};
 
int main()
{
    
    
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

コンパイラーによって生成されるデフォルトの代入演算子のオーバーロード関数は、バイトオーダーの値のコピーをすでに完了できるため、日付クラスなどを自分で実装する必要はありません。
次のクラスが記述されていない場合、コンパイラによって生成されるデフォルトの代入演算子のオーバーロード関数を使用しますか?

ここで、次のプログラムがクラッシュすることがわかります。ここでは、後で学習して解決するディープ コピーが必要です。

typedef int DataType;
class Stack
{
    
    
public:
	Stack(size_t capacity = 10)
	{
    
    
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
    
    
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
    
    
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
    
    
		if (_array)
		{
    
    
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
 
private:
	DataType *_array;
	size_t _size;
	size_t _capacity;
};
 
int main()
{
    
    
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2;
	s2 = s1;
	return 0;
}

リソース管理がクラスに関与していない場合、代入演算子は実装してもしなくても構いません。リソース管理が関与した場合は、実装する必要があります。
ここに画像の説明を挿入

要約:

Date 関数は、コンパイラーによって自動的に生成された代入オーバーロード関数が組み込み型の浅いコピー (値のコピー) を完了するため、代入オーバーロード関数を作成する必要はありません。MyQueue 関数は、代入オーバーロード関数を作成する必要はありません。カスタム
型の場合、コンパイラ ハンドラーは独自のデフォルトの代入オーバーロード関数を呼び出します。Stack
のようなクラスの場合、浅いコピーは 2 回の構造化やプログラムのクラッシュなどの問題を引き起こします。対応する代入オーバーロード関数を自分で記述する必要があります。
ここに画像の説明を挿入

定数メンバー

const 変更クラスのメンバー関数

const 修飾されたクラス メンバー関数を const メンバー関数と呼びます。const 修飾されたクラス メンバー関数は、実際にはクラス メンバー関数の暗黙的な this ポインターを変更します。これは、このポインターが指すオブジェクトがこのメンバー関数内で変更できないことを示します。

ここに画像の説明を挿入

たとえば、クラス メンバー関数内の印刷関数を const 変更して、関数本体内のオブジェクトが誤って変更されるのを避けることができます。

class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	void Print()
	{
    
    
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
			
	}
	void Print() const
	{
    
    
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1(2023, 5, 5);
	d1.Print(); //d1.Print(&d1)

	const Date d2(2023, 5, 5);
	d2.Print();//d2.Print(&d2)

	return 0;
}

ここに画像の説明を挿入

const を括弧内に追加するのではなく、void Print() の後に const を追加する理由は、this ポインタを表示したり書き込むことができず、const は後にのみ追加できること、const の追加は const Date* const this と同等であり、追加された const は変更されるためです。 *これにより

メンバー関数に const を追加すると、通常のオブジェクトと const オブジェクトの両方を呼び出すことができ、通常のオブジェクトの権限が削減され、const オブジェクトの権限が移動されます。

すべてのメンバー関数がこの const を追加できますか?
いいえ、変更対象のオブジェクトメンバ変数の関数は追加できません

結論: メンバー変数がメンバー関数内で変更されない限り、
const オブジェクトと通常のオブジェクトの両方を呼び出せるようにconst を追加する必要があります。

次の質問 (典型的な面接の質問) について考えてください。
1. const オブジェクトは非 const メンバー関数を呼び出すことができますか?
2. 非 const オブジェクトは const メンバー関数を呼び出すことができますか?
3. const メンバー関数内で他の非 const メンバー関数を呼び出すことはできますか?
4. 他の cosnt メンバー関数を非 cosnt メンバー関数で呼び出すことはできますか?

ここに画像の説明を挿入

答えは、「いいえ、はい、いいえ、はい」です
。 説明は次のとおりです。
 1. 非 const メンバー関数、つまり、メンバー関数の this ポインターは const によって変更されず、const によって変更されたオブジェクトを渡します。 const によって変更されていないオブジェクトを使用します。 このポインタは、アクセス許可の増幅に属し、受信され、関数呼び出しは失敗します。
 2. const メンバー関数。つまり、メンバー関数の this ポインターが const によって変更されている場合、const によって変更されていないオブジェクトを渡し、const によって変更された this ポインターでそれを受け取ります。これは、次の削減に属します。権限があり、関数呼び出しは成功します。
 3. const で変更されたメンバ関数内で、const で変更されていない他のメンバ関数を呼び出す、つまり、 const で変更された this ポインタの値を const で変更されていない this ポインタに代入する、これは関数呼び出しの権限の拡大に属します。失敗した。
 4. const によって変更された他のメンバー関数を const によって変更されていないメンバー関数内で呼び出す、つまり const によって変更されていない this ポインターの値を const によって変更された this ポインターに代入する場合、権限の削減に属し、関数呼び出しは成功します。

const int a =10  ;
int b = a ;
const int a =10  ;
int& b = a ;

ここに画像の説明を挿入

日付クラスの実装

C++ の 6 つのデフォルトのメンバー関数を学習した後、完全な日付クラスを実装します。
 これは、日付クラスに含まれるメンバー関数とメンバー変数です。

class Date
{
    
    
public:
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1);
	// 打印函数
	void Print() const;
	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day) const;
	// 日期-=天数
	Date& operator-=(int day);
	// 日期-天数
	Date operator-(int day) const;
	// 前置++
	Date& operator++();
	// 后置++
	Date operator++(int);
	// 前置--
	Date& operator--();
	// 后置--
	Date operator--(int);
	// 日期的大小关系比较
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;
	bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
	// 日期-日期
	int operator-(const Date& d) const;

	// 析构,拷贝构造,赋值重载可以不写,使用默认生成的即可

private:
	int _year;
	int _month;
	int _day;
};

日付クラスのコンストラクター

// 获取某年某月的天数
inline int GetMonthDay(int year, int month)
{
    
    
	// 数组存储平年每个月的天数
	static int dayArray[13] = {
    
     0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	int day = dayArray[month];
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
    
    
		//闰年2月的天数
		day = 29;
	}
	return day;
}
// 构造函数
Date::Date(int year, int month, int day)
{
    
    
	// 检查日期的合法性
	if (year >= 0
	&& month >= 1 && month <= 12
	&& day >= 1 && day <= GetMonthDay(year, month))
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
    
    
		// 严格来说抛异常更好
		cout << "非法日期" << endl;
		cout << year << "年" << month << "月" << day << "日" << endl;
	}
}

GetMonthDay 関数の注意点は次の 3 つです。
 1. GetMonthDay 関数は複数回呼び出されるため、インライン関数として設定されており、コード効率を向上させるために関数スタック フレームを作成する必要がありません。 2
 . GetMonthDay 関数が呼び出されるたびに配列を開き直す必要があるため、新しいスタック フレームを作成し、dayArray 配列を static で変更し、静的領域に格納します。 3. 閏年の判定: 4 年ごとに閏、1 年間は閏
 なし百年、また四百年跳ぶ

int Date::GetMonthDay(int year, int month)
{
    
    
	static int daysArr[13] = {
    
     0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	//是闰年 
	if  (month == 2 && (  (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) )) 
	{
    
    
		//四年一闰,百年不闰,四百年再闰
		return 29;
	}
	else
	{
    
    
		return daysArr[month];
	}
}

日付クラスの出力関数

// 打印函数
void Date::Print() const
{
    
    
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

日付クラスの大小関係の比較

日付クラスの大小関係を比較するためにオーバーロードする必要がある演算子は6つあるようですが、実際には<と==を実装するだけで、残りはこの2つの演算子を再利用することで実現できます。

< 演算子のオーバーロード

bool Date::operator< (const Date& x)
{
    
    
	//bool Date::operator ( Date * const this , const Date &x )
	if (this->_year < x._year)
	{
    
    
		return true;
	}
	else if (this->_year == x._year && this->_month < x._month)
	{
    
    
		return true;
	}
	else if (this->_year == x._year && this->_month == x._month && this->_day < x._day)
	{
    
    
		return true;
	}

	return false;
}

== 演算子のオーバーロード


bool Date:: operator== (const Date& x)
{
    
    
	return this->_year == x._year
		&&	this->_month == x._month
		&&  this->_day == x._day;
}

>= 演算子のオーバーロード

日付 += 日数

+= 演算子のオーバーロードされた関数は参照戻りを使用します。これは、関数がスコープ外にあるときに this ポインターが指すオブジェクトが破棄されないためです。

// 日期+=天数
Date& Date::operator+= (int day)
//Date Date::operator+ (Date *  const this ,int day)
{
    
    
	this->_day += day;
    
	while (   this->_day   >     GetMonthDay( this->_year,  this->_month)  )
	{
    
    
		this->_day -= GetMonthDay(this->_year, this->_month);
		this->_month++;
		if (this->_month == 13)
		{
    
    
			this->_year++;
			this->_month = 1;
		}
	}
	return *this;
}

<= 演算子のオーバーロード

bool Date::operator<= (const Date& x)
{
    
    
	//复用之前的函数 
	return  *this < x  ||  *this ==x ;
}

!= 演算子のオーバーロード

bool Date::operator!=  (const Date& x)
{
    
    
	return !(*this == x);
}

> 演算子のオーバーロード

bool Date::operator> (const Date& x)
//bool Date::operator> ( Date *this , const Date &x )
//d1 >d2 ,d1 是*this ,d2 是x
{
    
    
	return !(*this<=x);
}
bool Date::operator>=  (const Date& x)
{
    
    
	return !(*this < x);
}
bool Date::operator!=  (const Date& x)
{
    
    
	return !(*this == x);
}
在这里插入代码片

日付+日数

+ 演算子のオーバーロードは、前の += 演算子のオーバーロードされた関数を再利用します。post ++ は追加前の値を返しますが、オブジェクト自体の値は変更されていませんa = b + 1 と同様に、b + 1 の戻り値は b + 1 ですが、b の値は変わりません。+ 演算子のオーバーロードされた関数の戻り値は値によってのみ返されます。オブジェクト tmp は関数のスコープ外に出ると破棄され、参照によって返すことはできないためです。

Date Date::operator+ (int day)//后置++
//Date Date::operator+ (Date* const this ,int day  )
{
    
    
	Date tmp(*this);//用已存在的对象*this拷贝创建tmp 

	//tmp._day += day;

	//while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	//{
    
    
	//	tmp._day -= GetMonthDay(tmp._year, tmp._month);
	//	tmp._month++;
	//	if (tmp._month == 13)
	//	{
    
    
	//		tmp._year++;
	//		tmp._month = 1;
	//	}
	//}

	//复用复用operator+=
	tmp += day;
	return tmp;
}

プレフィックス++
pre-++ と post-++ は両方とも ++ であり、pre-++ は ++ の後のオブジェクトを返し、post-++ は ++ の前のオブジェクトを返します。組み込み型の場合、pre-++ と post-++ の間にほとんど違いはありませんが、カスタム型の場合、pre-++ と post-++ の差は非常に大きく、pre-++ はカスタムタイプに推奨
pre-++ と post-++ の演算子のオーバーロードを区別するために、post-++ の演算子のオーバーロードのパラメーターに int パラメーターを追加します。この int パラメータに実パラメータを追加します。この int パラメータの追加は特定の値に対するものではなく、単なるプレースホルダであり、前置詞 ++ は関数のオーバーロードを構成します

//前置++
Date& Date::operator++()
{
    
    
	
	*this += 1;
	return *this;
}

日付 += 日数

Date& Date::operator+= (int day)
//Date Date::operator+ (Date *  const this ,int day)
{
    
    
	if (day < 0)
	{
    
    
		return *this -= -day;
	}
	this->_day += day;
    
	while (   this->_day   >     GetMonthDay( this->_year,  this->_month)  )
	{
    
    
		this->_day -= GetMonthDay(this->_year, this->_month);
		this->_month++;
		if (this->_month == 13)
		{
    
    
			this->_year++;
			this->_month = 1;
		}
	}
	return *this;
}

日付 -= 日数

Date Date::operator-= (int day)
//Date Date::operator( Date * const this  ,int day )
{
    
    
	this->_day -= day;
    
	while (this->_day <=0)
	{
    
    
		--this->_month;
		if (this->_month == 0)
		{
    
    
			this->_month = 12;
			--this->_year;
		}
		this->_day += GetMonthDay(this->_year ,this->_month);
	}
	return *this ;
}


日付 - 日数

+ 演算子のオーバーロードと同様に、上記の -= 演算子のオーバーロード関数は再利用され、tmp オブジェクトは関数のスコープ外で破棄されるため、参照によって返すことはできません。

// 日期-天数
Date Date::operator-(int day) const
{
    
    
	Date tmp(*this);// 拷贝构造tmp,用于返回
	// 复用operator-=
	tmp -= day;

	return tmp;
}

日付.h

#include<iostream>
using namespace std;

class Date
{
    
    
	//友元函数声明
	//friend void operator<< (ostream& out, const Date& d);
	friend ostream&  operator<< (ostream& out, const Date& d);

	friend istream& operator>> (istream& in, Date& d);


public:
	//构造函数 
	Date(int year = 1, int month = 1, int day = 1);

	//内联 
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//声明和定义分离 
	bool operator< (const Date& x);
	bool operator== (const Date& x);

	bool operator<= (const Date& x);
	bool operator> (const Date& x);
	bool operator>=  (const Date& x);
	bool operator!=  (const Date& x);

static	int GetMonthDay(int year ,int month);
	Date& operator+= (int day);
	Date operator+ (int day);
	Date& operator++();  //前置++
	Date  operator++(int );//后置++

  Date&	operator-= (int day);
  Date operator- (int day);
  Date& operator-- (); //前置-- ,返回--之后的值
  Date operator--(int); //后置 -- ,返回--之前的值 

 int   operator- (const Date & d); //d1 -d2 


 //流插入不能写成成员函数 
 //因为Date对象默认占了第一个参数,就做了左操作数
 // 写出来就一定是下面这样子 ,不符合使用习惯 
// d1 << cout; // d1.operator << cout

 //void operator<< (ostream& out);

 int GetYear()
 {
    
    
	 return this->_year;
	}

private:
	//内置类型
	int _year;
	int _month;
	int _day;
};

//void operator<< (ostream& out);

ostream& operator<< (ostream& out, const Date& d);

istream& operator>> (istream& in, Date& d);


日付.cpp

#include"Date.h"
//构造函数 
Date::Date(int year , int month , int day )
{
    
    
	if (month > 0 && month < 13
		&& day > 0 && day <=GetMonthDay(year, month)  )
	{
    
    

		this->_year = year;
			this->_month = month;
			this->_day = day;
	}
	else
	{
    
    
		cout << "非法日期"<<endl;
	}
}
bool Date::operator< (const Date& x)
{
    
    
	//bool Date::operator ( Date * const this , const Date &x )
	if (this->_year < x._year)
	{
    
    
		return true;
	}
	else if (this->_year == x._year && this->_month < x._month)
	{
    
    
		return true;
	}
	else if (this->_year == x._year && this->_month == x._month && this->_day < x._day)
	{
    
    
		return true;
	}

	return false;
}


bool Date:: operator== (const Date& x)
{
    
    
	return this->_year == x._year
		&&	this->_month == x._month
		&&  this->_day == x._day;
}




//bool Date::operator<= ( Date * const this, const Date& x)
//假设 d1<=d2  ,d1 是*this ,d2 是x

bool Date::operator<= (const Date& x)
{
    
    
	//复用之前的函数 
	return  *this < x  ||  *this ==x ;
}
bool Date::operator> (const Date& x)
//bool Date::operator> ( Date *this , const Date &x )
//d1 >d2 ,d1 是*this ,d2 是x
{
    
    
	return !(*this<=x);
}
bool Date::operator>=  (const Date& x)
{
    
    
	return !(*this < x);
}
bool Date::operator!=  (const Date& x)
{
    
    
	return !(*this == x);
}

int Date::GetMonthDay(int year, int month)
{
    
    
	static int daysArr[13] = {
    
     0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	//是闰年 
	if  (month == 2 && (  (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) )) 
	{
    
    
		//四年一闰,百年不闰,四百年再闰
		return 29;
	}
	else
	{
    
    
		return daysArr[month];
	}
}


Date& Date::operator+= (int day)
//Date Date::operator+ (Date *  const this ,int day)
{
    
    
	if (day < 0)
	{
    
    
		return *this -= -day;
	}
	this->_day += day;
    
	while (   this->_day   >     GetMonthDay( this->_year,  this->_month)  )
	{
    
    
		this->_day -= GetMonthDay(this->_year, this->_month);
		this->_month++;
		if (this->_month == 13)
		{
    
    
			this->_year++;
			this->_month = 1;
		}
	}
	return *this;
}




Date Date::operator+ (int day)//后置++
//Date Date::operator+ (Date* const this ,int day  )
{
    
    
	Date tmp(*this);//用已存在的对象*this拷贝创建tmp 

	//tmp._day += day;

	//while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	//{
    
    
	//	tmp._day -= GetMonthDay(tmp._year, tmp._month);
	//	tmp._month++;
	//	if (tmp._month == 13)
	//	{
    
    
	//		tmp._year++;
	//		tmp._month = 1;
	//	}
	//}

	//复用函数 
	tmp += day;
	return tmp;
}


//前置++
Date& Date::operator++()
//Date& Date::operator++( Date * const this )
{
    
    
	
	*this += 1;

	return *this;
}

//后置++
//增加这个int参数不是为了具体的值 ,仅仅是占位 ,和前置++构成函数重载 
Date Date::operator++(int)
{
    
    
	Date tmp = *this; //
	*this += 1;
	return tmp;  // tmp出了作用域就被销毁 ,用传值返回 
}

Date& Date::operator-= (int day)
//Date Date::operator( Date * const this  ,int day )
{
    
    

	if (day < 0)
	{
    
    
		return *this += -day;
	}


	this->_day -= day;
    
	while (this->_day <=0)
	{
    
    
		--this->_month;
		if (this->_month == 0)
		{
    
    
			this->_month = 12;
			--this->_year;
		}
		this->_day += GetMonthDay(this->_year ,this->_month);
	}
	return *this ;//出了函数作用域,this指针指向的对象没有被销毁。
}

Date Date::operator- (int day)
//Date Date::operator-(int day )
{
    
    

	Date(tmp) = *this;

	tmp-=day;

	return tmp;

}
Date& Date::operator-- () //前置--
{
    
    
 //前置-- ,返回--之后的值 
	*this -= 1;;
	return *this;

}
Date Date::operator--(int) //后置 -- ,返回--之前的值 
{
    
    
	Date tmp = *this;//拷贝构造
	*this -= 1;
	
		return tmp; 
}

int   Date::operator- (const Date& d) //d1-d2
//int   operator- (  Date * const this,const Date& d) //d1-d2
{
    
    
	//假设 *this 比 d 大
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d) 
	{
    
    
		Date max = d;
		Date min = *this;
		int flag = -1;
	}
	
	int n = 0;
	while (max!=min)
	{
    
    
		
		++min;
		++n;
	 }

	return  flag * n;
}
//void Date::operator<< (ostream& out) //out 是cout 的别名 
//
void Date::operator<< (Date* const this ,stream& out) //out 是cout 的别名 
//{
    
    
//	out <<this->_year << "年" << this->_month << "月" << this->_day << "日" << endl;
//}


//void operator<< (ostream& out ,const Date &d)
//{
    
    
//	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
//
//}

ostream & operator<< (ostream& out, const Date& d)
{
    
    
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;// out 就是cout 
}

istream& operator>> (istream& in, Date& d)

{
    
    
	
	
	int  month, year, day;
	in >> year >> month >> day;
	if (month > 0 && month < 13 && day>0 && day <=Date::GetMonthDay(year, month))
	{
    
    
		d._year = year;
		d._month = month;
		d._day = day;
	}
	return in;
}

高度

ここに画像の説明を挿入

address および const アドレス演算子のオーバーロード

これら 2 つのデフォルトのメンバー関数は通常、再定義する必要はなく、コンパイラーはデフォルトで生成します。

class Date
{
    
    
 
public:
	Date* operator&()
	{
    
    
		return this;
	}
 
	const Date* operator&()const
 
	{
    
    
		return this;
	}
 
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
 
};

これら 2 つの演算子は通常、オーバーロードする必要はありません。他の演算子に指定されたコンテンツを取得させたくない場合は、コンパイラーによって生成されたデフォルトのアドレス オーバーロードを使用するだけです。

	const Date* operator&()const
 
	{
    
    
		return this;
	}

これを戻り値として必要な場合は、const 変更アドレス演算子をオーバーロードする必要があります。これは、その前にある const によって変更され、戻り値として const によって変更される必要があるためです。

コンストラクターについて話しましょう

コンストラクター本体の割り当て

オブジェクトを作成するとき、コンパイラはコンストラクターを呼び出して、オブジェクト内の各メンバー変数に適切な初期値を与えます。

class Date
{
    
    
public:
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

上記のコンストラクターを呼び出した後、オブジェクト内の各メンバー変数は初期値を持ちますが、コンストラクター内のステートメントは初期化ではなく、初期値の代入のみを呼び出すことができることに注意してください。初期化は 1 回しか行うことができず、コンストラクター本体で複数の代入を行うことができるためです。

class Date
{
    
    
public:
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
	{
    
    
		_year = year;// 第一次赋值
		_year = 2022;// 第二次赋值
		//...
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

初期化リスト

初期化リスト: コロンで始まり、その後にデータ メンバーのコンマ区切りのリストが続き、各メンバー変数の後に括弧で囲まれた初期値または式が続きます。

class Date
{
    
    
public:
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
    
    }
private:
	int _year;
	int _month;
	int _day;
};

1.各メンバー変数は初期化リストに最大 1 回しか出現できません。

初期化は 1 回しか実行できないため、同じメンバー変数を初期化リストに複数回出現させることはできません。

2.クラスには次のメンバーが含まれており、初期化のために初期化リストに配置する必要があります。
参照メンバー変数

class B
{
    
    
public:
	//初始化列表:对象成员定义的位置 
	B(int a, int ref)
		//初始化 
		:_ref(ref)
		,_n(1)

	{
    
    
		//赋值
		;
	}
private:
	//声明 
	//特征:必须在定义的时候初始化
	int& _ref;//引用
	const int _n; //const
};
int main()
{
    
    
	//对象整体定义
	B bb1(10, 1);
	B bb2(11, 2);
	return 0;
}

const メンバー変数

const によって変更される変数には、定義時に初期値を与える必要があり、初期化リストを使用して初期化する必要もあります。

class B
{
    
    
public:
	//初始化列表:对象成员定义的位置 
	B(int a, int ref)
		//初始化 
		:_ref(ref)
		,_n(1)

	{
    
    
		//赋值
		;
	}
private:
	//声明 
	//特征:必须在定义的时候初始化
	int& _ref;//引用
	const int _n; //const
};
int main()
{
    
    
	//对象整体定义
	B bb1(10, 1);
	B bb2(11, 2);
	return 0;
}

カスタム型メンバー (クラスにはデフォルトのコンストラクターがありません)
クラスにデフォルトのコンストラクターがない場合は、クラス オブジェクトをインスタンス化するときにパラメーターを渡して初期化する必要があるため、デフォルトのコンストラクターを使用せずにクラス オブジェクトをインスタンス化する場合は、初期化リストを使用して初期化する必要があります。

class  A
{
    
    
public:
	//构造函数 
	A(int a =0) //给了缺省值 
		:_a(a)
	{
    
    
		cout << "A(int a = 0 )" << endl;
	}
private:
	int _a;

};
class B
{
    
    
public:
	//初始化列表:对象成员定义的位置 
	B(int a, int ref)
		//初始化 
		:_ref(ref)
		,_n(1)
		,_x(2)
		,_aobj(10)

	{
    
    
		//赋值
		;
	}
private:
	//声明 
	//特征:必须在定义的时候初始化
	A _aobj;  //自定义类型 , 没有默认构造函数 
	int& _ref;//引用
	const int _n; //const
	int _x =1; //缺省值  ,这个缺省值是给初始化列表的
};
int main()
{
    
    
	//对象整体定义
	B bb1(10, 1);
	B bb2(11, 2);
	return 0;
}

要約:

カスタム型メンバ、const メンバ変数、および参照メンバ変数には、定義時に初期化する必要があり、定義時に初期化する必要がある変数型は初期化用の初期化リストに含める必要があるという特性があります。

参照:参照も、それが定義されている場所で初期化される機会が 1 回だけあります。

[注意]:初期化リストはメンバーが定義されているため、各メンバーは初期化リストを通過する必要があります。
初期化リストに表示されない場合でも、初期化リストを通過します。
組み込み型の場合、デフォルト値がある場合はデフォルト値を使用し、デフォルトがない場合は値コンパイラがランダムな値に処理します。
カスタム型の場合はデフォルトの構造を呼び出し、デフォルトの構造がない場合はエラーを報告します。

3.初期化リストの初期化を使用してみる

オブジェクトをインスタンス化する際、そのオブジェクトのメンバ変数を定義する場所が初期化リストとなり、初期化リストを使用するか否かに関わらず、このような処理を経ることになります(メンバ変数の定義が必要です)。

  • 組み込み型の場合、次のコードのように、初期化リストを使用する場合と (初期化リストを使用せずに) コンストラクター本体で初期化する場合にほとんど違いはありません。
// 使用初始化列表
int a = 10
// 在构造函数体内初始化(不使用初始化列表)
int a;
a = 10;

  • カスタム型の場合、初期化リストを使用するとコードの効率が向上します。
class Time
{
    
    
public:
	Time(int hour = 0)
	{
    
    
		_hour = hour;
	}
private:
	int _hour;
};
class Test
{
    
    
public:
	// 使用初始化列表
	Test(int hour)
		:_t(12)// 调用一次Time类的构造函数
	{
    
    
	}
private:
	Time _t;
};

上記のコードでは、Test クラスのオブジェクトをインスタンス化する必要がある場合、初期化リストが使用され、 Time クラスのコンストラクターはインスタンス化プロセス中に1 回だけ呼び出されます。
 初期化リストを使用せずに目的の効果を実現したい場合は、次のように変更するだけです。

class Time
{
    
    
public:
	Time(int hour = 0)
	{
    
    
		_hour = hour;
	}
private:
	int _hour;
};
class Test
{
    
    
public:
	// 在构造函数体内初始化(不使用初始化列表)
	Test(int hour)
	{
    
     //初始化列表调用一次Time类的构造函数(不使用初始化列表但也会走这个过程)
		Time t(hour);// 调用一次Time类的构造函数
		_t = t;// 调用一次Time类的赋值运算符重载函数
	}
private:
	Time _t;
};

Test クラスのオブジェクトをインスタンス化するには、インスタンス化プロセスで、リストが初期化されるときに Time クラスのコンストラクターが 1 回呼び出され、次に t オブジェクトがインスタンス化されるときに Time クラスのコンストラクターが 1 回呼び出され、最後にTime クラスは 1 回呼び出されます 代入演算子は関数をオーバーロードし、複数回呼び出され、効率が低下します
4.クラス内でメンバー変数が宣言される順序は、初期化リストで初期化される順序です。初期化リスト内の順序は関係ありません。

class A
{
    
    
public:
	//构造函数 
	A(int a)
		//初始化列表,按照声明的顺序初始化
		:_a1(a)
		, _a2(_a1)
	{
    
    
	}
	void Print() 
	{
    
    
		cout << _a1 << " " << _a2 << endl;
	}
private:
	//声明
	int _a2;
	int _a1;
};
int main()
{
    
    
	A aa(1);//调用构造函数 
	aa.Print();//1 随机值
}

メンバ変数は、最初に_a2が宣言され、その後に_a1が宣言されていますが、初期化リストの初期化順序に従って、クラス内でメンバ変数が宣言されているため、クラスAのコンストラクタの初期化リストでは、メンバ変数_a2が宣言されています。が最初に初期化され、メンバー変数 _a1 が初期化されます。つまり、最終結果は 1 とランダムな値になります。

提案:宣言の順序は定義の順序と一致しています。

明示的なキーワード

コンストラクターは、オブジェクトを構築して初期化するだけでなく、単一のパラメーターの型変換、またはデフォルト値のない最初のパラメーターを除くデフォルト値を持つコンストラクターの機能も持ちます。

#include <iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 0) //单个参数的构造函数
		:_year(year)
	{
    
    
	}
	void Print()
	{
    
    
		cout << _year << endl;
	}
private:
	int _year;
};
int main()
{
    
    
	Date d1 = 2023; //2023先构造一个Date的临时对象,临时对象再拷贝构造d1——>编译器会优化成用2直接构造
	d1.Print();
	return 0;
}

初期のコンパイラでは、コード Date d1 = 2023 に遭遇すると、最初に Date の一時オブジェクトを構築し、次にその一時オブジェクトを使用して d1 をコピーして構築します。コンパイラの改良により、構築 + コピー構築 --- - > 最適化: 直接構築

そこで、ここでは暗黙的な型変換を避けるために、コンストラクターを明示的に変更します。暗黙的な型変換と呼ばれるDate d1(2023)のコードに従って処理されます。

int a = 10;
double b = a; //隐式类型转换

このプロセスでは、コンパイラはまず a の値を受け取る double 型の一時変数を構築し、次にその一時変数の値を b に割り当てます。これが、関数がローカル変数の値を返すことができる理由です。関数が破棄されると、戻り値として使用される変数も破棄されますが、暗黙的な型変換中に生成された一時変数は破棄されないため、値はそのまま残ります。存在。

ここに画像の説明を挿入

ただし、単一パラメーターのカスタム型の場合、Date d1 = 2023 のコードはあまり読みにくく、単一パラメーターのコンストラクターの暗黙的な変換を禁止したい場合は、明示的なキーワードを使用してコンストラクターを変更できます。

#include <iostream>
using namespace std;
class Date
{
    
    
public:
 explicit	Date(int year = 0) //单个参数的构造函数
		:_year(year)
	{
    
    
	}
	void Print()
	{
    
    
		cout << _year << endl;
	}
private:
	int _year;
};
int main()
{
    
    
	Date d1 = 2023; //2023先构造一个Date的临时对象,临时对象再拷贝构造d1——>编译器会优化成用2直接构造
	d1.Print();
	return 0;
}

同じ式内で、コンパイラは効率を向上させるために、基本的に連続した構造を最適化します。

静的メンバー

コンセプト

静的として宣言されたクラス メンバーは、クラスの静的メンバーと呼ばれます。static で変更されたメンバー変数は静的メンバー変数と呼ばれ、static で変更されたメンバー関数は静的メンバー関数と呼ばれます。静的メンバー変数はクラスの外部で初期化する必要があります。

特性

1.静的メンバーはすべてのクラス オブジェクトで共有され、特定のオブジェクトに属しません。

class A
{
    
    
public:
	//构造函数 
	A()
	{
    
    
		++_scount;
	}
	//拷贝构造函数 
	A(const A& aa)
	{
    
    
		++_scount;
	}
	//析构函数 
	~A()
	{
    
    
		--_scount;
	}
	static int   GetACount()
	{
    
    
		return _scount;
	}
private:
	//都是声明 
	//静态成员变量 属于类,属于类的每个对象共享 ,存储在静态区 ,生命周期和全局变量一样
  static	int _scount;
  //成员变量属于每一个类对象 ,存储在对象里面
  int _a1 = 1;
  int _a2 = 1;
};
//全局位置,必须在类外面定义
int A::_scount = 0;
int main() 
{
    
    
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);//拷贝构造 
	cout << A::GetACount() << endl;
	return 0; 
}

静的メンバー_n は静的領域に格納され、クラス全体に属し、クラスの各オブジェクトでも共有されます。クラスのサイズまたはクラス オブジェクトのサイズを計算する場合、静的メンバーは合計に含まれません。合計サイズ

メンバー変数は各クラス オブジェクトに属し、オブジェクト内に格納されます。

2.静的メンバー変数は、定義時に static キーワードを追加せずに、クラスの外部で定義する必要があります。

class Test
{
    
    
private:
	static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;

注: ここでは静的メンバー変数 _n はプライベートですが、クラス ドメインを突破してクラスの外から直接アクセスします。これは特殊なケースであり、アクセス修飾子によって制限されません。それ以外の場合、静的メンバー変数を定義および初期化する方法はありません。

3.静的メンバー関数にはこのポインターが隠されていないため、非静的メンバーにアクセスできません。

class Test
{
    
    
public:
    //没有this指针 ,指定类域和访问限定符就可以访问
	static void Fun()
	{
    
    
		cout << _a << endl; //error不能访问非静态成员
		cout << _n << endl; //correct
	}
private:
	int _a; //非静态成员
	static int _n; //静态成员
};

静的メンバー変数を持つクラスには、通常、静的メンバー変数にアクセスするための静的メンバー関数が含まれています。つまり、一般的な静的メンバ関数と静的メンバ変数が混在する
4.1静的メンバ変数がパブリックの場合、以下のアクセス方法があります。

#include <iostream>
using namespace std;
class Test
{
    
    
public :
	static int _n;

};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
    
    
	Test test; //创建对象 
	cout << Test::_n << endl;  //通过类名突破类域
	cout << test._n << endl; //通过类对象突破类域
	cout << Test()._n << endl; //通过匿名对象突破类域 
	return 0;
}

4.2静的メンバ変数がプライベートの場合、以下のアクセス方法があります。

#include <iostream>
using namespace std;
class Test
{
    
    
public:
	static int GetN()
	{
    
    
		return _n;
	}

private:
	static int _n;
};
//静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
    
    
	Test test; //创建对象 
	//通过对象调用成员函数 
	cout << test.GetN()<<endl;
	//通过匿名对象调用成员函数 
	cout << Test().GetN << endl;
	//通过类名调用成员函数 
	cout << Test::GetN << endl; 
	return 0;
}

5.静的メンバもクラスの通常のメンバと同様に、パブリック、プライベート、プロテクトの 3 つのアクセス レベルがあるため、
 静的メンバ変数がプライベートに設定されている場合は、クラス ドメインを突破してもアクセスできません。 。

静的メンバー関数は非静的メンバー関数を呼び出すことができますか?

:できません。非静的メンバー関数の最初の仮パラメーターはデフォルトで this ポインターになり、静的メンバー関数には this ポインターがないため、静的メンバー関数は非静的メンバー関数を呼び出すことができません。


非静的メンバー関数は静的メンバー関数を呼び出すことができますか?

:できる。静的メンバー関数と非静的メンバー関数の両方がクラス内にあるため、クラス内のアクセス修飾子によって制限されません。

友元

フレンドはフレンド機能とフレンドクラスに分かれています。友人はカプセル化を打破する方法を提供し、時には利便性を提供します。ただし、フレンドは結合度を高め、カプセル化を破壊するため、フレンドをより頻繁に使用するべきではありません。

フレンド機能

フレンド関数はクラスのプライベートメンバに直接アクセスできますが、クラスの外で定義されクラスに属さない通常の関数ですが、クラス内で宣言する必要があり、宣言時にfriendキーワードを追加する必要があります。 。

問題:
ここで演算子をオーバーロードしようとしましたが、演算子をメンバー関数にオーバーロードする方法がないことがわかりました。cout の出力ストリーム オブジェクトと暗黙的な this ポインターが最初のパラメーターの位置をプリエンプトしているためです。this ポインターのデフォルトは、左側のオペランドである最初のパラメーターです。ただし、実際の使用では、cout が通常使用される最初の仮パラメータ オブジェクトである必要があります。したがって、演算子はグローバル関数としてオーバーロードする必要があります。ただし、クラス外のメンバーにアクセスする方法がなくなるため、問題を解決するには友人が必要です。オペレーター>>同じ理由です。

class Date
 
{
    
    
 
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    }
	// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
 
	// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
 
	ostream& operator<<(ostream& _cout)
	{
    
    
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}
 
private:
	int _year;
	int _month;
	int _day;
};

フレンド関数はクラスのプライベートメンバに直接アクセスできますが、クラスの外で定義されクラスに属さない通常の関数ですが、クラス内で宣言する必要があり、宣言時にfriendキーワードを追加する必要があります。 。

class Date
 
{
    
    
  //声明 
 friend ostream& operator<<(ostream& _cout, const Date& d);
 friend istream& operator>>(istream& _cin, Date& d);
public:
 Date(int year = 1900, int month = 1, int day = 1)
 : _year(year)
 , _month(month)
 , _day(day)
 {
    
    
 }
 
private:
 int _year;
 int _month;
 int _day;
};
 
ostream& operator<<(ostream& _cout, const Date& d)
{
    
    
 _cout << d._year << "-" << d._month << "-" << d._day;
 return _cout; 
}
 
istream& operator>>(istream& _cin, Date& d)
{
    
    
 _cin >> d._year;
 _cin >> d._month;
 _cin >> d._day;
 return _cin;
}
 
int main()
{
    
    
 Date d;
 cin >> d;
 cout << d << endl;
 return 0;
}

知らせ:

1.フレンド関数は、クラスのプライベートおよび保護されたメンバーにアクセスできますが、クラスのメンバー関数にはアクセスできません。
 2.フレンド関数をconstで変更することはできません。
 3. フレンド関数はクラス定義のどこでも宣言でき、アクセス修飾子によって制限されません。
 4. 関数は複数のクラスのフレンド関数になることができます。
 5. フレンド関数の呼び出し原理は通常の関数と同じです。

フレンドクラス

フレンド クラスのすべてのメンバー関数は別のクラスのフレンド関数になることができ、別のクラスの非パブリック メンバーにアクセスできます

class Time
{
    
    

	//声明友元类,声明放在那个位置都是一样的,
	//只有成员才会受私有或者共有的限制 ,访问限定符限制的是一个成员的访问方式,成员包含成员变量和成员函数 
	friend class Date;
public:
	Time(int hour = 10, int minute = 50, int  second = 40)
		:_hour(hour)
		, minute(minute)
		, _second(second)
	{
    
    

	}
private:
	int _hour;
	int minute;
	int _second;
};
class Date
{
    
    
	
public :
	Date(int year = 2023 ,int month =5 ,int day =22)
		:_year(year)
		,_month(month)
		,_day (day )
	{
    
    

	}

	void SetTimeOfDate(int hour, int minute, int second)
	{
    
    
		_t._hour = hour;
		_t.minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

内部クラス

コンセプト:

クラスが別のクラス内に定義されている場合、この内部クラスは内部クラスと呼ばれます。内部クラスは、
外部クラスに属さない独立したクラスであり、ましてや、外部クラスのオブジェクトを通じて内部クラスのメンバーにアクセスすることはできません。外部クラスには、内部クラスへの特権アクセスがありません。

[注意]: 内部クラスは外部クラスのフレンド クラスです。フレンド クラスの定義を参照してください。内部クラスは、外部クラスのオブジェクト パラメータを通じて外部クラスのすべてのメンバーにアクセスできます。しかし、外側の階級は内側の階級の友人ではありません。

特性:

  1. 内部クラスは、外部クラスの public、protected、および private で定義できます。

  2. 内部クラスは、外部クラスのオブジェクト/クラス名を使用せずに、外部クラスの静的メンバーに直接アクセスできることに注意してください。

  3. sizeof(outer class) = 外部クラス。内部クラスとは関係ありません。

class A
{
    
    
public:
//private:
	//内部类

	//B在A的类域里面,但是B不占空间 
	class B
	{
    
    
	public:
		//内部类是外部类的友元
		void foo(const A & a)
		{
    
    
			cout << k << endl;
			cout << a.h << endl;
		}

	};
private:
	//成员变量 
	int h;
	//静态成员变量 
	static int k;//不占空间 ,静态成员变量不在对象里面 
};
//静态成员变量定义初始化 
int A::k = 1;

int main()
{
    
    
	A::B bb;
	cout <<sizeof(A)<<endl;
	return 0;
}

匿名オブジェクト

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout<< "	A(int a = 0)" << endl;
	}

	~A()
	{
    
    
		cout << "~A()" << endl;
	}
private:
	int _a;
};
class Solution
{
    
    
public:
	int Sum_Solution( int n)
	{
    
    
		cout << "Sum_Solution" << endl;

		return n;
	 }

};


int main()
{
    
    
	A aa(1) ; //有名对象 ,生命周期在当前函数的局部域
	A(2);//匿名对象  ,生命周期在当前行

	Solution sl;//有名对象调用 
	sl.Sum_Solution(10);

	//匿名对象调用 ,即用即销毁

	Solution().Sum_Solution(20);
	//匿名对象具有常性
	//A& ra = A(1);//err
	const A& ra = A(1); //const引用延长了匿名对象的生命周期 ,生命周期在当前函数的作用域 
	return 0;
}

ここに画像の説明を挿入

この記事が少しでも役に立ったと思っていただけましたら、ぜひ「いいね」「集めて」「進めて」とXi Lingに注目していただけますと、皆様の
応援が私が前に進む原動力に変わります!

おすすめ

転載: blog.csdn.net/qq_73478334/article/details/130543177