C++ 再識(shí)類和對(duì)象
類的6個(gè)默認(rèn)成員函數(shù)
一個(gè)類中如果什么成員都沒有,那么這個(gè)類稱為空類??疹愔惺鞘裁炊紱]有嗎?其實(shí)不然,任何一個(gè)類,再我們不寫的情況下,都會(huì)自動(dòng)生成下面6個(gè)默認(rèn)成員函數(shù):

本篇文章將對(duì)這幾個(gè)默認(rèn)成員函數(shù)進(jìn)行簡(jiǎn)單介紹。
構(gòu)造函數(shù)
1.概念
我們先來(lái)看一下下面這個(gè)日期類:
class Date
{
public:
void SetDate(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;
d1.SetDate();
d1.Print();
return 0;
}
對(duì)于Date類,每次創(chuàng)建對(duì)象時(shí)可以調(diào)用SetData函數(shù)來(lái)設(shè)置對(duì)象的日期,但是如果每次創(chuàng)建對(duì)象時(shí)都需要調(diào)用該函數(shù)來(lái)設(shè)置日期信息,未免有些麻煩,那么能否再對(duì)象創(chuàng)建的同時(shí)就進(jìn)行初始化呢?
這里就需要用到類的默認(rèn)成員函數(shù)–構(gòu)造函數(shù)了。
構(gòu)造函數(shù)是一個(gè)特殊的成員函數(shù),名字與類名相同,創(chuàng)建類類型對(duì)象時(shí)由編譯器自動(dòng)調(diào)用,保證每個(gè)數(shù)據(jù)成員都有 一個(gè)合適的初始值,并且在對(duì)象的生命周期內(nèi)只調(diào)用一次。
2.特性
需要注意,構(gòu)造函數(shù)雖然名為構(gòu)造函數(shù),但是其作用并非為成員變量開辟空間,而是初始化對(duì)象。其特征如下:
函數(shù)名與類名相同。
沒有返回值。
編譯器會(huì)再對(duì)象實(shí)例化時(shí)自動(dòng)調(diào)用構(gòu)造函數(shù)。
構(gòu)造函數(shù)可以重載。
需要注意的是在類實(shí)例化對(duì)象的時(shí)候,如果變量后面帶上了(),而括號(hào)內(nèi)沒有參數(shù),那么這就成了函數(shù)聲明,該函數(shù)無(wú)參,且返回值為類名。
class Date
{
public:
Date()//無(wú)參的構(gòu)造函數(shù)
{
_year = 0;
_month = 1;
_day = 1;
}
//帶參的構(gòu)造函數(shù)
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//調(diào)用無(wú)參的構(gòu)造函數(shù)
Date d2(0, 1, 1);//調(diào)用帶參的構(gòu)造函數(shù)
Date d3();//無(wú)參,返回值為Date的函數(shù)聲明
return 0;
}
隱式構(gòu)造函數(shù)
如果類中沒有顯式定義構(gòu)造函數(shù),那么c++編譯器將會(huì)自動(dòng)生成一個(gè)無(wú)參的默認(rèn)構(gòu)造函數(shù),而如果用戶顯式定義了構(gòu)造函數(shù),那么編譯器將不再生成構(gòu)造函數(shù)。
需要注意的是編譯器自己生成的構(gòu)造函數(shù)在初始化對(duì)象時(shí)做了一個(gè)偏心的處理:即對(duì)于內(nèi)置類型,編譯器不會(huì)處理;而對(duì)于自定義類型,編譯器會(huì)自定義類型調(diào)用它自己的默認(rèn)構(gòu)造函數(shù)。內(nèi)置類型指的是語(yǔ)法已經(jīng)定義好的類型,如:int,double,long等等;自定義類型是使用struct/class/union定義的類型。
這是什么意思呢?我們通過(guò)下面這個(gè)代碼來(lái)理解:
class C
{
public:
C()
{
cout << "C()" << endl;
}
private:
int _c;
};
class Date
{
public:
//若用戶顯式定義了構(gòu)造函數(shù),那么編譯器將不再生成
/*Date()
{
_year = 0;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}*/
private:
//內(nèi)置類型
int _year;
int _month;
int _day;
//自定義類型
C c1;
};
int main()
{
Date d1;//調(diào)用無(wú)參的構(gòu)造函數(shù)
return 0;
}


通過(guò)調(diào)試可以發(fā)現(xiàn),d1自身的內(nèi)置類型變量仍為隨機(jī)值,編譯器調(diào)用的構(gòu)造函數(shù)并沒有處理,而對(duì)于自定義類型,可以看到編譯器調(diào)用了自定義類型中的默認(rèn)函數(shù),但是實(shí)際上如果調(diào)用編譯器自己生成的默認(rèn)構(gòu)造函數(shù),最終的結(jié)果就是所有的內(nèi)置類型變量仍然為隨機(jī)值,這么看下來(lái)好像編譯器自己生成的構(gòu)造函數(shù)好像沒什么用?
實(shí)則不然,比如我們?cè)鲞^(guò)用棧實(shí)現(xiàn)隊(duì)列的題,這道題的思路是用兩個(gè)棧來(lái)回倒保證隊(duì)列的先進(jìn)先出,而這里面的兩個(gè)結(jié)構(gòu)棧和用棧實(shí)現(xiàn)的隊(duì)列的代碼為:
class Stack//棧
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
private:
int* _a;
int _top;
int _capacity;
};
struct MyQueue//用兩個(gè)棧實(shí)現(xiàn)隊(duì)列
{
Stack s1;
Stack s2;
};
可以看到在用MyQueue這個(gè)類實(shí)例化對(duì)象時(shí),編譯器調(diào)用Stack中的構(gòu)造函數(shù)分別對(duì)成員變量s1和s2初始化,因此,我們無(wú)需再對(duì)其進(jìn)行初始化了,這相對(duì)來(lái)說(shuō)方便了許多。
無(wú)參和全缺省的函數(shù)均為默認(rèn)構(gòu)造函數(shù)
無(wú)參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都被稱為默認(rèn)構(gòu)造函數(shù),但是需要注意的是:無(wú)參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)二者只能存在一個(gè),這是因?yàn)?,如果二者都存在的話,那么在?shí)例化對(duì)象不帶參數(shù)時(shí),編譯器無(wú)法區(qū)分是調(diào)用哪一個(gè)函數(shù)。
class Date
{
public:
Date()
{
_year = 0;
_month = 1;
_day = 1;
}
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//錯(cuò)誤,編譯器無(wú)法識(shí)別要調(diào)用哪一個(gè)構(gòu)造函數(shù)
return 0;
}
在實(shí)際過(guò)程中,我們更傾向于使用全缺省的構(gòu)造函數(shù),因?yàn)樗藷o(wú)參的構(gòu)造函數(shù)的情況。
成員變量的命名風(fēng)格
可以注意到的是在定義類的時(shí)候成員變量前都加了一個(gè)_,這是為了防止下面這種情況:
class Date
{
public:
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;
d1.Print();
return 0;
}

可以看到,d1調(diào)用了構(gòu)造函數(shù)后,其成員變量認(rèn)為隨機(jī)值。這是因?yàn)樵趛ear = year這句代碼中,兩個(gè)year變量均為函數(shù)形參,實(shí)際上編譯器在處理這種變量時(shí),會(huì)遵循局部?jī)?yōu)先原則,即編譯器在函數(shù)形參中找到了year變量,就不會(huì)繼續(xù)擴(kuò)大搜索范圍去尋找成員變量中的year變量,而在Print函數(shù)中,編譯器由于在形參中未找到y(tǒng)ear變量,因此繼續(xù)擴(kuò)大搜索范圍,在成員變量中找到了year并使用之。
因此,在聲明成員變量的命名時(shí)需要遵循一定的規(guī)范,常見的有:(1)在變量名前加_,如_year (2)在變量名后加_,如year_ (3)駝峰法,如mYear,m表示member。
另外,上述情況可以通過(guò)使用this指針進(jìn)行解決,即將代碼改為this->year = year;但在實(shí)際使用過(guò)程中,最好還是注重成員變量的命名
補(bǔ)充
由于早期c++語(yǔ)法設(shè)計(jì)的缺陷,編譯器默認(rèn)生成的構(gòu)造函數(shù)并不會(huì)對(duì)內(nèi)置類型變量初始化,因此在c++11后,語(yǔ)法委員會(huì)在成員變量聲明處打了一個(gè)補(bǔ)丁,運(yùn)行,變量聲明的同時(shí)加上缺省值,比如:
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
//注意,此處僅為缺省值,仍為變量聲明,而非初始化(定義)
int _year = 0;
int _month = 1;
int _day = 1;
};
析構(gòu)函數(shù)
1.概念
與構(gòu)造函數(shù)相比,析構(gòu)函數(shù)相對(duì)簡(jiǎn)單一些。析構(gòu)函數(shù)的作用與構(gòu)造函數(shù)的相反,析構(gòu)函數(shù)并不是完成對(duì)象的銷毀,因?yàn)榫植繉?duì)象的銷毀工作是由編譯器來(lái)完成的。一個(gè)詞來(lái)概括析構(gòu)函數(shù)的作用就是清理,即對(duì)象在銷毀的時(shí)候會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),完成類當(dāng)中的一些資源清理工作。
2.特性
析構(gòu)函數(shù)是一種特殊的成員函數(shù),其特征如下:
析構(gòu)函數(shù)名是類名前加上~號(hào)
析構(gòu)函數(shù)無(wú)參數(shù)無(wú)返回值
一個(gè)類有且只有一個(gè)析構(gòu)函數(shù)
若析構(gòu)函數(shù)為顯式定義,那么系統(tǒng)會(huì)自動(dòng)生成默認(rèn)的析構(gòu)函數(shù)。
與構(gòu)造函數(shù)一樣,系統(tǒng)的默認(rèn)析構(gòu)函數(shù)對(duì)于內(nèi)置類型變量不會(huì)處理,對(duì)于自定義變量會(huì)調(diào)用其自身的析構(gòu)函數(shù)。
其次,對(duì)于Date類這樣的類,由于其內(nèi)部沒有什么資源需要處理,因此不需要析構(gòu)函數(shù);對(duì)于Stack這樣的類,其內(nèi)部由資源需要處理,比如對(duì)malloc出來(lái)的空間進(jìn)行釋放,因此需要實(shí)現(xiàn)析構(gòu)函數(shù)。
還是之前的代碼,在用兩個(gè)棧實(shí)現(xiàn)隊(duì)列中,在Stack類中實(shí)現(xiàn)了構(gòu)造函數(shù)和析構(gòu)函數(shù),那么用MyQueue實(shí)例化my變量后無(wú)法自己實(shí)現(xiàn)初始化和空間的釋放:
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
free(_a);
_a = NULL;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
struct MyQueue
{
Stack s1;
Stack s2;
};
int main()
{
//我們無(wú)需自己對(duì)mq進(jìn)行初始化和清理空間
//編譯會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)
MyQueue mq;
return 0;
}
c++編譯器在對(duì)象生命周期結(jié)束時(shí)自動(dòng)調(diào)用析構(gòu)函數(shù)
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;//編譯器在執(zhí)行這句代碼的同時(shí)會(huì)調(diào)用類中的析構(gòu)函數(shù)
}
拷貝構(gòu)造函數(shù)
1.概念
拷貝構(gòu)造函數(shù),顧名思義,其作用就是創(chuàng)建一個(gè)和被拷貝對(duì)象一模一樣的對(duì)象。
拷貝構(gòu)造函數(shù)只有單個(gè)形參,該形參是對(duì)本類類型對(duì)象的引用(一般常用const修飾),在用已存在的類類型對(duì)象創(chuàng)建新對(duì)象時(shí)由編譯器自動(dòng)調(diào)用。
2.特性
拷貝構(gòu)造函數(shù)也是特殊的成員函數(shù),其特征是:
拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式
參數(shù)只有一個(gè)且為引用傳參
拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須為引用傳參,使用傳值方式會(huì)引發(fā)無(wú)窮遞歸調(diào)用。
class Date
{
public:
Date()
{
_year = 0;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
那么為什么說(shuō)傳值會(huì)導(dǎo)致無(wú)窮遞歸調(diào)用呢?首先我們需要理解到調(diào)用函數(shù)傳值給形參也是一種拷貝,比如說(shuō):

同樣的,對(duì)于拷貝構(gòu)造函數(shù),若形參為傳值調(diào)用,那么在上述代碼中將d2賦值給形參d時(shí)也會(huì)調(diào)用拷貝構(gòu)造函數(shù),而每一次調(diào)用拷貝構(gòu)造函數(shù)都會(huì)經(jīng)過(guò)依次賦值操作,從而導(dǎo)致無(wú)窮遞歸調(diào)用:

而傳引用就能夠很好的解決這個(gè)問(wèn)題,其次,傳指針也可以達(dá)到目的,不過(guò)一般傳引用的話可以增強(qiáng)代碼可讀性。
若未顯式定義,系統(tǒng)會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)
與構(gòu)造函數(shù)一樣,如果我們自己沒有實(shí)現(xiàn)拷貝構(gòu)造函數(shù),那么編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù);但是與構(gòu)造函數(shù)不同的是,默認(rèn)的拷貝構(gòu)造函數(shù)對(duì)于內(nèi)置類型和自定義類型變量都會(huì)處理:
(1)對(duì)于內(nèi)置類型,默認(rèn)的拷貝構(gòu)造函數(shù)會(huì)對(duì)對(duì)象進(jìn)行淺拷貝,即按照內(nèi)存存儲(chǔ)中的字節(jié)序?qū)?duì)象進(jìn)行拷貝,也叫值拷貝。
(2)對(duì)于自定義類型,默認(rèn)的拷貝構(gòu)造函數(shù)會(huì)調(diào)用自定義類型中自己的拷貝構(gòu)造函數(shù)。
class A
{
public:
A()
{
_a = 0;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
private:
int _a;
};
class Date
{
public:
Date()
{
_year = 0;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//調(diào)用默認(rèn)的拷貝構(gòu)造函數(shù)
/*Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}*/
private:
int _year;
int _month;
int _day;
A aa;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}

淺拷貝的注意事項(xiàng)
通過(guò)上面我們知道了默認(rèn)的拷貝構(gòu)造函數(shù)能夠?qū)崿F(xiàn)淺拷貝,也就是說(shuō),對(duì)于Date這樣的類,我們無(wú)需自己實(shí)現(xiàn)拷貝構(gòu)造函數(shù)只用默認(rèn)的拷貝構(gòu)造函數(shù)就能夠?qū)崿F(xiàn)拷貝目的,那么是否用編譯器自己的函數(shù)就夠了呢?
其實(shí)不然,比如我們熟知的Stack類,如果直接調(diào)用系統(tǒng)默認(rèn)的拷貝構(gòu)造函數(shù):
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
free(_a);
_a = NULL;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack s1(8);
Stack s2(s1);
return 0;
}
上述代碼,我們運(yùn)行后發(fā)現(xiàn),程序崩潰了,這是為什么呢?這是因?yàn)橄到y(tǒng)默認(rèn)的拷貝構(gòu)造函數(shù)拷貝出了一份與s1一模一樣的s2:

而我們知道當(dāng)對(duì)象的生命周期結(jié)束時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)對(duì)類空間進(jìn)行清理,由于s2是后壓棧的,因此會(huì)先清理,這時(shí)s2._a所指的空間已經(jīng)free還給操作系統(tǒng)了,但是s1還會(huì)再次調(diào)用析構(gòu)函數(shù),將已經(jīng)釋放的s1._a所指向的空間再一次釋放(注意,s2._a釋放完后s1._a仍指向原空間,此時(shí)s1._a為野指針),這個(gè)操作最終會(huì)導(dǎo)致程序崩潰。

可見編譯器默認(rèn)的拷貝構(gòu)造函數(shù)并不能解決所有的問(wèn)題,淺拷貝會(huì)導(dǎo)致一些錯(cuò)誤,那么要如何解決淺拷貝的帶來(lái)的問(wèn)題呢?這就要我們之后介紹的深拷貝來(lái)解決了。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)酒店管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)酒店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03
C/C++自主分配出現(xiàn)double free or corruption問(wèn)題解決
這篇文章主要為大家介紹了C/C++出現(xiàn)double free or corruption問(wèn)題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
C語(yǔ)言新手初階教程之三子棋實(shí)現(xiàn)
相信大家在小時(shí)候都用紙和筆與小伙伴們玩過(guò)一個(gè)經(jīng)典的游戲之井字棋,即三子棋,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言新手初階教程之三子棋實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2022-01-01
C語(yǔ)言的分支和循環(huán)語(yǔ)句你真的了解嗎
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言的分支和循環(huán)語(yǔ)句,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02
基于C語(yǔ)言實(shí)現(xiàn)鉆石棋游戲的示例代碼
獨(dú)立鉆石是源于18世紀(jì)法國(guó)的宮廷貴族的自我挑戰(zhàn)類單人棋游戲,可以鍛煉邏輯思維能力。本文將用C語(yǔ)言實(shí)現(xiàn)這一簡(jiǎn)單的游戲,感興趣的小伙伴可以了解一下2023-02-02
C++ 讀寫文件安全又簡(jiǎn)潔的簡(jiǎn)單實(shí)例
這篇文章主要介紹了C++ 讀寫文件安全又簡(jiǎn)潔的簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-06-06

