C++ のクラスとオブジェクト (3) 演算子のオーバーロード、const メンバー関数、日付クラスの実装

序文

        この記事では主に演算子のオーバーロードと const メンバー関数の知識を紹介します. 演算子のオーバーロードはクラスを設計するための重要なツールです. 中でも代入演算子のオーバーロードとアドレス演算子のオーバーロードはクラスのデフォルトのメンバー関数です. 最後に, 比較をベースに実装しますこの記事とクラスとオブジェクトに関する事前の知識について説明します。完全な日付クラス。

この記事の例は主に日付クラスのデモに基づいています。

// 日期类
class Date
{
public:
	// 参数全缺省的构造函数
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 拷贝构造函数
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

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

目次

序文

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

導入

オーバーロードされた代入演算子

アドレス取得リロード

他の演算子のオーバーロードのデモ

+-、+=、-= のオーバーロード

プレフィックス++、--

後置++、--

比較: >、<、>=、<=、==、!=

オーバーロードされた出力演算子<<

オーバーロードされた入力演算子 >>

編集

過負荷[ ]

2つのconstメンバー関数

3 日付クラスの実装


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

導入

オーバーロードされた演算子は、キーワード演算子と演算子記号        で構成される特別な名前を持つ関数です。演算子のオーバーロードにより、演算子をカスタム タイプの操作に拡張できるようになり、コードの可読性が大幅に向上します。

知らせ:

  • 新しい演算子は作成できません。つまり、演算子は既存の演算子をオーバーロードすることしかできません。
  • オーバーロードされた演算子にはクラス型のパラメータが必要です。
  • 組み込み型の演算子は再定義できません。

次の表の演算子はオーバーロードできないことに注意してください:: .* . ? :

演算子のオーバーロード例、日付型 == 演算子のオーバーロード:

// 重载成全局函数
bool operator==(const Date d1, const Date d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

date クラス オブジェクトを比較するために global == をオーバーロードできますが、その場合、date クラスのメンバー変数を public に設定するか、関数がフレンドを通じてプライベート メンバーにアクセスできるようにする必要があります (後で説明します)。カプセル化を実現するために、この記事では、関数パラメーターに暗黙的に含まれる this ポインターに注意しながら、演算子をクラスのメンバー関数に直接オーバーロードします。

// 在类中声明 bool operator == (const Date d);

bool Date::operator==(const Date d) // 定义
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

Date d1, d2;
d1 == d2; // 调用运算符函数
d1.operator==(d2); // 显式调用

オーバーロードされた代入演算子

代入演算子は、 operator=        という関数ですクラスは、代入演算子を介してそのオブジェクトに値を割り当てる方法を制御できます。コピー コンストラクターと同様に、クラスで代入演算子が定義されていない場合は、コンパイラが代入演算子を合成します。

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

理由: クラスで代入演算子を明示的に定義しない場合、コンパイラはグローバル代入演算子と競合するデフォルトの代入演算子を生成するため、代入演算子はクラスのメンバー関数にのみオーバーロードできます。 。

        二項演算子として、代入演算子の左側のオブジェクトは当然 this パラメータにバインドされ、右側のオブジェクトは同じ型のパラメータです。連続代入に準拠するには、代入演算子の戻り値の型を*this にする必要がありますパラメーターと戻り値の型は通常、効率を向上させるための参照です。

日付クラスの代入演算子をオーバーロードします。

// 赋值重载
Date& Date::operator=(const Date& d)
{
	if (this != &d) // 检查是否给自己赋值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	return *this;
}
int main()
{
	Date d1;
	Date d2(2023, 9, 1);
	d1.Print();

	d1 = d2;   // d1.operator(d2)
	d1.Print();
	return 0;
}

操作結果:

コンパイラによってデフォルトで生成される代入演算子のオーバーロードは、デフォルトのコピー コンストラクターに似ており、値をバイトごとにコピーします。組み込み型のメンバー変数には値が直接割り当てられますが、カスタム型のメンバーには代入演算子を呼び出すことによって値が割り当てられます例えば:

class Time
{
public:
	Time()
	{
		_hour = 12;
		_minute = 0;
		_second = 0;
	}

	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 DateTime
{
public:
	DateTime(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
	// 自定义类型成员
	Time _t;
};

int main()
{
	DateTime d1;
	DateTime d2;

	d1 = d2;// 调用默认的赋值运算符函数
	// 内置类型直接赋值
	// d1._year = d2._year;
	// d1._month = d2._month;
	// d1._day = d2._day;

	// d1._t = d2._t; // 调用Time类型的赋值运算符函数
	return 0;
}

日付クラスの場合は、デフォルトの代入演算子関数を使用できます。クラスにリソース管理が含まれる場合は、クラスの代入演算子を自分で実装する必要があります。たとえば、スタック クラスはコピー コンストラクターを参照して、代入演算子のオーバーロードを実装できます。

アドレス取得リロード

        アドレス オーバーロードと const ( const メンバー関数については以下を参照) アドレス オーバーロードはクラスのデフォルトのメンバー関数です。通常、定義する必要はなく、デフォルトでコンパイラによって生成されます。

// 日期类的默认取地址重载
Date* operator&()
{
	return this;
}

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

他の演算子のオーバーロードのデモ

まず、日付の繰り越しを容易にするために GetMonthDay 関数を定義します。

int Date::GetMonthDay(int year, int month)
{
	static int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

	if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)))
		return 29;

	return MonthDay[month];
}

+-、+=、-= のオーバーロード

// 日期+天数
Date& Date::operator+=(int day)
{
	if (day < 0)
		return *this -= (-day);

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;

		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}

	return *this;
}

Date Date::operator+(int day) 
{
	Date tmp(*this);
	tmp += day;  // 复用+=

	return tmp;
}

// 日期-天数
Date& Date::operator-=(int day)
{
	if (day < 0)
		return *this += (-day);

	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day; // 复用-=

	return tmp;
}

プレフィックス++、--

// 日期+1天
Date& Date::operator++()
{
	*this += 1;  // 复用+=
	return *this;// 返回++后的日期
}
// 日期-1天
Date& Date::operator--()
{
	*this -= 1;  // 复用-=
	return *this;// 返回--后的日期
}

後置++、--

接頭辞 ++ および -- と区別するために、C++ では後置 ++ および -- に int 仮パラメータを追加する必要があると規定しています。postfix ++ を明示的に呼び出す場合、 -- int 値 (通常は 0) を渡す必要があります。

Date Date::operator++(int)
{
	Date tmp(*this);

	*this += 1;
	
	return tmp; // 返回++前的值
}

Date Date::operator--(int)
{
	Date tmp(*this);

	*this -= 1;
 
	return tmp; // 返回--前的值
}

Date d1;
d1++;            // 后置++
d1.operator++(0);// 显式调用

比較: >、<、>=、<=、==、!=

bool Date::operator>(const Date& d) 
{
	if (_year > d._year)
		return true;
	else if (_year == d._year && _month > d._month)
		return true;
	else if (_year == d._year && _month == d._month && _day > d._day)
		return true;
	else
		return false;
}

bool Date::operator==(const Date& d) 
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

bool Date::operator>=(const Date& d) 
{
	return *this > d || *this == d; // 复用>,==
}

bool Date::operator<(const Date& d) 
{
	return !(*this >= d); // 复用>=
}

bool Date::operator<=(const Date& d) 
{
	return !(*this > d); // 复用>
}

bool Date::operator!=(const Date& d) 
{
	return !(*this == d); // 复用==
}

オーバーロードされた出力演算子<<

        IO 標準ライブラリは、>> と << を使用して入出力操作を実行します。ライブラリは、以下に示すように、組み込み型バージョンをオーバーロードしています。クラスの >> と << をオーバーロードして、クラスの入出力を実装できます。オブジェクト。

        出力演算子の最初の仮パラメータは、ostream の非定数オブジェクトへの参照です。通常、オブジェクトを出力してもその内容は変更されないため、2 番目の仮パラメータは通常、定数参照です。連続出力をサポートするために、戻り値の型は ostream の仮パラメータです。

たとえば、日付クラスの << をオーバーロードします。

// 在类中声明友元函数	
friend ostream& operator<<(ostream& out, const Date& d);

// 重载日期类的全局<<
ostream& operator<<(ostream& cout, const Date& d)
{
	cout << d._year << "年" << d._month << "月" << d._day << "日";

	return out;
}

このとき、メンバー関数 Print は存在​​する必要はありません。

入力演算子と出力演算子は非メンバー関数である必要があります入力演算子と出力演算子がクラスのメンバー関数である場合、それらの左側のオブジェクトはクラスのオブジェクトのみになることができます。この形式は標準ライブラリと互換性がありません。

日付d;

d << cout; // 演算子<< がクラスのメンバー関数の場合

オーバーロードされた入力演算子 >>

        入力演算子の最初のパラメータは、読み取られるストリーム (iostream オブジェクトなど) の導入であり、2 番目のパラメータは、読み取られる (非 const) オブジェクトへの参照です。通常、戻り値はストリームへの参照です。

たとえば、日付クラス >> をオーバーロードします。

// 类中声明友元函数
friend istream& operator>>(istream& in, Date& d);

// 重载日期类的全局>>
istream& operator>>(istream& cin, Date& d)
{
	in >> d._year >> d._month >> d._day;

	return in;
}

入出力テスト:

int main()
{
	Date d1(2023,1,1);

	cout << d1 << endl;

	cout << "输入日期:";
	cin >> d1;

	cout << d1 << endl;

	return 0;
}

操作結果:

過負荷[ ]

C言語でシーケンステーブルを実装する場合、Print関数を定義するだけでシーケンステーブルの要素を印刷できます。ここで、シーケンス テーブルを設計し、[ ] 演算子をオーバーロードするだけで、シーケンス テーブル データへのアクセスが配列と同じくらい便利で直観的になります。

#include<assert.h>
// 顺序表
struct SeqList
{
public:
	void PushBack(const int& x)
	{
		// ... 扩容
		_a[_size++] = x;
	}

	size_t size() 
	{
		return _size;
	}

	// 读/写
	int& operator[](size_t i)
	{
		assert(i < _size);

		return _a[i];
	}

private:
	// C++11
	int* _a = (int*)malloc(sizeof(int) * 10);
	size_t _size = 0;
	size_t _capacity = 0;
};

int main()
{
	SeqList sl;
	sl.PushBack(1);
	sl.PushBack(2);
	sl.PushBack(3);
	sl.PushBack(4);

	for (size_t i = 0; i < sl.size(); i++)
	{
		sl[i] *= 2; // 修改
		cout << sl[i] << " "; // 读取
	}

	return 0;
}

操作結果:

2つのconstメンバー関数

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

たとえば、date クラスの Print 関数の場合、const 変更されたオブジェクトを出力するには、const 変更されたバージョンをオーバーロードする必要があります。

// 非const修饰
void Date::Print()
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
// const修饰版本
void Date::Print() const
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

Date d1;
const Date d2(2023,9,10);
d1.Print(); // 调用Print()
d2.Print(); // 调用Print() const

実際、const 仮パラメータは非 const 実パラメータを受け取ることができるため、date クラスの Print 関数は const の変更バージョンを保持するだけで済みます。

シーケンス テーブルの場合、const オブジェクトの読み取り専用メンバーを実現するには、const で変更されたoperator[] の戻り値も const で変更する必要があります。

// 只读
const int& SeqList::operator[](size_t i) const
{
	assert(i < _size);

	return _a[i];
}

// 读/写
int& SeqList::operator[](size_t i)
{
	assert(i < _size);

	return _a[i];
}

コストの変更には主に権限の理解が含まれます。次の質問について考えてください。

1. const オブジェクトは非 const メンバー関数を呼び出すことができますか? いいえ
2. 非 const オブジェクトは const メンバー関数を呼び出すことができますか?
3. const メンバー関数内で他の非 const メンバー関数を呼び出すことはできますか? いいえ
4. 他の cosnt メンバー関数を非 cosnt メンバー関数内で呼び出すことはできますか? できる

説明する:

1,3 const オブジェクト/メンバー関数は非 const メンバー関数を呼び出します。つまり、const で変更された実パラメーター (読み取り専用) を非 const 仮パラメーター (読み取り/書き込み) に渡します。これは、権限があり、呼び出すことはできません。

2,4 const オブジェクト/メンバー関数は const メンバー関数を呼び出します。つまり、const で変更されていない実パラメータ (読み取りおよび書き込み) を const 仮パラメータ (読み取り専用) に渡します。これにより、権限が削減され、次のことが可能になります。と呼ばれた。

3 日付クラスの実装

        前の説明で、date クラスのほとんどの機能が実装されましたが、次に、主に演算子関数 (date-date) を追加し、必要なメンバー関数を const で変更する、より完全な date クラスのコードを示します。コードの演習を参照できます。

分離コンパイルを使用し、Date.h ヘッダー ファイルで日付クラスを宣言し、Date.cpp ファイルでクラスのメンバー関数を定義します。

Date.h ファイル

#include<iostream>
using namespace std;

class Date
{
    // 友元声明
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

public:

	// 全缺省的构造函数
	Date(int year = 1970, int month = 1, int day = 1);

	// 拷贝构造函数
	Date(const Date& d);

	// 获取某年某月的天数
	int GetMonthDay(int year, int month);

	// 赋值运算符重载
	Date& operator=(const Date& d);

	// 日期+=天数
	Date& operator+=(int day);

	// 日期+天数
	Date operator+(int day) const;

	// 日期-=天数
	Date& operator-=(int day);

	// 日期-天数
	Date operator-(int day) const;


	// 前置++
	// d++ -> d.operator()
	Date& operator++();

	// 后置++
	// d++ -> d.operator(0)
	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;
};

Date.cpp ファイル

#include"Date.h"

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

	return out;
}

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;

	return in;
}

int Date::GetMonthDay(int year, int month)
{
	static int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

	if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)))
		return 29;

	return MonthDay[month];
}

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;

	// 检查日期是否合法
	if (month < 1 || month > 12
		|| day < 1 || day > GetMonthDay(year, month))
	{
		cout << "非法日期" << endl;
	}
}

Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

Date& Date::operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	
	return *this;
}

Date& Date::operator+=(int day)
{
	if (day < 0)
		return *this -= (-day);

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;

		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}

	return *this;
}

Date Date::operator+(int day) const
{
	Date tmp(*this);
	tmp += day;

	return tmp;
}

Date& Date::operator-=(int day)
{
	if (day < 0)
		return *this += (-day);

	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

Date Date::operator-(int day) const
{
	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;
}

Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

Date Date::operator--(int)
{
	Date tmp(*this);

	*this -= 1;

	return tmp;
}

bool Date::operator > (const Date& d) const
{
	if (_year > d._year)
		return true;
	else if (_year == d._year && _month > d._month)
		return true;
	else if (_year == d._year && _month == d._month && _day > d._day)
		return true;
	else
		return false;
}

bool Date::operator == (const Date& d) const
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

bool Date::operator >= (const Date& d) const
{
	return *this > d || *this == d;
}

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

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

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

int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++min;
		n++;
	}

	return n * flag;
}

この記事の内容が役に立った場合は、「いいね!」や「保存」をしていただけますよう、よろしくお願いいたします。

次の記事のプレビュー: C++ のクラスとオブジェクト (4) 初期化リスト、静的メンバー、フレンド、内部クラス

おすすめ

転載: blog.csdn.net/2301_79391308/article/details/132579955