🥁作者: 華丞臧
創(chuàng)新互聯(lián)建站專注于企業(yè)營銷型網(wǎng)站、網(wǎng)站重做改版、新邵網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、H5響應(yīng)式網(wǎng)站、商城網(wǎng)站建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為新邵等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
📕????專欄:【C++】
各位讀者老爺如果覺得博主寫的不錯,請諸位多多支持(點贊+收藏+關(guān)注
)。如果有錯誤的地方,歡迎在評論區(qū)指出。推薦一款刷題網(wǎng)站 👉LeetCode
如果一個類當(dāng)中什么成員都沒有,簡稱為空類。
空類中并不是什么都沒有,任何類在什么都不寫時,編譯器會自動生成一下6個默認(rèn)成員函數(shù)。
默認(rèn)成員函數(shù):用戶沒有顯示實現(xiàn),編譯器生成的成員函數(shù)稱為默認(rèn)成員函數(shù)。
請看如下日期類:
#includeusing namespace std;
class Date
{public:
//void Init(int year, int month, int day)
//{//_year = year;
//_month = month;
//_day = day;
//}
Date(int year, int month, int day)
{_year = year;
_month = month;
_day = day;
}
void Print()
{cout<< _year<< "-"<< _month<< "-"<< _day<< endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{//Date d1;
//d1.Init(2022, 7, 5);
Date d1(2022, 7, 5);//創(chuàng)建對象時,直接初始化
d1.Print();
//Date d2;
//d2.Init(2022, 7, 6);
Date d2(2022, 7, 6);
d2.Print();
return 0;
}
對于Date類,可以通過Init公有方法給對象設(shè)置日期,但如果每次創(chuàng)建對象時都需要調(diào)用該方法設(shè)置,未免有點麻煩;構(gòu)造函數(shù)很好的解決了上述問題。
構(gòu)造函數(shù)是一個特殊的成員函數(shù),名字與類名相同,創(chuàng)建類類型對象時由編譯器自動調(diào)用,以保存每個數(shù)據(jù)成員都有一個合適的初始值,并且在對象整個生命周期內(nèi)只調(diào)用一次。
構(gòu)造函數(shù)是特殊的成員函數(shù),需要注意的是,構(gòu)造函數(shù)雖然稱為構(gòu)造,但是其主要任務(wù)并不是開空間創(chuàng)建對象,而是初始化對象。
構(gòu)造函數(shù)特征如下:
class Date
{public:
Date()
{}
Date(int year, int month, int day)
{_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{Date d1;//調(diào)用無參構(gòu)造函數(shù)
Date d2(2022, 12, 13);//調(diào)用帶參構(gòu)造函數(shù)
//錯誤示范
//注意:如果通過無參構(gòu)造函數(shù)創(chuàng)建對象時,對象后面不能跟括號;
//因為編譯器不知道這是一個函數(shù)聲明還是要創(chuàng)建一個對象
// warning C4930: “Date d3(void)”: 未調(diào)用原型函數(shù)(是否是有意用變量定義的?)
Date d3();
}
#includeclass Stack
{public:
Stack(int capacity = 4)
{_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{ exit(-1);
}
_capacity = capacity;
_top = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class Myqueue
{private:
Stack _push;
Stack _pop;
int _top;
};
int main()
{Myqueue q1;
return 0;
}
從程序運(yùn)行結(jié)果來看,編譯器對自定義類型會調(diào)用它的默認(rèn)構(gòu)造函數(shù),對內(nèi)置類型不處理;但是從結(jié)果來看內(nèi)置內(nèi)型被初始化為0了,在C++標(biāo)準(zhǔn)中并沒有規(guī)定處不處理(默認(rèn)不處理),不同平臺的編譯器可能會處理。
注意:C++11 中針對對內(nèi)置類型成員不初始化的缺陷,又打了補(bǔ)丁,即:內(nèi)置類型成員變量在類中聲明時可以給默認(rèn)值(缺省值)。
//上述Myqueue類可以改成如下代碼:
class Myqueue
{private:
Stack _push;
Stack _pop;
int _top = 0;
//運(yùn)行代碼調(diào)試,可以看到_top被初始化為0
};
析構(gòu)函數(shù) 析構(gòu)函數(shù)概念注意:無參構(gòu)造函數(shù)、全缺省構(gòu)造函數(shù)、我們沒寫編譯器默認(rèn)生成的構(gòu)造函數(shù),都可以認(rèn)為是默認(rèn)構(gòu)造函數(shù)。
析構(gòu)函數(shù):與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完全對對象本身的銷毀,局部對象銷毀工作是由編譯器完成的,而對象在銷毀時會自動調(diào)用析構(gòu)函數(shù),完成對象中資源的清理工作。析構(gòu)函數(shù)與類名相似,~
(與C當(dāng)中取反位操作符一致)后加上類名就是析構(gòu)函數(shù)名,如下:
class Date
{~Date() //析構(gòu)函數(shù)
{}
};
我們知道當(dāng)一個類對象銷毀時,對象中的成員變量會跟著銷毀,但只有定義在棧上的成員變量銷毀,而定義在堆上的成員變量就必須讓我們進(jìn)行清理,因此需要用到析構(gòu)函數(shù)。
析構(gòu)函數(shù)特性析構(gòu)函數(shù)是特殊的成員函數(shù),其特征如下:
~
;編譯器默認(rèn)生成的析構(gòu)函數(shù),對于內(nèi)置內(nèi)型不處理,對于自定義類型會調(diào)用其析構(gòu)函數(shù)。
注意:析構(gòu)函數(shù)不能重載
。
拷貝構(gòu)造函數(shù) 拷貝構(gòu)造函數(shù)概念一般的,對象是定義在棧上面的,那么其析構(gòu)就符合棧的特性,即先進(jìn)后出,也就是說先定義的后析構(gòu),后定義的先析構(gòu)。
在創(chuàng)建對象時,無疑存在一個問題:要如何創(chuàng)建與已存在對象一模一樣的新對象呢?
拷貝構(gòu)造函數(shù):只有單個形參,該形參是對本來類型對象的引用(一般常用const修飾),在用已存在的類類型對象創(chuàng)建新對象時由編譯器自動調(diào)用。
拷貝構(gòu)造函數(shù)名與類名相同,參數(shù)是本類類型對象的引用。
拷貝構(gòu)造函數(shù)也是特殊的成員函數(shù),其特征如下:
傳值傳參就是把實參的值拷貝給形參,既然需要拷貝就需要調(diào)用拷貝構(gòu)造函數(shù),而因為是傳值傳參每次傳參都是一個拷貝構(gòu)造,在傳參的這一步上就會無窮遞歸調(diào)用拷貝構(gòu)造函數(shù)。
typedef int DataType;
class Stack
{public:
Stack(size_t capacity = 10)
{_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{ perror("malloc申請空間失敗");
return;
}
_size = 0;
_capacity = capacity;
}
~Stack()
{if (_array)
{ free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
注意:在編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)中,內(nèi)置類型是按照字節(jié)方式直接拷貝的,而自定義類型是調(diào)用其拷貝構(gòu)造函數(shù)完成拷貝的。
此時我們再使用編譯器默認(rèn)生成的拷貝構(gòu)造函數(shù),當(dāng)一個對象拷貝構(gòu)造另一個對象通過調(diào)試發(fā)現(xiàn),兩個對象中的數(shù)組維護(hù)的是同一個地址,這無疑是不符合我們的要求的,我們想要的是兩個互相獨(dú)立且相同的對象;因此我們需要深拷貝。
注意:類中如果沒有涉及資源申請時,拷貝構(gòu)造函數(shù)是否寫都可以;而一旦涉及到資源申請時,則拷貝構(gòu)造函數(shù)時一定要寫的,否則就是淺拷貝。
拷貝構(gòu)造函數(shù)典型調(diào)用場景:
賦值運(yùn)算符重載 運(yùn)算符重載注意:為了提高程序效率,一般對象傳參時,盡量使用引用類型,返回時根據(jù)實際場景,能用引用盡量使用引用。
首先,我們得知道運(yùn)算符重載是什么。
C++為了增強(qiáng)代碼的可讀性引入了運(yùn)算符重載,運(yùn)算符重載是具有特殊函數(shù)名的函數(shù),也具有返回值類型、函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似。
函數(shù)名為:關(guān)鍵字operator后面接需要重載的運(yùn)算符符號。
函數(shù)原型如下:
返回值類型 operator操作符(參數(shù)列表)
//例子
class Date
{public:
Date(int year = 1, int month = 1, int day = 1)
{_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
//運(yùn)算符重載
bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{Date d1(2022, 12, 12);
Date d2(2022, 12, 13);
d1 == d2;
//也可以寫成這樣operator==(d1,d2);
return 0;
}
注意:
- 不能通過連接其他符號來創(chuàng)建新的操作符,比如:operator@;
- 重載操作符必須有一個類類型參數(shù);
- 用于內(nèi)置類型的運(yùn)算符,其含義不能改變,例如:內(nèi)置整型+,不能改變其含義;
- 作為類成員函數(shù)重載時,其形參看起來比操作數(shù)數(shù)目少1,因為成員函數(shù)的第一個參數(shù)為隱含的this;
.*
::
sizeof
?:
.
以上5個運(yùn)算符不能重載。
那么為什么需要運(yùn)算符重載呢?
賦值運(yùn)算符重載在C++中,一些類需要進(jìn)行加減乘除的操作,而C++內(nèi)置的運(yùn)算符都只能用于內(nèi)置類型,因此需要我們自己實現(xiàn),而C++中提供了兩種方式:一是用一個函數(shù)實現(xiàn),二是運(yùn)算符重載,與函數(shù)對比無疑運(yùn)算符重載更具可讀性。
賦值運(yùn)算符重載格式
賦值運(yùn)算符只能重載成類的成員函數(shù)不能重載成全局函數(shù),因為一般類的成員變量是私有的。
賦值運(yùn)算符重載定義在類外有幾個缺點:
- 類外不能訪問類當(dāng)中的私有成員變量;
- 可能會與編譯器默認(rèn)生成的賦值運(yùn)算符發(fā)生沖突;
- 破壞封裝。
注意:內(nèi)置類型成員變量是直接賦值的,而自定義類型成員變量需要調(diào)用對應(yīng)類的賦值運(yùn)算符重載完成賦值。
//例子
class Date
{public:
Date(int year = 1, int month = 1, int day = 1)
{_year = year;
_month = month;
_day = day;
}
//運(yùn)算符重載
bool operator==(const Date& d)
{return _year == d._year
&& _month == d._month
&& _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{Date d1(2022, 12, 12);
Date d2(2022, 12, 13);
d1 == d2;
//也可以寫成這樣d1.operator==(d2); -->顯式調(diào)用
return 0;
}
const成員將const修飾的“成員函數(shù)”稱之為const成員函數(shù),const修飾類成員函數(shù),實際修飾該成員函數(shù)隱含的this指針,表明在該成員函數(shù)中不能對類的任何成員進(jìn)行修改。
取地址及const取地址操作符重載這兩個默認(rèn)成員函數(shù)一般不用重新定義,編譯器默認(rèn)會生成。
class Date
{public:
Date* operator&()
{return this;
}
const Date* operator&()const
{return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
這兩個運(yùn)算符一般不需要重載,使用編譯器生成的默認(rèn)取地址的重載即可,只有特殊情況才需要重載,比如想讓別人獲取到指定的內(nèi)容、又或者不想讓別取地址。
日期類的實現(xiàn)在類和對象這一章節(jié),日期類是一個很好的鍛煉機(jī)會,實現(xiàn)日期類能讓我們更好地掌握理解類和對象的知識點以及其中的細(xì)節(jié)。
日期類接口#includeclass Date
{friend ostream& operator<<(ostream& out, const Date& d);//友元函數(shù)
friend istream& operator>>(istream& in, Date& d);//友元函數(shù)
public:
// 獲取某年某月的天數(shù)
int GetMonthDay(int year, int month);
// 全缺省的構(gòu)造函數(shù)
Date(int year = 1900, int month = 1, int day = 1);
// 拷貝構(gòu)造函數(shù)
// d2(d1)
Date(const Date& d);
//日期類打印
void Print() const;
// 賦值運(yùn)算符重載
// d2 = d3 ->d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析構(gòu)函數(shù)
~Date();
// 日期+=天數(shù)
Date& operator+=(int day);
// 日期+天數(shù)
Date operator+(int day) const;
// 日期-天數(shù)
Date operator-(int day) const;
// 日期-=天數(shù)
Date& operator-=(int day);
// 前置++
Date & operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >運(yùn)算符重載
bool operator>(const Date& d) const;
// ==運(yùn)算符重載
bool operator==(const Date& d) const;
// >=運(yùn)算符重載
bool operator >= (const Date& d) const;
//<運(yùn)算符重載
bool operator< (const Date& d) const;
//<=運(yùn)算符重載
bool operator<= (const Date& d) const;
// !=運(yùn)算符重載
bool operator != (const Date& d) const;
// 日期-日期 返回天數(shù)
int operator-(const Date& d) const;
private:
int _year;
int _month;
int _day;
};
日期類實現(xiàn)
獲取某年某月的天數(shù)唯一需要注意的是,每個月的天數(shù)都不是固定的,尤其是二月需要分閏年和非閏年。
// 獲取某年某月的天數(shù)
int Date::GetMonthDay(int year, int month)
{static int monthday[] = {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 monthday[month];
}
}
構(gòu)造函數(shù)這里最好寫一個全缺省構(gòu)造函數(shù),這樣即使不傳參對象也會被初始化。
// 全缺省的構(gòu)造函數(shù)
Date::Date(int year, int month, int day) //注意缺省值在聲明的地方給出
{_year = year;
_month = month;
_day = day;
}
拷貝構(gòu)造函數(shù)拷貝構(gòu)造必須是傳引用傳參,并且最好加上const,這樣可以防止拷貝的實參被改變。
// 拷貝構(gòu)造函數(shù)
//日期類可以不寫拷貝構(gòu)造函數(shù),編譯器默認(rèn)生成的即可
Date::Date(const Date& d)
{_year = d._year;
_month = d._month;
_day = d._day;
}
打印日期C++ setw() 函數(shù)用于設(shè)置字段的寬度,語法格式如下: setw(n) n 表示寬度,用數(shù)字表示。 setw() 函數(shù)只對緊接著的輸出產(chǎn)生作用。 當(dāng)后面緊跟著的輸出字段長度小于 n 的時候,在該字段前面用空格補(bǔ)齊,當(dāng)輸出字段長度大于 n 時,全部整體輸出。
C++ 函數(shù) std::setfill 的行為就像在流上調(diào)用 c 作為參數(shù)的成員填充,它作為操縱器插入(它可以插入到輸出流上)。 它用于將 c 設(shè)置為流的填充字符。
//日期類打印
void Date::Print() const
{cout<< setw(2)<< setfill('0')<< _year<< "年"<< setw(2)<< setfill('0')<< _month<< "月"<< setw(2)<< setfill('0')<< _day<< "日"<< endl;
}
賦值運(yùn)算符重載// 賦值運(yùn)算符重載
// d2 = d3 ->d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
日期+=/+天數(shù)
日期+=天數(shù)按照內(nèi)置類型的+=運(yùn)算,+=會改變左操作數(shù)的值,并且返回運(yùn)算之后的左操作數(shù);那么在實現(xiàn)日期類的+=時,一要注意+=會改變左操作數(shù)的值,二要注意返回運(yùn)算之后的對象。
// 日期+=天數(shù)
Date& Date::operator+=(int day)
{_day += day;
while (_day >GetMonthDay(_year, _month)) //當(dāng)前天大于本月的天數(shù)時循環(huán)繼續(xù)
{_day -= GetMonthDay(_year, _month);
++_month;
//如果月滿了,年加一
if (_month >12)
{_month = 1;
++_year;
}
}
return *this;
}
日期+天數(shù)復(fù)用日期+=天數(shù)即可,注意日期+天數(shù)并不會改變?nèi)掌?,而是會產(chǎn)生一個臨時對象。
// 日期+天數(shù)
Date Date::operator+(int day) const
{Date tmp(*this);
return tmp += day;
}
日期-=/-天數(shù)
日期-=天數(shù)式子中的日期對象會被改變,式子的值為操作之后日期類對象的值。
// 日期-=天數(shù)
Date& Date::operator-=(int day)
{_day -= day;
//小于0表示這個月的天數(shù)已經(jīng)減完
if (_day< 0)
{--_month;
}
while (_day<= 0)
{--_month;
_day += GetMonthDay(_year, _month);
//月份減完,年減一
if (_month< 1)
{_month = 12;
--_year;
}
}
return *this;
}
日期-天數(shù)復(fù)用 日期-=天數(shù)即可。
// 日期-天數(shù)
Date Date::operator-(int day) const
{Date tmp(*this);
return tmp -= day;
}
前置和后置(++/–)前置和后置有所不同,前置操作并沒有參數(shù),為了和前置區(qū)分后置操作的參數(shù)多了一個int。(約定俗成)編譯器在識別為后置操作時會傳一個參數(shù)進(jìn)去但是這個參數(shù)并沒有使用只是為了區(qū)分。實現(xiàn)時需要注意兩點:
- 前置需要返回操作之后的值;
- 后置需要返回操作之前的值。
// 前置++
Date& Date::operator++()
{return *this += 1;
}
// 后置++
Date Date::operator++(int)
{Date tmp(*this);
*this += 1;
return tmp;
}
// 后置--
Date Date::operator--(int)
{Date tmp(*this);
*this -= 1;
return tmp;
}
// 前置--
Date& Date::operator--()
{return *this -= 1;
}
比較運(yùn)算符重載
==運(yùn)算符重載==即判斷日期是否相等,因此年月日都相等時返回真。
// ==運(yùn)算符重載
bool Date::operator==(const Date& d) const
{return _year == d._year
&& _month == d._month
&& _day == d._day;
}
>運(yùn)算符重載比較日期大小必須從年開始比較,年大則返回真,年相等則再比較月,此時月大則返回真,若月又相等則最后比較日,日大則返回真,否則以上三種情況皆不滿足返回假。
// >運(yùn)算符重載
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;
}
return false;
}
其它運(yùn)算符重載寫完==和>運(yùn)算符其它比較大小的運(yùn)算符復(fù)用即可實現(xiàn)。
// >=運(yùn)算符重載
bool Date::operator >= (const Date& d) const
{return *this >d || *this == d;
}
//<運(yùn)算符重載
bool Date::operator< (const Date& d) const
{return !(*this >= d);
}
//<=運(yùn)算符重載
bool Date::operator<= (const Date& d) const
{return !(*this >d);
}
// !=運(yùn)算符重載
bool Date::operator != (const Date& d) const
{return !(*this == d);
}
日期-日期日期-日期是有意義的,比如:我想知道過去或者未來某天距離當(dāng)前的天數(shù)就需要使用。日期-日期 需要注意我們不知道前后日期的大小,因為日期-日期可以為負(fù)數(shù),因此首先要區(qū)別其中的大小日期;區(qū)分了大小就很好辦了,小的日期一直累加循環(huán)到等于大的日期,循環(huán)次數(shù)就是兩個日期相差的天數(shù)。
// 日期-日期 返回天數(shù)
int Date::operator-(const Date& d) const
{//區(qū)分大小
Date min = d;
Date max = *this;
int flag = 1;
if (*this< d)
{min = *this;
max = d;
flag = -1;
}
//計算相差天數(shù)
int count = 0;//計數(shù)
while (min != max)
{++min;
++count;
}
return count*flag;
}
流插入和流提取
流插入cout方法一:
流插入重載不能寫成成員函數(shù)因為重載之后左邊第一個參數(shù)對象默認(rèn)傳給this指針,而流插入左邊是cout。在后面我們會學(xué)習(xí)友元函數(shù),即友元函數(shù)能訪問類當(dāng)中的所有成員。
//友元格式
friend 返回類型 函數(shù)名(參數(shù)列表);
//流插入重載
流插入是屬于ostream
ostream& operator<<(ostream& out, const Date& d)
{cout<< setw(2)<< setfill('0')<< d._year<< "年"<< setw(2)<< setfill('0')<< d._month<< "月"<< setw(2)<< setfill('0')<< d._day<< "日"<< endl;
return out; //為了能連續(xù)流插入
}
方法二:
這種方式需要把是有成員變量變成公有的,會破壞封裝。
inline ostream& operator<<(ostream& out, const Date& d)
{cout<< setw(2)<< setfill('0')<< d._year<< "年"<< setw(2)<< setfill('0')<< d._month<< "月"<< setw(2)<< setfill('0')<< d._day<< "日"<< endl;
return out; //為了能連續(xù)流插入
}
流提取cin流提取和流插入類似,最優(yōu)使用友元函數(shù)。
//流提取是屬于istream
istream& operator>>(istream& in, Date& d)
{in >>d._year >>d._month >>d._day;
return cin; //為了能連續(xù)進(jìn)行流提取
}
日期類測試#include "Date.h"
void TestDate()
{Date d1(2022, 12, 17);
Date d2(2022, 12, 10);
Date d3(d1);
Date d4;
//d3 += 30;
d4 = d2 - 10;
//d2 -= 10;
d1 = d1++;
++d2;
--d3;
d4 = d4--;
d1.Print();
d2.Print();
d3.Print();
d4.Print();
}
void TestDateCompare()
{Date d1(2021, 12, 17);
Date d2(2022, 12, 10);
Date d3(d1);
Date d4(2019, 9, 8);
if (d1 == d3)
{cout<< "=="<< endl;
}
if (d1 >= d3)
{cout<< ">="<< endl;
}
if (d1<= d3)
{cout<< "<="<< endl;
}
cout<< d4 - d1<< endl;
}
void TestCoutCin()
{Date d1;
Date d2;
cin >>d1 >>d2;
cout<< d1<< d2;
}
int main()
{TestDateCompare();
TestDate();
TestCoutCin()
return 0;
}
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧