【本章目標(biāo)】
- 類的6個(gè)默認(rèn)成員函數(shù)
- 構(gòu)造函數(shù)
- 析構(gòu)函數(shù)
- 拷貝構(gòu)造函數(shù)
- 賦值運(yùn)算符重載
- const成員函數(shù)
- 取地址及const取地址操作符重載
目錄
【本章目標(biāo)】
1.類的6個(gè)默認(rèn)成員函數(shù)
2.構(gòu)造函數(shù)
2.1概念
2.2構(gòu)造函數(shù)的特性
特性一
特性二
特性三
特性四
特性五
特性六
特性七
2.3總結(jié)
3.析構(gòu)函數(shù)
3.1概念:
3.2特性
特性一
特性二
特性三
特性四
特性五
4.拷貝構(gòu)造函數(shù)
4.1概念
4.2拷貝構(gòu)造的特性
特性一
特性二
特性三
特性四:
4.3 總結(jié)
5.賦值運(yùn)算符重載
5.1運(yùn)算符重載
5.2賦值運(yùn)算符重載
5.3賦值運(yùn)算符重載的一個(gè)特性
6. const 成員
6.1const成員函數(shù)
6.2思考
7.取地址及const取地址操作符重載
練習(xí)
1.類的6個(gè)默認(rèn)成員函數(shù)
?空類里面不是什么都沒有,而是會自動(dòng)生成上面6個(gè)默認(rèn)成員函數(shù)
主要:
這6個(gè)默認(rèn)的成員函數(shù)是“缺省”的,你不寫這6個(gè)函數(shù),編譯器就會自動(dòng)生成,但是你如果寫了某一個(gè),那編譯器就不會生成了
2.構(gòu)造函數(shù)
2.1概念
對于以下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, 3, 1);
d1.Print();
Date d2;
d2.Init(2023, 5, 1);
d2.Print();
return 0;
}
這個(gè)Date類可以通過 Init 公有方法給對象設(shè)置日期,但如果每次創(chuàng)建對象時(shí)都調(diào)用該方法設(shè)置信息,未免有點(diǎn)麻煩,那能否在對象創(chuàng)建時(shí),就將信息設(shè)置進(jìn)去呢?
這時(shí)候就需要用到我們的構(gòu)造函數(shù)
2.2構(gòu)造函數(shù)的特性
構(gòu)造函數(shù)是一個(gè)特殊的成員函數(shù),名字與類名相同,創(chuàng)建類類型對象時(shí)由編譯器自動(dòng)調(diào)用,以保證每個(gè)數(shù)據(jù)成員都有 一個(gè)合適的初始值,并且在對象整個(gè)生命周期內(nèi)只調(diào)用一次。
特性一
(1)構(gòu)造函數(shù)的函數(shù)名與類名相同
特性二
(2)構(gòu)造函數(shù)無返回值,這里指的是返回值不用寫,而不是void
特性三
(3)對象實(shí)例化時(shí)編譯器自動(dòng)調(diào)用對應(yīng)的構(gòu)造函數(shù)
當(dāng)你用類創(chuàng)建一個(gè)對象時(shí),編譯器會自動(dòng)調(diào)用該類的構(gòu)造函數(shù)對新創(chuàng)建的變量進(jìn)行初始化。
特性四
(4)構(gòu)造函數(shù)可以重載
這就意味著我們可以寫很多不同初始化的構(gòu)造函數(shù),當(dāng)我們需要那種初始化,我們傳遞對應(yīng)的參數(shù)即可
class Date
{
public:
//無參的構(gòu)造函數(shù)
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//帶參的構(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)用無參的構(gòu)造函數(shù)
d1.Print();
Date d2(2023, 1, 1);//調(diào)用有參的構(gòu)造函數(shù)
d2.Print();
return 0;
}
注意:在通過無參構(gòu)造函數(shù)創(chuàng)建對象時(shí),對象后面不用加括號,否則就是函數(shù)聲明
特性五
(5)默認(rèn)構(gòu)造函數(shù)
無參構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都稱為默認(rèn)構(gòu)造函數(shù),并且默認(rèn)構(gòu)造函數(shù)只能有一個(gè)
class Date
{
public:
//全缺省的構(gòu)造函數(shù)
Date(int year = 2023, int month = 5, 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;
d1.Print();
Date d2(2023, 1, 1);
d2.Print();
return 0;
}
d1沒有傳參,會直接調(diào)用構(gòu)造函數(shù)的缺省值,d2會進(jìn)行賦值操作
特性六
(6)如果類中沒有顯式定義構(gòu)造函數(shù),則C++編譯器會自動(dòng)生成一個(gè)無參的默認(rèn)構(gòu)造函數(shù),一旦用戶顯式定義編譯器將不再生成。
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; // 此處調(diào)用的是編譯器生成的默認(rèn)構(gòu)造函數(shù)
//可以看到我們沒有定義構(gòu)造函數(shù),對象也可以創(chuàng)建成功,
d1.Print();
return 0;
}
打印結(jié)果:
我們可以看到d1調(diào)用了編譯器創(chuàng)建的默認(rèn)構(gòu)造函數(shù),單初始化結(jié)構(gòu)卻是 -858993460,
而不是初始化為0
這時(shí)候就有人想說:這編譯器生成的默認(rèn)構(gòu)造函數(shù)有個(gè)√8用?
這是因?yàn)榫幾g器有一套自動(dòng)生成的構(gòu)造函數(shù)機(jī)制:
- 編譯器自動(dòng)生成的構(gòu)造函數(shù)對內(nèi)置類型不做處理
- 對自定義類型,編譯器會再去調(diào)用他們自己的構(gòu)造函數(shù)
內(nèi)置類型就是編譯器創(chuàng)建的基本類型,如int,char等等
自定義類型就是class、struct定義的類對象
如下代碼:
class Week
{
public:
Week()
{
cout << " Week()" << endl;
_week = 1;
}
private:
int _week;
};
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 內(nèi)置類型
int _year; // 年
int _month; // 月
int _day; // 日
// 自定義類型
Week w1;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
調(diào)用結(jié)果:
可以看到:
默認(rèn)生成的構(gòu)造函數(shù)堆內(nèi)置類型成員變量不做處理,
對于自定義類型成員變量才會處理(去調(diào)用他們自己的默認(rèn)構(gòu)造函數(shù))
特性七
(7)如果一個(gè)類中的成員全是自定義類型,我們就可以用默認(rèn)生成的構(gòu)造函數(shù);
如果有內(nèi)置類型的成員,或者需要顯示傳參初始化,那么都要自己實(shí)現(xiàn)構(gòu)造函數(shù)。
2.3總結(jié)
默認(rèn)構(gòu)造函數(shù)有三種:
- 我們不寫,讓編譯器自動(dòng)生成的
- 我們自己寫的? 無參? 的構(gòu)造函數(shù)
- 我們自己寫的? 全缺省? 的構(gòu)造函數(shù)
雖然我們在不寫的情況下,編譯器會自動(dòng)生成構(gòu)造函數(shù),但是編譯器自動(dòng)生成的構(gòu)造函數(shù)可能達(dá)不到我們想要的效果,
所以大多數(shù)情況下都需要我們自己寫構(gòu)造函數(shù),并且最好是寫全缺省的構(gòu)造函數(shù)
3.析構(gòu)函數(shù)
3.1概念:
與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完成對對象本身的銷毀,局部對象銷毀工作是由編譯器完成的。而對象在銷毀時(shí)會自動(dòng)調(diào)用析構(gòu)函數(shù),完成對象中資源的清理工作。
3.2特性
析構(gòu)函數(shù)和構(gòu)造函數(shù)一樣,也是一個(gè)特殊的成員函數(shù)
我們都知道,當(dāng)一個(gè)類對象銷毀時(shí),其中的局部表里也會是隨著該對象的銷毀而銷毀
我們通常會寫一個(gè)destroy函數(shù)來進(jìn)行銷毀操作
而析構(gòu)函數(shù)就相當(dāng)于destroy函數(shù)的作用
class Stack
{
public:
// 構(gòu)造函數(shù)
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
// 析構(gòu)函數(shù)
~Stack()
{
free(_a);
_a = nullptr;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st;
return 0;
}
在數(shù)據(jù)結(jié)構(gòu)中,我們實(shí)現(xiàn)棧時(shí)都會寫一個(gè)Destroy函數(shù)再程序結(jié)束前銷毀動(dòng)態(tài)開辟的內(nèi)存,
而如果我們在使用完后沒有及時(shí)銷毀,那么就可能導(dǎo)致內(nèi)存泄露的問題
而析構(gòu)函數(shù)的出現(xiàn)就是為了防止我們忘記銷毀,對象實(shí)例化后,同構(gòu)造函數(shù)一樣,它不需要我們主動(dòng)調(diào)用,它是在對象生命周期結(jié)束后自動(dòng)調(diào)用,需要注意的是,析構(gòu)函數(shù)沒有參數(shù)所以不能重載。
特性一
(1)析構(gòu)函數(shù)的名是在類名前面加上字符? ??~??
class Date
{
public:
// 構(gòu)造函數(shù)
Date()
{
}
// 析構(gòu)函數(shù)
~Date()
{
}
private:
int _year;
int _month;
int _day;
}
特性二
(2)析構(gòu)函數(shù)無參數(shù)不能重載,無返回值
特性三
(3)對象聲明周期結(jié)束時(shí),編譯器會自動(dòng)第哦啊用析構(gòu)函數(shù)
如下代碼:
class Date
{
public:
// 全缺省的構(gòu)造函數(shù)
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
// 析構(gòu)函數(shù)
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 5, 1);
d1.Print();
return 0;
}
編譯結(jié)果:
可以看到我們沒有調(diào)用~Date()這個(gè)函數(shù),但是編譯器是會默認(rèn)調(diào)用這個(gè)析構(gòu)函數(shù)的
特性四
(4)一個(gè)類有且只有一個(gè)析構(gòu)函數(shù)。若未顯示定義,系統(tǒng)會自動(dòng)生成默認(rèn)的析構(gòu)函數(shù)
- 編譯器自動(dòng)生成的析構(gòu)函數(shù)對內(nèi)置類型不做處理
- 對于自定義類型,編譯器會再去調(diào)用他們自己的析構(gòu)函數(shù)
和構(gòu)造函數(shù)一樣
特性五
(5)先構(gòu)造的后析構(gòu),后構(gòu)造的先析構(gòu)
析構(gòu)函數(shù)的析構(gòu)順序和棧一樣,先進(jìn)后出
4.拷貝構(gòu)造函數(shù)
4.1概念
在我們編寫代碼的過程中,免不了要對以一個(gè)類對象進(jìn)行拷貝,生成一個(gè)新的和原來一樣的對象
拷貝構(gòu)造函數(shù):只有單個(gè)形參,該形參是對本類類型對象的引用(一般常用const修飾),在用已存在的類類型對象創(chuàng)建新對象時(shí)由編譯器自動(dòng)調(diào)用。
來看下面的代碼:
class Date
{
public:
//構(gòu)造函數(shù)
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷貝構(gòu)造
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 5, 1);
Date d2(d1); // 用已存在的對象d1創(chuàng)建對象d2
return 0;
}
我們這里對象 d2 就是對 對象 d1 的拷貝構(gòu)造,通過調(diào)試我們就可以看到:
4.2拷貝構(gòu)造的特性
特性一
(1)拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式
因?yàn)榭截悩?gòu)造函數(shù)的函數(shù)名也與類名相同
特性二
(2)拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須用引用傳參
傳值傳參會發(fā)生錯(cuò)誤,無限遞歸的錯(cuò)誤
傳值傳參為什么會發(fā)生無限遞歸呢?
?當(dāng)進(jìn)行傳值傳參的時(shí)候,我們要調(diào)用? 拷貝構(gòu)造函數(shù)? 就需要先? 傳參,這時(shí)的傳參是傳值傳參,在傳參的過程中又需要進(jìn)行對象的拷貝構(gòu)造,如此循環(huán)發(fā)生無限遞歸。
這里用引用來解決了問題,加const的原因是因?yàn)槲覀兣掠行┤藢戝e(cuò)順序,導(dǎo)致原來的d被改變
//拷貝構(gòu)造
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
特性三
(3)若未進(jìn)行顯示定義,系統(tǒng)生成默認(rèn)拷貝構(gòu)造函數(shù)
系統(tǒng)生成的默認(rèn)拷貝構(gòu)造函數(shù)對象,按內(nèi)存存儲字節(jié)完成拷貝,
這種拷貝我們叫做淺拷貝(或值拷貝)
如下代碼所示:
class Date
{
public:
// 構(gòu)造函數(shù)
Date(int year = 0, 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(2023, 5, 1);
Date d2(d1); // 用已存在的對象d1創(chuàng)建對象d2
d1.Print();
d2.Print();
return 0;
}
運(yùn)行結(jié)果:
在上述代碼中,我們沒有編寫拷貝構(gòu)造函數(shù),但編譯器通過自動(dòng)生成的拷貝構(gòu)造函數(shù)完成了d2對d1的拷貝
這就要談?wù)劸幾g器自動(dòng)生成拷貝構(gòu)造函數(shù)的機(jī)制:
- 編譯器自動(dòng)生成的拷貝構(gòu)造函數(shù)對內(nèi)置類型會完成淺拷貝(值拷貝)
- ?對于自定義類型,編譯器會再去調(diào)用他們自己的默認(rèn)拷貝構(gòu)造函數(shù)
特性四:
(4)編譯器自動(dòng)生成的拷貝構(gòu)造函數(shù)不能實(shí)現(xiàn)?深拷貝?
什么是? 淺拷貝? 什么是? 深拷貝?
淺拷貝:對值進(jìn)行拷貝,不會另外開空間
深拷貝:會另開辟一塊空間,再將需要拷貝的值放入該空間中
顯然,對于日期這樣的類是沒有必要進(jìn)行深拷貝的,但對于一些復(fù)雜的數(shù)據(jù)結(jié)構(gòu),我們就不能再進(jìn)行淺拷貝了,否則會出現(xiàn)指向同一區(qū)域的錯(cuò)誤
如下代碼:
class Stack
{
public:
Stack(int capacity = 4)
{
_data = (int*)malloc(sizeof(int) * capacity);
if (_data == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
free(_data);
_data = nullptr;
}
private:
int* _data;
int _top;
int _capacity;
};
int main()
{
Stack st1;
Stack st2(st1);
return 0;
}
我們用的是編譯器自動(dòng)生成的拷貝構(gòu)造函數(shù),進(jìn)行調(diào)試我們可以看到:st2已經(jīng)完成了對st1的拷貝
但是程序在運(yùn)行的時(shí)候還是崩潰了
原因就是進(jìn)行了淺拷貝的問題!
我們仔細(xì)看上述調(diào)試結(jié)構(gòu),會發(fā)現(xiàn)st1和st2指向的地址是同一塊地址,這就說明再進(jìn)行拷貝的時(shí)候,st2并沒有開辟新的空間
這時(shí)我們不管對他們兩個(gè)哪一個(gè)做出該改變,都會影響到另一個(gè)
如果在我們自己定義的析構(gòu)函數(shù)是正確的情況下,當(dāng)程序運(yùn)行結(jié)束,st2 棧將被先析構(gòu),然后再去析構(gòu)st1
而此時(shí),st2析構(gòu)的時(shí)候?st2 和 st1 指向同一塊兒空間已經(jīng)被釋放了,那么當(dāng) st1 棧再去調(diào)用析構(gòu)函數(shù)的時(shí)候,會再次對那一塊空間進(jìn)行釋放,造成一塊空間被多次釋放的問題,導(dǎo)致程序崩潰!
顯然這種結(jié)構(gòu)是我們不想看到的,所以再這種復(fù)雜數(shù)據(jù)結(jié)構(gòu)的拷貝過程中,編譯器自動(dòng)生成的拷貝構(gòu)造函數(shù)就不能滿足我們的要求了。
4.3 總結(jié)
我們不寫,編譯器會默認(rèn)生成一個(gè)拷貝構(gòu)造:
(1)內(nèi)置類型的成員會完成值拷貝,也就是淺拷貝。
像 Date 這樣的類,需要的就是淺拷貝,那么編譯器自動(dòng)生成的拷貝構(gòu)造函數(shù)就夠用了,我們不需要自己寫。
(2)自定義類型的成員,去調(diào)用這個(gè)成員的拷貝構(gòu)造
像 Stack 這樣的類,它是自己直接管理資源,那么需要自己實(shí)現(xiàn)深拷貝,淺拷貝的話會導(dǎo)致析構(gòu)兩次、程序崩潰的問題。
5.賦值運(yùn)算符重載
運(yùn)算符重載和函數(shù)重載是不一樣的
- 函數(shù)重載:支持函數(shù)名相同、參數(shù)不同的函數(shù),可以同時(shí)使用
- 運(yùn)算符重載:自定義類型對象可以使用運(yùn)算符
5.1運(yùn)算符重載
創(chuàng)建一個(gè)日期Date類,現(xiàn)在要判斷對象d1和對象d2是否相等要如何來做呢?
class Date
{
public:
//構(gòu)造函數(shù)
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 5, 1);
Date d2(2023, 3, 1);
return 0;
}
如果我們直接這樣寫:
肯定是錯(cuò)誤的,可以看到編譯器報(bào)的錯(cuò)誤是沒有與Date類型匹配的運(yùn)算符
這時(shí)因?yàn)?運(yùn)算符默認(rèn)都是個(gè)內(nèi)置類型使用的。自定義類型的變量用這些運(yùn)算符,得自己編寫運(yùn)算符重載函數(shù)來實(shí)現(xiàn)
C++為了增強(qiáng)代碼的可讀性引入了運(yùn)算符重載,運(yùn)算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類型,函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似。
函數(shù)名:關(guān)鍵字operator后面接需要重載的運(yùn)算符符號。
函數(shù)原型:?返回值類型 operator操作符(參數(shù)列表)?
注意:
- 不能通過連接其他符號來創(chuàng)建新的操作符:比如operator@?
- 重載操作符必須有一個(gè)類類型參數(shù)
- 用于內(nèi)置類型的運(yùn)算符,其含義不能改變,例如:內(nèi)置的整型+,不 能改變其含義
- 作為類成員函數(shù)重載時(shí),其形參看起來比操作數(shù)數(shù)目少1,因?yàn)槌蓡T函數(shù)的第一個(gè)參數(shù)為隱藏的this
- ? . *?? ? ? ::?? ?? sizeof?? ? ?? :? ? .? ? 注意以上5個(gè)運(yùn)算符不能重載。這個(gè)經(jīng)常在筆試選擇題中出現(xiàn)
下面以? d1==d2為例,通過代碼的形式來學(xué)習(xí)運(yùn)算符重載
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1) // 構(gòu)造函數(shù)
{
_year = year;
_month = month;
_day = day;
}
//==的運(yùn)算符重載函數(shù)
bool operator==(const Date& d) // 這里等價(jià)于 bool operator==(Date* this, const Date& d2)
{
// 這里需要注意的是,左操作數(shù)是this指向的調(diào)用函數(shù)的對象
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 5, 1);
Date d2(2021, 3, 1);
//顯示調(diào)用:
d1.operator==(d2);
//我們一般就直接這樣寫
d1 == d2;//同上,編譯器會自動(dòng)識別轉(zhuǎn)換為 d1.operator==(d2) --> d1.operator(&d1, d2);
cout << (d1 == d2) << endl;
// 注意運(yùn)算符重載打印時(shí)要加括號(),因?yàn)檫\(yùn)算符優(yōu)先級的關(guān)系
return 0;
}
當(dāng)然,我們也可以將運(yùn)算符重載放到類外,但這樣就無妨訪問到收private私有類型保護(hù)的成員變量,我們可以用將成員變量設(shè)置為public的公共類型,這樣當(dāng)然是不好的,等到后面學(xué)習(xí)了友元函數(shù)就可以解決
我們先將成員函數(shù)設(shè)為public來看一看,放到類外的運(yùn)算符重載,代碼應(yīng)該怎么寫
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1) // 構(gòu)造函數(shù)
{
_year = year;
_month = month;
_day = day;
}
//private: //將成員函數(shù)變?yōu)楣差? int _year;
int _month;
int _day;
};
bool operator==(const Date& d1, const Date& d2)// ==運(yùn)算符重載函數(shù)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
Date d1(2023, 5, 1);
Date d2(2021, 3, 1);
//顯示調(diào)用
operator==(d1, d2);
//一般寫法:
d1 == d2; //同上,如果沒有重載會報(bào)錯(cuò),如果重載了它會轉(zhuǎn)換為 operator==(d1, d2);
cout << (d1 == d2) << endl;
return 0;
}
可以看到在類外時(shí),因?yàn)闆]有 this 指針,所以再顯示調(diào)用函數(shù)時(shí),其形參我們需要設(shè)置兩個(gè)
放到類外的寫法是不推薦的,因?yàn)樗茐牧朔庋b,這里只是為了讓大家看看類內(nèi)寫法和類外寫法的區(qū)別,以及理解隱藏的this參數(shù)
在編寫好運(yùn)算符重載函數(shù)后,無論它是在類內(nèi)還是類外,我們寫的時(shí)候都是直接寫? d1 == d2?
這里我們可以在寫一下其他的比較,比如 < , > , <=,? >= , !=
代碼如下:
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1) // 構(gòu)造函數(shù)
{
_year = year;
_month = month;
_day = day;
}
//==的運(yùn)算符重載函數(shù)
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// d1 < d2
bool operator<(const Date& d)
{
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;
}
else
{
return false;
}
}
// d1 <= d2
bool operator<=(const Date& d)
{
return *this < d || *this == d;
}
// d1 > d2
bool operator>(const Date& d)
{
return !(*this <= d);
}
// d1 >= d2
bool operator>=(const Date& d)
{
return !(*this < d);
}
// d1 != d2
bool operator!=(const Date& d)
{
return !(*this == d);
}
private:
int _year;
int _month;
int _day;
};
5.2賦值運(yùn)算符重載
賦值運(yùn)算符: =?
賦值運(yùn)算符的重載還是以日期Date類未例:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//賦值運(yùn)算符重載
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
至于我們?yōu)槭裁匆@樣寫賦值運(yùn)算符重載,有以下原因:
原因1:
為什么參數(shù)類型要設(shè)置為引用,并用const進(jìn)行修飾?
由于是自定義類型傳參,我們?nèi)绻褂?傳值 傳參,會額外調(diào)用一次拷貝構(gòu)造函數(shù),所以函數(shù)的第二個(gè)參數(shù)最好使用 引用傳參(第一個(gè)參數(shù)是默認(rèn)的 this 指針,我們不用管)。
其次,第二個(gè)參數(shù),即賦值運(yùn)算符的右操作數(shù),我們在函數(shù)體內(nèi)不會對其進(jìn)行修改,所以最好加上 const 進(jìn)行修飾。
原因2:
為什么函數(shù)要寫返回值?并且函數(shù)的返回值要使用 引用返回 ?
寫返回值是因?yàn)?,在賦值操作的時(shí)候我們經(jīng)常會遇到連續(xù)賦值的場景,如d1 = d2 = d3 = d4
寫返回值就能實(shí)現(xiàn)連續(xù)賦值的情況,更加模擬賦值運(yùn)算符
用引用返回是為了避免不必要的拷貝,適用于出來作用域返回值沒有被銷毀的場景
具體可以看之前我寫的c++入門那篇文章
原因3
為什么要用 if ,他是用來干嘛的?
這個(gè) if 條件判斷是用來檢查,賦值是否是給自己賦值的,
因?yàn)槲覀冊谫x值操作時(shí)有時(shí)會寫成:d1 = d1? 的情況為了防止不必要的操作
原因4
為什么返回的是 *this?
對于? 賦值運(yùn)算?d1 = d2? 的情況,是將d2的值賦值給d1
對其顯示處理就是:d1.operator = (d2)
又因?yàn)?strong>賦值運(yùn)算的計(jì)算順序是從右往左走的,在連續(xù)賦值的時(shí)候就會有所體現(xiàn)
所以我們應(yīng)該返回賦值運(yùn)算的左操作數(shù),而在類中,左操作數(shù)就是this指針
this是d1的地址,返回*this,就是返回d1
5.3賦值運(yùn)算符重載的一個(gè)特性
一個(gè)類如果沒有顯式定義賦值運(yùn)算符重載,編譯器也會生成一個(gè),完成對象按字節(jié)進(jìn)行的值拷貝。
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print() // 打印函數(shù)
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2023, 5, 1);
// 這里d1調(diào)用的編譯器生成 operator= 完成拷貝,d2和d1的值也是一樣的。
d1 = d2;
d1.Print();
d2.Print();
return 0;
}
結(jié)果:
可以看到賦值運(yùn)算符重載編譯器也可以自動(dòng)生成,并且也是支持連續(xù)賦值的。
6. const 成員
6.1const成員函數(shù)
我們把 const 修飾的? “成員函數(shù)”? 稱為 const 成員函數(shù)
const修飾類成員函數(shù),實(shí)際修飾該成員函數(shù)隱含的this指針,表明在該成員函數(shù)中不能對類的任何成員進(jìn)行修改。
class Date
{
public:
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()
{
const Date d1(2022, 9, 1);//用const修飾對象d1,使其成員在使用時(shí)不會改變
d1.Print();
return 0;
}
以上代碼中,我們用const來修飾類對象d1,使得d1里面的成員變量不會再之后的使用中被修改
但是再編譯時(shí)會報(bào)錯(cuò):
很明顯我們的Print函數(shù)沒有去該改變成員變量,那為什么還會報(bào)錯(cuò)呢?
這是因?yàn)椋?d1 對象去調(diào)用 Print?函數(shù)的時(shí)候,實(shí)參會把 d1 的地址傳過去,但是 d1 是被 const 修飾的,也就是傳過去的是 const Date* ;
而在 Print1 函數(shù)這邊,形參部分會有一個(gè)隱含的 this 指針,也是 Date* ?this
也就是把?const Date* 傳給了 Date* ?this,在這里屬于權(quán)限的放大,所以編譯會不通過。
?
那么我們應(yīng)該怎么解決呢?
這里我們就可以用const來修飾成員函數(shù):把 const 放在成員函數(shù)之后,實(shí)際就是修飾 this 指針:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// const成員函數(shù)
void Print1() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
const Date d1(2022, 9, 1);//用const修飾對象d1,使其成員在使用時(shí)不會改變
d1.Print1();
return 0;
}
那么在參數(shù)傳遞部分,實(shí)參還是和上面一樣,形參部分因?yàn)?const 修飾的成員函數(shù),所以就變成了 const Date* ?this,那么此時(shí)就是權(quán)限相等了。
6.2思考
1. const對象可以調(diào)用非const成員函數(shù)嗎?
?
不可以,就像上面的Print函數(shù)一樣,會報(bào)錯(cuò)
非 const 成員函數(shù),即成員函數(shù)的 this 指針沒有被 const 所修飾,我們傳入一個(gè)被 const 修飾的對象,使用沒有被 const 修飾的 this 指針進(jìn)行接收,屬于權(quán)限的放大,函數(shù)調(diào)用失敗。
2. 非const對象可以調(diào)用const成員函數(shù)嗎?
?
可以,因?yàn)槭菣?quán)限縮小
我們傳入 成員函數(shù)的是一個(gè) 大的,而成員函數(shù)在接受時(shí),由于const的存在,將其 縮小
屬于權(quán)限縮小,可以被執(zhí)行
3. const成員函數(shù)內(nèi)可以調(diào)用其它的非const成員函數(shù)嗎?
?
不可以,在一個(gè)被 const 所修飾的成員函數(shù)中調(diào)用其他沒有被 const 所修飾的成員函數(shù),
也就是將一個(gè)被 const 修飾的 this 指針的值賦值給一個(gè)沒有被 const 修飾的 this 指針,屬于權(quán)限的放大,函數(shù)調(diào)用失敗。
4. 非const成員函數(shù)內(nèi)可以調(diào)用其它的const成員函數(shù)嗎?
?
可以,在一個(gè)沒有被 const 所修飾的成員函數(shù)中調(diào)用其他被 const 所修飾的成員函數(shù),
也就是將一個(gè)沒有被 const 修飾的 this 指針的值賦值給一個(gè)被 const 修飾的 this 指針,屬于權(quán)限的縮小,函數(shù)調(diào)用成功
7.取地址及const取地址操作符重載
取地址操作符重載 和 const 取地址操作符重載,這兩個(gè)默認(rèn)成員函數(shù)一般不用自己重新定義,使用編譯器自動(dòng)生成的就行。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//顯示寫出:取地址操作符重載
//普通對象 取地址操作符重載
Date* operator&()
{
return this;
}
//const對象 取地址操作符重載
const Date* operator&() const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 5, 7);
const Date d2(2023, 5, 7);
cout << &d1 << endl;
cout << &d2 << endl;
return 0;
}
這兩個(gè)運(yùn)算符一般不需要重載,使用編譯器生成的默認(rèn)取地址的重載即可,我們作為了解、知道有這兩個(gè)東西存在即可
只有特殊情況,才需要重載,比如想讓別人獲取到指定的內(nèi)容,就可以自己實(shí)現(xiàn)。
練習(xí):
根據(jù)前面所學(xué)知識的一個(gè)練習(xí):日期類的實(shí)現(xiàn)
要求:
1.能獲取某年某月的天數(shù)
2.寫出構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、析構(gòu)函數(shù)
3.判斷兩個(gè)日期的關(guān)系: > ; < ; <= ; >= ; !=?; ==
4.日期+天數(shù)、日期+=天數(shù)
5.日期-天數(shù)、日期-=天數(shù)
6.前置++、后置++,前置--,后置--
7.日期-日期,返回天數(shù)文章來源:http://www.zghlxwxcb.cn/news/detail-438034.html
代碼如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-438034.html
#pragma once
#include<iostream>
#include<string.h>
using namespace std;
//聲明:
class Date
{
public:
void Print();
//構(gòu)造函數(shù)
Date(int year = 2023, int month = 1, int day = 1);
//拷貝構(gòu)造函數(shù)
Date(const Date& d);
//析構(gòu)函數(shù)
~Date();
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+= (int day);
Date operator+ (int day);
Date& operator-= (int day);
Date operator- (int day);
// ++d
Date& operator++();
// d++
Date operator++(int);
// --d
Date& operator--();
// d--
Date operator--(int);
//日期-日期
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};
//定義:
void Date::Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
int GetMonthDay(int year, int month)
{
int monthArray[13] = { 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 monthArray[month];
}
}
Date::Date(int year, int month, int day)
{
if (month > 0 && month < 13 && (day > 0 && day <= GetMonthDay(year, month)))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "日期非法" << endl;
}
}
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date::~Date()
{
_year = 2023;
_month = 1;
_day = 1;
}
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator<(const Date& d)
{
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;
}
else
{
return false;
}
}
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);
}
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;
}
Date Date::operator+ (int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
Date& Date::operator-= (int day)
{
if (day < 0)
{
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0)
{
if (--_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator- (int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
// ++d 顯示去調(diào):d1.operator++();
Date& Date::operator++()
{
*this += 1;//調(diào)用上面寫的運(yùn)算符重載
return *this;
}
// d++ 后置++的類型是編譯器規(guī)定的這樣寫
//顯示去調(diào):d1.operator++(0);
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
// --d
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// d--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return n * flag;
}
//測試
void text4()
{
Date d1(2023, 5, 6);
Date d2(2001, 3, 6);
int life = d1 - d2;
cout << life << endl;
}
int main()
{
text4();
return 0;
}
到了這里,關(guān)于C++好難(3):類和對象(中篇)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!