類的6個默認成員函數(shù)
如果我們定義一個類,然后這個類中什么也沒有。那么這里的類就什么也沒有嗎?其實不然,任何類在里面什么都不寫時,編譯器都會生成6個默認成員函數(shù)。
概念
用戶沒有顯式實現(xiàn),編譯器會生成的成員函數(shù)稱為默認成員函數(shù)。
六個默認成員函數(shù)
構(gòu)造函數(shù)
我們來看一個Date類
#include <iostream>
using namespace std;
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, 7, 23);
d1.Print();
Date d2;
d2.Init(2023, 7, 24);
d2.Print();
return 0;
}
觀察上述代碼,我們需要顯式的調(diào)用Init函數(shù),這個函數(shù)可以給私有的成員變量賦值,但是如果每次都使用這個方法設(shè)置信息,就顯得有點麻煩了,這里我們引出了構(gòu)造函數(shù)的概念:
概念
構(gòu)造函數(shù)時特殊的成員函數(shù),名字與類名相同,創(chuàng)建類類型對象時由編譯器自動調(diào)用,以保證每個數(shù)據(jù)成員都有一個合適的初始值,并且在對象整個生命周期內(nèi)只調(diào)用一次。
特點
函數(shù)名和類名相同,沒有返回值,可以發(fā)生重載,對象實例化時編譯器自動調(diào)用構(gòu)造函數(shù)。除此之外我們還需要了解一下的特點,我們看一個代碼:
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
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 d;
d.Print();
return 0;
}
我們看到上述代碼中我們顯式的定義了一個構(gòu)造函數(shù),編譯器會調(diào)用我們自己定義的構(gòu)造函數(shù),也就是說當我們自己定義了一個構(gòu)造函數(shù),編譯器就不會生成無參的默認構(gòu)造函數(shù)。
接下來我們了解一下默認構(gòu)造函數(shù):
我們再來看一段代碼
class Date
{
public:
//構(gòu)造函數(shù) 任務初始化對象
//無參構(gòu)造函數(shù)
//編譯器會自動調(diào)用構(gòu)造函數(shù)。
//需要注意,我們在顯式的定義構(gòu)造函數(shù)時,編譯器不會生成默認的構(gòu)造函數(shù)。
//編譯器生成的默認構(gòu)造函數(shù)不會對內(nèi)置類型進行初始化,生成的構(gòu)造函數(shù)是
//無參的構(gòu)造函數(shù)。
//默認構(gòu)造函數(shù)包括一下三類:我們自己寫的無參構(gòu)造函數(shù),還有我們不寫時編譯器默認生成的無參構(gòu)造函數(shù)。
//全缺省構(gòu)造函數(shù),這三個構(gòu)造函數(shù)只能在類中出現(xiàn)一個,否則就會出現(xiàn)調(diào)用的二義性。
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
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 d;
d.Print();
return 0;
}
我們可以看到,上述代碼發(fā)生了編譯錯誤,多個默認構(gòu)造函數(shù)和對重載函數(shù)的調(diào)用不明確。這是因為C++規(guī)定默認構(gòu)造函數(shù)分為編譯器自動生成的構(gòu)造函數(shù),自己定義的構(gòu)造函數(shù)和全缺省構(gòu)造函數(shù),這三個構(gòu)造函數(shù)只能在類中出現(xiàn)一個,所以我們在定義構(gòu)造函數(shù)時,不能將兩個默認構(gòu)造函數(shù)同時寫在同一個類中。
我們再來思考一個問題,編譯器自動生成的構(gòu)造函數(shù),會對內(nèi)置類型進行初始化嗎?答案是不會,我們通過以下代碼來驗證這一點
class Date
{
public:
//需要注意,我們在顯式的定義構(gòu)造函數(shù)時,編譯器不會生成默認的構(gòu)造函數(shù)。
//編譯器生成的默認構(gòu)造函數(shù)不會對內(nèi)置類型進行初始化,生成的構(gòu)造函數(shù)是
//無參的構(gòu)造函數(shù)。
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//實例化對象
Date d;
d.Print();
return 0;
}
我們可以看到運行結(jié)果中沒有對內(nèi)置類型初始化,那么默認生成的構(gòu)造函數(shù)會做些什么呢,C++把類型分為內(nèi)置類型和自定義類型,內(nèi)置類型就是語言提供的數(shù)據(jù)類型,如int,char等,自定義類型就是我們使用class/struct關(guān)鍵字自己定義的類型,編譯器默認生成的構(gòu)造函數(shù)會對自定義類型成員_t 調(diào)用它的默認構(gòu)造函數(shù)。
class Time
{
public:
//自己定義的默認構(gòu)造函數(shù)
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
//基本類型
int _year;
int _month;
int _day;
//自定義類型
Time _t;
};
int main()
{
//實例化對象
Date d;
return 0;
}
以上代碼是調(diào)用了Time的構(gòu)造函數(shù)的。那么對于內(nèi)置類型成員不初始化的缺陷,C++11又定義:內(nèi)置類型成員變量在類中聲明時可以給默認值。
class Time
{
public:
//自己定義的默認構(gòu)造函數(shù)
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:
//基本類型
//缺省值
int _year = 1;
int _month = 1;
int _day = 1;
//自定義類型
Time _t;
};
int main()
{
//實例化對象
Date d;
d.Print();
return 0;
}
析構(gòu)函數(shù)
我們來看下面的代碼:
typedef int DateType;
class Stack
{
public:
//默認構(gòu)造函數(shù)
Stack(size_t capacity = 3)
{
cout << "Stack(size_t capacity = 3)" << endl;
_a = (DateType*)malloc(sizeof(DateType) * capacity);
if (NULL == _a)
{
perror("malloc failed!");
return;
}
_capacity = capacity;
_top = 0;
}
void Push(DateType data)
{
//擴容
_a[_top] = data;
_top++;
}
//其他方法……
void Destory()
{
free(_a);
_a = NULL;
_top = _capacity = 0;
}
private:
size_t _capacity;
size_t _top;
DateType* _a;
};
int main()
{
Stack st;
st.Push(1);
st.Push(2);
st.Push(3);
st.Destory();
return 0;
}
上面的代碼我們會發(fā)現(xiàn),在最后我們寫了一個函數(shù)叫做Destory,這里的這個函數(shù)的作用是銷毀資源,需要我們顯式調(diào)用,有點麻煩,那么我們怎么樣可以讓系統(tǒng)自動調(diào)用呢?我們就引出了析構(gòu)函數(shù)的概念。
概念
析構(gòu)函數(shù):析構(gòu)函數(shù)和構(gòu)造函數(shù)的功能相反,析構(gòu)函數(shù)不是完成對對象本身的銷毀,局部對象銷毀工作是由編譯器完成的。而對象在銷毀時會自動調(diào)用析構(gòu)函數(shù),完成對象中資源的清理工作。看以下代碼:
typedef int DateType;
class Stack
{
public:
//默認構(gòu)造函數(shù)
Stack(size_t capacity = 3)
{
cout << "Stack(size_t capacity = 3)" << endl;
_a = (DateType*)malloc(sizeof(DateType) * capacity);
if (NULL == _a)
{
perror("malloc failed!");
return;
}
_capacity = capacity;
_top = 0;
}
void Push(DateType data)
{
//擴容
_a[_top] = data;
_top++;
}
//其他方法……
//析構(gòu)函數(shù)
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = NULL;
_top = _capacity = 0;
}
/*void Destory()
{
free(_a);
_a = NULL;
_top = _capacity = 0;
}*/
private:
size_t _capacity;
size_t _top;
DateType* _a;
};
int main()
{
Stack st;
st.Push(1);
st.Push(2);
st.Push(3);
//st.Destory();
return 0;
}
我們可以看到上面的代碼調(diào)用了顯式定義的析構(gòu)函數(shù),析構(gòu)函數(shù)的特征是什么和構(gòu)造函數(shù)的特征相同嗎,我們來簡單討論一下:
特征
從上述代碼可知析構(gòu)函數(shù)具有如下特點:
- 函數(shù)名和類名相同且在函數(shù)名的前面加上字符~。
- 沒有返回值類型。
- 一個類只能有一個析構(gòu)函數(shù)。若為顯示定義,系統(tǒng)會自動生成默認的析構(gòu)函數(shù),注意:析構(gòu)函數(shù)不能重載。
- 對象聲明周期結(jié)束時,C++編譯系統(tǒng)自動調(diào)用析構(gòu)函數(shù)。編譯器默認生成的析構(gòu)函數(shù)不會free堆上的空間。
typedef int DateType;
class Stack
{
public:
//默認構(gòu)造函數(shù)
Stack(size_t capacity = 3)
{
cout << "Stack(size_t capacity = 3)" << endl;
_a = (DateType*)malloc(sizeof(DateType) * capacity);
if (NULL == _a)
{
perror("malloc failed!");
return;
}
_capacity = capacity;
_top = 0;
}
void Push(DateType data)
{
//擴容
_a[_top] = data;
_top++;
}
//其他方法……
//析構(gòu)函數(shù)
//~Stack()
//{
// cout << "~Stack()" << endl;
// free(_a);
// _a = NULL;
// _top = _capacity = 0;
//}
/*void Destory()
{
free(_a);
_a = NULL;
_top = _capacity = 0;
}*/
private:
size_t _capacity;
size_t _top;
DateType* _a;
};
int main()
{
Stack st;
//st.Destory();
return 0;
}
- 編譯器默認生成的析構(gòu)函數(shù),對自定義類型成員調(diào)用它們的析構(gòu)函數(shù)
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
//基本類型
int _year = 1970;
int _month = 1;
int _day = 1;
//自定義類型
Time _t;
};
int main()
{
Date d;
return 0;
}
上面的程序運行結(jié)束后輸出 ~Time()
在main方法中根本沒有直接創(chuàng)建Time類的對象,為什么最后會調(diào)用Time類的析構(gòu)函數(shù)?
因為:main方法中創(chuàng)建了Date對象d,而d中包含4個成員變量,其中_year, _month,
_day三個是內(nèi)置類型成員,銷毀時不需要資源清理,最后系統(tǒng)直接將其內(nèi)存回收即可;而_t是Time類對象,所以在d銷毀時,要將其內(nèi)部包含的Time類的_t對象銷毀,所以要調(diào)用Time類的析構(gòu)函數(shù)。但是:main函數(shù)中不能直接調(diào)用Time類的析構(gòu)函數(shù),實際要釋放的是Date類對象,所以編譯器會調(diào)用Date類的析構(gòu)函數(shù),而Date沒有顯式提供,則編譯器會給Date類生成一個默認的析構(gòu)函數(shù),目的是在其內(nèi)部調(diào)用Time類的析構(gòu)函數(shù),即當Date對象銷毀時,要保證其內(nèi)部每個自定義對象都可以正確銷毀main函數(shù)中并沒有直接調(diào)用Time類析構(gòu)函數(shù),而是顯式調(diào)用編譯器為Date類生成的默認析構(gòu)函數(shù)。
注意:創(chuàng)建哪個類的對象則調(diào)用該類的析構(gòu)函數(shù),銷毀那個類的對象則調(diào)用該類的析構(gòu)函數(shù)。
- 如果類中沒有申請資源時,析構(gòu)函數(shù)可以不寫,直接使用編譯器生成的默認析構(gòu)函數(shù),比如Date類;有資源申請時,一定要寫,否則會造成資源泄漏,比如Stack類。
拷貝構(gòu)造函數(shù)
我們來看下面的代碼引出拷貝構(gòu)造函數(shù)
class Date
{
public:
//全缺省默認構(gòu)造
Date(int year = 2023, int month = 7, int day = 30)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
//基本類型
int _year = 1970;
int _month = 1;
int _day = 1;
};
int main()
{
Date d;
//我們?nèi)绾斡靡汛嬖诘膶ο髞順?gòu)造一個和它相同的對象呢?顯然,
//我們使用拷貝構(gòu)造可以完成這個任務。
//調(diào)用拷貝構(gòu)造
Date d1(d);
return 0;
}
上述代碼我們知道想要利用已存在的對象,實例化一個和它相同的對象我們需要調(diào)用拷貝構(gòu)造函數(shù)。
概念
拷貝構(gòu)造函數(shù):只有單個形參,該形參是對類類型對象的應用一般常用const修飾,目的是為了不能對原有的對象進行修改,在用已存在的類類型對象創(chuàng)建新對象時由編譯器自動調(diào)用。
特點
- 拷貝構(gòu)造函數(shù)時構(gòu)造函數(shù)的一個重載形式。
- 拷貝構(gòu)造函數(shù)的參數(shù)只有一個且必須是類類型對象的引用,使用傳值方式編譯器直接報錯,因為會引發(fā)無窮遞歸調(diào)用拷貝構(gòu)造。我們來看下面的代碼;
class Date
{
public:
//全缺省默認構(gòu)造
Date(int year = 2023, int month = 7, int day = 30)
{
_year = year;
_month = month;
_day = day;
}
Date(Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
//基本類型
int _year = 1970;
int _month = 1;
int _day = 1;
};
int main()
{
Date d;
//我們?nèi)绾斡靡汛嬖诘膶ο髞順?gòu)造一個和它相同的對象呢?顯然,
//我們使用拷貝構(gòu)造可以完成這個任務。
//調(diào)用拷貝構(gòu)造
Date d1(d);
return 0;
}
上面的拷貝構(gòu)造函數(shù)參數(shù)為傳值調(diào)用,這會引發(fā)編譯器報錯,這是因為在傳值傳參時會進行拷貝構(gòu)造,而調(diào)用拷貝構(gòu)造還是需要傳參,這就會導致函數(shù)一直傳參,傳值傳參就會拷貝構(gòu)造。那么就造成了無窮遞歸調(diào)用
- 若為顯式定義,編譯器會自動生成默認的拷貝構(gòu)造函數(shù),默認的拷貝構(gòu)造函數(shù)對象按內(nèi)存存儲按字節(jié)序完成拷貝,這種拷貝叫做淺拷貝或者值拷貝,但是我們在拷貝堆上的數(shù)據(jù)時需要把堆上的數(shù)據(jù)也拷貝過來,而不單單是拷貝一個堆上空間的一個指針。這不是真正的拷貝,也算是淺拷貝,上面的這種拷貝叫做深拷貝,這里的深拷貝會在以后詳細講解。我們這里只關(guān)心淺拷貝。
class Date
{
public:
//全缺省默認構(gòu)造
Date(int year = 2023, int month = 7, int day = 30)
{
_year = year;
_month = month;
_day = day;
}
private:
//基本類型
int _year = 1970;
int _month = 1;
int _day = 1;
};
int main()
{
Date d;
//我們?nèi)绾斡靡汛嬖诘膶ο髞順?gòu)造一個和它相同的對象呢?顯然,
//我們使用拷貝構(gòu)造可以完成這個任務。
//調(diào)用拷貝構(gòu)造,我們沒有顯式定義拷貝構(gòu)造函數(shù),編譯器會默認生成拷貝構(gòu)造。
Date d1(d);
return 0;
}
這里還是一樣,對于自定義類型,編譯器默認生成的拷貝構(gòu)造函數(shù)會去調(diào)用自定義類型的拷貝構(gòu)造函數(shù)和普通的構(gòu)造函數(shù)相同。
- 編譯器生成的默認拷貝構(gòu)造函數(shù)已經(jīng)可以完成字節(jié)序的值拷貝了,我們對于沒有涉及到資源申請的類,拷貝構(gòu)造函數(shù)是否寫都可以;一旦涉及到資源申請時,則拷貝構(gòu)造函數(shù)是一定要寫的,否則就是淺拷貝,我們需要深拷貝。
我們來看下面的代碼:
typedef int DataType;
class Stack
{
public:
//全缺省默認構(gòu)造函數(shù)
Stack(size_t capacity = 10)
{
_a = (DataType*)malloc(capacity * sizeof(int));
if (NULL == _a)
{
perror("malloc failed!");
return;
}
_capacity = capacity;
_top = 0;
}
void Push(const DataType x)
{
//擴容
_a[_top] = x;
_top++;
}
//析構(gòu)函數(shù)
~Stack()
{
if (_a)
{
free(_a);
_a = NULL;
_capacity = _top = 0;
}
}
private:
DataType* _a;
size_t _top;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
該段程序運行崩潰,因為上述程序?qū)嵗艘粋€對象s1,入棧4個數(shù)據(jù)。然后再使用該對象來拷貝構(gòu)造一個新的對象,當程序結(jié)束時,s2會調(diào)用析構(gòu)函數(shù)對資源進行銷毀,而s1也會調(diào)用析構(gòu)函數(shù)對資源進行銷毀,那么這就出現(xiàn)了free兩次的情況,解決方案只能是進行深度拷貝,這里的深度拷貝會在以后的文章中講解
- 拷貝構(gòu)造的應用場景
使用已存在對象創(chuàng)建新對象,上面的代碼就是這種場景
函數(shù)參數(shù)類型為類類型
函數(shù)返回值類型為類類型對象
看一下下面的代碼
class Date
{
public:
//不是默認構(gòu)造函數(shù)
Date(int year, int minute, int day)
{
cout << "Date(int year, int minute, int day)" << this << endl;
}
//拷貝構(gòu)造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
//析構(gòu)函數(shù)
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);
return temp;
}
int main()
{
//調(diào)用構(gòu)造函數(shù)
Date d1(2023, 7, 30);
Test(d1);
return 0;
}
上面的代碼在構(gòu)造d1對象時使用了構(gòu)造函數(shù),在給類類型形參傳參的時候調(diào)用了一次拷貝構(gòu)造,在用d構(gòu)造temp對象時使用了一次拷貝構(gòu)造,在返回temp對象時調(diào)用了一次拷貝構(gòu)造,程序運行結(jié)束后銷毀Test中的temp對象使用了一次析構(gòu)函數(shù),銷毀Test函數(shù)的參數(shù)d使用了析構(gòu)函數(shù),銷毀Test函數(shù)返回時創(chuàng)建的臨時對象使用了一次析構(gòu)函數(shù),銷毀main函數(shù)中的d1使用了一次析構(gòu)函數(shù)。為了提高效率我們在做類類型傳參和類類型作為返回值的時候,盡量使用引用。
賦值運算符重載
在這一節(jié)我們需要先了解運算符重載的概念和實現(xiàn)
運算符重載的概念
C++為了增強代碼的可讀性引入了運算符重載,運算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回類型,函數(shù)名字以及參數(shù)列表,其返回值類型和參數(shù)列表與普通的函數(shù)類似,下面是一個運算符重載的例子:
class Date
{
public:
//不是默認構(gòu)造函數(shù)
Date(int year, int minute, int day)
{
cout << "Date(int year, int minute, int day)" << this << endl;
}
//拷貝構(gòu)造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
}
//析構(gòu)函數(shù)
~Date()
{
cout << "~Date()" << endl;
}
public:
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(2023,7,30);
Date d2(2023,7,30);
//可以隱式調(diào)用也可以顯式調(diào)用
cout << (d1 == d2) << endl;
//顯式調(diào)用
cout << operator==(d1, d2) << endl;
}
為了解決上述問題,我們需要把運算符重載函數(shù),寫到類里面去,但是函數(shù)參數(shù)會少一個,這是由于成為成員函數(shù)就會增加一個this指針,而運算符重載函數(shù)的參數(shù)有幾個是由操作符的操作數(shù)決定的。也可以用友元函數(shù)進行解決,這個我們后面會說。
class Date
{
public:
//不是默認構(gòu)造函數(shù)
Date(int year, int minute, int day)
{
cout << "Date(int year, int minute, int day)" << this << endl;
}
//拷貝構(gòu)造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
}
//析構(gòu)函數(shù)
~Date()
{
cout << "~Date()" << endl;
}
//運算符重載
bool operator==(const Date& d1)
{
return (*this)._year == d1._year && (*this)._month == d1._month && (*this)._day == d1._day;
}
public:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023,7,30);
Date d2(2023,7,30);
//可以隱式調(diào)用也可以顯式調(diào)用
cout << (d1 == d2) << endl;
//顯式調(diào)用
cout <<d1.operator==(d2) << endl;
}
特性
- 不能通過連接其他符號來創(chuàng)建新的操作符:比如operator@
- 重載操作符必須有一個類類型參數(shù)
- 用于內(nèi)置類型的運算符,其含義不能改變,例如:內(nèi)置類型的+,不能改變其含義。
- 作為類成員函數(shù)重載時,其形參看起來比操作數(shù)數(shù)目少1,因為成員函數(shù)的第一個參數(shù)為隱藏的this。
.* :: sizeof ?: .
不能被重載。
賦值運算符重載
class Date
{
public:
//不是默認構(gòu)造函數(shù)
Date(int year, int minute, int day)
{
cout << "Date(int year, int minute, int day)" << this << endl;
}
//拷貝構(gòu)造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
}
//析構(gòu)函數(shù)
~Date()
{
cout << "~Date()" << endl;
}
//運算符重載
bool operator==(const Date& d1)
{
return _year == d1._year &&
_month == d1._month &&
_day == d1._day;
}
//賦值運算符重載
//賦值運算符不能在類外定義,因為我們沒有在類中顯式定義賦值運算符重載函數(shù)
//編譯器會自動生成,這就和類外定義的賦值運算符重載函數(shù)沖突了。另外定義成全局函數(shù)
//就沒有this指針這一說法了,就必須寫兩個參數(shù)
Date& operator=(const Date& d1)
{
//我們需要確定不是兩個相同的對象賦值
if (this != &d1)
{
cout << "Date& operator=(const Date& d1)" << endl;
_year = d1._year;
_month = d1._month;
_day = d1._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//Date d1(2023, 7, 30);
//Date d2(2023, 7, 30);
可以隱式調(diào)用也可以顯式調(diào)用
//cout << (d1 == d2) << endl;
顯式調(diào)用
//cout << d1.operator==(d2) << endl;
//兩個對象賦值操作就要用到賦值運算符重載
Date d3(1,1,1);
Date d4(1,1,1);
d3 = d4;
//注意寫成這樣為調(diào)用拷貝構(gòu)造
Date d5 = d3;
}
格式
- 賦值運算符重載格式
- 參數(shù)類型:const T&,傳遞引用可以提高傳參效率
- 返回值類型:T&: 返回引用可以提高返回的效率,有返回值目的是為了支持連續(xù)賦值
- 檢測是否自己給自己賦值
- 返回* this:要符合連續(xù)賦值的含義。 a = b = c
特性
賦值運算符只能重載成類的成員函數(shù)不能重載成全局函數(shù),因為賦值運算符重載函數(shù)是一個默認成員函數(shù),所以編譯器會默認生成賦值重載函數(shù),跟構(gòu)造函數(shù)和拷貝函數(shù)的行為一樣。Date和MyQueue不需要我們自己實現(xiàn)賦值重載,stack需要我們自己實現(xiàn),因為默認生成的是淺拷貝。
class Date
{
friend Date& operator=(const Date& d1, const Date& d2);
public:
//不是默認構(gòu)造函數(shù)
Date(int year, int minute, int day)
{
cout << "Date(int year, int minute, int day)" << this << endl;
}
//拷貝構(gòu)造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
}
//析構(gòu)函數(shù)
~Date()
{
cout << "~Date()" << endl;
}
//運算符重載
bool operator==(const Date& d1)
{
return _year == d1._year &&
_month == d1._month &&
_day == d1._day;
}
//賦值運算符重載
//賦值運算符不能在類外定義,因為我們沒有在類中顯式定義賦值運算符重載函數(shù)
//編譯器會自動生成,這就和類外定義的賦值運算符重載函數(shù)沖突了。另外定義成全局函數(shù)
//就沒有this指針這一說法了,就必須寫兩個參數(shù)
private:
int _year;
int _month;
int _day;
};
Date& operator=(const Date& d1,const Date& d2)
{
//我們需要確定不是兩個相同的對象賦值
if (&d2 != &d1)
{
cout << "Date& operator=(const Date& d1)" << endl;
d1._year = d2._year;
d1._month = d2._month;
d1._day = d2._day;
}
return d1;
}
int main()
{
//Date d1(2023, 7, 30);
//Date d2(2023, 7, 30);
可以隱式調(diào)用也可以顯式調(diào)用
//cout << (d1 == d2) << endl;
顯式調(diào)用
//cout << d1.operator==(d2) << endl;
//兩個對象賦值操作就要用到賦值運算符重載
Date d3(1, 1, 1);
Date d4(1, 1, 1);
d3 = d4;
//注意寫成這樣為調(diào)用拷貝構(gòu)造
Date d5 = d3;
}
C++ primer中有一句話說:我們可以重載賦值運算符。不論形參的類型是什么,賦值運算符都必須定義為成員函數(shù)。
用戶沒有顯式實現(xiàn)時,編譯器會生成一個默認賦值運算符重載,以值得方式逐字節(jié)拷貝。注意:內(nèi)置類型成員變量是直接賦值的,而自定義類型成員變量需要調(diào)用對應類的賦值運算符重載完成賦值。
class Time
{
public:
//構(gòu)造函數(shù)
Time(int hour = 0, int minute = 0, int second = 0)
{
cout << "Date(int hour = 0, int minute = 0, int second = 0)" << endl;
}
Time& operator=(const Time& t)
{
//判斷是不是同一個對象
if (this != &t)
{
cout << "Time& operator=(const Time& t)" << endl;
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
//不是默認構(gòu)造函數(shù)
Date(int year, int month, int day)
{
cout << "Date(int year, int month, int day)" << this << endl;
}
//拷貝構(gòu)造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
}
//析構(gòu)函數(shù)
~Date()
{
cout << "~Date()" << endl;
}
private:
//內(nèi)置類型
int _year;
int _month;
int _day;
//自定義類型
Time _t;
};
int main()
{
Date d3(1, 1, 1);
Date d4(1, 1, 1);
d3 = d4;
//注意寫成這樣為調(diào)用拷貝構(gòu)造
Date d5 = d3;
}
如果類中未涉及到資源管理,賦值運算符的重載是否實現(xiàn)都可以,一旦涉及到資源管理則必須要顯式實現(xiàn)。
取地址及const取地址操作符重載
這里就不做太多的介紹,這兩個運算符一般不需要重載,使用編譯器生成的默認取地址的重載即可,只有特殊情況,才需要重載,比如想讓別人獲取到指定的內(nèi)容。文章來源:http://www.zghlxwxcb.cn/news/detail-618709.html
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const //這里的const代表了不能修改對象
{
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
cout << &d1 << endl;
Date* p1 = &d1;
cout << p1 << endl;
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-618709.html
到了這里,關(guān)于C++ ------類和對象詳解六大默認成員函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!