[導(dǎo)讀]。
前言
在上一則教程中,通過與 C 語言相比較引出了 C 的相關(guān)特性,其中就包括函數(shù)重載,引用,this 指針,以及在脫離 IDE 編寫 C 程序時,所要用到的?Makefile的相關(guān)語法。本節(jié)所要敘述的是?C 的另外兩個重要的特性,也就是構(gòu)造函數(shù)和析構(gòu)函數(shù)的相關(guān)內(nèi)容,這兩部分內(nèi)容也是有別于?c語言而存在的,也是?c 的一個重要特性。構(gòu)造函數(shù)
類的構(gòu)造函數(shù)是類的一種特殊的成員函數(shù),它會在每次創(chuàng)建新的對象的時候執(zhí)行,構(gòu)造函數(shù)的名稱和類的名稱是完全相同的,并不會返回任何的類型,也不會返回 void。構(gòu)造函數(shù)可以用于為某些成員變量設(shè)置初始值。比方說,我們現(xiàn)在有如下所示的一段代碼:#include?
using?namespace?std;
class?Person{
private:
????char?*name;
????int?age;
????char?*work;
public:
????Person()?{cout?<"Person()"?<endl;}
};
int?main(int?argc,?char?**argv)
{
????Person?per;
????return?0;
}
在主函數(shù)中,定義 Person per 的同時,就會自動地調(diào)用 Person() 函數(shù),那么不難猜出,執(zhí)行 test 文件的時候,輸出結(jié)果如下:#include?
using?namespace?std;
class?Person
{
private:
????char?*name;
????int?age;
public:
????Person(char?*name,?int?age)
????{
????????cout?<"Person(char?*,int)"?<endl;
????????this->name?=?name;
????????this->age?=?age;
????}
????Person(){cout?<"Person()"?<endl;}
};
int?main(int?argc,?char?**argv)
{
????Person?per;
????Person?per2("zhangsan",18);
????return?0;
}
上述代碼中,定義第一個 Person 實(shí)例的時候,就會自動地調(diào)用無形參地構(gòu)造函數(shù),當(dāng)實(shí)例化第二個 Person 類的時候,就會自動地調(diào)用有形參地構(gòu)造函數(shù)。這個時候,運(yùn)行函數(shù)的輸出結(jié)果如下所示:#include?
using?namespace?std;
class?Person
{
private:
????char?*name;
????int?age;
public:
????Person(char?*name,?int?age,?char?*work?=?"none")
????{
????????cout?<"Person(char?*,int)"?<endl;
????????this->name?=?name;
????????this->age?=?age;
????????this->work?=?work;
????}
????Person(){cout?<"Person()"?<endl;}
????void?printInfo(void)
????{
????????cout?<"name?="?<",age?=?"<",work?="<endl;
????}
};
int?main(int?argc,?char?**argv)
{
????Person?per;
????Person?per2("zhangsan",18);
????Person?per3();
????per2.printInfo();
????return?0;
}
上述代碼中,第一條代碼和第二條代碼創(chuàng)建了兩個 Person 實(shí)例,在創(chuàng)建時依次調(diào)用構(gòu)造函數(shù),這里需要注意的是,第三條語句,這條語句看起來像是實(shí)例化了一個 per3 對象,但是 per3 括號里并沒有實(shí)參,這其實(shí)是定義了一個函數(shù),函數(shù)的形參為void,返回值為 Person ,并非是一個對象。這里還需要注意的一點(diǎn)是 per2 對象,它在調(diào)用構(gòu)造函數(shù)時,形參有一個默認(rèn)值,所以最終,程序輸出的結(jié)果如下所示:#include?
#include?
using?namespace?std;
class?Person
{
private:
????char?*name;
????int?age;
????char?*work;
public:
????Person(){cout?<"person()"?<endl;}
????Person(char?*name,int?age,?char?*work)
????{
????????cout?<"Person(char?*,int,?char?*)"?<endl;
????????this->name?=?new?char[strlen(name)? ?1];
????????strcpy(this->name,name);
????????this->age?=?age;
????????this->work?=?new?char[strlen(work)? ?1];
????????strcpy(this->work,work);
????}
????void?printInfo(void)
????{
????????cout?<"name?is:"?<",age?is:"?<",work?is:"?<endl;
????}
};
int?main(int?argc,char?*argv)
{
????Person?per("zhangsan",18,"teacher");
????Person?per2;
????Person?*per4?=?new?Person;
????Person?*per5?=?new?Person();?/*?這兩種方式定義的效果是一樣的?*/
????Person?*per6?=?new?Person[2];
????Person?*per7?=?new?Person("lisi",?18,"doctor");
????per.printInfo();
????per7.printInfo();
????delete?per4;
????delete?per5;
????delete?[]per6;
????delete?per7;
}
上述代碼中,使用了new 來分配給對象空間,再分配完之后,系統(tǒng)會自動的進(jìn)行釋放,或者說是使用手動的方式進(jìn)行釋放內(nèi)存,在手動釋放內(nèi)存的時候,我們采用?delete?的方式來進(jìn)行釋放,當(dāng)創(chuàng)建了兩個指針數(shù)組的時候,在手動釋放的時候,要在指針變量前面加上?[],在實(shí)例化指針對象的時候,也可以帶上參數(shù)或者說是不帶參數(shù)。下面是上述代碼的運(yùn)行結(jié)果:析構(gòu)函數(shù)
析構(gòu)函數(shù)的引出
上述我們知道,在函數(shù)運(yùn)行完之后,用 new 分配到的空間才會被釋放掉,那么如果是在函數(shù)調(diào)用里用 new 獲取到的空間會隨著函數(shù)調(diào)用的結(jié)束而釋放么,我們現(xiàn)在來做這樣一個實(shí)驗(yàn),把上述中的代碼中的主函數(shù)寫成?test()函數(shù),然后在?main()?函數(shù)里調(diào)用。代碼如下所示:#include?
#include?
#include?
using?namespace?std;
class?Person
{
private:
????char?*name;
????int?age;
????char?*work;
public:
????Person(){cout?<"person()"?<endl;}
????Person(char?*name,int?age,?char?*work)
????{
????????cout?<"Person(char?*,int,?char?*)"?<endl;
????????this->name?=?new?char[strlen(name)? ?1];
????????strcpy(this->name,name);
????????this->age?=?age;
????????this->work?=?new?char[strlen(work)? ?1];
????????strcpy(this->work,work);
????}
????void?printInfo(void)
????{
????????//cout?<"name?is:"?<
????}
};
void?test(void)
{
????Person?per("zhangsan",18,"teacher");
????Person?per2;
????Person?*per4?=?new?Person;
????Person?*per5?=?new?Person();?/*?這兩種方式定義的效果是一樣的?*/
????Person?*per6?=?new?Person[2];
????Person?*per7?=?new?Person("lisi",?18,"doctor");
????per.printInfo();
????per7->printInfo();
????delete?per4;
????delete?per5;
????delete?[]per6;
????delete?per7;
}
int?main(int?argc,?char?**argv)
{
????for?(int?i?=?0;?i?1000000;?i )
????????test();
????cout?<"run?test?end"?<endl;
????sleep(10);
????return?0;
}
這是運(yùn)行前的空閑內(nèi)存的大?。?/p>test?函數(shù)之后的空閑內(nèi)存大?。?/p>new給?this->name分配的空間。也就是在主函數(shù)沒有運(yùn)行完是不會被釋放掉的,也就是說只有在主函數(shù)運(yùn)行完之后,子函數(shù)里用 new 分配的空間才會被釋放掉,因此,如果想要在子函數(shù)調(diào)用完之后就釋放掉用 new 分配的空間,就需要編寫代碼來實(shí)現(xiàn)。而這個操作, C 提供了析構(gòu)函數(shù)來完成,下面是使用析構(gòu)函數(shù)來進(jìn)行釋放內(nèi)存的代碼:#include?
#include?
#include?
using?namespace?std;
class?Person
{
private:
????char?*name;
????int?age;
????char?*work;
public:
????Person(){cout?<"person()"?<endl;}
????Person(char?*name,int?age,?char?*work)
????{
????????cout?<"Person(char?*,int,?char?*)"?<endl;
????????this->name?=?new?char[strlen(name)? ?1];
????????strcpy(this->name,name);
????????this->age?=?age;
????????this->work?=?new?char[strlen(work)? ?1];
????????strcpy(this->work,work);
????}
????~Person()
????{
????????if?(this->name)
????????????delete?this->name;
????????if?(this->work)
????????????delete?this->work;
????}
????void?printInfo(void)
????{
????????//cout?<"name?is:"?<
????}
};
void?test(void)
{
????Person?per("zhangsan",18,"teacher");
????Person?per2;
????Person?*per4?=?new?Person;
????Person?*per5?=?new?Person();?/*?這兩種方式定義的效果是一樣的?*/
????Person?*per6?=?new?Person[2];
????Person?*per7?=?new?Person("lisi",?18,"doctor");
????per.printInfo();
????per7->printInfo();
????delete?per4;
????delete?per5;
????delete?[]per6;
????delete?per7;
}
int?main(int?argc,?char?**argv)
{
????for?(int?i?=?0;?i?1000000;?i )
????????test();
????cout?<"run?test?end"?<endl;
????sleep(10);
????return?0;
}
下述就是代碼運(yùn)行之前,和主函數(shù)在休眠的時候的剩余內(nèi)存的容量,可以看出,剩余內(nèi)存的容量是一樣的,換句話說,也就是在?test()函數(shù)運(yùn)行完成之后,用 new 分配的空間就已經(jīng)被釋放掉了,就算執(zhí)行了 1000000 次也沒有造成內(nèi)存泄漏。這也說明了我們的析構(gòu)函數(shù)是有作用的。析構(gòu)函數(shù)在什么地方被調(diào)用
上述析構(gòu)函數(shù)的存在避免了內(nèi)存泄漏,那么析構(gòu)函數(shù)是在什么時候被調(diào)用的呢,用一句話描述就是:在實(shí)例化對象被銷毀的前一瞬間被調(diào)用的,另外還要注意的是構(gòu)造函數(shù)可以有很多個,有參的,無參的構(gòu)造函數(shù),但是對于析構(gòu)函數(shù)來講,它只有一個,并且它是無參的。具體的來看如下所示的代碼,在剛才那段代碼的基礎(chǔ)上,我們添加一些打印信息,從而推斷我們析構(gòu)函數(shù)調(diào)用的位置:#include?
#include?
#include?
using?namespace?std;
class?Person
{
private:
????char?*name;
????int?age;
????char?*work;
public:
????Person()
????{
????????name?=?NULL;
????????work?=?NULL;
????}
????Person(char?*name,int?age,?char?*work)
????{
????????this->name?=?new?char[strlen(name)? ?1];
????????strcpy(this->name,name);
????????this->age?=?age;
????????this->work?=?new?char[strlen(work)? ?1];
????????strcpy(this->work,work);
????}
????~Person()
????{
????????cout?<"~Person()"?<endl;
????????if?(this->name)
????????{
????????????delete?this->name;
????????????cout?<"The?name?is:"?<endl;???
????????}
????????if?(this->work)
????????{
????????????delete?this->work;
????????????cout?<"The?work?is:"?<endl;
????????}
????}
????void?printInfo(void)
????{
????????//cout?<"name?is:"?<
????}
};
void?test(void)
{
????Person?per("zhangsan",18,"teacher");
????Person?*per7?=?new?Person("lisi",?18,"doctor");
????delete?per7;
}
int?main(int?argc,?char?**argv)
{
????test();
????return?0;
}
我們來看輸出的結(jié)果:lisi,后輸出的是?zhangsan,而在實(shí)例化對象的時候,是先創(chuàng)建的 per 對象,并初始化為?zhangsan,后創(chuàng)建的 per7 對象,并初始化為?lisi,再調(diào)用析構(gòu)函數(shù)的時候順序卻是顛倒過來的。因此,總結(jié)一下就是:per 這個實(shí)例化對象是在?test()函數(shù)執(zhí)行完之后,再調(diào)用的析構(gòu)函數(shù),而對于?per7對象來說,是在執(zhí)行?delete per7這條語句之后調(diào)用的析構(gòu)函數(shù),所以也就有了上述的輸出結(jié)果。另外,引出一點(diǎn),如果我們在上述的代碼中把delete per7這條語句給注釋掉,那么會怎么樣呢,下圖是去掉該語句之后的結(jié)果:zhangsan的析構(gòu)函數(shù),并沒有執(zhí)行lisi的析構(gòu)函數(shù),這也告訴我們,在使用 new 創(chuàng)建的實(shí)例化對象,必須使用 delete 將其釋放掉,如果沒有使用 delete 來將其釋放,那么在系統(tǒng)退出之后,會自動地釋放掉它地內(nèi)存,但是這個時候是不會調(diào)用它地析構(gòu)函數(shù)的。最后,關(guān)于構(gòu)造函數(shù)和析構(gòu)函數(shù),如果類里沒有實(shí)現(xiàn)任何構(gòu)造函數(shù)和析構(gòu)函數(shù),那么其系統(tǒng)本身會調(diào)用一個默認(rèn)的構(gòu)造函數(shù)和析構(gòu)函數(shù)。那么,除了默認(rèn)的構(gòu)造函數(shù)和默認(rèn)的析構(gòu)函數(shù),還存在一個默認(rèn)的拷貝構(gòu)造函數(shù),接下來,來敘述這個拷貝構(gòu)造函數(shù)。拷貝構(gòu)造函數(shù)
默認(rèn)拷貝構(gòu)造函數(shù)
我們直接來看這樣一段代碼:#include?
#include?
#include?
using?namespace?std;
class?Person?{
private:
????char?*name;
????int?age;
????char?*work;
public:
????Person()?{//cout?<<"Pserson()"<
????????name?=?NULL;
????????work?=?NULL;
????}
????Person(char?*name)?
????{
????????//cout?<<"Pserson(char?*)"<
????????this->name?=?new?char[strlen(name)? ?1];
????????strcpy(this->name,?name);
????????this->work?=?NULL;
????}
????Person(char?*name,?int?age,?char?*work?=?"none")?
????{
????????//cout?<<"Pserson(char*,?int)"<
????????this->age?=?age;
????????this->name?=?new?char[strlen(name)? ?1];
????????strcpy(this->name,?name);
????????this->work?=?new?char[strlen(work)? ?1];
????????strcpy(this->work,?work);
????}
????~Person()
????{
????????cout?<"~Person()"<<endl;
????????if?(this->name)?{
????????????cout?<"name?=?"<endl ;
????????????delete?this->name;
????????}
????????if?(this->work)?{
????????????cout?<"work?=?"<endl ;
????????????delete?this->work;
????????}
????}
????void?printInfo(void)
????{
????????//printf("name?=?%s,?age?=?%d,?work?=?%s\n",?name,?age,?work);?
????????cout<<"name?=?"<",?age?=?" <",?work?=?"<endl;
????}
};
int?main(int?argc,?char?**argv)
{
????Person?per("zhangsan",?18);
????Person?per2(per);
????per2.printInfo();
????return?0;
}
在主函數(shù)的第二行代碼中,我們可以看到我們創(chuàng)建了一個實(shí)例,并且傳入的參數(shù)是?per,但是我們看類里面的代碼實(shí)現(xiàn),并沒有發(fā)現(xiàn)有一個構(gòu)造函數(shù)的形參為 Person ,那這個時候,會發(fā)生什么函數(shù)調(diào)用呢,實(shí)際上是會調(diào)用一個系統(tǒng)的默認(rèn)構(gòu)造函數(shù),這個默認(rèn)的構(gòu)造函數(shù)會進(jìn)行值拷貝,會將?per中的內(nèi)容拷貝到?per2中去,下圖是這個過程的一個示意圖:address1,per2 的 name 同樣也指向了?adress,從而完成了值拷貝的過程,下面是代碼運(yùn)行的結(jié)果:per?的初始化內(nèi)容,在主函數(shù)運(yùn)行完之后,就要執(zhí)行析構(gòu)函數(shù)來釋放使用 new 分配的空間,首先是釋放?per?的內(nèi)容,然后緊接著是釋放?per2的內(nèi)容,但是在剛剛的敘述中,使用默認(rèn)構(gòu)造函數(shù)進(jìn)行拷貝的時候,使用的是值拷貝,從而造成的效果是 per2 的 name 和 work 指向的地址是?per?中的同一塊地址,這樣,在執(zhí)行析構(gòu)函數(shù)的時候,同一塊內(nèi)存空間就會被釋放兩次,從而導(dǎo)致錯誤。因此,使用默認(rèn)的拷貝構(gòu)造函數(shù)存在一定的問題,也就需要我們自己來定義拷貝構(gòu)造函數(shù),下面介紹自定義的拷貝構(gòu)造函數(shù)。自定義拷貝構(gòu)造函數(shù)
我們根據(jù)在上述代碼的基礎(chǔ)上,修改得到我們自定義的拷貝構(gòu)造函數(shù)如下:#include?
#include?
#include?
using?namespace?std;
class?Person?{
private:
????char?*name;
????int?age;
????char?*work;
public:
????Person()?{//cout?<<"Pserson()"<
????????name?=?NULL;
????????work?=?NULL;
????}
????Person(char?*name)?
????{
????????//cout?<<"Pserson(char?*)"<
????????this->name?=?new?char[strlen(name)? ?1];
????????strcpy(this->name,?name);
????????this->work?=?NULL;
????}
????Person(char?*name,?int?age,?char?*work?=?"none")?
????{
????????cout?<<"Pserson(char*,?int)"<<endl;
????????this->age?=?age;
????????this->name?=?new?char[strlen(name)? ?1];
????????strcpy(this->name,?name);
????????this->work?=?new?char[strlen(work)? ?1];
????????strcpy(this->work,?work);
????}
????Person(Person 




