C++ 如何實現(xiàn)一個日期類
一. 對日期類的介紹
通過對類和對象(這里鏈接是類和對象的介紹)的學習,類就是一種模型一樣的東西,是對象一種抽象的描述。所以實現(xiàn)日期類,就是實現(xiàn)一個日期模型,里面有所對應(yīng)的成員變量(屬性:年/月/日),還有一些它的方法(成員函數(shù):打印該日期、對日期加減等)。
二. 實現(xiàn)日期類
在實現(xiàn)日期類之前,我們還需要了解一下運算符重載,這是為了后續(xù)我們對日期的加、減天數(shù)以及日期減日期等做準備,因為當運算符被用于類類型的對象時,需要我們?nèi)ブ剌d運算符去指定新的含義。(C++規(guī)定類類型對象使用運算符時,必須轉(zhuǎn)換成調(diào)用對應(yīng)運算符重載,若沒有對應(yīng)的運算符重載,則會編譯報錯。)
//內(nèi)置類型 int i = 1; int j = 2; int sum = 0; i = i + 1; sum = i - j; //自定義類型對象若想用類似上面的就需要在類里面聲明定義對應(yīng)的運算符重載函數(shù)
1. 運算符重載
C++規(guī)定類類型對象使用運算符時,必須轉(zhuǎn)換成對應(yīng)運算符重載,若沒有對應(yīng)的運算符重載,則會編譯報錯。
也就是運算符被用于類類型的對象時,可以通過運算符重載的形式指定新的含義。
運算符重載是一個函數(shù),它的名字是由關(guān)鍵字operator和運算符組成,也具有返回類型、參數(shù)列表、函數(shù)體。
class A { public: A(int a = 1, int b = 1) :_a(a) ,_b(b) {} //這里就是賦值運算符重載 A& operator=(const A& a) { _a = a._a; _b = a._b; return *this; } private: int _a; int _b; }; int main() { A a1; A a2(5, 2); a1 = a2; return 0; }
我們來查看驗證是否按照指定新的含義進行:
賦值之前:
賦值之后:
可以看到a1里的成員確實被a2里的成員變量賦值了。
注意:
//上述代碼中 a1 = a2; //其實詳細寫法是: a1.operator=(a2); //但它們倆意義相同都是為了調(diào)用賦值運算符重載//上述代碼中 a1 = a2; //其實詳細寫法是: a1.operator=(a2); //但它們倆意義相同都是為了調(diào)用賦值運算符重載
一元運算符只有一個參數(shù),二元運算符要有兩個參數(shù)。(注意:二元運算符的左側(cè)運算對象傳給第一個參數(shù),右側(cè)運算對象傳給第二個參數(shù))。
也就是說,重載運算符函數(shù)的參數(shù)個數(shù)和該運算符作用的運算對象數(shù)量一致的。
上面我們看到A類重載=運算符只寫了一個函數(shù)參數(shù),這是因為它是成員函數(shù),第一個參數(shù)默認傳了this指針(類和對象有介紹)。
注意:重載運算符必須至少有一個類類型的形參,不能通過運算符重載改變內(nèi)置類型對象的含義。
若不是成員函數(shù),就按照上面規(guī)則來:
class A { public: A(int a = 1, int b = 1) :_a(a) ,_b(b) {} A& operator=(const A& a) { _a = a._a; _b = a._b; return *this; } int _a; int _b; }; //注意:operator + 必須至少有一個類類型的形參,否則會報錯,就比如下面注釋掉的情況 //int operator(int a,int n) //{ // return a-n; //} int operator+(const A& a, int& n) { return a._a + n; //這里成員變量需要放公有,否則不能直接訪問 } int main() { A a; int n = 10; cout << a + n << endl; return 0; }
運算符重載以后,優(yōu)先級和結(jié)合性與對應(yīng)的內(nèi)置類型運算符保持一致的。
不能重載的情況:
不能用沒有的符號創(chuàng)建新的操作符(運算符)
//例如: operator@ //就不能創(chuàng)建
有五個運算符是不能重載的:.* :: sizeof ?: .
后四個是經(jīng)常用到的,第一個非常用,下面單獨拿出來解釋
//函數(shù)指針較為特別,typedef類型需要這樣寫 typedef void (*PF)(); class A { public: void func() { cout << "A::func()" << endl; } }; //若typedef成員函數(shù)指針類型,就加個指定類域即可 typedef void(A::*PF)(); // .*運算符就用于以下方式回調(diào)函數(shù)的場景(成員函數(shù)回調(diào)) int main() { //成員函數(shù)要加&才能取到函數(shù)指針 PF pf = &A::func; //這里相當于 void(A::*pf)() = &A::func; A a; (a.*pf)(); return 0; }
(深入理解指針)
//這里是普通全局函數(shù)回調(diào)的形式 (*pf)();
所以這個符號的意義是對象調(diào)用成員函數(shù)指針時,成員函數(shù)的回調(diào) (注意:考慮有隱含的this指針,不能顯示寫形參和傳實參)
重載++運算符時,前置++與后置++的運算符重載函數(shù)名都是operator++,無法來區(qū)分。 C++規(guī)定,后置++重載時,增加一個int形參,跟前置++構(gòu)成函數(shù)重載,方便區(qū)分。
class Date { public: //構(gòu)造函數(shù) Date(int year = 1900,int month =1,int day=1) :_year(year) ,_month(month) ,_day(day) {} //前置++ Date& operator++() { cout << "這里是前置++" << endl; //這里演示一下,先不實現(xiàn) return *this; } //后置++ Date& operator++(int) //這里的加不加形參名都可以,必須要有int (只要整型) { Date tmp; cout << "這里是后置++" << endl; return tmp; } private: int _year; int _month; int _day; }; int main() { Date d1; Date d2; ++d1; d2++; d2.operator++(10); //這里傳不傳值都可以,因為那個int一般不接收。 }
注意:重載的++運算符也要與內(nèi)置類型++運算符規(guī)則一致(前置++:先++,再返回++后的結(jié)果,不產(chǎn)生拷貝;后置++:進行++,++之后返回的是++前的結(jié)果,會產(chǎn)生拷貝。所以,一般開以選擇前置++來減少拷貝)。
我們實現(xiàn)日期類,還想需要可以對這個日期cout和cin來方便輸出和輸入,所以<<和>>也是可以重載的,但是需要重載成全局函數(shù),重載為全局函數(shù)把ostream/istream放到第一個形參位置就可以了,第二個形參位置當類類型對象。否則會有隱含的this指針,導致調(diào)用時變成:對象<<cout ,不符合使用習慣和可讀性(想要的是:cout<<對象)。
2.日期類實現(xiàn)代碼
我們可以聲明定義分離來實現(xiàn),分別創(chuàng)建Date.h頭文件和Date.cpp用來定義頭文件聲明的函數(shù)。
//Date.h #include<iostream> #include <assert.h> using namespace std; class Date { //友元函數(shù)聲明 --這兩個全局函數(shù)就可以訪問對象的私有成員 friend ostream& operator<<(ostream& out, const Date& d); friend istream& operator>>(istream& in, Date& d); public: //構(gòu)造函數(shù),若沒有傳參 就是默認構(gòu)造 全缺省函數(shù) Date(int year = 1900, int month = 1, int day = 1); //拷貝構(gòu)造 Date(const Date& d); void Print()const; //打印日期 bool CheckDate() const; //檢查日期輸入是否正確 //頻繁調(diào)用的建議直接定義類里面,默認inline內(nèi)聯(lián) int GetMonthDay(int year, int month)const { assert(month > 0 && month < 13); static int monthDay[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 }; //因為沒有0月,將0置-1空出來 //用static修飾是因為這個數(shù)組會頻繁調(diào)用,直接放在靜態(tài)區(qū) if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { return 29; } return monthDay[month]; } 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; //d1 = d2 Date& operator=(const Date& d); Date operator+(int day) const; Date& operator+=(int day); Date operator-(int day) const; Date& operator-=(int day); // d1++; // d1.operator++(0); Date operator++(int); // ++d1; // d1.operator++(); Date& operator++(); // d1--; // d1.operator--(0); Date operator--(int); // --d1; // d1.operator--(); Date& operator--(); // d1 - d2 int operator-(const Date& d) const; //void operator<<(ostream& out); Date* operator&() //取地址運算符重載 //普通對象返回類型Date* { return (Date*)0x2673FF40; //返回假地址 } const Date* operator&() const //取地址運算符重載 //const對象要調(diào)用const成員函數(shù)要返回類型const Date* { return (Date*)0x2673FE30; //返回假地址 } private: int _year; int _month; int _day; }; //重載流插入流提取 // ostream/istream類型對象不能傳值只能傳引用,否則會報錯,因為c++不支持它們的拷貝構(gòu)造。 //要寫成全局函數(shù) 否則調(diào)用的時候需要這樣寫:d1<<cout d1>>cin 用著會比較別扭 //因為如果寫成了成員函數(shù)第一個參數(shù)有隱藏的this指針,并且不能修改 //cout是ostream類型的 (也可以用void返回類型但不建議) //cin默認關(guān)聯(lián)cout //在cin進行i/o操作前會刷新cout的緩沖區(qū) //流插入 ostream& operator<<(ostream& out, const Date& d); //流提取 //這里第一個參數(shù)不能加const修飾了,因為提取的值要放日期類對象里 istream& operator>>(istream& in, Date& d);
//Date.cpp #include "Date.h" bool Date::CheckDate() const//檢查日期正確 { if (_month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month)) { return false; } else { return true; } } Date::Date(int year, int month, int day) //構(gòu)造函數(shù)(全缺省,前面聲明中已經(jīng)給了缺省值,定義這里就不能再寫出來了) { _year = year; _month = month; _day = day; if (!CheckDate()) { cout << "非法日期:"; Print(); } cout << endl; } Date::Date(const Date& d) //拷貝構(gòu)造 { _year = d._year; _month = d._month; _day = d._day; } void Date::Print()const //打印日期 { cout << _year << "/" << _month << "/" << _day << endl; } //d1 = d2 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) //加等可以讓自己改變 { //先判斷day是否是負數(shù) ---- _day+(-day)==_day-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 //只加不能改變自己 //這里不能用傳引用返回 因為這里用const修飾只讀取不想改變自己,引用會把權(quán)限放大 { Date tmp = *this; tmp += day; return tmp; } //- -=同+ += Date Date::operator-(int day) const { Date tmp = *this; tmp -= day; return tmp; } Date& Date::operator-=(int day) { //先判斷day是否是負數(shù) --->_day-(-day)==_day+day if (day < 0) { return *this += (-day); } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { _month = 12; --_year; } _day += GetMonthDay(_year, _month); } return *this; } //d1<d2 bool Date::operator<(const Date& d) const //這里傳引用 d就是d2的別名 也就是d2的本身 { if (_year < d._year) { return true; } else if (_year == d._year) { if (_month < d._month) { return true; } if (_month == d._month) { return _day < d._day; } } return false; } //d1<=d2 //這里就可以復(fù)用函數(shù)了 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 (_year == d._year) && (_month == d._month) && (_day == d._day); } bool Date::operator!=(const Date& d) const { return !(*this == d); } // d1++; 后置++有拷貝 // d1.operator++(0); //用的時候括號里只要是整數(shù)都可以 Date Date::operator++(int) // 加不加形參名都可以,一般不接收 { Date tmp = *this; *this += 1; return tmp; } // ++d1; //前置++無拷貝 // d1.operator++(); Date& Date::operator++() { *this += 1; return *this; } //-- 同理++ // d1--; // d1.operator--(0); Date Date::operator--(int) { Date tmp = *this; *this -= 1; return tmp; } // --d1; // d1.operator--(); Date& Date::operator--() { *this -= 1; return *this; } // d1 - d2 int Date::operator-(const Date& d) const { Date max = *this; //假設(shè)第一個大 Date min = d; //第二個小 int flag = 1; //等于1時表示第一個大第二個小 if (*this < d) { max = d; min = *this; flag = -1; //第二個大第一個小 } int n = 0; while (min != max) { ++min; ++n; } return n *= flag; } //流插入cout<<d1<<d2 ostream& operator<<(ostream& out, const Date& d) { cout << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; } //流提取cin>>d1>d2 istream& operator>>(istream& in, Date& d) { cout << "請依次輸入年月日:>"; in >> d._year >> d._month >> d._day; //檢查日期是否非法 if (!d.CheckDate()) { cout << "非法日期: " << d << "請重新輸入" << endl; while (1) { cout << "請依次輸入年月日:>"; in >> d._year >> d._month >> d._day; if (!d.CheckDate()) { cout << "輸入日期非法:"; d.Print(); cout << "請重新輸入!!!" << endl; } else { break; } } } return in; }
有些注意事項,在上面代碼的實現(xiàn)注釋中有一些解釋。
到此這篇關(guān)于C++ 如何實現(xiàn)一個日期類的文章就介紹到這了,更多相關(guān)C++日期類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++/JAVA/C#子類調(diào)用父類函數(shù)情況總結(jié)
今天小編就為大家分享一篇關(guān)于C++/JAVA/C#子類調(diào)用父類函數(shù)情況總結(jié),小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03Visual Studio中scanf函數(shù)報錯的幾種解決方法
本文主要介紹了Visual Studio中scanf函數(shù)報錯的幾種解決方法,文中通過圖文示例介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2025-03-03關(guān)于C++中定義比較函數(shù)的三種方法小結(jié)
下面小編就為大家?guī)硪黄P(guān)于C++中定義比較函數(shù)的三種方法小結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10C++中的類型轉(zhuǎn)換static_cast、dynamic_cast、const_cast和reinterpret_cas
這篇文章主要介紹了C++中的類型轉(zhuǎn)換static_cast、dynamic_cast、const_cast和reinterpret_cast總結(jié),需要的朋友可以參考下2014-10-10C++實現(xiàn)LeetCode(169.求大多數(shù))
這篇文章主要介紹了C++實現(xiàn)LeetCode(169.求大多數(shù)),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08