用html5做的網(wǎng)站素材山東seo百度推廣
目錄
1.類的6個(gè)默認(rèn)成員函數(shù)?
2. 構(gòu)造函數(shù)
2.1 構(gòu)造函數(shù)的概念
2.2 構(gòu)造函數(shù)的特性
3. 析構(gòu)函數(shù)
3.1 析構(gòu)函數(shù)的概念
3.2 析構(gòu)函數(shù)的特性
4.拷貝構(gòu)造函數(shù)
4.1 拷貝構(gòu)造函數(shù)的概念
4.2 拷貝構(gòu)造函數(shù)的特性
5.賦值運(yùn)算符重載函數(shù)
5.1運(yùn)算符重載函數(shù)
5.2 賦值運(yùn)算符重載函數(shù)
?5.3?前置++和后置++重載
前置++:
后置++:
6.(&)取地址操作符重載函數(shù)和const取地址操作符重載函數(shù)
1.類的6個(gè)默認(rèn)成員函數(shù)?
如果一個(gè)類中什么成員都沒(méi)有,簡(jiǎn)稱為空類??墒强疹愔姓娴氖裁炊紱](méi)有嗎?
其實(shí)并不是的,任何類在什么都不寫(xiě)時(shí),編譯器會(huì)自動(dòng)生成以下6個(gè)默認(rèn)成員函數(shù)。 默認(rèn)成員函數(shù):用戶沒(méi)有顯式實(shí)現(xiàn),編譯器會(huì)生成的成員函數(shù)稱為默認(rèn)成員函數(shù)。
類的6個(gè)默認(rèn)成員函數(shù)編譯器都會(huì)自己生成,如果編譯器生成的默認(rèn)成員函數(shù)能夠滿足我們的需求,我們就無(wú)需再自己實(shí)現(xiàn);
相反,如果編譯器生成的默認(rèn)成員函數(shù)不能滿足我們的需求,我們就必須要自己實(shí)現(xiàn)了。
本篇博客正是介紹類的這6個(gè)默認(rèn)成員函數(shù)都有哪些特性,講述什么情況下只需使用默認(rèn)成員函數(shù),什么情況下需要自己實(shí)現(xiàn)以及要怎樣實(shí)現(xiàn)的問(wèn)題!
2. 構(gòu)造函數(shù)
2.1 構(gòu)造函數(shù)的概念
如下代碼,我們定義一個(gè)日期類并且調(diào)用成員函數(shù):
#include <iostream>
using namespace std;class Data
{
public:void Init(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()
{Data d1;d1.Init(2023, 5, 23);d1.Print();Data d2;d2.Init(2022, 5, 23);d2.Print();return 0;
}
?
按照我們之前學(xué)過(guò)的,按部就班地先調(diào)用初始化成員函數(shù),再調(diào)用打印成員函數(shù),運(yùn)行結(jié)果也中規(guī)中矩地跑出來(lái)了。
可是有一天,我需要很多個(gè)Data變量,寫(xiě)代碼又太急躁,在創(chuàng)建某個(gè)Data變量時(shí)忘記調(diào)用Init成員函數(shù)了,如下所示:
#include <iostream>
using namespace std;class Data
{
public:void Init(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()
{Data d1;d1.Init(2023, 5, 23);d1.Print();Data d2;d2.Print();return 0;
}
結(jié)果d2出現(xiàn)了隨機(jī)值:
通過(guò)以上贅述:對(duì)于Data類,可以通過(guò)Init公有方法給對(duì)象設(shè)置日期,如果忘記一次初始化就會(huì)導(dǎo)致bug的產(chǎn)生,那就不得不每次創(chuàng)建對(duì)象時(shí)都調(diào)用該方法設(shè)置信息,可是這樣是不是有些太麻煩了呢?有沒(méi)有方法在對(duì)象創(chuàng)建時(shí),就將對(duì)象設(shè)置進(jìn)去呢?
答案當(dāng)然是有的,這就引出了C++的1個(gè)默認(rèn)成員函數(shù)——構(gòu)造函數(shù):
構(gòu)造函數(shù)是一個(gè)特殊的成員函數(shù),名字與類名相同,創(chuàng)建類類型對(duì)象時(shí)由編譯器自動(dòng)調(diào)用,以保證每個(gè)數(shù)據(jù)成員都有一個(gè)合適的初始值,并且在對(duì)象整個(gè)生命周期內(nèi)只調(diào)用一次。
2.2 構(gòu)造函數(shù)的特性
構(gòu)造函數(shù)是特殊的成員函數(shù),需要注意的是,構(gòu)造函數(shù)沒(méi)有用我們經(jīng)常熟悉的Init來(lái)命名,雖然名稱叫構(gòu)造,但是構(gòu)造函數(shù)的主要任 務(wù)并不是開(kāi)空間創(chuàng)建對(duì)象,而是初始化對(duì)象。
其特征如下:
1. 函數(shù)名與類名相同;
2. 無(wú)返回值;
3. 對(duì)象實(shí)例化時(shí)編譯器自動(dòng)調(diào)用對(duì)應(yīng)的構(gòu)造函數(shù);
4. 構(gòu)造函數(shù)可以重載;
驗(yàn)證如下:
#include <iostream>
using namespace std;class Data
{
public://退出歷史舞臺(tái):/*void Init(int year, int month, int day){_year = year;_month = month;_day = day;}*///1. 函數(shù)名與類名相同;2. 無(wú)返回值;Data(int year, int month, int day){_year = year;_month = month;_day = day;}//4. 構(gòu)造函數(shù)可以重載;Data(){_year = 8;_month = 8;_day = 8;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{//調(diào)用含參構(gòu)造函數(shù):Data d1(2023, 5, 23); //3. 對(duì)象實(shí)例化時(shí)編譯器自動(dòng)調(diào)用對(duì)應(yīng)的構(gòu)造函數(shù);d1.Print();//調(diào)用無(wú)參構(gòu)造函數(shù):Data d2; //注意這里不能用諸如:Data d2();不能加(),因?yàn)闀?huì)與函數(shù)聲明產(chǎn)生歧義;d2.Print();return 0;
}
當(dāng)然這里也完全可以用到缺省參數(shù):
#include <iostream>
using namespace std;class Data
{
public:Data(int year = 8, int month = 8, int day = 8){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Data d1(2023, 5, 23); d1.Print();Data d2(2023, 5);d2.Print();Data d3;d3.Print();return 0;
}
5.?如果類中沒(méi)有顯式定義構(gòu)造函數(shù),則C++編譯器會(huì)自動(dòng)生成一個(gè)無(wú)參的默認(rèn)構(gòu)造函數(shù),一旦用戶顯式定義編譯器將不再生成。
代碼驗(yàn)證如下:
#include <iostream>
using namespace std;class Data
{
public:如果顯示定義,編譯器將不再生成//Data(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()
{Data d1;d1.Print();return 0;
}
將Date類中構(gòu)造函數(shù)屏蔽后,代碼可以通過(guò)編譯,因?yàn)榫幾g器生成了一個(gè)無(wú)參的默認(rèn)構(gòu)造函數(shù):
將Date類中構(gòu)造函數(shù)放開(kāi),代碼編譯失敗,因?yàn)橐坏╋@式定義任何構(gòu)造函數(shù),編譯器將不再生成無(wú)參構(gòu)造函數(shù),放開(kāi)后報(bào)錯(cuò):error C2512: “Date”: 沒(méi)有合適的默認(rèn)構(gòu)造函數(shù)可用:
這時(shí)你可能要問(wèn)了:
在不顯示定義構(gòu)造函數(shù)的情況下,編譯器會(huì)生成默認(rèn)的構(gòu)造函數(shù)。但是看起來(lái)默認(rèn)構(gòu)造函數(shù)似乎并沒(méi)有什么用處呀!?
上面d1對(duì)象調(diào)用了編譯器生成的默認(rèn)構(gòu)造函數(shù),但是d1的對(duì)象_year/_month/_day,結(jié)果顯示依舊是隨機(jī)值,上面的運(yùn)行結(jié)果就是鐵錚錚的事實(shí)呀!這不是恰恰證明了這里編譯器生成的默認(rèn)構(gòu)造函數(shù)并沒(méi)有什么卵用嗎?
這就涉及到了構(gòu)造函數(shù)的第6個(gè)特性:
6.C++把類型分成內(nèi)置類型(基本類型)和自定義類型。內(nèi)置類型就是語(yǔ)言提供的數(shù)據(jù)類型,如:int/char...,自定義類型就是我們使用class/struct/union等自己定義的類型,編譯器生成默認(rèn)的構(gòu)造函數(shù)會(huì)對(duì)自定義類型成員調(diào)用的它的默認(rèn)成員函數(shù),而內(nèi)置類型則不做處理。
代碼驗(yàn)證如下:
#include <iostream>
using namespace std;class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{public:
void Print()
{cout << _year << "-" << _month << "-" << _day << endl;
}
private:// 基本類型(內(nèi)置類型)int _year;int _month;int _day;// 自定義類型Time _t;
};
int main()
{Date d;d.Print();return 0;
}
說(shuō)到這里,我又有些不解,同樣都是變量,為什么還要分自定義類型調(diào)用它的默認(rèn)成員函數(shù),內(nèi)置類型卻不做處理呢?這難道不是一件畫(huà)蛇添足的事情嗎?
這次不否定了,說(shuō)的確實(shí)有道理,所以在C++11 中針對(duì)內(nèi)置類型成員不初始化的缺陷,又打了補(bǔ)丁,即:內(nèi)置類型成員變量在類中聲明時(shí)可以給默認(rèn)值。
代碼驗(yàn)證如下:
#include <iostream>
using namespace std;
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:// 基本類型(內(nèi)置類型)// C++11支持,聲明時(shí)給缺省值int _year = 2023;int _month = 5;int _day = 23;// 自定義類型Time _t;
};
int main()
{Date d;d.Print();return 0;
}
思考如下代碼能否正常運(yùn)行:
#include <iostream>
using namespace std;
class Date
{
public:Date(){_year = 2023;_month = 5;_day = 23;}Date(int year = 2023, int month = 5, int day = 23){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
// 以下測(cè)試函數(shù)能正常運(yùn)行嗎?
void Test()
{Date d1;
}
答案是否定的:
針對(duì)以上現(xiàn)象,可以引出構(gòu)造函數(shù)的第7個(gè)特性:
7.無(wú)參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都稱為默認(rèn)構(gòu)造函數(shù),并且默認(rèn)構(gòu)造函數(shù)只能有一個(gè)。 注意:無(wú)參構(gòu)造函數(shù)、全缺省構(gòu)造函數(shù)、我們沒(méi)寫(xiě)編譯器默認(rèn)生成的構(gòu)造函數(shù),都可以認(rèn)為是默認(rèn)構(gòu)造函數(shù)。?
3. 析構(gòu)函數(shù)
3.1 析構(gòu)函數(shù)的概念
通過(guò)上面構(gòu)造函數(shù)的學(xué)習(xí),我們知道一個(gè)對(duì)象是怎么來(lái)的,那一個(gè)對(duì)象又是怎么沒(méi)的呢?這就需要我們學(xué)習(xí)析構(gòu)函數(shù)了:
析構(gòu)函數(shù):與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完成對(duì)對(duì)象本身的銷毀,局部對(duì)象銷毀工作是由編譯器完成的。而對(duì)象在銷毀時(shí)會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),完成對(duì)象中資源的清理工作。
3.2 析構(gòu)函數(shù)的特性
析構(gòu)函數(shù)也是特殊的成員函數(shù),其特征如下:
1. 析構(gòu)函數(shù)名是在類名前加上字符 ~;
2. 無(wú)參數(shù)無(wú)返回值類型;
3. 一個(gè)類只能有一個(gè)析構(gòu)函數(shù)。若未顯式定義,系統(tǒng)會(huì)自動(dòng)生成默認(rèn)的析構(gòu)函數(shù)。注意:與構(gòu)造函數(shù)不同的是,析構(gòu)函數(shù)不能重載;
4. 對(duì)象生命周期結(jié)束時(shí),C++編譯系統(tǒng)自動(dòng)調(diào)用析構(gòu)函數(shù);
#include <iostream>
using namespace std;class Data
{
public:Data(int year = 2023, int month = 5, int day = 23){_year = year;_month = month;_day = day;}void Ptint(){cout << _year << "-" << _month << "-" << _day << endl;}//1. 析構(gòu)函數(shù)名是在類名前加上字符 ~;2. 無(wú)參數(shù)無(wú)返回值類型;~Data(){cout << "~Data" << endl;}private:int _year;int _month;int _day;
};
int main()
{Data d;d.Ptint();//這里為調(diào)用~Data,4. 對(duì)象生命周期結(jié)束時(shí),C++編譯系統(tǒng)自動(dòng)調(diào)用析構(gòu)函數(shù);return 0;
}
當(dāng)然,Data類并不需要析構(gòu)函數(shù),這里只是為了證明C++自動(dòng)調(diào)用了析構(gòu)函數(shù)。
我們將析構(gòu)函數(shù)用到順序表中,可能會(huì)對(duì)析構(gòu)函數(shù)有更深刻的理解:
#include <iostream>
using namespace std;typedef int DataType;class SeqList
{
public:SeqList(){cout << "已經(jīng)調(diào)用了SeqList()構(gòu)造函數(shù);" << endl;_a = (DataType*)malloc(sizeof(DataType) * 4);if (_a == nullptr){perror("malloc failed");//如果擴(kuò)容失敗,說(shuō)明原因exit(-1);}_size = 0;//當(dāng)size≥capacity時(shí)就動(dòng)態(tài)開(kāi)辟空間_capacity = 4;//初始化數(shù)組容量為4}~SeqList(){cout << "已經(jīng)調(diào)用了~SeqList()析構(gòu)函數(shù);" << endl;free(_a);_a = nullptr;_size = _capacity = 0;}private:int* _a;int _size;int _capacity;
};int main()
{SeqList sl;return 0;
}
對(duì)于第3條特性,系統(tǒng)自動(dòng)生成默認(rèn)的析構(gòu)函數(shù),會(huì)不會(huì)完成一些事情呢?
5. 答案與構(gòu)造函數(shù)相似,編譯器生成的默認(rèn)析構(gòu)函數(shù),對(duì)自定義類型成員調(diào)用它的析構(gòu)函數(shù),而內(nèi)置類型則不做處理。
代碼驗(yàn)證如下:
#include <iostream>
using namespace std;class Time
{
public:~Time(){cout << "已經(jīng)調(diào)用了~Time()析構(gòu)函數(shù)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內(nèi)置類型)int _year = 2023;int _month = 5;int _day = 23;// 自定義類型Time _t;
};
int main()
{Date d;return 0;
}
運(yùn)行結(jié)果為:
對(duì)以上結(jié)果和第3、第5條特性的詳細(xì)解釋:
程序運(yùn)行結(jié)束后輸出:“已經(jīng)調(diào)用了~Time()析構(gòu)函數(shù)”,在main中根本沒(méi)有直接創(chuàng)建Time類的對(duì)象,為什么最后會(huì)調(diào)用Time類的析構(gòu)函數(shù)?
因?yàn)?#xff1a;main中創(chuàng)建了Date對(duì)象d,而d中包含4個(gè)成員變量,其中_year, _month, _day三個(gè)是內(nèi)置類型成員,銷毀時(shí)不需要資源清理,最后系統(tǒng)直接將其內(nèi)存回收即可;
而_t是Time類對(duì)象,所以在d銷毀時(shí),要將其內(nèi)部包含的Time類的_t對(duì)象銷毀,所以要調(diào)用Time類的析構(gòu)函數(shù);
但是:main函數(shù)中不能直接調(diào)用Time類的析構(gòu)函數(shù),實(shí)際要釋放的是Date類對(duì)象,所以編譯器會(huì)調(diào)用Date類的析構(gòu)函數(shù),而Date沒(méi)有顯式提供,則編譯器會(huì)給Date類生成一個(gè)默認(rèn)的析構(gòu)函數(shù),目的是在其內(nèi)部調(diào)用Time類的析構(gòu)函數(shù);
即:當(dāng)Date對(duì)象銷毀時(shí),要保證其內(nèi)部每個(gè)自定義對(duì)象都可以正確銷毀,main函數(shù)中并沒(méi)有直接調(diào)用Time類析構(gòu)函數(shù),而是顯式調(diào)用編譯器為Date類生成的默認(rèn)析構(gòu)函數(shù);
注意:創(chuàng)建哪個(gè)類的對(duì)象則調(diào)用該類的構(gòu)造函數(shù),銷毀哪個(gè)類的對(duì)象則調(diào)用該類的析構(gòu)函數(shù)
6. 如果類中沒(méi)有申請(qǐng)資源時(shí),析構(gòu)函數(shù)可以不寫(xiě),直接使用編譯器生成的默認(rèn)析構(gòu)函數(shù),比如 Date類;有資源申請(qǐng)時(shí),一定要寫(xiě),否則會(huì)造成資源泄漏,比如SeqList類。?
4.拷貝構(gòu)造函數(shù)
4.1 拷貝構(gòu)造函數(shù)的概念
電視劇中以及現(xiàn)實(shí)中,雙胞胎的例子不在少數(shù),我們甚至可以說(shuō)簡(jiǎn)直他們就是一個(gè)模子里刻出來(lái)的!那么,在創(chuàng)建對(duì)象時(shí),可否創(chuàng)建一個(gè)與已存在對(duì)象一某一樣的新對(duì)象呢?答案的肯定的。
拷貝構(gòu)造函數(shù):只有單個(gè)形參,該形參是對(duì)本類 類型對(duì)象的引用(一般常用const修飾),在用已存在的類 類型對(duì)象創(chuàng)建新對(duì)象時(shí)由編譯器自動(dòng)調(diào)用。
4.2 拷貝構(gòu)造函數(shù)的特性
1. 拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式。即:拷貝構(gòu)造函數(shù)是一個(gè)特殊的構(gòu)造函數(shù);
2. 拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須是類 類型對(duì)象的引用,使用傳值方式編譯器直接報(bào)錯(cuò),因?yàn)闀?huì)引發(fā)無(wú)窮遞歸調(diào)用;
代碼驗(yàn)證如下:
不考慮特性2,我們偏偏就要直接傳值調(diào)用:
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 2023, int month = 7, int day = 7){_year = year;_month = month;_day = day;}Date( Date d) {_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Print();Date d2(d1);d2.Print();return 0;
}
會(huì)發(fā)現(xiàn)程序報(bào)錯(cuò):
這是為什么呢?答案就像特征2中所說(shuō)的,在此過(guò)程中引發(fā)了無(wú)窮遞歸調(diào)用:
當(dāng)我們直接傳值調(diào)用時(shí),會(huì)發(fā)生先傳值再調(diào)用拷貝構(gòu)造函數(shù)的情況,即:
所以正確應(yīng)該如特性2所說(shuō)的那樣:
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 2023, int month = 7, int day = 7){_year = year;_month = month;_day = day;}// Date( Date d) // 錯(cuò)誤寫(xiě)法:編譯報(bào)錯(cuò),會(huì)引發(fā)無(wú)窮遞歸Date( Date& d) // 正確寫(xiě)法{_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Print();Date d2(d1);d2.Print();return 0;
}
那么在概念中又提到:(一般常用const修飾),這是為什么呢?
這是為了防止我們?cè)诙x拷貝構(gòu)造函數(shù)時(shí)寫(xiě)反了:
Date( Date& d) {d._year = _year;d._month = _month;d._day = _day;}
那么運(yùn)行結(jié)果不但不會(huì)正確,反而會(huì)偷雞不成蝕把米:
所以加上const,即使出現(xiàn)了這樣的低級(jí)錯(cuò)誤,編譯器就會(huì)報(bào)錯(cuò),我們也能及時(shí)發(fā)現(xiàn):
Date( const Date& d) {d._year = _year;d._month = _month;d._day = _day;}
正確代碼:
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 2023, int month = 7, int day = 7){_year = year;_month = month;_day = day;}// Date( Date d) // 錯(cuò)誤寫(xiě)法:編譯報(bào)錯(cuò),會(huì)引發(fā)無(wú)窮遞歸Date( const Date& d) // 正確寫(xiě)法{_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Print();Date d2(d1);d2.Print();return 0;
}
3.與構(gòu)造函數(shù)和析構(gòu)函數(shù)相似,若未顯式定義,編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)。 默認(rèn)的拷貝構(gòu)造函數(shù)對(duì)象按內(nèi)存存儲(chǔ)按字節(jié)序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。
在編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)中,內(nèi)置類型是按照字節(jié)方式直接拷貝的,而自定義類型是調(diào)用其拷貝構(gòu)造函數(shù)完成拷貝的。
代碼驗(yàn)證如下:
#include <iostream>
using namespace std;
class Time
{
public:Time(){_hour = 8;_minute = 8;_second = 8;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "已經(jīng)調(diào)用了!Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:// 基本類型(內(nèi)置類型)int _year = 2023;int _month = 7;int _day = 7;// 自定義類型Time _t;
};
int main()
{Date d1;// 用已經(jīng)存在的d1拷貝構(gòu)造d2,此處會(huì)調(diào)用Date類的拷貝構(gòu)造函數(shù)// 但Date類并沒(méi)有顯式定義拷貝構(gòu)造函數(shù),則編譯器會(huì)給Date類生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)Date d2(d1);d2.Print();return 0;
}
與前面的構(gòu)造函數(shù)和析構(gòu)函數(shù)相似的問(wèn)題:編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)已經(jīng)可以完成字節(jié)序的值拷貝了,還需要自己顯式實(shí)現(xiàn)嗎?
當(dāng)然像上面的Data類這樣的類是沒(méi)必要的。那么像順序表之類的類呢?驗(yàn)證如下:
#include <iostream>
using namespace std;typedef int DataType;class SeqList
{
public:SeqList(){cout << "已經(jīng)調(diào)用了SeqList()構(gòu)造函數(shù);" << endl;_a = (DataType*)malloc(sizeof(DataType) * 4);if (_a == nullptr){perror("malloc failed");//如果擴(kuò)容失敗,說(shuō)明原因exit(-1);}_size = 0;//當(dāng)size≥capacity時(shí)就動(dòng)態(tài)開(kāi)辟空間_capacity = 4;//初始化數(shù)組容量為4}//打印void Print(){for (int i = 0; i < _size; i++){cout << _a[i] << endl;}}//尾插void PushBack(const DataType& x){_a[_size] = x;_size++;}~SeqList(){cout << "已經(jīng)調(diào)用了~SeqList()析構(gòu)函數(shù);" << endl;free(_a);_a = nullptr;_size = _capacity = 0;}private:int* _a;int _size;int _capacity;
};int main()
{SeqList sl1;sl1.PushBack(1);sl1.PushBack(2);sl1.PushBack(3);sl1.PushBack(4);sl1.Print();SeqList sl2(sl1);sl2.Print();return 0;
}
運(yùn)行結(jié)果如圖:
我們可以看到,程序崩潰了!這是為什么呢?
打開(kāi)監(jiān)視窗口看一下sl1和sl2的內(nèi)存地址:
發(fā)現(xiàn)二者的地址相同,所以我們就知道了:?
1.sl1對(duì)象調(diào)用構(gòu)造函數(shù)創(chuàng)建,在構(gòu)造函數(shù)中,申請(qǐng)了(_capacity)4個(gè)元素的空間,然后里面存儲(chǔ)了4個(gè)元素:1 2 3 4;
2. sl2對(duì)象使用sl1對(duì)象拷貝構(gòu)造,而SeqList類沒(méi)有顯示定義拷貝構(gòu)造函數(shù),則編譯器會(huì)給SeqList類生成一份默認(rèn)的拷貝構(gòu)造函數(shù),默認(rèn)拷貝構(gòu)造函數(shù)是按照值拷貝的,即將sl1中的內(nèi)容原封不動(dòng)地拷貝到sl2中。因此sl1與sl2指向了同一塊內(nèi)存空間;
3. 當(dāng)程序退出時(shí),sl2和sl1要銷毀。sl2先銷毀,sl2銷毀時(shí)調(diào)用析構(gòu)函數(shù),已經(jīng)將0x00b59580的空間釋放了,但是sl1并不知道,到sl1銷毀時(shí),會(huì)將0x00b59580的空間再釋放一次(正如3.2的第5條特性說(shuō)的那樣),一塊內(nèi)存空間多次釋放,必然會(huì)導(dǎo)致bug的產(chǎn)生。
現(xiàn)在我已經(jīng)知道原因了,那么正確的代碼應(yīng)該怎么寫(xiě)呢?這就需要用到深拷貝去解決(關(guān)于深拷貝后面會(huì)有詳解):
//自定義拷貝構(gòu)造函數(shù),不用編譯器默認(rèn)生成的(深拷貝)SeqList( const SeqList& sl){_a = (DataType*)malloc(sizeof(DataType) * 4);//我也開(kāi)辟一個(gè)空間if (_a == nullptr){perror("malloc failed");//如果擴(kuò)容失敗,說(shuō)明原因exit(-1);}memcpy(_a, sl._a, sizeof(int) * sl._capacity);_size = sl._size;_capacity = sl._capacity;}
所以,我們應(yīng)該要明白:
4.類中如果沒(méi)有涉及資源申請(qǐng)時(shí),拷貝構(gòu)造函數(shù)是否寫(xiě)都可以;一旦涉及到資源申請(qǐng) 時(shí),則拷貝構(gòu)造函數(shù)是一定要寫(xiě)的,否則就是淺拷貝。
5.賦值運(yùn)算符重載函數(shù)
5.1運(yùn)算符重載函數(shù)
在學(xué)習(xí)賦值運(yùn)算符重載之前,我們先來(lái)了解一下運(yùn)算符重載:
通過(guò)上面的學(xué)習(xí),我們已經(jīng)知道了內(nèi)置類型和自定義類型的區(qū)別,思考這樣一個(gè)問(wèn)題:
顯而易見(jiàn),內(nèi)置類型對(duì)象可以直接用各種運(yùn)算符,內(nèi)置類型是語(yǔ)言自己定義的,編譯直接轉(zhuǎn)換成指令
舉個(gè)簡(jiǎn)單的例子,內(nèi)置類型的int類型2和1,編譯器可以輕松知道2>1;內(nèi)置類型的double類型2.2和1.1,編譯器輕松知道2.2>1.1,諸如此類......
那么問(wèn)題來(lái)了,我們通篇寫(xiě)的Data類對(duì)象,我這時(shí)候需要判斷2012年7月7日與2013年7月7日哪個(gè)日期更大,編譯器能直接判斷出2012年7月7日<2013年7月7日嗎?顯然是不能的!因?yàn)樽远x類型編譯器不支持直接轉(zhuǎn)換成指令。
那么這時(shí)候就需要我們自己寫(xiě)一個(gè)函數(shù)來(lái)實(shí)現(xiàn):
寫(xiě)一個(gè)大于比較函數(shù):
bool Greater(Data d1, Data d2)
{if (d1._year > d2._year){return true;}else if (d1._year == d2._year && d1._month > d2._month){return true;}else if (d1._year == d2._year && d1._month ==d2._month && d1._day > d2._day){return true;}return false;
}
再比如我寫(xiě)一個(gè)等于比較的函數(shù):
bool Equal(Data d1, Data d2)
{return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
運(yùn)行一下:
#include <iostream>
using namespace std;
class Data
{
public:Data(int year = 2012, int month = 7, int day = 7){_year = year;_month = month;_day = day;}Data(const Data& d){_year = d._year;_month = d._month;_day = d._day;}void Ptint(){cout << _year << "-" << _month << "-" << _day << endl;}//private:int _year;int _month;int _day;
};//布爾類型(bool)用于表示真(true)和假(false)的值。
//它只有兩個(gè)取值:true 和 false,分別對(duì)應(yīng) 1 和 0 。
bool Greater(Data d1, Data d2)
{if (d1._year > d2._year){return true;}else if (d1._year == d2._year && d1._month > d2._month){return true;}else if (d1._year == d2._year && d1._month ==d2._month && d1._day > d2._day){return true;}return false;
}bool Equal(Data d1, Data d2)
{return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}int main()
{Data d1(2013, 7, 7);Data d2(2012, 7, 7);cout << Greater(d1, d2) << endl;cout << Equal(d1, d2) << endl; return 0;
}
說(shuō)到這里,我們來(lái)說(shuō)一個(gè)題外話,關(guān)于函數(shù)的命名,其實(shí)在C語(yǔ)言中我們就遇到過(guò)很多了,一個(gè)函數(shù)命名就如同給自己的孩子取名字一樣,比如上面的判斷大于和判斷相等函數(shù),我能用Greater、Equal,為什么就不能用DaYu、DengYu,或者Compare1、Compare2,又或者func1、func2呢?
這些確實(shí)都是可以的呀!我創(chuàng)建的函數(shù),我樂(lè)意怎樣取名我就怎樣取名!
可是話說(shuō)回來(lái),你的孩子在你口中叫狗蛋兒、在老師口中叫張三,有一天你去開(kāi)家長(zhǎng)會(huì),老師問(wèn)你是誰(shuí)的家長(zhǎng),你說(shuō)你是狗蛋兒的家長(zhǎng),你這樣說(shuō)老師會(huì)知道張三就是狗蛋兒,狗蛋兒就是張三嗎?
話再說(shuō)回來(lái),你寫(xiě)的函數(shù)叫DaYu、DengYu,而你的同事要用這個(gè)函數(shù),你寫(xiě)的DaYu、DengYu,同事能知道這是個(gè)什么函數(shù)嗎?
所以,為了規(guī)避這種情況,增強(qiáng)代碼的可讀性,C++引入了運(yùn)算符重載,運(yùn)算符重載是具有特殊函數(shù)名的函數(shù),也具有返回值類型,函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似。
函數(shù)名字為:關(guān)鍵字operator后面接需要重載的運(yùn)算符符號(hào)。
函數(shù)原型:返回值類型?operator操作符(參數(shù)列表)
因此上述兩個(gè)函數(shù)就可以寫(xiě)為(更加規(guī)范,我們加上const和&):
//bool Greater(Data d1, Data d2)
bool operator>(const Data& d1, const Data& d2)
{if (d1._year > d2._year){return true;}else if (d1._year == d2._year && d1._month > d2._month){return true;}else if (d1._year == d2._year && d1._month ==d2._month && d1._day > d2._day){return true;}return false;
}//bool Equal(Data d1, Data d2)
bool operator == (const Data& d1, const Data& d2)
{return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
int main()
{Data d1(2013, 7, 7);Data d2(2012, 7, 7);/*cout << Greater(d1, d2) << endl;cout << Equal(d1, d2) << endl; */cout << operator > (d1, d2) << endl;cout << operator == (d1, d2) << endl;return 0;
}
以及為了令自定義類型更貼合與內(nèi)置類型一樣讓編譯器自己計(jì)算,直接轉(zhuǎn)為指令:
int main()
{Data d1(2013, 7, 7);Data d2(2012, 7, 7);/*cout << Greater(d1, d2) << endl;cout << Equal(d1, d2) << endl; *//*cout << operator > (d1, d2) << endl;cout << operator == (d1, d2) << endl;*/bool ret1 = d1 > d2; //d1>d2嗎?是為1,否為0;bool ret2 = d1 == d2; //d1=d2嗎?是為1,否為0;int a = 3 > 2; //3>2嗎?是為1,否為0;int b = 3 == 2; //3=2嗎?是為1,否為0;cout << ret1 << endl << ret2 << endl;cout << a << endl << b << endl;return 0;
}
仔細(xì)觀察我們上面寫(xiě)的Data類,可以發(fā)現(xiàn)我把private注釋掉了,那現(xiàn)在我把注釋關(guān)掉:
成員變量變私有了該怎么辦呢?其實(shí)C++常用的解決方法是直接將函數(shù)放到類里面,因?yàn)轭惱锩婵梢噪S便訪問(wèn)private:
class Data
{
public:Data(int year = 2012, int month = 7, int day = 7){_year = year;_month = month;_day = day;}Data(const Data& d){_year = d._year;_month = d._month;_day = d._day;}bool operator == (const Data& d1, const Data& d2){return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;}void Ptint(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};
可是這樣寫(xiě)發(fā)現(xiàn)還是編譯不通過(guò):
它說(shuō)函數(shù)參數(shù)太多?!我放到類外面參數(shù)就不多,怎么放到類里面就多參數(shù)了呢?
對(duì)!因?yàn)榇嬖谝粋€(gè)隱含的this指針(詳細(xì)請(qǐng)看C++入門2——類與對(duì)象(1)中的3),所以這時(shí)就可以這樣修改:
class Data
{
public:Data(int year = 2012, int month = 7, int day = 7){_year = year;_month = month;_day = day;}Data(const Data& d){_year = d._year;_month = d._month;_day = d._day;}bool operator>(const Data& d2){if (_year > d2._year){return true;}else if (_year == d2._year && _month > d2._month){return true;}else if (_year == d2._year && _month == d2._month && _day > d2._day){return true;}return false;}bool operator == ( const Data& d2){return _year == d2._year && _month == d2._month && _day == d2._day;}void Ptint(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};
那么在函數(shù)調(diào)用時(shí),編譯器就會(huì)幫我們負(fù)重前行:
int main()
{Data d1(2013, 7, 7);Data d2(2012, 7, 7);bool ret1 = d1 > d2; //d1.operator>(d2)--->d1.operator>(&d1,d2)bool ret2 = d1 == d2; //d1.operator==(d2)--->d1.operator==(&d1,d2)cout << ret1 << endl << ret2 << endl;return 0;
}
所以運(yùn)算符重載歸納有以下特點(diǎn):
1. 不能通過(guò)連接其他符號(hào)來(lái)創(chuàng)建新的操作符:比如operator@;
2. 重載操作符必須有一個(gè)類類型參數(shù);
3.?用于內(nèi)置類型的運(yùn)算符,其含義不能改變,例如:內(nèi)置的整型+,不能改變其含義;
4.?作為類成員函數(shù)重載時(shí),其形參看起來(lái)比操作數(shù)數(shù)目少1,因?yàn)槌蓡T函數(shù)的第一個(gè)參數(shù)為隱藏的this;
5.? .*? ? ::? ? ?sizeof? ? ??:? ? ?.? ??以上5個(gè)運(yùn)算符不能重載。
5.2 賦值運(yùn)算符重載函數(shù)
鋪了那么多前戲,終于來(lái)到我們要學(xué)習(xí)的賦值運(yùn)算符重載函數(shù)了:
我們知道,拷貝構(gòu)造就是將一個(gè)已經(jīng)初始化的變量A拷貝到未初始化的變量B中,
那么如果存在兩個(gè)都已經(jīng)初始化的變量A、B,我想把A的值拷貝到B,顯然就不能再用拷貝構(gòu)造了,要用到我們就要開(kāi)始講的賦值運(yùn)算符重載:
1. 賦值運(yùn)算符重載格式:
參數(shù)類型:const T&,傳遞引用可以提高傳參效率
返回值類型:T&,返回引用可以提高返回的效率,有返回值目的是為了支持連續(xù)賦值檢測(cè)是否自己給自己賦值
返回*this :要復(fù)合連續(xù)賦值的含義
(詳解精華都在代碼里):
#include <iostream>
using namespace std;
class Data
{
public:Data(int year = 2012, int month = 7, int day = 7){_year = year;_month = month;_day = day;}Data(const Data& d){_year = d._year;_month = d._month;_day = d._day;}//d1=d2;d1傳給this,d2傳給d//返回值應(yīng)該是什么類型呢?當(dāng)然是Data類型;應(yīng)該返回d1的地址,所以用&引用返回Data& operator=(const Data& d){//判斷是否為自己給自己賦值,&放到這里不是引用,是取地址://判斷d2的地址是否與d1地址相等if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;//返回d1的地址}void Ptint(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Data d1(2023, 7, 7);Data d2(2022, 8, 8);//拷貝構(gòu)造:一個(gè)已經(jīng)存在的對(duì)象去拷貝初始化另一個(gè)對(duì)象Data d3(d2);d1.Ptint();d2.Ptint();d3.Ptint();cout << endl;//賦值運(yùn)算符重載:兩個(gè)已經(jīng)存在的對(duì)象拷貝d1 = d2;//運(yùn)算自定義類型,就要用到運(yùn)算符重載operator=d1.Ptint();d2.Ptint();d3.Ptint();
}
2. 賦值運(yùn)算符只能重載成類的成員函數(shù)不能重載成全局函數(shù)
?原因:賦值運(yùn)算符如果不顯式實(shí)現(xiàn),編譯器會(huì)生成一個(gè)默認(rèn)的。此時(shí)用戶再在類外自己實(shí)現(xiàn) 一個(gè)全局的賦值運(yùn)算符重載,就和編譯器在類中生成的默認(rèn)賦值運(yùn)算符重載沖突了,故賦值 運(yùn)算符重載只能是類的成員函數(shù)。
3. 用戶沒(méi)有顯式實(shí)現(xiàn)時(shí),編譯器會(huì)生成一個(gè)默認(rèn)賦值運(yùn)算符重載,以值的方式逐字節(jié)拷貝。注意:內(nèi)置類型成員變量是直接賦值的,而自定義類型成員變量需要調(diào)用對(duì)應(yīng)類的賦值運(yùn)算符重載完成賦值。?
4.如果類中未涉及到資源管理,賦值運(yùn)算符是否實(shí)現(xiàn)都可以;一旦涉及到資源管理則必 須要實(shí)現(xiàn)。
這些特性與拷貝構(gòu)造函數(shù)有極大的相似性,這里不再過(guò)多贅述。
?5.3?前置++和后置++重載
在C語(yǔ)言中,我們已經(jīng)知道了:前置++,先加后用;后置++,先用后加 這樣的基本常識(shí)
那么我們學(xué)了運(yùn)算符重載,現(xiàn)在我要自定義類型Data前置++和后置++,要怎么定義和實(shí)現(xiàn)呢?
前置++:
前置++為先+1后使用;所以前置++的返回值應(yīng)該是返回+1之后的結(jié)果;
故前置++重載函數(shù)的實(shí)現(xiàn)為:
// 前置++:返回+1之后的結(jié)果Date& operator++()//返回d1,所以返回值類型當(dāng)然為Date類型{_day += 1;return *this;//this指向的對(duì)象函數(shù)結(jié)束后不會(huì)銷毀,故以&引用方式返回提高效率}
后置++:
后置++為先使用后+1;所以后置++的返回值應(yīng)該為+1之前的舊值,故需在實(shí)現(xiàn)時(shí)需要先將this保存一份,然后給this+1;
那么問(wèn)題來(lái)了,前置++和后置++都是一元運(yùn)算符,實(shí)現(xiàn)起來(lái)兩個(gè)函數(shù)名字相同,都是operator++,怎么才能區(qū)分這兩個(gè)函數(shù)呢?
為了讓前置++與后置++形成能正確重載,C++規(guī)定:后置++重載時(shí)多增加一個(gè)int類型的參數(shù),但調(diào)用函數(shù)時(shí)該參數(shù)不用傳遞,編譯器自動(dòng)傳遞
故后置++重載函數(shù)的實(shí)現(xiàn)為:
Date operator++(int){Date temp(*this);// 先將this保存一份,然后給this + 1_day += 1;return temp; // temp是臨時(shí)對(duì)象,出了作用域就會(huì)被銷毀,//因此只能以值的方式返回,不能返回引用}
驗(yàn)證:
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 2023, int month = 7, int day = 7){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的結(jié)果Date& operator++()//返回d1,所以返回值類型當(dāng)然為Date類型{_day += 1;return *this;//this指向的對(duì)象函數(shù)結(jié)束后不會(huì)銷毀,故以&引用方式返回提高效率}Date operator++(int){Date temp(*this);// 先將this保存一份,然后給this + 1_day += 1;return temp; // temp是臨時(shí)對(duì)象,出了作用域就會(huì)被銷毀,//因此只能以值的方式返回,不能返回引用}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(2022, 7, 7);d1 = ++d2;//前置++,編譯器默認(rèn)調(diào)用不帶(int)參數(shù)的operator++d1.Print();d1 = d2++;//后置++,編譯器默認(rèn)調(diào)用帶(int)參數(shù)的operator++d1.Print();return 0;
}
小知識(shí):在C語(yǔ)言中前置++和后置++二者的效率高低可能并不明顯,但是在C++中,一般來(lái)說(shuō)前置++的效率要高于后置++
6.(&)取地址操作符重載函數(shù)和const取地址操作符重載函數(shù)
終于來(lái)到類的最后兩個(gè)默認(rèn)成員成員函數(shù)了,二者的形式為:
class Date
{
public://(&)取地址操作符重載Date* operator&(){return this;}//const(&)取地址操作符重載const Date* operator&()const{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};
這兩個(gè)默認(rèn)成員函數(shù)一般不用重新定義 ,編譯器默認(rèn)會(huì)生成。
這兩個(gè)運(yùn)算符一般不需要重載,使用編譯器生成的默認(rèn)取地址的重載即可,只有特殊情況,才需 要重載,比如想讓別人獲取到指定的內(nèi)容!所以這里就不再過(guò)多介紹這兩個(gè)默認(rèn)成員函數(shù)。
(本篇完)?