C++ クラスとオブジェクト (4) - Date クラスの実装

目次

1. クラスの作成とメソッドの宣言

2. 出力と演算子のオーバーロード

3. 合法性を確認する

1.年月に対応する日数を取得する

2. 初期化

4. 加算と加算演算を実装する

1. 最初に += を書き、次に + 

2. 最初に + を書き、次に += を書きます。

3. 2 つの方法の比較

5. ++ 自己増加と -- 自己減少を実現する

1. 自己インクリメント

2. 自己減少

6. 減算と減算演算を実装する

1. 待機日数を減らす

2. 負の数を加算し、正の数を減算します。

3. 日数を減らす

4. 日付から日付を引いた値

完全版:

日付.h

日付.cpp

テスト.cpp


これまでの学習を通じて、今回はデート クラスを使用して、学習した内容を実践します。まず始めましょう確認

 

デフォルトのメンバー関数を書かなければコンパイラが自動生成しますが、書いてもコンパイラは自動生成しません。

コンストラクターとデストラクターはデフォルトで生成されます。

  • 組み込み型の構造は処理されず、デストラクターによってクリーンアップされます。
  • カスタム タイプは、対応する構築/破壊を呼び出します。

コピー構築と代入のオーバーロードがデフォルトで生成されます

  • 組み込み型は浅いコピー/値のコピー (バイトごとに 1 つずつコピー) を完了します。
  • カスタム型。このメンバーを呼び出すには、構築/代入のオーバーロードをコピーします。 

1. クラスの作成とメソッドの宣言

dateクラスのメンバ変数(プロパティ)とメンバ関数(メソッド)の宣言をヘッダファイルに作成し、その定義をソースファイルに配置します。

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;

class Date {
public:
	Date(int year = 0, int month = 0, int day = 0);

	int GetMonthDay(int year, int month);

    void Print();
	bool operator==(const Date& d);
	bool operator!=(const Date& d);
	bool operator< (const Date& d);
	bool operator<=(const Date& d);
	bool operator> (const Date& d);
	bool operator>=(const Date& d);

	Date& operator+=(int day);
	Date operator+(int day);
	
	Date& operator-=(int day);

	// d1 - 100
	Date operator-(int day);

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

	// ++d1
	Date& operator++();

	// d1++
	Date operator++(int);
	
	Date& operator--();

	Date operator--(int);

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

2. 出力と演算子のオーバーロード

詳細な説明については、演算子のオーバーロードをご覧ください。

void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

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

// d1 < d2
bool Date::operator<(const Date& d)
{
	return _year < d._year
		|| (_year == d._year && _month < d._month)
		|| (_year == d._year && _month == d._month && _day < d._day);
}

// d1 <= d2
bool Date::operator<=(const Date& d)
{
	return *this < d || *this == d;
}

// d1 > d2
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);
}

3. 合法性を確認する

まず、日付の有効性を確認する必要があります。月は 1 ~ 12 の範囲にあり、GetMonthDay を通じて年と月に対応する日数を取得し、初期化に使用されるコンストラクターで日付を確認します。

1.年月に対応する日数を取得する

int Date::GetMonthDay(int year, int month)
{
	assert(month > 0 && month < 13);
	int _month[12] = { 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 _month[month - 1];
	}
}
  • まず、assert を使用してパラメータ month が正当であるかどうかを判断し、正当でない場合はエラーが報告されます。
  • 次に、対応する月の日数を格納する整数配列 _month を作成します。
  • 渡された月が 2 月で、年が閏年の場合は 29 日が返され、それ以外の場合は、_month 配列内の対応する月の日数が返されます。

2. 初期化

Date::Date(int year, int month, int day)
{
	if (month > 0 && month < 13
		&& (day > 0 && day <= GetMonthDay(year, month)))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "日期非法" << endl;
	}
}
  • まず、月と日数が正しいかどうかを確認し、初期化します。
  • それ以外の場合は、「Illegal date」が出力され、初期化エラーが発生します。

4. 加算と加算演算を実装する

1. 最初に += を書き、次に + 

日付と日数を計算するにはどうすればよいですか?

Date d1(2023, 2, 4);
Date d2 = d1 + 100;

アイデア:

  • 現在の日付の日数を加算し、当月の日数より大きい場合は月を切り上げ、月が12月より大きい場合は年を切り上げ、追加した結果 ※これが返されますが、このメソッドはd1を変更するため、実際には+=を実装しています。
Date& Date::operator+=(int day)
{
    _day += day;
    while (_day>GetMonthDay(_year,_month))
    {
        _day -= GetMonthDay(_year,_month);
        month++;
        if(month == 13)
        {
            _year++;
            _month = 1;
        }
    }
    return *this;
}

+= も値を返す必要があります。+= は連続 += をサポートしますが、日付クラスは加算やその他の型をサポートしません。次の状況では、+= には戻り値が必要です。

Date d3 = d1;
d1 = d3 += 100;

d1=d3+=100などの日付演算もサポート可能ですが、この際演算の戻り値を加算する必要がありますが、演算子の特性上戻り値が必要となります。

加算演算などを再利用して加算演算を実装します。

Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}
  • *thisを一時的に保存するために日付クラスオブジェクトtmpを作成します。
  • tmp に対して加算などの操作を実行し、
  • 最後に、tmp が返されます (一時変数を使用すると、元の変数が変更されないことが保証され、これは + 演算の定義と一致します)。

また、tmp は一時的なオブジェクトであり、スコープ外に出ると消滅するため、参照で返すことはできません。

2. 最初に + を書き、次に += を書きます。

2 番目の方法では、最初に + 演算を実装し、次に + 演算を再利用して += 演算を実装できます。

Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp._day += day;
	while (tmp._day > GetMonthDay(_year, _month))
	{
		tmp._day -= GetMonthDay(_year, _month);
		tmp._month++;
		if (tmp._month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}
	return tmp;
}
Date& Date::operator+=(int day)
{
	*this = *this + day;
	return *this;
}
  • +演算では一時変数tmpを*thisの変更値として保存し、tmpのメンバ日に+演算を実行し、最終的にtmpを返します。
  • += 演算で *this を使用すると、関数を呼び出すオブジェクトに対して + 演算を実行し、最後に *this を返します。​ 

static を使用して + 操作で tmp を変更し、静的変数およびグローバル ライフサイクルにする場合、それを参照によって返すことができますか?

//Date.cpp
Date& Date::operator+(int day)
{
	static Date tmp(*this);
	tmp._day += day;
	while (tmp._day > GetMonthDay(_year, _month))
	{
		tmp._day -= GetMonthDay(_year, _month);
		tmp._month++;
		if (tmp._month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}
	return tmp;
}
//test.cpp
void TestDate2()
{
	Date d1(2023, 2, 4);
	d1.Print();

	Date d2 = d1 + 100;
	d2.Print();

	Date d3 = d1 + 100;
	d3.Print();
}


int main()
{
	TestDate2();
	return 0;
}

 d2 の結果は正しいが、d3 の結果は正しくないことがわかります。d3 は d1 に 100 を加算する必要がありますが、実際の出力結果は d2 に 100 を加算します。

次にデバッグして見てみましょう。

+ 演算内で tmp が初期化される前に、Date d3 = d1 + 100; まで実行すると、tmp のメンバー値が *this のメンバー値と異なるのはなぜですか?

理由: 静的変数は 1 回しか初期化できません。日付 d2 = d1 + 100; は、最初に使用するときに初期化されます。2 回目に初期化すると、効果はありません。したがって、静的変数は注意して使用する必要があります。 static を使用して tmp を参照渡し用に変更します。

3. 2 つの方法の比較

1 つ目は、最初に add などの操作を記述し、add などを再利用して add 操作を実装することです。

2 つ目は、最初に加算演算を記述してから、その加算を再利用して加算や他の演算を実装する方法です。

それでは、上記の 2 つの方法のうちどちらが優れているのでしょうか?

結論: 最初の方が優れていますが、2 番目の + 操作は高価です。tmp の初期化にはコピー構造が必要で、値による戻りに​​もコピー構造が必要です。一方、追加などにはコピー動作はありません。

add、wait、reuse、addを繰り返すとコピー構造の使用回数が2回増えてしまうので、先にadd、add…と書いて再利用と追加をすると合計4つのコピーが消費されてしまいます。 add 関数と wait 関数を先に書いた場合、コピー コンストラクターは存在せず、add、reuse、add、wait のみが 2 つのコピー コンストラクターを使用します。

このような比較から、最初に加算を書いてから加算を書く最初の方法の方が優れており、コピー数を減らすことができると結論付けることができます。

削減は演算子のオーバーロードと関数のオーバーロードを構成します

5. ++ 自己増加と -- 自己減少を実現する

1. 自己インクリメント

インクリメント演算子 ++ は、前置 ++ と後置 ++ に分かれています。

デフォルトでは、プレフィックス ++ のみがあり、同時に ++ には隠された this ポインタであるオペランドが 1 つだけあり、*this の値が返され、参照リターンを使用できます。

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

前処理と後処理を区別するにはどうすればよいですか?

コンパイラは、パラメーター int がサフィックスに追加されていることを識別しやすくするために特別な処理を実行しました。これには実際的な意味はありません。関数のオーバーロードを区別するためだけです。サフィックスは ++ の前の値を返す必要があるため、一時変数 tmp は *this. を *this++ に保存するために使用され、tmp. によって保存された変更されていない *this を返します。

//d++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

できる限りプレフィックスを使用すると、コピー操作を減らすことができます。​ 

ポストパラメータを使用してパラメータを渡します。どれでも十分です。このパラメータは、プレパラメータのオーバーロードと区別するための単なるプレースホルダです。

テストしてみましょう: 

void TestDate3()
{
	Date d1(2023, 6, 6);
	d1.Print();

	Date ret1 = ++d1;
	d1.Print();
	ret1.Print();

	Date ret2 = d1++;
	d1.Print();
	ret2.Print();
}
int main()
{
	TestDate3();
	return 0;
}

出力結果: 

2. 自己減少

同様に、自己デクリメントも自己インクリメントと同様に実装されます。

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

// d-- 
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;

	return tmp;
}

6. 減算と減算演算を実装する

1. 待機日数を減らす

日付で減算を行い、減算がゼロ以下の場合は前月の日付を借用し、1 月に借用した場合は前年の 12 月の日付を借用します。

Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
void TestDate4()
{
	Date d1(2023, 2, 4);
	d1.Print();

	d1 -= 100;
	d1.Print();
}

int main()
{
	TestDate4();
	return 0;
}

出力結果: 

 

2. 負の数を加算し、正の数を減算します。

次のような状況が発生した場合はどうなりますか?

void TestDate4()
{
	Date d2(2023, 6, 6);
	d2 += -100;

	Date d3(2023, 7, 7);
	d3 -= -200;
}

加算と減算の判定文を追加し、加算が負の場合は減算を、整数を減算して負の場合は加算を呼び出します。

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}
	_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)
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

もう一度テストしてみましょう 

void TestDate4()
{
	Date d2(2023, 6, 6);
	d2.Print();
	d2 += -100;
	d2.Print();

	Date d3(2023, 7, 7);
	d3.Print();
	d3 -= -200;
	d3.Print();
}

int main()
{
	TestDate4();
	return 0;
}

 出力結果:

 

3. 日数を減らす

これは、減算などの演算を再利用したり、一時変数 tmp を使用したりすることで実装されます。​ 

Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}

4. 日付から日付を引いた値

この関数は、2 つの日付オブジェクトが等しくなるまで小さい方の日付オブジェクトを増分することにより、日数の差を計算します。現在の日付オブジェクトが渡された日付オブジェクトより大きい場合、結果は正の数になります。現在の日付オブジェクトが渡された日付オブジェクトより小さい場合、結果は負の数になります。

int Date::operator-(const Date& d)
{
	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;
}
  • まず、2 つの一時日付オブジェクト max と min を作成し、それぞれ現在の日付オブジェクトと受信日付オブジェクト d に初期化します。
  • 現在の日付オブジェクトが渡された日付オブジェクト d より小さい場合、最大値と最小値が交換され、フラグ flag は -1 に設定され、結果が負の数であることを示します。
  • 整数変数 n を作成して、日数の差の絶対値を記録します。
  • ループを使用して、min が max と等しくない場合は、min の日付をインクリメントします。つまり、min.operator++() を実行します。 nもインクリメントします。
  • 最終的な差異を日数で取得するために、n にフラグを乗算して返します。

テストを受けてください

void TestDate5()
{
	Date d1(2024, 1, 12);
	d1.Print();

	Date d2(2023, 11, 23);
	d2.Print();

	cout << d2 - d1 << endl;
	cout << d1 - d2 << endl;
}
int main()
{
	TestDate5();
	return 0;
}

成功した実装:

完全版:

日付.h

#include <iostream>
#include <assert.h>
using namespace std;

class Date {
public:
	Date(int year = 0, int month = 0, int day = 0);
	void Print();
	int GetMonthDay(int year, int month);

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

	Date& operator+=(int day);
	Date operator+(int day);
	
	Date& operator-=(int day);

	// d1 - 100
	Date operator-(int day);

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

	// ++d1
	Date& operator++();

	// d1++
	Date operator++(int);
	
	Date& operator--();

	Date operator--(int);

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

日付.cpp

#include "Date.h"

int Date::GetMonthDay(int year, int month)
{
	assert(month > 0 && month < 13);
	int _month[12] = { 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 _month[month - 1];
	}
}

Date::Date(int year, int month, int day)
{
	if (month > 0 && month < 13
		&& (day > 0 && day <= GetMonthDay(year, month)))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "日期非法" << endl;
	}
}

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}
	_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)
//{
//	Date tmp(*this);
//	tmp._day += day;
//	while (tmp._day > GetMonthDay(_year, _month))
//	{
//		tmp._day -= GetMonthDay(_year, _month);
//		tmp._month++;
//		if (tmp._month == 13)
//		{
//			tmp._year++;
//			tmp._month = 1;
//		}
//	}
//	return tmp;
//}
//Date& Date::operator+=(int day)
//{
//	*this = *this + day;
//	return *this;
//}

//++d
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
//d++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
//--d
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

// d-- 
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;

	return tmp;
}

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	_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;
}

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

void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

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

// d1 < d2
bool Date::operator<(const Date& d)
{
	return _year < d._year
		|| (_year == d._year && _month < d._month)
		|| (_year == d._year && _month == d._month && _day < d._day);
}

// d1 <= d2
bool Date::operator<=(const Date& d)
{
	return *this < d || *this == d;
}

// d1 > d2
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);
}

テスト.cpp

#include "Date.h"

void TestDate1()
{
	Date d1(2023, 2, 4);
	cout << d1.GetMonthDay(2022,12) << endl;
	//d1.Print();

	//Date d2 = d1 + 100;
	//d2.Print();
}

void TestDate2()
{
	Date d1(2023, 2, 4);
	d1.Print();

	Date d2 = d1 + 100;
	d2.Print();

	Date d3 = d1 + 100;
	d3.Print();
}

void TestDate3()
{
	Date d1(2023, 6, 6);
	d1.Print();

	Date ret1 = ++d1;  // d1.operator++();
	d1.Print();
	ret1.Print();

	Date ret2 = d1++;  // d1.operator++(0);
	d1.Print();
	ret2.Print();


	/*d1.operator++();
	d1.operator++(0);*/
}

void TestDate4()
{
	//Date d1(2023, 2, 4);
	//d1.Print();

	//d1 -= 100;
	//d1.Print();

	Date d2(2023, 6, 6);
	d2.Print();
	d2 += -100;
	d2.Print();

	Date d3(2023, 7, 7);
	d3.Print();
	d3 -= -200;
	d3.Print();
}
void TestDate5()
{
	Date d1(2024, 1, 12);
	d1.Print();

	Date d2(2023, 11, 23);
	d2.Print();

	cout << d2 - d1 << endl;
	cout << d1 - d2 << endl;
}
int main()
{
	TestDate5();
	return 0;
}

おすすめ

転載: blog.csdn.net/m0_73800602/article/details/134581207