【C++grammar】析构、友元、拷贝构造函数、深浅拷贝
目錄
- 1、Destructor(析構(gòu)函數(shù))
- 在堆和棧(函數(shù)作用域與內(nèi)嵌作用域)上分別創(chuàng)建Employee對象,觀察析構(gòu)函數(shù)的行為
- 2、Friend(友元)
- 1、為何需要友元
- 2、友元函數(shù)和友元類
- 3、關(guān)于友元的一些問題
- 3、Copy Constructor(拷貝構(gòu)造函數(shù))
- 拷貝構(gòu)造
- 隱式聲明的拷貝構(gòu)造函數(shù)
- 在堆和棧上分別拷貝創(chuàng)建Employee對象
- 4、深拷貝與淺拷貝
- 1、Customizing Copy Constructor(定制拷貝構(gòu)造函數(shù))
- 2、待解決的疑問
1、Destructor(析構(gòu)函數(shù))
析構(gòu)函數(shù)與構(gòu)造函數(shù)正好相反。
注意,重載函數(shù)以函數(shù)參數(shù)的個數(shù)以及順序來區(qū)分,析構(gòu)函數(shù)沒有參數(shù)也就不可重載了。
在堆和棧(函數(shù)作用域與內(nèi)嵌作用域)上分別創(chuàng)建Employee對象,觀察析構(gòu)函數(shù)的行為
#include<iostream> #include<string> using namespace std;class Date { private:int year = 2019, month = 1, day = 1; public:int getYear() { return year; }int getMonth() { return month; }int getDay() { return day; }void setYear(int y) { year = y; }void setMonth(int m) { month = m; }void setDay(int d) { day = d; }Date() = default;Date(int y, int m, int d) :year(y), month(m), day(d) {std::cout << "Date" << toString() << std::endl;}std::string toString() {return (std::to_string(year) + '-' + std::to_string(month) + '-' + std::to_string(day));} };enum class Gender {male,female, };class Employee { private:std::string name;Gender gender;Date* birthday; public://靜態(tài)成員,用于計算雇員對象的數(shù)量static int numberOfObjects;void setName(std::string name) { this->name = name; }void setGender(Gender gender) { this->gender = gender; }void setBirthday(Date birthday) { *(this->birthday) = birthday; }std::string getName() { return name; }Gender getGender() { return gender; }Date getBirthday() { return *birthday; }std::string toString(){return (name +( (gender == Gender::male ? std::string(" male ") : std::string(" female ") )+ birthday->toString()));}//帶參構(gòu)造函數(shù)Employee(std::string name,Gender gender,Date birthday):name{name},gender{gender}{//自增運算,完成每構(gòu)造一次對象就數(shù)目+1numberOfObjects++;//注意,構(gòu)造函數(shù)new出來的對象在析構(gòu)函數(shù)要delete//在堆上構(gòu)造了一個新的Date對象,然后存在數(shù)據(jù)成員里面,這樣就將new出來的新的data地址傳遞到了當前對象的birthday變量this->birthday = new Date(birthday);std::cout << "Now there are : " << numberOfObjects << " employees" << std::endl;}//默認構(gòu)造函數(shù)Employee():Employee("Alan",Gender::male,Date(2000,4,1)){}//析構(gòu)函數(shù)~Employee(){//當析構(gòu)掉一個對象時,成員個數(shù)-1numberOfObjects--;//將在堆上面構(gòu)造的變量釋放掉,由于這里沒有淺拷貝函數(shù),不需要特別注意delete birthday;birthday = nullptr;std::cout << "析構(gòu)掉一個->Now there are : " << numberOfObjects << " employees" << std::endl;} }; int Employee::numberOfObjects = 0; //在堆和棧(函數(shù)作用域與內(nèi)嵌作用域)上分別創(chuàng)建Employee對象,觀察析構(gòu)函數(shù)的行為 int main() {Employee e1;std::cout << e1.toString() << std::endl;Employee* e2 = new Employee{"John",Gender::male,Date(1990,3,2) };std::cout << e2->toString() << std::endl;//e3是在內(nèi)嵌作用域內(nèi)定義的對象,出了這個作用域就被析構(gòu)了。{Employee e3{ "Alice",Gender::female,{1989,2,14} };std::cout << e3.toString() << std::endl;}std::cout << "Now there are : " << Employee::numberOfObjects << " employees" << std::endl;return 0; }e3是在內(nèi)嵌作用域內(nèi)定義的對象,出了這個作用域就被析構(gòu)了。
2、Friend(友元)
1、為何需要友元
1、私有成員無法從類外訪問
2、但有時又需要授權(quán)某些可信的函數(shù)和類訪問這些私有成員
2、友元函數(shù)和友元類
1、用friend關(guān)鍵字聲明友元函數(shù)或者友元類
2、友元的缺點:打破了封裝性
3、可以在類外面定義,但是必須在類里面聲明。
下面的例子中,Kid類和print函數(shù)都可以直接訪問Date類中的私有成員
class Date { private:int year{ 2019 } , month{ 1 };int day{ 1 }; public:friend class Kid;friend void print(const Date& d); }; void print(const Date& d) {cout << d.year << "/" << d.month << "/" << d.day << endl; } class Kid { private:Date birthday; public:Kid() { cout << "I was born in " << birthday.year << endl; } }; int main() {print(Date());Kid k;cin.get(); }3、關(guān)于友元的一些問題
1、兩個類可以互為友元類嗎?如果你能舉出例子就更好了
2、其它的面向?qū)ο缶幊陶Z言中,有friend這種東西或者類似的東西嗎?
3、一個類可以有友元,友元能夠訪問這個類中的私有/保護成員;那么,一個函數(shù)是否可以有友元,通過友元訪問這個函數(shù)中的局部變量?
1、可以。
我們可以把Screen類聲明為Window類的友元類,同時把Window類也聲明為Screen類的友元類。這樣兩個類的成員函數(shù)就可以相互訪問對方的私有和保護成員了。
2、沒有
3、不可以
3、Copy Constructor(拷貝構(gòu)造函數(shù))
拷貝構(gòu)造
拷貝構(gòu)造:用一個對象初始化另一個同類對象
拷貝構(gòu)造函數(shù)可以簡寫為 copy ctor,或者 cp ctor。
如何聲明拷貝構(gòu)造函數(shù)(copy ctor)
帶有額外的默認參數(shù)的拷貝構(gòu)造函數(shù)
class X { //來自C++11標準: 12.8節(jié) // ... public:X(const X&, int = 1); }; X b(a, 0); // calls X(const X&, int); X c = b; // calls X(const X&, int);兩個對象obj1和obj2已經(jīng)定義。然后這種形式的語句:
obj1 = obj2;
不是調(diào)用拷貝構(gòu)造函數(shù),而是對象賦值。
反之,如下語句:
AClass aObject = bObject; // bObject也是AClass類型的對象
雖然有“等號(=)”,但由于是在定義對象的時候“賦值”,此時的“等號(=)”被解釋為初始化,需要調(diào)用拷貝構(gòu)造函數(shù)。
隱式聲明的拷貝構(gòu)造函數(shù)
一般情況下,如果程序員不編寫拷貝構(gòu)造函數(shù),那么編譯器會自動生成一個。自動生成的拷貝構(gòu)造函數(shù)叫做“隱式聲明/定義的拷貝構(gòu)造函數(shù)”。
一般情況下,隱式聲明的copy ctor簡單地將作為參數(shù)的對象中的每個數(shù)據(jù)域復(fù)制到新對象中。
在堆和棧上分別拷貝創(chuàng)建Employee對象
#include<iostream> #include<string> using namespace std;class Date { private:int year = 2019, month = 1, day = 1; public:int getYear() { return year; }int getMonth() { return month; }int getDay() { return day; }void setYear(int y) { year = y; }void setMonth(int m) { month = m; }void setDay(int d) { day = d; }Date() = default;Date(int y, int m, int d) :year(y), month(m), day(d) {std::cout << "Date" << toString() << std::endl;}std::string toString() {return (std::to_string(year) + '-' + std::to_string(month) + '-' + std::to_string(day));} };enum class Gender {male,female, };class Employee { private:std::string name;Gender gender;Date* birthday; public://靜態(tài)成員,用于計算雇員對象的數(shù)量static int numberOfObjects;void setName(std::string name) { this->name = name; }void setGender(Gender gender) { this->gender = gender; }void setBirthday(Date birthday) { *(this->birthday) = birthday; }std::string getName() { return name; }Gender getGender() { return gender; }Date getBirthday() { return *birthday; }std::string toString(){return (name + ((gender == Gender::male ? std::string(" male ") : std::string(" female ")) + birthday->toString()));}//帶參構(gòu)造函數(shù)Employee(std::string name, Gender gender, Date birthday) :name{ name }, gender{ gender }{//自增運算,完成每構(gòu)造一次對象就數(shù)目+1numberOfObjects++;//注意,構(gòu)造函數(shù)new出來的對象在析構(gòu)函數(shù)要delete//在堆上構(gòu)造了一個新的Date對象,然后存在數(shù)據(jù)成員里面,這樣就將new出來的新的data地址傳遞到了當前對象的birthday變量this->birthday = new Date(birthday);std::cout << "Now there are : " << numberOfObjects << " employees" << std::endl;}//默認構(gòu)造函數(shù)Employee() :Employee("Alan", Gender::male, Date(2000, 4, 1)) {}//拷貝構(gòu)造函數(shù)Employee(const Employee& e1) {this->birthday = e1.birthday;this->name = e1.name;this->gender = e1.gender;//個數(shù)也需要+1numberOfObjects++;std::cout << "Employee(const Employee&) is invoked" << std::endl;}//析構(gòu)函數(shù)~Employee(){//當析構(gòu)掉一個對象時,成員個數(shù)-1numberOfObjects--;//注意如果析構(gòu)的是淺拷貝函數(shù)且被拷貝對象已經(jīng)被delete了,則不需要delete這個數(shù)據(jù)//delete birthday;//birthday = nullptr;std::cout << "析構(gòu)掉一個->Now there are : " << numberOfObjects << " employees" << std::endl;} }; int Employee::numberOfObjects = 0; //在堆和棧上分別拷貝創(chuàng)建Employee對象 int main() {//默認構(gòu)造Employee e1;std::cout << e1.toString() << std::endl;//拷貝構(gòu)造Employee e2 = {e1};std::cout << e2.toString() << std::endl;//在堆上構(gòu)造Employee* e3 = new Employee{ "John",Gender::male,Date(1990,3,2) };std::cout << e3->toString() << std::endl;std::cout << std::endl;std::cout << "Now there are : " << Employee::numberOfObjects << " employees" << std::endl;return 0; }4、深拷貝與淺拷貝
由于上面的拷貝函數(shù),我們是將一個對象的所有數(shù)據(jù)成員否賦值給一個新的對象,所以會出現(xiàn)一個問題。
如果一個數(shù)據(jù)成員是指針類型(地址),那么我們新構(gòu)造的對象的這個數(shù)據(jù)的地址也是這個。
對于非地址數(shù)據(jù),則不會有這個問題。
我感覺,這也是拷貝函數(shù)的一個漏洞,一般來說我直觀理解的拷貝就是深拷貝而非淺拷貝。
淺拷貝:數(shù)據(jù)域是一個指針,只拷指針的地址,而非指針指向的內(nèi)容
在兩種情況下會出現(xiàn)淺拷貝:
創(chuàng)建新對象時,調(diào)用類的隱式/默認構(gòu)造函數(shù)
為已有對象賦值時,使用默認賦值運算符
深拷貝:拷貝指針指向的內(nèi)容
解釋:
前提條件:類A中有個指針p,指向一個外掛對象b(b是B類型的對象);如果類A里面沒有指針成員p,那也就不要談深淺拷貝了。
現(xiàn)在有一個類A的對象a1(a1的指針p指向外掛對象b1)。以拷貝構(gòu)造的方式,創(chuàng)建a1的一個拷貝a2。
(1) 如果僅僅將a1.p的值(這個值是個地址)拷貝給 a2.p,這就是淺拷貝。淺拷貝之后,a1.p和a2.p都指向外掛對象 b1
(2) 如果創(chuàng)建一個外掛對象b2,將 a2.p指向b2;并且將b1的值拷貝給b2,這就是深拷貝
創(chuàng)建 e3 對象時,調(diào)用了Employee的拷貝構(gòu)造函數(shù)。
上面的代碼執(zhí)行之后,e3.birthday指針指向了 e1.birthday所指向的那個Date對象,這樣會導致修改e1,e2對象也會被修改。
1、Customizing Copy Constructor(定制拷貝構(gòu)造函數(shù))
如何深拷貝
(1) 自行編寫拷貝構(gòu)造函數(shù),不使用編譯器隱式生成的(默認)拷貝構(gòu)造函數(shù)
(2) 重載賦值運算符,不使用編譯器隱式生成的(默認)賦值運算符函數(shù)
此時我們根據(jù)被拷貝對象來生成一個新的對象,然后把這個對象賦給拷貝對象。
class Employee { public:// Employee(const Employee &e) = default; //淺拷貝ctorEmployee(const Employee& e){ //深拷貝ctorbirthdate = new Date{ e.birthdate };} // ... } Employee e1{"Jack", Date(1999, 5, 3), Gender::male}; Employee e2{"Anna", Date(2000, 11, 8),, Gender:female}; Employee e3{ e1 }; //cp ctor 深拷貝2、待解決的疑問
有關(guān)淺拷貝對象以及它的析構(gòu)的一個問題
如果我們使用淺拷貝構(gòu)造函數(shù):
Employee(const Employee& e1) {
this->birthday = e1.birthday;this->name = e1.name;this->gender = e1.gender;//個數(shù)也需要+1numberOfObjects++;std::cout << "Employee(const Employee&) is invoked" << std::endl;}然后我們在主函數(shù)用到了這個淺拷貝構(gòu)造函數(shù),由于我們帶參構(gòu)造函數(shù)是在堆new了一個新的數(shù)據(jù)對象
//帶參構(gòu)造函數(shù)Employee(std::string name, Gender gender, Date birthday) :name{ name }, gender{ gender }{//自增運算,完成每構(gòu)造一次對象就數(shù)目+1numberOfObjects++;//注意,構(gòu)造函數(shù)new出來的對象在析構(gòu)函數(shù)要delete//在堆上構(gòu)造了一個新的Date對象,然后存在數(shù)據(jù)成員里面,這樣就將new出來的新的data地址傳遞到了當前對象的birthday變量this->birthday = new Date(birthday);std::cout << "Now there are : " << numberOfObjects << " employees" << std::endl;}所以在析構(gòu)函數(shù)中我們會delete這個數(shù)據(jù)
delete birthday;birthday = nullptr;那么問題來了:
我們在delete拷貝構(gòu)造出來的對象時,如果它指向?qū)ο笠呀?jīng)被析構(gòu)了,也就是說birthday 已經(jīng)被delete了,這時候編譯器就會報錯,如何解決這個問題呢?
這個問題我已經(jīng)在慕課上提問了,等老師回復(fù)再做更新。
解決回復(fù):
一般來說,普通構(gòu)造函數(shù)中有為類成員分配內(nèi)存的操作,那么拷貝構(gòu)造函數(shù)、重載的賦值運算符函數(shù)均需要執(zhí)行深拷貝。
總結(jié)
以上是生活随笔為你收集整理的【C++grammar】析构、友元、拷贝构造函数、深浅拷贝的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 有什么办法可以把机顶盒自带的语音助手改成
- 下一篇: 开封看无精子症最好的医院推荐