6.類和對象(4)
文章目錄
- 6.類和對象(4)
- 回顧
- 簡單日期類的實現
- 代碼
- 補充:前置++與后置++的重載區別
- 補充:關于流插入運算符(<<)的解釋
- 拓展:仿照流插入操作符(<<)的作用創建一個可以直接實現日期的流插入重載
- const成員
- 總結
回顧
【C++語法】類和對象(3)
前面講解了C++中類的三個關鍵成員函數:析構函數用于對象銷毀時釋放資源(如動態內存),名稱格式為~類名
;拷貝構造函數通過同類對象初始化新對象,參數必須為引用以避免無限遞歸,默認淺拷貝需注意指針問題;賦值運算符重載(operator=
)實現對象間賦值,需返回引用以支持連續賦值,并處理自賦值和深拷貝。核心思想是:涉及資源管理(如指針)的類必須自定義這三個函數,而簡單類可依賴編譯器默認實現,否則可能導致內存泄漏或重復釋放。
【C++語法】類和對象(2)
主要介紹了C++中的默認構造函數及其特性。文章指出空類實際上包含6個默認成員函數,其中構造函數在對象實例化時自動調用,用于初始化對象而非創建對象。構造函數具有類同名、無返回值、支持重載等特點,且不能被設為私有。編譯器生成的默認構造函數對內置類型無效,但會調用自定義類型的默認構造函數。C++11允許為內置類型成員提供默認值。重點闡釋了默認構造函數的概念,包括無參、全缺省和編譯器生成的構造函數都屬于默認構造函數,且一個類只能有一個默認構造函數。文章還強調了默認構造函數與普通構造函數的區別,并提供了實踐建議和使用注意事項。
【C++語法】類和對象(1)
本文圍繞C++類和對象展開。介紹對象概念,對比C與C++編程范式;講解類定義、訪問限定、作用域、實例化;說明類存儲大小含內存對齊,即便無成員變量也占1字節;還闡述this指針特性,它是成員函數隱式形參。
簡單日期類的實現
日期類通過規范化調整(Adjust
函數)處理跨月/跨年邊界,復用運算符實現高效日期計算(如+
復用+=
),支持完整的比較、算術和自增/自減操作,核心是閏年判斷和月份天數計算。這份代碼中還有一些沒有提到的內容,后面會有筆記。
代碼
頭文件:
#pragma once
#include<iostream>
using namespace std;class Date
{friend ostream& operator<<(ostream& out, Date d);
public:// 獲取某年某月的天數int GetMonthDay(int year, int month);// 全缺省的構造函數Date(int year = 1900, int month = 1, int day = 1);// 拷貝構造函數// d2(d1)Date(const Date& d);// 賦值運算符重載// d2 = d3 -> d2.operator=(&d2, d3)Date& operator=(const Date& d);// 析構函數~Date();// 日期+=天數Date& operator+=(int day);// 日期+天數Date operator+(int day) const;// 日期-天數Date operator-(int day) const;// 日期-=天數Date& operator-=(int day);// 前置++Date& operator++(int);// 后置++Date operator++();// 后置--Date operator--(int);// 前置--Date& operator--();// >運算符重載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;//調整void Adjust(Date& d);//打印void Print() const;流插入//void operator<<(ostream& out);private:int _year;int _month;int _day;
};
源文件:
#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"//判斷閏年
bool IsLeapYear(int year)
{if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)){return true;}return false;
}
//調整
void Date::Adjust(Date& d)
{while (d._day > GetMonthDay(d._year, d._month)){d._day -= GetMonthDay(d._year, d._month);d._month++;if (d._month > 12){d._month -= 12;d._year++;}}while (d._day <= 0){//上一個月的天數d._day += GetMonthDay(d._year, d._month - 1);d._month--;if (d._month <= 0){d._month += 12;d._year--;}}
}
// 獲取某年某月的天數
int Date::GetMonthDay(int year, int month)
{if (month == 2){if (IsLeapYear(year)){return 29;}else{return 28;}}else{if (month == 1 || month == 3 || month == 5 ||month == 7 || month == 8 || month == 10 || month == 12){return 31;}else{return 30;}}
}
// 全缺省的構造函數
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
// 拷貝構造函數
// d2(d1)
Date::Date(const Date& d)
{ _year = d._year;_month = d._month;_day = d._day;
}
// 賦值運算符重載
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;return *this;
}
// 析構函數
Date::~Date()
{//cout << "~Date()" << endl;
}
// 日期+=天數
Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}_day += day;Adjust(*this);return *this;
}
// 日期-=天數
Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;Adjust(*this);return *this;
}
// 日期+天數
Date Date::operator+(int day)
{Date tmp = *this;//方法一//tmp._day += day;//Adjust(tmp);//方法二tmp += day;return tmp;
}
// 日期-天數
Date Date::operator-(int day)
{Date tmp = *this;//方法一//tmp._day -= day;//Adjust(tmp);//方法二tmp -= day;return tmp;
}
// 前置++
Date& Date::operator++(int)
{*this += 1;return *this;
}
// 后置++
Date Date::operator++()
{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)
{if (_year > d._year){return true;}else if (_year == d._year){if (_month > d._month){return true;}else if (_month == d._month){if (_day == d._day){return true;}}}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);
}
// 日期-日期 返回天數
int Date::operator-(const Date& d)
{Date max = *this;Date min = d;int flag = 1;if (min > max){min = *this;max = d;flag = -1;}int day = 0;while (min != max){++min;++day;}return day * flag;
}
//打印
void Date::Print() const
{cout << _year << "-" << _month << "-" << _day << endl;
}流插入
//void Date::operator<<(ostream& out)
//{
// out << _year << "-" << _month << "-" << _day << endl;
//}
補充:前置++與后置++的重載區別
1>我們需要清楚前置和后置的區別:
前置是先運算在使用,后置是先使用再運算,所以運算的本質是對變量自身的值的改變,而使用是這個表達式的輸出結果。因此前置運算可以直接返回運算后的值,此時用引用可以節約資源;后置需要先創建臨時變量,臨時變量的值不變,但形參要進行運算,最后返回這個臨時變量。我們會發現前置比后置消耗的資源要少很多,自定義類型對象一般情況下最好用前置運算。
2>在C++中我們為了能把前置++/–和后置++/–進行區分,規定后置運算要在形參上加上一個int與前置構成運算符重載;
需要注意:這條規定是死的,不能定義前置運算加上int,要不然運算會出錯但編譯器不會報錯
在 C++ 中,區分前置和后置自增/自減運算符重載的關鍵是函數簽名中的占位參數 int
,在 C++ 中,后置自增/自減運算符需要 int
占位參數的原因是為了解決函數重載的歧義問題。這是 C++ 語言設計者為區分前置和后置版本而制定的特殊語法規則。具體實現需要注意以下要點:
📌 區分方式
- 前置版本:無參數
Date& operator++();
Date& operator--();
- 后置版本:帶
int
占位參數
Date operator++(int);
Date operator--(int);
(調用時編譯器自動傳入 0 作為參數)
?? 實現注意事項
特性 | 前置版本 | 后置版本 |
---|---|---|
返回值類型 | 返回當前對象引用 (Date& ) | 返回操作前的臨時副本 (Date ) |
操作邏輯 | 先自增/自減,后返回新值 | 先保存舊值,再自增/自減,返回舊值 |
效率 | 高效(無拷貝開銷) | 較低(需構造臨時對象) |
實現復用 | 通常直接修改成員變量 | 通常調用前置版本實現核心邏輯 |
鏈式操作支持 | 支持(如 ++++d ) | 不支持(返回右值) |
補充:關于流插入運算符(<<)的解釋
現在我們可以解釋為什么在C++中使用cout<<
可以自己判斷參數類型并進行輸出了,因為在庫函數源文件中就已經定義了<<
多種重載形式,所以對于C++中的內置類型對象的輸出可以自己識別但對于自定義類型就做不到了
拓展:仿照流插入操作符(<<)的作用創建一個可以直接實現日期的流插入重載
const成員
內置變量類型可以用const修飾,自然自定義類型也可以用const進行修飾,但是會有一些區別。
如果定義一個被const修飾的類類型的對象,在調用其成員函數的時候可能會出現權限被放大的情況
因為d2是被const修飾的對象,d2的值不能被修改,當我們調用d2.Printf()
時,我們知道會有一個this
指針變量作為隱含的參數,但這個this
指針變量默
認情況下的類型是Date*const this
,this
指針指向的對象(d2)的值是可以通過this
指針修改的,因此編譯不通過,權限被放大。
解決方法:在成員函數后面加上一個const修飾,如圖,這樣this指針的類型就變成了const Date*const this
,不會發生權限的放大了。
對于前面實現的日期類或其他類,如果某成員函數的功能只是讀取數據而不需要修改數據,最好在函數后面加上const修飾,這樣被const修飾的對象也可以
正常使用該函數。
通過以上的學習可以回答一下這幾個問題:
-
const對象可以調用非const成員函數嗎? 否
-
非const對象可以調用const成員函數嗎? 可以
-
const成員函數內可以調用其它的非const成員函數嗎? 否
-
非const成員函數內可以調用其它的const成員函數嗎? 可以
總結
本文實現了一個完整的C++日期類,重點包括:
核心功能:通過Adjust函數處理日期跨月/跨年邊界,支持日期的加減運算(+=/-=復用實現+/-);
運算符重載:完整實現了比較運算符(>,==等)、算術運算符(±)和自增/自減(前置/后置);
關鍵算法:包含閏年判斷和月份天數計算;
類設計:包含構造函數、拷貝控制成員、流輸出友元等;
代碼復用:通過+=實現+、-=實現-等技巧減少重復代碼。
該日期類實現了規范的日期計算功能,通過運算符重載提供了直觀的日期操作接口,是C++類和對象特性的典型應用案例。