C++ 初級: クラスとオブジェクト (2)

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

クラスにメンバーが存在しない場合、そのクラスは単に空のクラスと呼ばれます。
空のクラスには何もありませんか? いいえ、記述しないと、どのクラスでも次の 6 つのデフォルトのメンバー関数が自動的に生成されます。
デフォルトのメンバー関数: ユーザーによる明示的な実装を行わずにコンパイラによって生成されたメンバー関数は、デフォルトのメンバー関数と呼ばれます。

class Date {
    
    };

ここに画像の説明を挿入

2. コンストラクター

(1) コンセプト

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

(2) 特長

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

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

(1) 関数名はクラス名と同じです。

(2) 戻り値はありません。

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

(4) コンストラクターはオーバーロードできます。

#include<iostream>
using namespace std;
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:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
 
int main()
{
    
    
 
	Date d1;	//无参写法
	d1.Print();
 
	Date d2(2022, 5, 15);	//含参写法,全缺省函数传3个参数可以
	d2.Print();
 
 
	return 0;
}

ここに画像の説明を挿入

class Date
{
    
    
public:
	//Date()    //不传参写法(不建议这么写)
	//{
    
    
	//	_year = 1;
	//	_month = 1;
	//	_day = 1;
	//}
 
	//Date(int year, int month, int day)    //传参写法(不建议这么写)
	//{
    
    
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
 
	Date(int year = 1, int month = 1, int day = 1)	//全缺省写法是最佳写法,相传几个参数都行
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
 
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
 
int main()
{
    
    
	//Date d1(); // 无参不能这么写,编译器无法区分函数声明还是类的定义
	Date d1;	//无参写法
	d1.Print();
 
	Date d2(2022, 5, 15);	//含参写法,全缺省函数传3个参数可以
	d2.Print();
 
	Date d3(2022);    //全缺省函数传1个参数可以
	d3.Print();
 
	Date d4(2022, 10);    //全缺省函数传2个参数可以
	d4.Print();
 
	return 0;
}

オブジェクトが引数なしのコンストラクターを通じて作成された場合、オブジェクトの後にかっこを続ける必要はありません。そうでない場合、オブジェクトは関数宣言になります。
次のコードの関数: d3 関数が宣言されています。この関数はパラメーターを持たず、日付型のオブジェクトを返します。
警告 C4930: "Date d3(void)": プロトタイプ関数が呼び出されません (意図的に変数で定義されましたか?)

(5) クラス内に明示的に定義されたコンストラクタがない場合、C++ コンパイラはパラメータなしのデフォルト コンストラクタを自動的に生成しますが、ユーザーがコンパイラを明示的に定義すると、それ以降は生成されなくなります。

1. 組み込み型/基本型: int/char/double/pointer...
2. カスタム型: 型オブジェクトを定義するクラス/構造体
3. デフォルトで生成されるコンストラクターは組み込み型のメンバー変数を処理しません。型メンバー変数が処理されます

class Date{
    
    
public:
	 如果用户显式定义了构造函数,编译器将不再生成
	//Date(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;
	return 0;
}

上記のプログラムは正常に実行されます。

class Date{
    
    
public:
	// 如果用户显式定义了构造函数,编译器将不再生成
	Date(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;
	return 0;
}

1. Date クラスのコンストラクターをマスクした後、コンパイラーはパラメーターのないデフォルトのコンストラクターを生成するため、コードをコンパイルできます。
2. Date クラスのコンストラクターを手放すと、コードはコンパイルに失敗します。コンストラクターが明示的に定義されると、コンパイラーはそれを生成しなくなるからです。
3. 引数のないコンストラクター。リリース後にエラーが報告されます: エラー C2512: “Date”: 使用可能な適切なデフォルト コンストラクターがありません。

コンストラクタを自分で設計しない場合、コンパイラが自らコンストラクタを生成することになりますが、コンパイラが生成したコンストラクタがどのような動作をするかを見てみましょう。

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

ここに画像の説明を挿入

コンストラクターが実装されていない場合、コンパイラーはデフォルトのコンストラクターを生成します。しかし、デフォルトのコンストラクターは役に立たないようです? d オブジェクトはコンパイラによって生成されたデフォルトのコンストラクターを呼び出しますが、d オブジェクト _year/_month/_day は依然としてランダムな値です。つまり、コンパイラによって生成されるデフォルトのコンストラクタはここでは役に立たないということですか??
回答: C++ では、型を組み込み型 (基本型) とカスタム型に分類します。組み込み型は、int/char... など、言語によって提供されるデータ型です。カスタム型は、class/struct/union などを使用して自分たちで定義する型です。次のプログラムを見ると、コンパイラーによって生成されるデフォルトのコンストラクターは、カスタム型メンバー _t で呼び出されるデフォルトのメンバー関数であることがわかります。

class Time
{
    
    
public:
	Time()
	{
    
    
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
 
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
public:
	void Print()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
	
};
int main()
{
    
    
	Date d;
	d.Print();
	return 0;
}

ここに画像の説明を挿入
ここでは「Print()」が出力されます。これは、コンパイラーが _t メンバー変数のコンストラクターを呼び出すことを意味します。これは、d オブジェクト内でコンストラクターを独自に実装せず、コンパイラーによって実装されるコンストラクターのみがメンバーを処理することを意味します。カスタム型の変数 (対応するコンストラクターを呼び出します)。組み込み型は処理されません。

上記の結果については、疑問を持つ必要があります。コンストラクターを実装しない場合、手動でコンストラクターを実装する以外に、組み込み型のメンバー変数を初期化する方法はありますか?

C++11 では、組み込み型のメンバーが初期化されないという欠陥に対処するパッチが適用されました。つまり、クラスで宣言されたときに組み込み型のメンバー変数にデフォルト値を与えることができます。次のコードのように:

class Date
{
    
    
public:
	void Print()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	
private:
	// 基本类型(内置类型)
	int _year = 2022;
	int _month = 2;
	int _day = 5;
};
int main()
{
    
    
	Date d;
	d.Print();
	return 0;
}

ここに画像の説明を挿入

3. コンストラクターに関する質問

コンストラクターに関する次の説明のうち、正しいものはどれですか ( )

A. コンストラクターは戻り値の型を宣言できます。

B. コンストラクターは private で変更できません

C. コンストラクターはクラスと同じ名前でなければなりません

D. コンストラクターはパラメーターを受け取ることができません

回答分析:

A. コンストラクターは、void 型を含む戻り値を持つことはできません。

B. コンストラクターはプライベートにすることができますが、その後オブジェクトを直接インスタンス化することはできません

C. 必要です

D. コンストラクターはパラメーターを取るだけでなく、複数のコンストラクターを使用してオーバーロードを形成することもできます

そこでCを選択します

(6) デフォルト コンストラクター: パラメーターのないコンストラクターと完全なデフォルト コンストラクターは両方ともデフォルト コンストラクターと呼ばれ、デフォルト コンストラクターは 1 つだけです。

class Date
{
    
    
public:
	Date()
	{
    
    
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
// 以下测试函数能通过编译吗?
int main(){
    
    
{
    
    
	Date d1;
}

(6) デフォルト コンストラクター: パラメーターのないコンストラクターと完全なデフォルト コンストラクターは両方ともデフォルト コンストラクターと呼ばれ、デフォルト コンストラクターは 1 つだけです。

引数のないコンストラクター、 完全なデフォルト コンストラクター、およびデフォルトでコンパイラーによって生成されるように記述されていないコンストラクターはすべて、デフォルト コンストラクターと見なすことができます。したがって、オブジェクトがインスタンス化されるとき、コンパイラーはどのコンストラクターを呼び出すかを区別できません。そのため、デフォルトのコンストラクターは 1 つのコンストラクターのみに関連付けることができます。

d1 オブジェクトに指定する実際のパラメータのリストは次のとおりです。

class Date{
    
    
public:
	Date(){
    
    
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, int month = 1, int day = 1){
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
// 以下测试函数能通过编译吗?
int main(){
    
    
	//给出实际参数
	Date d1(10,200,300);
}

この時点で、再度正常にコンパイルできるようになります。パラメータを記述するときに、コンパイラの呼び出しコンストラクターも指定します。

3. デストラクター

Leetcode の効果的な括弧の問題をまだ覚えていますか? 私たちはスタック構造を使用して括弧を照合します。

その中のメインロジックコードは次のとおりです。

bool isValid(char * s){
    
    
    ST* stact =StactInit();
    while(*s)
    {
    
    
        if(*s=='('||*s=='{'||*s=='[')
        {
    
    
            StactPushBank(stact,*s);
            s++;
        }
        else
        {
    
    
            if(StactEmpty(stact))
            {
    
    
                StactDestory(stact);
                return false;
            }
            char ch=StactTop(stact);
            StactPopBank(stact);
            if((ch=='(' && *s==')')||
               (ch=='[' && *s==']')||
                ch=='{' && *s=='}')
                {
    
    
                    s++;
                }
                else
                {
    
    
                    StactDestory(stact);
                    return false;
                }
        }
    }
    if(!StactEmpty(stact))
    {
    
    
        StactDestory(stact);
        return false;
    }
    StactDestory(stact);
    return true;
}

起こり得るさまざまな終了状況を十分に考慮する必要があるため、各終了の前にスタックを破壊するのは非常に不快です。

C++ では、この問題はデストラクターによって非常にうまく解決されます。

(1) コンセプト

デストラクター: コンストラクターの機能とは異なり、デストラクターはオブジェクト自体の破棄を完了しません。ローカル オブジェクトの破棄はコンパイラーによって行われます。オブジェクトが破棄されると、自動的にデストラクターが呼び出され、オブジェクト内のリソースのクリーンアップが完了します。

(2) 特徴

デストラクターは、次のような特性を持つ特別なメンバー関数です。

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

2. パラメータも戻り値の型もありません。

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

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

typedef int DataType;
class Stack
{
    
    
public:
	Stack(size_t capacity = 3)
	{
    
    
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
    
    
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
    
    
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	// 其他方法...
 
	//析构函数
	~Stack()
	{
    
    
		if (_array)
		{
    
    
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
	
private:
	DataType* _array;
	int _capacity;
	int _size;
};

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

class Time
{
    
    
public:
	~Time()
	{
    
    
		cout << "Time()" << endl;
	}
private:
	int _hour=0;
	int _minute=0;
	int _second=0;
};
class Date
{
    
    
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
    
    
	Date d();
	return 0;
}

ここに画像の説明を挿入
プログラムが実行されると、次の出力が表示されます。 ~Time() は、
メイン メソッドで Time クラスのオブジェクトを直接作成しません。なぜ最後に Time クラスのデストラクターを呼び出すのですか?

理由:
1. Date オブジェクト d は main メソッドで作成され、d には 4 つのメンバー変数が含まれており、そのうち _year、_month、および _day は組み込み型メンバーであり、破棄時にリソースのクリーニングが必要なく、最終的にシステムが破棄されるためです。記憶を直接取り戻すそれだけです。
2. _t は Time クラスのオブジェクトなので、d が破棄されると、その中に含まれる Time クラスの _t オブジェクトも破棄される必要があるため、Time クラスのデストラクターを呼び出す必要があります。
3. ただし: Time クラスのデストラクタを main 関数内で直接呼び出すことはできません。実際に解放されるのは Date クラスのオブジェクトなので、コンパイラは Date クラスのデストラクタを呼び出します。Date が明示的に指定されていない場合は、 Date クラスはデフォルトのデストラクターを生成します。その目的は、その内部の Time クラスのデストラクターを呼び出すことです。つまり、Date オブジェクトが破棄されると、その内部の各カスタム オブジェクトが確実にデストラクターを呼び出すことができるようにする必要があります。正しく破棄され、メイン関数 Time クラス デストラクターで直接呼び出されるのではなく、コンパイラーによって生成された Date クラスのデフォルト デストラクターを明示的に呼び出します。
4. 注: どのクラスのコンストラクタを呼び出すクラスのオブジェクトを作成し、そのクラスのデストラクタを呼び出すにはそのクラスのオブジェクトを破棄します。

6. クラス内にリソース アプリケーションが存在しない場合はデストラクタを記述できず、Date
クラスなどのコンパイラによって生成されるデフォルトのデストラクタを直接使用する必要がありますが、リソース アプリケーションがある場合はそれを記述する必要があります。 、および Space に適用した正しい free() です。そうしないと、Stack クラスなどのメモリ リークが発生します。

4. コンストラクターのコピー

2 つの同一のオブジェクトをインスタンス化する必要がある場合は、オブジェクトのコピー コンストラクターを呼び出す必要があります。
ここに画像の説明を挿入

(1) コンセプト

C++ でオブジェクトをコピーする場合は、対応するコピー コンストラクターを呼び出すことしかできません。

一般概念: 新しいオブジェクトを作成するときは、既存のオブジェクトを新しいオブジェクトに完全にコピーします: 日付 d2(d1)

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

(2) 特徴

コピー コンストラクターも、次の特性を持つ特別なメンバー関数です。

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

2. コピー コンストラクターのパラメーターは 1 つだけで、クラス型オブジェクトへの参照である必要があります。値渡しメソッドを使用すると、無限の再帰呼び出しが発生するため、コンパイラーは直接エラーを報告します。

コピー コンストラクターのパラメーターは 1 つだけであり、クラス型のオブジェクトへの参照である必要があります。値の場合、関数の無限再帰呼び出しが発生します。
理由: 関数が値によって呼び出される場合、仮パラメータは実際のパラメータの一時コピーであることがわかっています。パラメータがクラス型の場合、対応するコピー コンストラクタを呼び出す必要があり、コピー コンストラクタへの呼び出しは渡される必要があります。クラスの Type パラメーターですが、コピー コンストラクターも呼び出します ~~~~ **
ここに画像の説明を挿入

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

class Time
{
    
    
public:
	Time()
	{
    
    
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	//拷贝构造函数
	Time(const Time& t)
	{
    
    
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
public:
	void show()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
    
    
	Date d1;
 
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	d1.show();
	d2.show();
	return 0;
}

ここに画像の説明を挿入

説明: Date d2 (d1): 既存の d1 を使用して d2 をコピーおよび構築します。ここで Date クラスのコピー コンストラクターが呼び出されます。コピー コンストラクターが明示的に定義されていない場合、コンパイラーは Date のデフォルトのコピー コンストラクターを生成します。クラス関数。出力「Time::Time(const Time&)」は、カスタム型がそのコピー コンストラクターを呼び出すことによってコピーされたことを証明しています;
注: これもコピー構造です。
日付 d2 = d1;

注: コンパイラによって生成されるデフォルトのコピー コンストラクターでは、組み込み型はバイト モードで直接コピーされますが、カスタム型はコピー コンストラクターを呼び出すことによってコピーされます。

4. コンパイラによって生成されるデフォルトのコピー コンストラクターは、すでにバイト順の値をコピーできますが、自分で明示的に実装する必要がありますか? もちろん Date クラスのようなクラスは不要です。以下のクラスはどうでしょうか?検証してみてはいかがでしょうか?

5. コピー コンストラクターの一般的な呼び出しシナリオ:

  • 既存のオブジェクトを使用して新しいオブジェクトを作成する
  • 関数パラメータの型はクラス型オブジェクトです
  • 関数の戻り値の型がクラス型オブジェクトである
class Person {
    
    
public:
	Person() {
    
    
		cout << "无参构造函数!" << endl;
		mAge = 0;
	} 
	Person(int age) {
    
    
		cout << "有参构造函数!" << endl;
		mAge = age;
	} 
	Person(const Person& p) {
    
    
		cout << "拷贝构造函数!" << endl;
		mAge = p.mAge;
	} 
	//析构函数在释放内存之前调用
	~Person() {
    
    
		cout << "析构函数!" << endl;
	}
public:
	int mAge;
};

//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
    
    

	Person man(100); //p对象已经创建完毕
	Person newman(man); //调用拷贝构造函数
	Person newman2 = man; //拷贝构造

	//Person newman3;
	//newman3 = man; //不是调用拷贝构造函数,赋值操作
} 

	//2. 值传递的方式给函数参数传值
	//相当于Person p1 = p;
void doWork(Person p1) {
    
    }
void test02() {
    
    
	Person p; //无参构造函数
	doWork(p);
}

//3. 以值方式返回局部对象
Person doWork2()
{
    
    
	Person p1;
	cout << (int *)&p1 << endl;
	return p1;
}

void test03()
{
    
    
	Person p = doWork2();
	cout << (int *)&p << endl;
} 
int main() {
    
    
	//test01();
	//test02();
	test03();

	system("pause");
	return 0;
}

(3) ディープコピーとシャローコピー

  1. 浅いコピー: 単純な割り当てコピー
  2. ディープコピー: ヒープ領域のスペースを再申請し、コピー操作を実行します。

例:

class Person {
    
    
public:
	//无参(默认)构造函数
	Person() {
    
    
		cout << "无参构造函数!" << endl;
	} 
	//有参构造函数
	Person(int age ,int height) {
    
    
		cout << "有参构造函数!" << endl;
		m_age = age;
		m_height = new int(height);
	} 
	//拷贝构造函数
	Person(const Person& p) {
    
    
		cout << "拷贝构造函数!" << endl;
		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_age = p.m_age;
		m_height = new int(*p.m_height);
} 
	//析构函数
	~Person() {
    
    
		cout << "析构函数!" << endl;
		if (m_height != NULL)
		{
    
    
			delete m_height;
		}
	}
public:
	int m_age;
	int* m_height;
};

void test01()
{
    
    
	Person p1(18, 180);
	Person p2(p1);
	
	cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;

	cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
} 
int main() {
    
    
	test01();
	
	system("pause");
	
	return 0;
}

概要: 属性がヒープ領域で開かれている場合は、浅いコピーによって引き起こされる問題を防ぐために、コピー コンストラクターを自分で提供する必要があります。

5. 演算子のオーバーロード

C++ では、コードの可読性を高めるために演算子のオーバーロードが導入されています。演算子のオーバーロードは、特別な関数名を持つ関数であり、戻り値の型、
関数名、およびパラメーターのリストも存在します。戻り値の型とパラメーターのリストは、通常の関数と似ています。

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

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

新しい日付を取得するために日付と日数を実装する必要がある場合、以前の考え方によれば、関数を実装することになりますが、演算子のオーバーロードを使用すると、より直感的かつ明確になります。

class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void show()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	
	int _year;
	int _month;
	int _day;
};
//函数判断日期相等
bool DateEqual(const Date& d1, const Date& d2)
{
    
    
	return d1._year == d2._year &&
		d1._month == d2._month &&
		d1._day == d2._day;
}
//运算符重载的定义 使用关键字 operator 【运算符】
bool operator==(const Date& d1 ,const Date& d2)
{
    
    
	return d1._year == d2._year &&
		   d1._month == d2._month && 
		   d1._day == d2._day;
}
int main()
{
    
    
	Date d2(2022, 4, 23);
	Date d1(2022, 1, 13);
	//函数使用
	if (DateEqual(d1, d2))
		cout << "true" << endl;
	else
		cout << "false" << endl;
 
	//运算符重载的使用
	if (d1 == d2)
		cout << "true" << endl;
	else
		cout << "false" << endl;
	
	return 0;
}

ここに画像の説明を挿入
しかし、通常、メンバー変数はプライベートです。プライベートの場合、直接アクセスすることはできません。これは、get 関数を追加するか、演算子をオーバーロードした関数をクラスに記述してメンバー関数にすることで解決できます。ここでは直接カプセル化されています。クラスに:

class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
 
    // bool operator==(Date* this, const Date& date)
    // 这里需要注意的是,左操作数是this,指向调用函数的对象
	bool operator ==(const Date& date)
	{
    
    
		return _year == date._year &&
               _month == date._month && 
               _day == date._day;
	}
            
	void show()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d2(2022, 1, 13);
	Date d1(2022, 1, 13);
	//运算符重载的使用
	if (d1 == d2)
		cout << "true" << endl;
	else
		cout << "false" << endl;
	
	return 0;
}

注: 演算子のオーバーロードがメンバー メンバー関数として記述される場合、オーバーロードされた演算子は 1 つのパラメーターのみを記述する必要があります。これは、1 つのパラメーターがオブジェクト自体 (メンバー関数リストに隠されている this ポインター) であるためです。

6. 代入演算子のオーバーロード形式:

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

2. 戻り値のタイプ: T&、戻り参照により戻りの効率が向上します。戻り値の目的は、値をそれ自体に割り当てるかどうかを検出するための連続代入をサポートすることです。

3. *this: を返して、継続的な代入の意味を複合化します。

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
    //赋值运算符重载
	Date& operator=(const Date& d)
	{
    
    
        //如果不是自身赋值,即d1=d1,才需要成员变量对用赋值。
		if (this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	void show()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date date1;
	Date date2(2023, 2, 7);
	date1.show();
	//因为有返回值就可以实现链式访问
	(date1 = date2).show();
	return 0;
}

ここに画像の説明を挿入
4. 代入演算子はクラスのメンバー関数としてのみオーバーロードでき、グローバル関数としてオーバーロードできません。

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;
}

代入演算子はグローバル関数にオーバーロードされます。グローバル関数にオーバーロードする場合、このポインターは存在しないことに注意してください。2 つのパラメーターを指定する必要があります。

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

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

class Time
{
    
    
public:
	Time()
	{
    
    
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
    
    
		if (this != &t)
		{
    
    
			cout << "Time:operator=" << endl;
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
	
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void show()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	// 基本类型(内置类型)
	int _year ;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};
int main()
{
    
    
	Date d1;
	d1.show();
	Date d2(2023,2,7);
	d1 = d2;
	d1.show();
	return 0;
}

ここに画像の説明を挿入
注: 組み込み型のメンバー変数は直接割り当てられますが、カスタム型のメンバー変数は、割り当てを完了するために対応するクラスの代入演算子オーバーロードを呼び出す必要があります。
しかし、これはスペースアプリケーションには必要であり、コンパイラ自体によって生成される代入演算子のオーバーロードだけに依存することはできません。

class A
{
    
    
public:
	A()
	{
    
    
		a = 10;
		arr = (int*)malloc(sizeof(int) * a);
		for (int i = 0; i < a; i++)
		{
    
    
			arr[i] = i;
		}
	}
	void show()
	{
    
    
		for (int i = 0; i < a; i++)
		{
    
    
			cout << arr[i] << " ";
		}
		cout << endl;
	}
 
	int* arr;
	int a;
};
int main()
{
    
    
	A a;
	A b;
	a.show();
	b.show();
	b = a;
	//修改b对象的arr数组
	for (int i = 0; i < b.a; i++)
	{
    
    
		b.arr[i] = 0;
	}
	//观察两个对象中arr的元素
	b.show();
	a.show();
	return 0;
}

ここに画像の説明を挿入
b オブジェクトの arr 配列を変更しただけですが、それに応じて a オブジェクトの arr 配列も変更されました。

具体的な状況は次のとおりです。
ここに画像の説明を挿入

7. Pre-++ および post-++ のオーバーロード

pre-++ と post-++ の唯一の違いは、戻り値が異なることであることがわかっています。Pre-++ は ++ の後の結果を返し、pre-++ は ++ の後の結果を返します。次に、演算子がオーバーロードされた後、pre-++ と post-++ の機能もサポートする必要があります。

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	// 前置++:返回+1之后的结果
	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
	Date& operator++()
	{
    
    
		_day += 1;
		return *this;
	}
	// 后置++:
	// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
	// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
	// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
	Date operator++(int)
	{
    
    
		Date temp(*this);
		_day += 1;
		return temp;
	}
	void show()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
 
int main()
{
    
    
	Date d1(2022, 10, 1);
	d1.show();
	Date d2 = d1++;
	d2.show();
	d1.show();
 
	d2 = ++d1;
	d1.show();
	d2.show();
 
	return 0;
}

ここに画像の説明を挿入

8.const メンバー

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

ここに画像の説明を挿入

class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void ConstPrint() 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(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.ConstPrint();
	return 0;
}

ここに画像の説明を挿入

次の質問について考えてみましょう。
1. const オブジェクトは非 const メンバー関数を呼び出すことができますか?
回答: 非 const メンバー関数の this はconst によって変更されないため、const オブジェクトは非 const メンバー関数を呼び出すことはできません。const メンバー関数が呼び出された場合、それは増幅されたアクセス許可と同等です。

2. 非 const オブジェクトは const メンバー関数を呼び出すことができますか?
回答: const によって変更されたメンバー関数は実際にこれを変更するため、非 const オブジェクトは const メンバー関数を呼び出すことができます。これはアクセス許可を狭めることと同等であり、許可されます。

3. const メンバー関数は他の非 const メンバー関数を呼び出すことができますか?
回答: const メンバー関数は他の非 const メンバー関数を呼び出すことができません。これは特権増幅の一例でもあります。

4.他の const メンバー関数を非 const メンバー関数内で呼び出すことはできますか?
回答: 非 const メンバー関数は他の const メンバー関数を呼び出すことができます。これは特権削減の一例でもあります。

9. アドレス演算子と定数アドレス演算子のオーバーロード

未完

10. cout 出力クラス情報

未完

11.Date クラスの実装

未完

おすすめ

転載: blog.csdn.net/weixin_47952981/article/details/129071677