歡迎各位小伙伴關(guān)注我的專(zhuān)欄,和我一起系統(tǒng)學(xué)習(xí)C++,共同探討和進(jìn)步哦!
學(xué)習(xí)專(zhuān)欄:
《進(jìn)擊的C++》
引言
在C++的學(xué)習(xí)中,類(lèi)和對(duì)象章節(jié)的學(xué)習(xí)尤為重要,猶如堅(jiān)固的地基,基礎(chǔ)不牢,地動(dòng)山搖;而默認(rèn)成員函數(shù)的學(xué)習(xí),在類(lèi)和對(duì)象的學(xué)習(xí)里最為重要。所以要學(xué)好C++,學(xué)好默認(rèn)成員函數(shù)是一道必經(jīng)之路,這樣后續(xù)才能很好的學(xué)習(xí)后續(xù)模板,繼承,多態(tài)等知識(shí)。
一、類(lèi)的6個(gè)默認(rèn)成員函數(shù)
如果一個(gè)類(lèi)中什么成員都沒(méi)有,簡(jiǎn)稱(chēng)為空類(lèi)。
空類(lèi)中真的什么都沒(méi)有嗎?并不是,任何類(lèi)在什么都不寫(xiě)時(shí),編譯器會(huì)自動(dòng)生成以下6個(gè)默認(rèn)成員函數(shù)。
默認(rèn)成員函數(shù):用戶(hù)沒(méi)有顯式實(shí)現(xiàn),編譯器會(huì)生成的成員函數(shù)稱(chēng)為默認(rèn)成員函數(shù)。
二、構(gòu)造函數(shù)(constructor)
2.1 引入
先來(lái)簡(jiǎn)易實(shí)現(xiàn)一個(gè)日期類(lèi)Date
class Date
{
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()
{
Date d1;
d1.Init(2023, 12, 5);
d1.Print();
return 0;
}
我們發(fā)現(xiàn),如果每次都要自己調(diào)用初始化函數(shù),未免有點(diǎn)麻煩,并且容易忘記從而導(dǎo)致出錯(cuò)。那能否在對(duì)象創(chuàng)建時(shí),就將信息設(shè)置進(jìn)去呢?
那么,構(gòu)造函數(shù)就可以完美解決這個(gè)問(wèn)題。
2.2 概念
構(gòu)造函數(shù)(constructor)是一個(gè)特殊的成員函數(shù),名字與類(lèi)名相同,創(chuàng)建類(lèi)類(lèi)型對(duì)象時(shí)由編譯器自動(dòng)調(diào)用,以保證每個(gè)數(shù)據(jù)成員都有一個(gè)合適的初始值,并且在對(duì)象整個(gè)生命周期內(nèi)只調(diào)用一次。
2.3 特性
構(gòu)造函數(shù)是特殊的成員函數(shù),需要注意的是,構(gòu)造函數(shù)雖然名稱(chēng)叫構(gòu)造,但是構(gòu)造函數(shù)的主要任務(wù)并不是開(kāi)空間創(chuàng)建對(duì)象,而是初始化對(duì)象。
構(gòu)造函數(shù)特性如下:
- 函數(shù)名與類(lèi)名相同。
- 無(wú)返回值。
- 對(duì)象實(shí)例化時(shí)編譯器自動(dòng)調(diào)用對(duì)應(yīng)的構(gòu)造函數(shù)。
- 構(gòu)造函數(shù)可以重載。
class Date
{
public:
// 1.無(wú)參構(gòu)造函數(shù)
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 2.帶參構(gòu)造函數(shù)
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;//調(diào)用無(wú)參構(gòu)造函數(shù)
d1.Print();
Date d2(2023, 12, 5);//調(diào)用帶參構(gòu)造函數(shù)
d2.Print();
//warning
Date d3();//函數(shù)聲明
return 0;
}
注意:
- 無(wú)返回值,不是寫(xiě)void類(lèi)型,而是什么都不寫(xiě)
- 如果通過(guò)無(wú)參構(gòu)造函數(shù)創(chuàng)建對(duì)象時(shí),對(duì)象后面不用跟括號(hào),否則就成了函數(shù)聲明
上述簡(jiǎn)化寫(xiě)法:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)//使用缺省參數(shù)
{
_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.Print();
Date d2(2023, 12, 5);
d2.Print();
return 0;
}
- 如果類(lèi)中沒(méi)有顯式定義構(gòu)造函數(shù),則C++編譯器會(huì)自動(dòng)生成一個(gè)無(wú)參的默認(rèn)構(gòu)造函數(shù),一旦用戶(hù)顯式定義編譯器將不再生成。
有人可能會(huì)疑惑,既然編譯器會(huì)自動(dòng)生成默認(rèn)構(gòu)造函數(shù),那是不是就不用自己顯式定義了呢?其實(shí),并不然。
- 因?yàn)榫幾g器自動(dòng)生成的構(gòu)造函數(shù),只會(huì)對(duì)自定義類(lèi)型成員調(diào)用其構(gòu)造函數(shù),而不會(huì)對(duì)內(nèi)置類(lèi)型成員(char、int……)處理。
- 所以,我們要盡量顯式定義,而不去用自動(dòng)生成的構(gòu)造函數(shù)。
- 當(dāng)然,C++11 中針對(duì)內(nèi)置類(lèi)型成員不初始化的缺陷,又打了補(bǔ)丁,即:內(nèi)置類(lèi)型成員變量在類(lèi)中聲明時(shí)可以給默認(rèn)值。
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 1;//聲明時(shí)給默認(rèn)值
int _month = 1;
int _day = 1;
};
int main()
{
Date d1;//調(diào)用默認(rèn)構(gòu)造函數(shù)
d1.Print();
return 0;
}
- 一個(gè)類(lèi)中只能存在一個(gè)默認(rèn)構(gòu)造函數(shù)。比如:無(wú)參構(gòu)造函數(shù),全缺省構(gòu)造函數(shù),編譯器自動(dòng)生成的默認(rèn)構(gòu)造函數(shù)。
簡(jiǎn)單理解:不用傳參,就是調(diào)用默認(rèn)構(gòu)造函數(shù)。
舉個(gè)例子,請(qǐng)看下面代碼:
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//報(bào)錯(cuò),對(duì)重載函數(shù)的調(diào)用不明確
d1.Print();
return 0;
}
分析:上述代碼包含兩個(gè)默認(rèn)構(gòu)造函數(shù),在無(wú)參調(diào)用時(shí),就會(huì)產(chǎn)生歧義。所以,才會(huì)規(guī)定一個(gè)類(lèi)只能出現(xiàn)一個(gè)默認(rèn)構(gòu)造函數(shù)。
三、析構(gòu)函數(shù)(destructor)
3.1 概念
通過(guò)前面構(gòu)造函數(shù)的學(xué)習(xí),我們知道一個(gè)對(duì)象是怎么自動(dòng)初始化的,那一個(gè)對(duì)象的內(nèi)容又是如何自動(dòng)銷(xiāo)毀的呢?
析構(gòu)函數(shù)(destructor):與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完成對(duì)對(duì)象本身的銷(xiāo)毀,局部對(duì)象銷(xiāo)毀工作是由編譯器完成的。而對(duì)象在銷(xiāo)毀時(shí)會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),完成對(duì)象中資源的清理工作。
3.2 特性
析構(gòu)函數(shù)是特殊的成員函數(shù),其特性如下:
- 析構(gòu)函數(shù)名是在類(lèi)名前加上字符 ~。
- 無(wú)參數(shù)無(wú)返回值。
- 對(duì)象生命周期結(jié)束時(shí),C++編譯系統(tǒng)系統(tǒng)自動(dòng)調(diào)用析構(gòu)函數(shù)。
- 析構(gòu)函數(shù)不能重載。
- 一個(gè)類(lèi)只能有一個(gè)析構(gòu)函數(shù)。
簡(jiǎn)易實(shí)現(xiàn)一個(gè)棧類(lèi)Stack
class Stack
{
public:
Stack(int capacity = 4)//構(gòu)造函數(shù)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc fail");
return;
}
_top = 0;
_capacity = capacity;
}
void Push(int x)
{
//CheckCapacity();
_a[_top++] = x;
}
//...
~Stack()//析構(gòu)函數(shù)
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st;
st.Push(1);
st.Push(2);
return 0;
}
- 同樣,與構(gòu)造函數(shù)相同,若未顯式定義,系統(tǒng)會(huì)自動(dòng)生成默認(rèn)的析構(gòu)函數(shù)。只會(huì)對(duì)自定義類(lèi)型成員調(diào)用其析構(gòu)函數(shù),而不會(huì)對(duì)內(nèi)置類(lèi)型成員(char、int……)處理。
回顧往期題目232.用棧實(shí)現(xiàn)隊(duì)列(LeetCode),當(dāng)時(shí)我們用C語(yǔ)言實(shí)現(xiàn)的隊(duì)列,對(duì)比一下如今用C++實(shí)現(xiàn)。
C:
typedef struct
{
ST pushst;
ST popst;
} MyQueue;
MyQueue* myQueueCreate()
{
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
if (obj == NULL)
{
perror("malloc fail");
return NULL;
}
STInit(&obj->pushst);
STInit(&obj->popst);
return obj;
}
C++:
class MyQueue
{
public:
void Push()
{}
//...
private:
Stack _pushst;
Stack _popst;
};
分析:
- C++實(shí)現(xiàn),在MyQueue類(lèi)中,默認(rèn)生成構(gòu)造函數(shù)(MyQueue)和析構(gòu)函數(shù)(~MyQueue)。
- 對(duì)于Stack類(lèi)的自定義類(lèi)型成員,自動(dòng)調(diào)用對(duì)應(yīng)的默認(rèn)構(gòu)造函數(shù)(Stack)和析構(gòu)函數(shù)(~Stack),大大簡(jiǎn)化代碼。
- 如果類(lèi)中沒(méi)有申請(qǐng)資源時(shí),析構(gòu)函數(shù)可以不寫(xiě),直接使用編譯器生成的默認(rèn)析構(gòu)函數(shù),比如
Date
類(lèi);有資源申請(qǐng)時(shí),一定要寫(xiě),否則會(huì)造成資源泄漏,比如Stack
類(lèi)。
四、拷貝構(gòu)造函數(shù)(copy constructor)
4.1 概念
在創(chuàng)建對(duì)象時(shí),可否創(chuàng)建一個(gè)與已存在對(duì)象一模一樣的新對(duì)象呢?
拷貝構(gòu)造函數(shù):只有單個(gè)形參,該形參是對(duì)本類(lèi)類(lèi)型對(duì)象的引用(一般常用const修飾),在用已存在的類(lèi)類(lèi)型對(duì)象創(chuàng)建新對(duì)象時(shí)由編譯器自動(dòng)調(diào)用。
4.2 特性
拷貝構(gòu)造函數(shù)也是特殊的成員函數(shù),其特性如下:
- 拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式。
- 拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須是類(lèi)類(lèi)型對(duì)象的引用,使用傳值方式編譯器直接報(bào)錯(cuò),因?yàn)闀?huì)引發(fā)無(wú)窮遞歸調(diào)用。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷貝構(gòu)造函數(shù)
{
_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(2023, 12, 5);
d2.Print();
Date d3(d2);//拷貝構(gòu)造
d3.Print();
return 0;
}
大家可能會(huì)很疑惑,為什么只能是傳引用,而不能是傳值呢?
請(qǐng)看下面代碼:
//傳值傳參
void Func1(Date d)
{}
//傳引用傳參
void Func2(Date& d)
{}
int main()
{
Date d;
Func1(d);//傳值調(diào)用
Func2(d);//傳引用調(diào)用
return 0;
}
經(jīng)過(guò)調(diào)試,我們可以發(fā)現(xiàn):在傳值傳參時(shí),會(huì)調(diào)用拷貝構(gòu)造函數(shù);而在傳引用傳參時(shí),并不需要。
那么,再看看寫(xiě)成傳值傳參的拷貝構(gòu)造函數(shù),是多么的恐怖。
Date(Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
分析:
- 每次要拷貝對(duì)象時(shí),傳值傳參
- 而傳值傳參,又要拷貝對(duì)象
- 這樣,就會(huì)形成無(wú)窮遞歸。這其實(shí)類(lèi)似于先有雞,還是先有蛋的悖論。
- 若未顯式定義,編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)。 在編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)中,內(nèi)置類(lèi)型成員是按照字節(jié)方式直接拷貝的,這種拷貝叫做淺拷貝,或者值拷貝。而自定義類(lèi)型成員是調(diào)用其拷貝構(gòu)造函數(shù)完成拷貝的。
那么,如果每次都直接使用默認(rèn)生成的拷貝構(gòu)造函數(shù)可以嗎?答案是,當(dāng)然不可以!
再來(lái)回顧一下棧類(lèi)Stack
,試試默認(rèn)生成的拷貝構(gòu)造函數(shù)。
class Stack
{
public:
Stack(int capacity = 10)//構(gòu)造函數(shù)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc fail");
return;
}
_top = 0;
_capacity = capacity;
}
void Push(int x)
{
//CheckCapacity();
_a[_top++] = x;
}
//...
~Stack()//析構(gòu)函數(shù)
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
運(yùn)行的時(shí)候會(huì)發(fā)現(xiàn),上述程序會(huì)崩潰,這是為什么呢?
分析:
- 此時(shí)把s1拷貝給s2,因?yàn)闆](méi)有顯式定義拷貝構(gòu)造函數(shù),所以默認(rèn)生成拷貝構(gòu)造函數(shù)。
- 而默認(rèn)生成的拷貝構(gòu)造函數(shù),對(duì)于內(nèi)置類(lèi)型都是淺拷貝,即把值復(fù)制過(guò)去。
- 所以,就造成了s2的_a與s1的_a指向同一塊空間
- 那么在調(diào)用析構(gòu)函數(shù)資源銷(xiāo)毀時(shí),就會(huì)重復(fù)釋放同一塊動(dòng)態(tài)開(kāi)辟的空間,導(dǎo)致程序崩潰
那么,這種情況就不是淺拷貝能解決的了,則必須顯式定義拷貝構(gòu)造函數(shù)進(jìn)行深拷貝
- 類(lèi)中如果沒(méi)有涉及資源申請(qǐng)時(shí),拷貝構(gòu)造函數(shù)是否寫(xiě)都可以;一旦涉及到資源申請(qǐng)時(shí),則拷貝構(gòu)造函數(shù)是一定要寫(xiě)的,否則就是淺拷貝。
深拷貝實(shí)現(xiàn)如下:
typedef int DataType;
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (DataType*)malloc(capacity * sizeof(DataType));
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
}
Stack(const Stack& st)//深拷貝
{
_a = (DataType*)malloc(st._capacity * sizeof(DataType));
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = st._top;
_capacity = st._capacity;
}
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
DataType* _a;
int _top;
int _capacity;
};
五、構(gòu)造、析構(gòu)、拷貝構(gòu)造函數(shù)總結(jié)對(duì)比
5.1 構(gòu)造函數(shù)
- 函數(shù)名:類(lèi)名
- 無(wú)返回值
- 自動(dòng)調(diào)用
- 可重載
- 默認(rèn)生成的構(gòu)造函數(shù),只對(duì)自定義類(lèi)型成員調(diào)用其對(duì)應(yīng)的構(gòu)造函數(shù)
- 一個(gè)類(lèi)只能有一個(gè)默認(rèn)構(gòu)造函數(shù)
- 根據(jù)需求,自行判斷是否需要顯式定義
5.2 析構(gòu)函數(shù)
- 函數(shù)名:~類(lèi)名
- 無(wú)參無(wú)返回值
- 自動(dòng)調(diào)用
- 不可重載
- 默認(rèn)生成的析構(gòu)函數(shù),只對(duì)自定義類(lèi)型成員調(diào)用其對(duì)應(yīng)的析構(gòu)函數(shù)
- 一個(gè)類(lèi)只能有一個(gè)析構(gòu)函數(shù)
- 有資源申請(qǐng),一定要顯式定義
5.3 拷貝構(gòu)造函數(shù)
- 構(gòu)造函數(shù)的重載(擁有構(gòu)造函數(shù)特性1,2,3)
- 參數(shù):類(lèi)對(duì)象的引用
- 默認(rèn)生成的拷貝構(gòu)造函數(shù),只對(duì)自定義類(lèi)型成員調(diào)用其對(duì)應(yīng)的拷貝構(gòu)造函數(shù),對(duì)內(nèi)置類(lèi)型成員淺拷貝
- 一個(gè)類(lèi)一般只需要一個(gè)拷貝構(gòu)造函數(shù)
- 有資源申請(qǐng),一定要顯式定義
六、賦值運(yùn)算符重載
6.1 運(yùn)算符重載
內(nèi)置類(lèi)型可以直接使用運(yùn)算符,那么自定義類(lèi)型是否能使用運(yùn)算符呢?
C++為了增強(qiáng)代碼的可讀性引入了運(yùn)算符重載,運(yùn)算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類(lèi)型,函數(shù)名字以及參數(shù)列表,其返回值類(lèi)型與參數(shù)列表與普通的函數(shù)類(lèi)似。
函數(shù)名:關(guān)鍵字operator后面接需要重載的運(yùn)算符符號(hào)。
函數(shù)原型:返回值類(lèi)型 operator操作符(參數(shù)列表)
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
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;
Date d2(d1);
operator==(d1, d2);//一般不這樣寫(xiě)
d1 == d2;//等價(jià)于上述函數(shù)調(diào)用
cout << (d1 == d2) << endl;
return 0;
}
這里,我們是直接將private取消,才讓外部函數(shù)可以訪(fǎng)問(wèn)類(lèi)的內(nèi)置類(lèi)型。但是,這樣封裝性又得不到保證了。
所以,我們可以把運(yùn)算符重載定義為成員函數(shù)。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 這里需要注意的是,左操作數(shù)是this,指向調(diào)用函數(shù)的對(duì)象
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;
Date d2(d1);
d1.operator==(d2);//一般不這樣寫(xiě)
d1 == d2;//等價(jià)于上述寫(xiě)法
return 0;
}
注意:
- 不能通過(guò)連接其他符號(hào)來(lái)創(chuàng)建新的操作符:比如operator@
- 重載操作符必須有一個(gè)類(lèi)類(lèi)型參數(shù)
- 用于內(nèi)置類(lèi)型的運(yùn)算符,其含義不能改變,例如:內(nèi)置的整型+,不能改變其含義
- 作為類(lèi)成員函數(shù)重載時(shí),其形參看起來(lái)比操作數(shù)數(shù)目少1,因?yàn)槌蓡T函數(shù)的第一個(gè)參數(shù)為隱藏的this
-
.*
、::
、sizeof
、?:
、.
注意以上5個(gè)運(yùn)算符不能重載。這個(gè)經(jīng)常在筆試選擇題中出現(xiàn)。
6.2 賦值運(yùn)算符重載
前面講完了運(yùn)算符重載的前置知識(shí),那么我們就來(lái)實(shí)現(xiàn)d1 = d2這樣的賦值操作
賦值運(yùn)算符重載格式
- 參數(shù)類(lèi)型:const 類(lèi)名&,傳遞引用可以提高傳參效率
- 返回值類(lèi)型:類(lèi)名&,返回引用可以提高返回的效率,有返回值目的是為了支持連續(xù)賦值
- 檢測(cè)是否自己給自己賦值
- 返回*this :要符合連續(xù)賦值的含義
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 10, 24);
Date d2(2023, 5, 3);
d1 = d2;
return 0;
}
但是,要注意到賦值運(yùn)算符的特殊場(chǎng)景
- 連續(xù)賦值,比如d3 = d2 = d1
- 原地賦值,比如d1 = d1
所以,我們有以下改進(jìn)版本:
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
- 同樣,若未顯式定義,默認(rèn)生成的賦值重載,對(duì)自定義類(lèi)型成員調(diào)用對(duì)應(yīng)的賦值重載,對(duì)于內(nèi)置類(lèi)型成員淺拷貝。
- 所以,如果一旦涉及到資源申請(qǐng)時(shí),則賦值重載是一定要顯式定義的,否則就是淺拷貝。
七、日期類(lèi)的實(shí)現(xiàn)
注意:
- 日期+日期沒(méi)有意義,但是日期-日期有意義,代表間隔的天數(shù)
- 日期±天數(shù)都有意義,代表往前/后天數(shù)的日期
- 為了與前置++/- -區(qū)分,后置++/- -聲明參數(shù)類(lèi)型為int(無(wú)意義,僅用于占位符,以作區(qū)分)
- 這里重載了<<(流插入)和>>(流提?。┎僮鞣\(yùn)用了友元(后續(xù)會(huì)講)
date.h
#pragma once
#include<iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;
}
friend istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
public:
Date(int year = 1, int month = 1, int day = 1);
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=(const Date& d);
Date& operator+=(int day);
Date operator+(int day);
Date& operator-=(int day);
Date operator-(int day);
int operator-(const Date& d);
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
private:
int _year;
int _month;
int _day;
};
date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"date.h"
int GetMonthDay(int year, int month)
{
int a[] = { 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;
}
return a[month];
}
Date::Date(int year, int month, int day)
{
if((month > 0 && month <= 12) && (day > 0 && day <= GetMonthDay(year, month)))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout<<"日期非法"<<endl;
}
}
void Date::Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
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);
}
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);
}
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);
}
Date& Date::operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
//d1+=100
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;
}
//d1+100
Date Date::operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
//d1-=100
Date& Date::operator-=(int day)
{
if (day < 0)
{
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0)
{
_day += GetMonthDay(_year, _month);
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
}
return *this;
}
//d1-100
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
//d1-d2
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (max < min)
{
max = d;
min = *this;
flag = -1;
}
int i = 0;
while (min != max)
{
++min;
++i;
}
return i * flag;
}
//++d1
Date& Date::operator++()
{
*this += 1;
return *this;
}
//d1++
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
//--d1
Date& Date::operator--()
{
*this -= 1;
return *this;
}
//d1--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
八、const成員函數(shù)
8.1 概念
將const修飾的“成員函數(shù)”稱(chēng)之為const成員函數(shù),const修飾類(lèi)成員函數(shù),實(shí)際修飾該成員函數(shù)隱含的this指針,表明在該成員函數(shù)中不能對(duì)類(lèi)的任何成員進(jìn)行修改。
舉個(gè)例子:
class Date
{
public:
void Print()
{
cout << _year << "" << _month << "" << _day << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
void Func(const Date& d)
{
d.Print();//err
}
int main()
{
Date d1;
Func(d1);
return 0;
}
分析:上述代碼在Func函數(shù)內(nèi)部(常引用,const修飾),調(diào)用Print函數(shù)(this指針沒(méi)有const修飾),造成權(quán)限的放大,所以編譯器報(bào)錯(cuò)。
8.2 使用方式
那么,我們應(yīng)該怎么做呢?
我們無(wú)法顯式修飾const,所以語(yǔ)法規(guī)定了一種寫(xiě)法——在成員函數(shù)名后接const
class Date
{
public:
void Print() const
{
cout << _year << "" << _month << "" << _day << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
這樣,編譯器就會(huì)自動(dòng)對(duì)this指針進(jìn)行const修飾。
注意:只對(duì)功能不改變對(duì)象本身的成員函數(shù),進(jìn)行const修飾。
那么請(qǐng)思考下面的幾個(gè)問(wèn)題:
- const對(duì)象可以調(diào)用非const成員函數(shù)嗎?
- 非const對(duì)象可以調(diào)用const成員函數(shù)嗎?
- const成員函數(shù)內(nèi)可以調(diào)用其它的非const成員函數(shù)嗎?
- 非const成員函數(shù)內(nèi)可以調(diào)用其它的const成員函數(shù)嗎?
8.3 日期類(lèi)(const修飾版)
下面是日期類(lèi)實(shí)現(xiàn)中,const修飾后的版本:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
void Print() 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;
bool operator>=(const Date& d) const;
Date& operator=(const Date& d);
Date& operator+=(int day);
Date operator+(int day) const;
Date& operator-=(int day);
Date operator-(int day) const;
int operator-(const Date& d) const;
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
private:
int _year;
int _month;
int _day;
};
注意:聲明與定義分離時(shí),兩邊都要加上const修飾
九、取地址及const取地址操作符重載
這兩個(gè)默認(rèn)成員函數(shù)一般不用重新定義 ,編譯器默認(rèn)會(huì)生成。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&() const
{
return this;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
int main()
{
Date d1;
cout << &d1 << endl;
const Date d2;
cout << &d2 << endl;
return 0;
}
這兩個(gè)運(yùn)算符一般不需要重載,使用編譯器生成的默認(rèn)取地址的重載即可,只有特殊情況,才需要重載,比如想讓別人獲取到指定的內(nèi)容!
總結(jié)
本節(jié)學(xué)習(xí)了類(lèi)的6個(gè)默認(rèn)成員函數(shù),詳細(xì)了解其中的特性與使用方法。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-761390.html
- 其中構(gòu)造函數(shù),析構(gòu)函數(shù),拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載,才是我們需要重點(diǎn)掌握并且經(jīng)常要顯式定義的。
- 默認(rèn)生成的構(gòu)造/析構(gòu)函數(shù),對(duì)其自定義類(lèi)型成員調(diào)用對(duì)應(yīng)的構(gòu)造/析構(gòu)函數(shù),對(duì)內(nèi)置類(lèi)型成員不做處理
- 默認(rèn)生成的拷貝構(gòu)造/賦值重載函數(shù),對(duì)其自定義類(lèi)型成員調(diào)用對(duì)應(yīng)的拷貝構(gòu)造/賦值重載函數(shù),對(duì)內(nèi)置類(lèi)型成員淺拷貝
- 至于取地址運(yùn)算符重載,比較簡(jiǎn)單,且平常不用顯式定義。
看到這里了還不給博主扣個(gè):
?? 點(diǎn)贊??收藏 ?? 關(guān)注!
?? ?? ?? ?? ???? ?? ?? ?? ?? ??
拜托拜托這個(gè)真的很重要!
你們的點(diǎn)贊就是博主更新最大的動(dòng)力!
有問(wèn)題可以評(píng)論或者私信呢秒回哦。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-761390.html
到了這里,關(guān)于【C++練級(jí)之路】【Lv.3】類(lèi)和對(duì)象(中)(沒(méi)掌握類(lèi)的6個(gè)默認(rèn)成員函數(shù),那你根本就沒(méi)學(xué)過(guò)C++!)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!