[演算子のオーバーロード]日付クラスの実際の戦闘

この記事は、「新人クリエーションセレモニー」イベントに参加し、一緒にゴールドクリエーションの道を歩み始めました。

1.タスクリスト


  • 2つの日付オブジェクトのサイズ比較:<、>、=、==、> =、<=
  • 日付オブジェクトのプラスまたはマイナス日:++(前と後)、-(前と後)、+ =、-=、+、-
  • 2つの日付オブジェクト間の日数:-
  • ストリーム抽出とストリーム挿入の過負荷

2.基本原則


  • 値で返すのが正しい必要があります。つまり、追加のオーバーヘッドを増やすにはコピーが必要です。参照を返すことができるかどうかは、関数が終了した後も参照されたオブジェクトがまだそこにあるかどうかによって異なります。
  • ②機能間の再利用を可能な限り実施する
  • ③長さが短い関数には、インライン関数の形式を使用することをお勧めします。インライン関数の定義と宣言を分離できないことに注意してください。分離しないと、コンパイル中にインライン関数がシンボルテーブルに追加されず、リンクエラーが発生します。したがって、クラス内の関数はデフォルトでインラインであり、メンバー変数に簡単にアクセスできるため、クラスに書き込むことをお勧めします。
  • ④機能を再利用する場合は、効率を上げるためにできるだけコピー数を減らしてください。

3.機能の実現


①コンストラクター

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

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

②2つのオブジェクトのサイズ

 >と==のオーバーロードのみを実装し、他は再利用できます(スペースを減らすために、クラスは書き出されません)。

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


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

【コメント】

  • ①コピーを減らすために、できれば定数保護を使用して、値による参照を使用します
  • ②オブジェクトのメンバー変数を変更する必要がない場合は、このポインタにconst保護を追加して、passオブジェクトとconstオブジェクトの両方がメンバー関数を呼び出せるようにすることをお勧めします
bool operator>= (const Date& d) const
{
	return operator>(d) || operator==(d);
}

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

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

③日付オブジェクトプラスマイナス日

1.加算および減算演算子のオーバーロード
int Getmonthday(int year, int month) const // 之后反复用到,不再赘述
{
	static int day[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 day[month];
}


Date operator+ (const int day) const
{
	Date tmp(*this);  // 首先进行拷贝构造
	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._month = 1;
			tmp._year++;
		}
	}
	return tmp;
}
Date operator- (const int day) const
{
	Date tmp(*this);
	if (day < 0)
	{
		tmp = tmp + (-day);
		return tmp;
	}
	tmp._day -= day;
	while (tmp._day < 0)
	{
		tmp._month--;
		if (tmp._month == 0)
		{
			tmp._month = 12;
			tmp._year--;
		}
		tmp._day += Getmonthday(tmp._year, tmp._month);
	}
	return tmp;
}

【コメント】

  • 加算および減算操作を実行する場合、それは変更されないため、最初に一時オブジェクトをコピーし、最後に一時オブジェクトを返す必要があります
  • 一時オブジェクトはスコープ外になると破棄されるため、参照によって返すことはできません。
  • 上記のコードはそれぞれ2回コピーされます-一時変数を作成し、一時オブジェクトを返します
2.+=-=演算子のオーバーロード
Date& operator+= (const int day)
{
	_day += day;
	while (_day > Getmonthday(_year, _month))
	{
		_day -= Getmonthday(_year, _month);
		_month++;
		if (_month == 13)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}

Date& operator-= (int day)
{
	if (day < 0)
	{
		*this = *this + (-day);
		return *this;
	}
	_day -= day;
	while (_day < 0)
	{
		_month--;
		if (_month == 0)
		{
			_month = 12;
			_year--;
		}
		_day += Getmonthday(_year, _month);
	}
	return *this;
}

【コメント】

  • 基本的な考え方は、+-の実装と非常によく似ています。
  • 区别在于+=、-=对象本身是会改变的,所以不需要额外的拷贝,直接在原对象上操作
  • 出函数后原对象并没有被销毁,所以可以使用引用返回减少拷贝
  • +=、 -=的实现过程中没有用到拷贝构造
3.+- 与+= -=之间的复用
// +复用+=
Date operator+ (int day)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}

// +=复用+
Date& operator += (int day)
{
	*this = *this + day;
	return *this;
}

根据上面代码的比较我们可以得出以下结论:

  • +复用+=的时候,+的重载需要拷贝2次,而+=的重载本身不需要拷贝
  • +=复用+的时候,+的重载需要拷贝2次,而+=的重载也需要拷贝两次
  • 所以综上+复用+=的效果最佳
4.前置++与后置加加
// 前置减减后置减减同理,就不再这里赘述了
Date& operator++ ()    // 前置加加
{
	*this += 1;
	return *this;
}

Date operator++ (int)   // 后置加加
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

【评注】

  • 为了区别前置加加和后置加加,规定后置加加带一个int型参数,前置加加不带参。注意带的参数类型必须是int类型,形参名可写可不写。
  • 前置加加先加加后使用,所以将自增后的对象本身返回;后置加加先使用后加加,所以将自增前的对象本身返回

④两个对象之间的日期差

int operator- (const Date& d) const
{
	Date max = *this;
	Date min = d;
	if (max < min)
	{
		max = d;
		min = *this;
	}
	int cnt = 0;
	while (max != min)
	{
		min++;
		cnt++;
	}
	return cnt;
}

【评注】

  • 计算两个天数差的方法有很多,这是最简单的方法
  • 还可以计算两个到同一天的时间然后作差 + 1

⑤流插入与流提取运算符的重载

【问题一】为什么C++中的 cin 和 cout 会自动识别类型? 【答】因为流提取运算符和流插入运算符将所有常见的内置类型都给重载了 image-20220707083514762


【问题二】cout 和 cin 是什么? 【答】cout 是 ostream 的全局对象;cin 是 istream 的全局对象

image-20220707083903909

【问题三】流插入运算符和流提取运算符的重载可以写在类里面吗? 【答】不可以。因为类里面的函数第一次参数默认为 this 指针,所以在使用的时候不是 cout << xxx,而变成了xxx << cout 在这里插入图片描述


【问题四】写在函数外面如何访问成员变量 【答】使用友元函数,注意在类里面声明的时候前面需要加上 const,但是在定义的时候不需要加上 const。image-20220707085311574 image-20220707085709605 友元函数的特性:

  • 友元函数可访问类的私有和保护成员,但==不是==类的成员函数,因此也没有this指针
  • 友元函数不能用const修饰(因为没有this指针)
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制

【问题五】定义与声明分离的时候 【答】要在头文件声明,源文件定义,否则会出现链接错误。而类里面定义的函数默认是内联的,不会添加到符号表中,就不会出现问题。类里面长的函数也具有这样内联的属性

おすすめ

転載: juejin.im/post/7118271975658618917