C++/類與對象/默認成員函數(shù)@構(gòu)造函數(shù)的用法
名詞概念
默認構(gòu)造函數(shù):不用傳參就可以調(diào)用的構(gòu)造函數(shù)。有3種默認構(gòu)造函數(shù)(但是只能存在一個):
- 1、構(gòu)造函數(shù)的參數(shù)是全缺省的;
- 2、構(gòu)造函數(shù)是無參的;
- 3、編譯器自動生成的(當我們沒有編寫構(gòu)造函數(shù)時)
顯式構(gòu)造函數(shù):用戶自己編寫的構(gòu)造函數(shù)。
隱式構(gòu)造函數(shù):編譯器主動生成的。注意:當用戶自己編寫了構(gòu)造函數(shù)(包括拷貝構(gòu)造函數(shù)),也就是出現(xiàn)顯示構(gòu)造函數(shù)時,便不會有隱式構(gòu)造函數(shù),即編譯器不會主動生成了。
默認成員函數(shù)
在C++的類中,有6個默認的成員函數(shù)。所謂的默認成員函數(shù),就是用戶自己沒在類中編寫的“特殊”成員函數(shù),但是編譯器會自動生成的成員函數(shù)(也叫“隱式成員函數(shù)”)。
(ps:不要跟上面的 “ 默認成員函數(shù) ” 概念混淆了,說不清道不明的,靠自己領(lǐng)會了)
如下所示
#include <iostream>
using namespace std;
class Date
{
};
int main()
{
Date d;
// cout<<sizeof(d)<<endl; // 對象d的內(nèi)存大小是1
return 0;
}我們定義了一個類Date,而在Date這個類中既沒有成員變量,也沒有成員函數(shù),是一個空類。
然而實際上,編譯器自己默默地生成6個成員函數(shù)。分別是 默認構(gòu)造函數(shù) 、 默認析構(gòu)函數(shù) 、 默認拷貝構(gòu)造函數(shù) 、 默認賦值運算符重載函數(shù) 、默認取地址操作符重載函數(shù) 、 默認const取地址操作符重載函數(shù)。
題外話,我們在main函數(shù)里面,創(chuàng)建了一個空類Date的對象(也叫實例),請問對象d的大小是多少呢?驗證發(fā)現(xiàn)對象d的內(nèi)存大小是1。面對這種情況我會產(chǎn)生兩個疑問:
1、即然Date是空類,那由空類產(chǎn)生的對象不應(yīng)該內(nèi)存是0嗎,怎么會是1?
2、即然有默認的成員函數(shù),那成員函數(shù)不是也有內(nèi)存大小嗎?6個成員函數(shù)內(nèi)存大小所占空間大小怎么會是1?
文章末尾對這兩個疑問進行解答。
下面我們對默認構(gòu)造函數(shù),展開講解
構(gòu)造函數(shù)
概念
在C++中,構(gòu)造函數(shù)是一種特殊類型的成員函數(shù),用于在對象被創(chuàng)建時執(zhí)行初始化操作。構(gòu)造函數(shù)的名稱與類名相同,沒有返回類型,甚至不使用 void。
函數(shù)特征
- ① 函數(shù)名 與 類名相同
- ② 沒有返回類型
如下面所示,類中的成員函數(shù) Date 便是該類的構(gòu)造函數(shù)
#include <iostream>
using namespace std;
class Date
{
public:
// 構(gòu)造函數(shù)
Date()
{
}
};在c語言中,如果我們創(chuàng)建了一個變量,沒有初始化,那么變量將是隨機值。因此良好的編程習(xí)慣是,創(chuàng)建一個變量的同時,順便給變量初始化,讓變量的值是可知的,可預(yù)測的。
在C++中同樣的道理,當我們定義了一個類,用類創(chuàng)建對象時,順便的就給對象初始化了。
與C語言的區(qū)別是,初始化的這個動作,不用我們自己做,編譯器自動幫我們做了 (當我們編寫定義了構(gòu)造函數(shù)時,編譯器自動去調(diào)用構(gòu)造函數(shù);當我們沒有編寫構(gòu)造函數(shù)時,編譯器自動調(diào)用自己生成的構(gòu)造函數(shù)) 。
可以這么理解:在C++的類中,構(gòu)造函數(shù)便是給對象初始化的函數(shù)(即 構(gòu)造函數(shù) 就是 初始化函數(shù))。
為了方便理解,先從顯示構(gòu)造函數(shù)說起,也就是自己編寫構(gòu)造函數(shù)。
顯示構(gòu)造函數(shù)
#include <iostream>
using namespace std;
class Date
{
public:
// 成員函數(shù)1
Date()
{
// 默認構(gòu)造函數(shù)
_year=2023;
_month=11;
_day=29;
}
// 成員函數(shù)2
//Date(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
// 成員函數(shù)3
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; // 會被編譯器轉(zhuǎn)換為 --> d1.Date(&d1)
d1.Print(); // 調(diào)用類中的成員函數(shù)Print,打印對象1中的日期
//Date d2(1976,9,9) // 對象2
//d2.Print(); // 調(diào)用類中的成員函數(shù)Print,打印對象2中的日期
return 0;
}如上代碼所示,我們用類Date實例出對象d1,然后調(diào)用類中的成員函數(shù)3打印對象d1的所有成員變量(也就是日期),運行程序,結(jié)果如下

驗證結(jié)論是,對象d1在創(chuàng)建時,編譯器自動的去類里面尋找默認構(gòu)造函數(shù)(也就是成員函數(shù)1),而我們在成員函數(shù)1里面完成了所有成員變量的初始化。總的效果來說編譯器主動的幫我們對對象d1初始化了。
到此構(gòu)造函數(shù)就可以結(jié)束了嗎?你覺得該程序還有什么不好的點,可以有更友善靈活的點嗎?
我們發(fā)現(xiàn),不管我們用Date創(chuàng)建多少的對象,對象的初始化都是同一個日期2023-11-29。那么如果我要創(chuàng)建一個對象,但是初始化的日期是自己指定的呢?如下面創(chuàng)建一個對象d2,指定日期是1976-9-9。
Date d2(1976,9,9);
這時,編譯器報錯提示 “沒有與參數(shù)列表匹配的構(gòu)造函數(shù)”。
這是因為前面我們創(chuàng)建對象d1時,編譯器找的是無參的、默認構(gòu)造函數(shù),而類中的成員函數(shù)1正好就與之匹配。而現(xiàn)在我們創(chuàng)建了對象d2,但是初始化時傳入了我們指定的日期參數(shù),這時編譯器去類中找的是帶有參數(shù)的構(gòu)造函數(shù)(也就是成員函數(shù)2,代碼中我注釋掉了),而我們還沒有編寫帶參的構(gòu)造函數(shù),所以編譯器找不到匹配的構(gòu)造函數(shù),便報錯編譯不通過了。
解決方法是,沒有條件就給創(chuàng)建條件,我們再在類中定義一個帶參的構(gòu)造函數(shù),也就成員函數(shù)2,如下:
// 成員函數(shù)2
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}這時,我們創(chuàng)建對象2
Date d2(1976,9,9); d2.Print();
時,編譯器便會找到成員函數(shù)2,完成對象2的成員變量的初始化。(clue:成員函數(shù)2不是默認構(gòu)造函數(shù))
運行結(jié)果如下:

到此,我們實現(xiàn)了構(gòu)造函數(shù)的全部功能,
- 1、當創(chuàng)建對象不給定參數(shù)時,調(diào)用默認構(gòu)造函數(shù)初始化;
- 2、當創(chuàng)建對象給指定參數(shù)時,調(diào)用帶參數(shù)的構(gòu)造函數(shù)初始化。
但是,我們發(fā)現(xiàn)要實現(xiàn)這兩個功能,要寫兩個構(gòu)造函數(shù),有點繁瑣,而且也很怪怪的,兩個構(gòu)造函數(shù)…那么有沒有方法用一個構(gòu)造函數(shù),實現(xiàn)上面兩個構(gòu)造函數(shù)的功能呢?如下:
實現(xiàn)方法:使用帶有全缺省參數(shù)的構(gòu)造函數(shù)(為了方便,記為成員函數(shù)4)。
class Date
{
public:
// 成員函數(shù)4
// 更好的實現(xiàn)(默認構(gòu)造函數(shù):不傳參就可以調(diào)用的函數(shù)) -> 全缺省
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date()" << this << endl; // 用來查看構(gòu)造函數(shù)和析構(gòu)函數(shù) 創(chuàng)建和銷毀的順序
}
// 成員函數(shù)3
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; // 會被編譯器轉(zhuǎn)換為 --> d1.Date(&d1)
d1.Print(); // 調(diào)用類中的成員函數(shù)Print,打印對象1中的日期
Date d2(1976,9,9) // 對象2
d2.Print(); // 調(diào)用類中的成員函數(shù)Print,打印對象2中的日期
}當創(chuàng)建對象不帶參時,構(gòu)造函數(shù)使用缺省值初始化;
當創(chuàng)建對象帶參時,構(gòu)造函數(shù)使用接收到的參數(shù)初始化。
最后,我們對以上,成員函數(shù)1、2、4進行總結(jié)分析:
1、當在類中只定義了成員函數(shù)1時,可以創(chuàng)建不帶參數(shù)的對象,但是卻無法創(chuàng)建指定初始值(帶參數(shù))的對象,如
Date d1(2023,11,29);
編譯器會報錯,錯誤類型是“沒有與參數(shù)列表匹配的構(gòu)造函數(shù)”
2、當在類中只定義了成員函數(shù)2時,可以創(chuàng)建指定初始值(帶參數(shù))的對象,但是卻不能創(chuàng)建不帶參數(shù)的對象,如
Date d1;
編譯器會報錯,錯誤類型是“類Date中沒有默認構(gòu)造函數(shù)”。
3、當在類定義了成員函數(shù)1和成員函數(shù)2,就可以解決以上兩種問題。
4、可以只在類中只定義成員函數(shù)4,便能實現(xiàn)成員函數(shù)1、2的功能。需要注意的是:當定義了成員函數(shù)4時,不能同時存在成員函數(shù)1 或 成員函數(shù)2,否則將導(dǎo)致編譯器報錯,因為編譯器不知道該調(diào)用哪一個構(gòu)造函數(shù)。
至此,顯示構(gòu)造函數(shù)學(xué)習(xí)完畢!下面繼續(xù)來看看隱式構(gòu)造函數(shù)。
隱式構(gòu)造函數(shù)
通過前面的介紹,我們能夠得知,隱式構(gòu)造函數(shù)就是,當用戶自己沒有編寫定義構(gòu)造函數(shù)時,編譯器自動生成的默認構(gòu)造函數(shù)。
下面我們驗證一下編譯器自動生成的默認構(gòu)造函數(shù),在背后干了個啥。
class Time
{
public:
// 構(gòu)造函數(shù)
Time
{
_hour = 0;
_minute = 0;
_second = 0;
cout<<"Time()"<<" :"<<" ";
cout<<_hour<<_minute<<second<<endl; // 將初始化后的結(jié)果打印輸出
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
// 成員函數(shù)1
void Print()
{
cout<<"Date:\t";
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t; // 類Time 實例出的對象 _t
};
int main()
{
Date d1;
d1.Print();
}運行程序,結(jié)果如下:

運行結(jié)果表明,編譯器自動生成的,隱式構(gòu)造函數(shù)做了以下的事情:
1、對于內(nèi)置類型的成員變量,沒有做任何處理;
2、對于自定義類型的成員變量,會去調(diào)用自定義類型對象的構(gòu)造函數(shù)。
下面分析代碼:
1、首先我們定義了兩個類,分別是類Time 和 類Date;
2、在類Time中定義了無參的構(gòu)造函數(shù)Time()。在函數(shù)內(nèi),對所有成員變量都初始化為0,同時打印輸出初始化的結(jié)果;
3、在類Date中,我們沒有定義構(gòu)造函數(shù),由編譯器自主去生成。只定義了成員函數(shù)Print(),在函數(shù)內(nèi),打印出經(jīng)過編譯器生成的構(gòu)造函數(shù)初始化后的成員變量。注意:在類Date的成員變量中,存在著一個由類Time實例出的對象 _t;
4、在main函數(shù)中,我們用類Date實例出一個對象d1,當我們創(chuàng)建出對象時,編譯器便會主動去調(diào)用自主產(chǎn)生的構(gòu)造函數(shù),對對象d1初始化。然后我們調(diào)用對象d1中的成員函數(shù)Print(),打印出初始化后的成員變量。根據(jù)最終的結(jié)果,我們可以總結(jié)出以下的結(jié)論:
編譯器自主生成的隱式構(gòu)造函數(shù),是一個大型的雙標現(xiàn)場,因為隱式構(gòu)造函數(shù)針對內(nèi)置類型的成員變量并沒有做任何處理,
而對于自定義類型的成員變量,會去調(diào)用自定義類型創(chuàng)建的對象的構(gòu)造函數(shù)進行初始化。(如果自定義類型中也沒有顯示構(gòu)造函數(shù),則調(diào)用自定義類型中的隱式構(gòu)造函數(shù))
ps:
內(nèi)置類型:char 、 int 、 double 、 float …
自定義類型 : 自己定義的類型,如類、結(jié)構(gòu)體、枚舉…
以上,便是對于C++中,類中6個默認成員函數(shù)之一的構(gòu)造函數(shù)的學(xué)習(xí)記錄。
結(jié)局彩蛋:
問題1:
給空類Date創(chuàng)建的對象d分配1個字節(jié)的空間,是因為存在。
雖然Date是空類,但是用Date創(chuàng)建出來的對象d是客觀上存在的,不要主觀的認為Date是空類,Date創(chuàng)建的對象d也就不存在了。
即然對象d是客觀上實實在在存在的,而每個對象又是獨一無二的,因此每個對象在內(nèi)存中都有自己獨一無二的內(nèi)存地址。所以編譯器會為空類Date創(chuàng)建的對象d分配一個字節(jié)的內(nèi)存,確保對象d也有自己獨特的地址,保證了“確保每個對象都有獨特的地址”的原則。
ps:這個字節(jié)通常被稱為 “空對象占用的內(nèi)存” 或者 “對象的內(nèi)存對齊” 。它不包含任何用戶定義的成員變量,而是用于區(qū)分不同對象在內(nèi)存中的位置。
問題2:
計算對象的大小時,是不考慮成員函數(shù)的大小的,只考慮成員變量的大小。思考一下,這樣設(shè)計的用意。
當我們用類創(chuàng)建很多不同的對象時,對于對象而言,類中的什么成員是必須的,而什么成員不是硬需的呢?
對于每個對象而言,擁有各自的成員變量是必須的,比如日期類Date(成員變量為年、月、日),對象之間擁有自己特定的記錄的年、月、日,才有意義,如果所有對象都是統(tǒng)一固定的,那將沒有意義了,因為不管創(chuàng)建多少對象,表示的都是同一個信息。
而成員函數(shù)是實現(xiàn)一些功能,比如日期的加減等等,而這些功能函數(shù)對于所有的對象來說,都是通用的,因此如果每個對象都存入這些通用的函數(shù),是不是對于內(nèi)存很不友善,于是在類中的成員函數(shù)的內(nèi)存是不計入對象中的內(nèi)存中的,成員函數(shù)是被存到代碼區(qū),供所有類的對象使用。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
STL區(qū)間成員函數(shù)及區(qū)間算法總結(jié)
這篇文章主要匯總介紹了STL區(qū)間成員函數(shù)及區(qū)間算法,有需要的小伙伴可以參考下。2015-07-07

