[C++] クラスとオブジェクト (3) 演算子のオーバーロード 代入のオーバーロード アドレスと Const のアドレッシング 演算子のオーバーロード

序文

この章では、前の章を引き継いで、クラスのデフォルトのメンバー関数、代入オーバーロードアドレスのオーバーロード、および const アドレス演算子のオーバーロードについて理解します。ただし、残りの 3 つのデフォルトのメンバー関数について説明する前に、まず演算子のオーバーロード

を理解する必要があります。.、代入オーバーロード、アドレス取得オーバーロード、および const アドレス取得演算子のオーバーロードは、実際には演算子のオーバーロードの一部であるためです。


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

1. 演算子のオーバーロードの概念

C++ の組み込み型には使用できる演算子が多数ありますが、これらの演算子はカスタム型には使用できず、演算子の関数と同様の関数を作成してカスタム型で呼び出すことしかできません。
例えば:

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//给一个给日期加num天,不修改原始值
	Date Add(int num)
	{
    
    
		//......
	}
	//给一个日期加num天,并修改原始值
	Date AddEqual(int num)
	{
    
    
		//.....
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	int a = 10;
	a + 10;
	a += 10;
	Date d1;
	d1.Add(10);
	//d1+10;    //想写成这样,这样更直观方便,可是编译器不允许啊啊啊
	d1.AddEqual(10);
	//d1+=10;   //想写成这样,这样更直观方便,可是编译器不允许啊啊啊
	return 0;
}

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

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

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

演算子のオーバーロードは定義を見ただけでは理解しにくいので、まずはコードを見て、コードを解析しながら定義や注意点を理解しましょう。

// 全局的operator==
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
//private:
	int _year;
	int _month;
	int _day;
};
// 这里会发现:运算符重载成全局的 就需要成员变量是公有的,不然下面的函数无法访问到成员变量,
// 那么问题来了,封装性如何保证?
//这点我们现在还没有办法解决,所以我们先暂时将成员设定为公有。
//还有一种办法就是把它写进类中,变成成员函数。(暂时不用此种方法)

//判断两个对象是否相同
bool operator==(const Date& d1, const Date& d2)
{
    
    
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

void Test()
{
    
    
	Date d1(2023, 2, 12);
	Date d2(2023, 2, 12);
	cout << operator==(d1, d2) << endl;//判断两个对象是否相同,第一种使用方法,直接调用函数
	cout << (d1 == d2) << endl;//判断两个对象是否相同,第二种使用方法,使用重载后的运算符
	//此处必须加括号,运算符优先级:<< 大于 ==
}

このコードを注意深く読んだ後は、演算子のオーバーロードについてはすでにある程度理解していると思います。つまり、カスタム型とほぼ同等の演算子は、実際には手動で作成する関数ですが、演算子のオーバーロード後は、構築された関数を使用できます。 -in 型の演算子はそのように関数を使用します。

2. 演算子の過負荷に対する注意事項

  • 他のシンボル(C++ に存在しない演算子) を連結して新しい演算子を作成することはできません(operator@ など)。
  • オーバーロードされた演算子にはクラス型パラメーターが必要です(演算子のオーバーロードは主にカスタム型で組み込み型のような演算子を使用できるようにするためです)
  • 組み込み型に使用される演算子の意味は変更できません。たとえば、組み込み整数 + はその意味を変更できません (演算子のオーバーロードは主にカスタム型で組み込み型、組み込み型などの演算子を使用できるようにするためです)演算子のオーバーロードは必要ありません)
  • クラス メンバー関数としてオーバーロードされると、メンバー関数の最初のパラメーターが非表示になるため、その仮パラメーターはオペランドの数より 1 少ないように見えます。this
  • .* :: sizeof ?: .上記の 5 つの演算子はオーバーロードできないことに注意してください。これは、記述式の多肢選択問題でよく出てきます。

2 番目に、演算子のオーバーロードの特殊なケース

1. フロント++とポスト++タイプ

演算子のオーバーロードについての上記の説明の後、次のような Date クラスの他の演算子に対して演算子のオーバーロードを記述することもできると思います。

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator+(int num)
	{
    
    
		//......
	}
	Date& operator-(Date& d)
	{
    
    
		//......
	}
	Date& operator+=(int num)
	{
    
    
		//......
	}
	//......
	//其实这么多运算符重载我们可以一一实现,也可以实现一到两个,然后让其他运算符重载
private:
	int _year;
	int _month;
	int _day;
};

しかし、C 言語を学習しているときに、 pre、この種の演算子のオーバーロードはどうすればよいでしょうか?
まず、関数の外にパラメータ リストを記述します。

//前置++
	Date& operator++()
	{
    
    

	}
//后置++
	Date operator++()
	{
    
    

	}

それらの関数の外部パラメーターのリストがまったく同じであるため、関数のオーバーロードをまったく構成できず、pre-++ と post-++ のいずれか 1 つしか実装できないことがわかりました。
この問題を解決するために、C++ では、この種の演算子がオーバーロードされる場合に、この種の前置詞 ++、後置詞 ++、前置詞 - -、後置詞 - - を特殊化します。ルールは次のとおりです。

  1. Prepend++ は正常に実装されます
  2. Postfix ++ は、演算子がオーバーロードされると追加の int パラメータを渡します (このパラメータは関数のオーバーロードを実現するためのプレースホルダ専用であり、他の効果はなく、実際にパラメータを渡す必要はありません)。

(- - 上記のルールと同様)

したがって、これを正しく実装するには、次のように実装する必要があります。

//前置++
	Date& operator++()
	{
    
    
		*this += 1;//假设 +=我们已经运算符重载过了
		return this;
	}
//后置++
	Date operator++(int a)//形参名可以不写,直接写成 int,调用时也不需要传递参数
	{
    
    
		Date tmp(this); //会去调用拷贝构造 
		*this += 1; //假设 +=我们已经运算符重载过了
		return tmp;
	}

2. ストリーム挿入《ストリーム抽出》演算子

演算子のオーバーロードを説明したときに、 << は実際にはシフト演算子であると言いましたが、これは C++ ではストリーム挿入演算子としてオーバーロードされます。演算子のオーバーロードについて学習したので、この問題について説明します。

まず、私たちがよく使う とcoutは、実際にはそれぞれ型オブジェクトと型オブジェクトcinです。これら 2 つの型は、それぞれ2 つの C++ 標準ライブラリの標準名前空間にあり、次に、私たちがよく使う C++ 標準ライブラリには とヘッダー ファイルが含まれています。したがって、 またはを使用する場合は、ヘッダー ファイルをインクルードし標準の名前空間の内容をグローバルに展開する必要があります。ostreamistream<ostream><istream>std<iostream><istream><ostream>cincout<iostream>using namespeace std;

ここに画像の説明を挿入
ストリーム抽出<<演算子のオーバーロードをもう一度見てみましょう。
ここに画像の説明を挿入

C++ のストリーム抽出演算子には、<<演算子のオーバーロード + 関数のオーバーロードが多数あることがわかります。組み込み型は自由<<に使用できますが、カスタム型は独自のものであり、C++ では使用できないため、カスタム型を使用する方法はありません。事前にどのようなカスタム タイプを書きたいかを予測し、カスタム タイプに対して演算子のオーバーロードを実行します。そのため、カスタム タイプをストリームで抽出したいため、カスタム タイプの演算子のオーバーロードを手動で実装する必要があり<<ます<<
ここに画像の説明を挿入


まずは最初の書き方を見てみましょう

//<< 运算符重载
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	ostream& operator<< (ostream&out)
	{
    
    
		out << _year << "年" << _month << "月" << _day << "日" << endl;
		return out;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1;
	int a = 10;
	cout << a << endl;//C++对自定义类型已经实现了 << 的运算符重载
	cout << d1;  
	return 0;
}

ここに画像の説明を挿入
コンパイルに失敗しました。注意深く確認したところ、67 行目を逆に書いていることがわかりました。次のように書く必要があります

d1 << cout;           //第一种写法
d1.operator<<(cout);  //第二种写法

演算子がオーバーロードされると、最初のパラメーターが左側のオペランドになり、2 番目のオペランドが右側のオペランドになるためです (最初のパラメーターは隠しポインターであることに注意してくださいthis)。

正しく記述したら、実行してみましょう。

ここに画像の説明を挿入
問題はありませんが、このように書くのはあまりにも倒錯的であり、それを使用する私たちの直感に違反します。

cout << d1;

次に、このストリームを<<関数の外側のオーバーロードされた定義に挿入する必要があります。クラス内でデフォルトで渡される最初のパラメーターはthisポインターであり、目的を達成することはできないからです。
したがって、外部で次のように定義します。

#include<iostream>
using namespace std;
class Date
{
    
    
	friend ostream& operator<<(ostream& out, 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& out, Date& d)
{
    
    
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

int main()
{
    
    
	Date d1;
	int a = 10;
	cout << a << endl;//C++对自定义类型已经实现了 << 的运算符重载
	cout << d1;
	return 0;
}

もう 1 つの質問は、<<演算子のオーバーロードを記述するときに参照を使用するのはなぜですか?
ここに画像の説明を挿入
注:<<左結合です。
戻り値を参照することで複数のカスタムタイプの連続印刷を実現!

//假如d1 d2 d3 都是Date类型
cout << d1 << d2 << d3 << endl;

4. 代入演算子のオーバーロード (デフォルトのメンバー関数)

1. はじめに

まず使用シナリオを見てみましょう。初期化されたカスタム タイプのデータを別の初期化されたカスタム タイプに割り当てたい場合はどうなるでしょうか(オブジェクトの初期化時の割り当てではなく、オブジェクトの初期化時の割り当てではコピー構築が使用されます)。 ?
以下のコードを見てください。

//赋值重载
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << _year << "年" << _month << "月" << _month << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1;
	Date d2 = d1;//或者Date d2(d1)  会调用默认生成的拷贝构造,对象初始化时赋值用的是拷贝构造
	Date d3;
	d3 = d1;//我们没有实现Date类的运算符 = 的赋值重载,所以会调用默认生成的赋值重载
	        //最后d3里面的数据与d1一样
}

2. 特長

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

注: 組み込み型のメンバー変数は直接割り当てられますが、カスタム型のメンバー変数は、割り当てを完了するために、対応するクラスの割り当て (=) 演算子オーバーロードを呼び出す必要があります。
コード例:
上記のコードなど

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

パラメータの型: const T&、参照を渡すとパラメータの受け渡しの効率が向上します
戻り値の型: T&、参照を返すと戻りの効率が向上します、戻り値の目的は、それ自体に代入するかどうかを検出するための連続代入をサポートすることです return
*this : 継続的な割り当てに従うことの意味

#include<iostream>
using namespace std;
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)
	{
    
    
		if (this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1(2023,2,12);
	Date d2;
	d2 = d1;
	return 0;
}

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

3. 代入演算子はクラスのメンバー関数としてのみオーバーロードでき、グローバル関数としてオーバーロードできません。

#include<iostream>
using namespace std;
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;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

ここに画像の説明を挿入


原因:赋值运算符如果不显式实现,编译器会生成一个默认的。
此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数

ここに画像の説明を挿入

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

コピー コンストラクターと同様に、次のことを考えてみましょう。コンパイラによって生成されるデフォルトの代入演算子のオーバーロード関数はバイト オーダーの値のコピーをすでに完了しているため、組み込み型に対して自分で実装する必要があるのでしょうか。
コピー構築と同様に、機能 4 もコピー オーバーロード関数を書くか書かないかの判断条件です。
例えば:

// 这里会发现下面的程序会崩溃掉,编译器生成的是浅拷贝,导致我们析构了两次空间,
//这里就需要我们以后讲的深拷贝去解决。
#include<iostream>
using namespace std;
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;
}

ここに画像の説明を挿入
ここで、 6 つのデフォルトのメンバー関数のうちの 4 つ目であるcopy overloading が完了しました。
コピーのオーバーロードは、実際には演算子のオーバーロードの一部です。

5. Take address および const take address 演算子のオーバーロード

1. アドレス演算子のオーバーロード (デフォルトのメンバー関数)

まずコードを見て、もう一度考えてみましょう。

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	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;
	cout << &d1 << endl;
}

ここに画像の説明を挿入
結果は私たちの期待どおりであり、何も考える必要はないと感じるかもしれません。ただし、カスタム型の場合、組み込み型のような演算子は使用できませんが、Date クラスのオブジェクト d1 にはアドレス演算子を使用し、演算子の
オーバーロードは実装しませんが、 と を使用できますうまくいきました。なぜ?&&&

これは、5 番目のデフォルトのメンバー関数:アドレス演算子のオーバーロード、つまり、これを書かなければ、コンパイラが自動的に生成するためで、その機能はカスタム型オブジェクトのアドレスを認識するのに役立ちます。

アドレスのオーバーロードの手動実装

通常、この関数は自分で作成せず、コンパイラーに自動的に生成させます。では、この関数を自分で実装したらどうなるでしょうか?

実装コードは次のとおりです。

//取地址重载函数
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//取地址重载
	Date* operator&()
	{
    
    
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1;
	cout << &d1 << endl;
}

ここに画像の説明を挿入

2、const アドレス演算子のオーバーロード (デフォルトのメンバー関数)

通常、 object を定義するときにそれを追加しませんconst。では、constそれをオブジェクトに追加するとどうなるでしょうか?
次に、別のコードを見てみましょう。

#include<iostream>
using namespace std;
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;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
    
    
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
	return 0;
}

コンパイルに失敗したことがわかりました。
ここに画像の説明を挿入
オブジェクトを追加したconst後で関数の呼び出しに失敗したのはなぜですか? エラーが追加される一般的な理由からconst、権限を拡大する必要があると考えるのは難しくありません。

thisポインターの種類を覚えていますか? 答えは「* constタイプ」です。ここでは、変更されたオブジェクトのアドレスをどの型で取得するDate * const
必要がありますか? const答えは次のとおりですconst *const Date*
ここでは、2 つの型が一致せず、constオブジェクトの変更後に内容を変更できないため、thisポインタの型を変更して*前にますconst
ここに画像の説明を挿入

しかし、thisポインタはコンパイラによって渡されるため、追加することができませんconst。どうすればよいでしょうか? ここでは、C++ コンパイラが特別な処理を行っているため、関数括弧の後にそれを追加してポインタを変更する
必要があります。constthis

正しいコード:

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	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(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
	return 0;
}

ここに画像の説明を挿入

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

次の質問について検討してください。

  1. const オブジェクトは非 const メンバー関数を呼び出すことができますか?
    回答: いいえ、thisポインタを渡すときに権限が拡大されます。
  2. 非 const オブジェクトは const メンバー関数を呼び出すことができますか?
    回答: はい、thisポインタを渡すときに権限が制限されます
  3. const メンバー関数は他の非 const メンバー関数を呼び出すことができますか?
    回答: いいえ、thisポインタを渡すときに権限が拡大されます。
  4. 他のconstメンバー関数を非 const メンバー関数で呼び出すことはできますか?
    回答: はい、thisポインタを渡すときに権限が制限されます

Const take アドレスのオーバーロードの手動実装

同様に、前のコードでは、consttype のアドレスを取得したときに&それをオーバーロードしませんでしたが、コンパイラーが自動的にconst address オーバーロードの実現を支援したため、それを使用することができます。

この 2 つは同じではないことに注意してください。2 つの関数は関数のオーバーロードを構成します。

演算子のアドレスのオーバーロード

Date* operator&()             //对非 const 对象取地址

constアドレスのオーバーロード

const Date* operator&()const  //对 const 对象取地址

手動で実装:

//const取地址重载函数
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year=0, int month=0, int day=0)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const
	{
    
    
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	const Date* operator&()const //返回值const Date * 是为了与this 指针保持一致
	{
    
    
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
    
    
	const Date d1;
	cout << &d1 << endl;
	return 0;
}

ここに画像の説明を挿入

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

おすすめ

転載: blog.csdn.net/qq_65207641/article/details/129001702