【C++grammar】析構、友元、拷貝構造函數、深淺拷貝

目錄

  • 1、Destructor(析構函數)
    • 在堆和棧(函數作用域與內嵌作用域)上分別創建Employee對象,觀察析構函數的行為
  • 2、Friend(友元)
    • 1、為何需要友元
    • 2、友元函數和友元類
    • 3、關于友元的一些問題
  • 3、Copy Constructor(拷貝構造函數)
    • 拷貝構造
    • 隱式聲明的拷貝構造函數
    • 在堆和棧上分別拷貝創建Employee對象
  • 4、深拷貝與淺拷貝
    • 1、Customizing Copy Constructor(定制拷貝構造函數)
    • 2、待解決的疑問

1、Destructor(析構函數)

析構函數與構造函數正好相反。
注意,重載函數以函數參數的個數以及順序來區分,析構函數沒有參數也就不可重載了。
在這里插入圖片描述

在堆和棧(函數作用域與內嵌作用域)上分別創建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://靜態成員,用于計算雇員對象的數量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()));}//帶參構造函數Employee(std::string name,Gender gender,Date birthday):name{name},gender{gender}{//自增運算,完成每構造一次對象就數目+1numberOfObjects++;//注意,構造函數new出來的對象在析構函數要delete//在堆上構造了一個新的Date對象,然后存在數據成員里面,這樣就將new出來的新的data地址傳遞到了當前對象的birthday變量this->birthday = new Date(birthday);std::cout << "Now there are : " << numberOfObjects << " employees" << std::endl;}//默認構造函數Employee():Employee("Alan",Gender::male,Date(2000,4,1)){}//析構函數~Employee(){//當析構掉一個對象時,成員個數-1numberOfObjects--;//將在堆上面構造的變量釋放掉,由于這里沒有淺拷貝函數,不需要特別注意delete birthday;birthday = nullptr;std::cout << "析構掉一個->Now there are : " << numberOfObjects << " employees" << std::endl;}
};
int Employee::numberOfObjects = 0;
//在堆和棧(函數作用域與內嵌作用域)上分別創建Employee對象,觀察析構函數的行為
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是在內嵌作用域內定義的對象,出了這個作用域就被析構了。{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是在內嵌作用域內定義的對象,出了這個作用域就被析構了。
在這里插入圖片描述

2、Friend(友元)

1、為何需要友元

1、私有成員無法從類外訪問
2、但有時又需要授權某些可信的函數和類訪問這些私有成員

2、友元函數和友元類

1、用friend關鍵字聲明友元函數或者友元類
2、友元的缺點:打破了封裝性
3、可以在類外面定義,但是必須在類里面聲明。

下面的例子中,Kid類和print函數都可以直接訪問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、關于友元的一些問題

1、兩個類可以互為友元類嗎?如果你能舉出例子就更好了
2、其它的面向對象編程語言中,有friend這種東西或者類似的東西嗎?
3、一個類可以有友元,友元能夠訪問這個類中的私有/保護成員;那么,一個函數是否可以有友元,通過友元訪問這個函數中的局部變量?

1、可以。
我們可以把Screen類聲明為Window類的友元類,同時把Window類也聲明為Screen類的友元類。這樣兩個類的成員函數就可以相互訪問對方的私有和保護成員了。
2、沒有
3、不可以

3、Copy Constructor(拷貝構造函數)

拷貝構造

拷貝構造:用一個對象初始化另一個同類對象
拷貝構造函數可以簡寫為 copy ctor,或者 cp ctor。
如何聲明拷貝構造函數(copy ctor)

Circle (Circle&);
Circle (const Circle&);
Circle c1( 5.0 ); 
Circle c2( c1 );    //c++03
Circle c3 = c1;     //c++03
Circle c4 = { c1 }; //c++11
Circle c5{ c1 };    //c++11

帶有額外的默認參數的拷貝構造函數

class X {  //來自C++11標準: 12.8節
// ...
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已經定義。然后這種形式的語句:

obj1 = obj2;

不是調用拷貝構造函數,而是對象賦值。

反之,如下語句:

AClass aObject = bObject; // bObject也是AClass類型的對象

雖然有“等號(=)”,但由于是在定義對象的時候“賦值”,此時的“等號(=)”被解釋為初始化,需要調用拷貝構造函數。

隱式聲明的拷貝構造函數

一般情況下,如果程序員不編寫拷貝構造函數,那么編譯器會自動生成一個。自動生成的拷貝構造函數叫做“隱式聲明/定義的拷貝構造函數”。
一般情況下,隱式聲明的copy ctor簡單地將作為參數的對象中的每個數據域復制到新對象中。

在堆和棧上分別拷貝創建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://靜態成員,用于計算雇員對象的數量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()));}//帶參構造函數Employee(std::string name, Gender gender, Date birthday) :name{ name }, gender{ gender }{//自增運算,完成每構造一次對象就數目+1numberOfObjects++;//注意,構造函數new出來的對象在析構函數要delete//在堆上構造了一個新的Date對象,然后存在數據成員里面,這樣就將new出來的新的data地址傳遞到了當前對象的birthday變量this->birthday = new Date(birthday);std::cout << "Now there are : " << numberOfObjects << " employees" << std::endl;}//默認構造函數Employee() :Employee("Alan", Gender::male, Date(2000, 4, 1)) {}//拷貝構造函數Employee(const Employee& e1) {this->birthday = e1.birthday;this->name = e1.name;this->gender = e1.gender;//個數也需要+1numberOfObjects++;std::cout << "Employee(const Employee&) is invoked" << std::endl;}//析構函數~Employee(){//當析構掉一個對象時,成員個數-1numberOfObjects--;//注意如果析構的是淺拷貝函數且被拷貝對象已經被delete了,則不需要delete這個數據//delete birthday;//birthday = nullptr;std::cout << "析構掉一個->Now there are : " << numberOfObjects << " employees" << std::endl;}
};
int Employee::numberOfObjects = 0;
//在堆和棧上分別拷貝創建Employee對象
int main()
{//默認構造Employee e1;std::cout << e1.toString() << std::endl;//拷貝構造Employee e2 = {e1};std::cout << e2.toString() << std::endl;//在堆上構造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、深拷貝與淺拷貝

由于上面的拷貝函數,我們是將一個對象的所有數據成員否賦值給一個新的對象,所以會出現一個問題。
如果一個數據成員是指針類型(地址),那么我們新構造的對象的這個數據的地址也是這個。
對于非地址數據,則不會有這個問題。
我感覺,這也是拷貝函數的一個漏洞,一般來說我直觀理解的拷貝就是深拷貝而非淺拷貝。
淺拷貝:數據域是一個指針,只拷指針的地址,而非指針指向的內容
在兩種情況下會出現淺拷貝:

創建新對象時,調用類的隱式/默認構造函數
為已有對象賦值時,使用默認賦值運算符

深拷貝:拷貝指針指向的內容
解釋:
前提條件:類A中有個指針p,指向一個外掛對象b(b是B類型的對象);如果類A里面沒有指針成員p,那也就不要談深淺拷貝了。
現在有一個類A的對象a1(a1的指針p指向外掛對象b1)。以拷貝構造的方式,創建a1的一個拷貝a2。

(1) 如果僅僅將a1.p的值(這個值是個地址)拷貝給 a2.p,這就是淺拷貝。淺拷貝之后,a1.p和a2.p都指向外掛對象 b1
(2) 如果創建一個外掛對象b2,將 a2.p指向b2;并且將b1的值拷貝給b2,這就是深拷貝

Employee e1{"Jack", Date(1999, 5, 3),  Gender::male};
Employee e2{"Anna", Date(2000, 11, 8), Gender:female};
Employee e3{ e1 };  //cp ctor,執行一對一成員拷貝

創建 e3 對象時,調用了Employee的拷貝構造函數。
上面的代碼執行之后,e3.birthday指針指向了 e1.birthday所指向的那個Date對象,這樣會導致修改e1,e2對象也會被修改。
在這里插入圖片描述

1、Customizing Copy Constructor(定制拷貝構造函數)

如何深拷貝

(1) 自行編寫拷貝構造函數,不使用編譯器隱式生成的(默認)拷貝構造函數
(2) 重載賦值運算符,不使用編譯器隱式生成的(默認)賦值運算符函數

此時我們根據被拷貝對象來生成一個新的對象,然后把這個對象賦給拷貝對象。

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、待解決的疑問

有關淺拷貝對象以及它的析構的一個問題
如果我們使用淺拷貝構造函數:

Employee(const Employee& e1) {

    this->birthday = e1.birthday;this->name = e1.name;this->gender = e1.gender;//個數也需要+1numberOfObjects++;std::cout << "Employee(const Employee&) is invoked" << std::endl;}

然后我們在主函數用到了這個淺拷貝構造函數,由于我們帶參構造函數是在堆new了一個新的數據對象

//帶參構造函數Employee(std::string name, Gender gender, Date birthday) :name{ name }, gender{ gender }{//自增運算,完成每構造一次對象就數目+1numberOfObjects++;//注意,構造函數new出來的對象在析構函數要delete//在堆上構造了一個新的Date對象,然后存在數據成員里面,這樣就將new出來的新的data地址傳遞到了當前對象的birthday變量this->birthday = new Date(birthday);std::cout << "Now there are : " << numberOfObjects << " employees" << std::endl;}

所以在析構函數中我們會delete這個數據

    delete birthday;birthday = nullptr;

那么問題來了:

我們在delete拷貝構造出來的對象時,如果它指向對象已經被析構了,也就是說birthday 已經被delete了,這時候編譯器就會報錯,如何解決這個問題呢?
這個問題我已經在慕課上提問了,等老師回復再做更新。

解決回復:

一般來說,普通構造函數中有為類成員分配內存的操作,那么拷貝構造函數、重載的賦值運算符函數均需要執行深拷貝。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/377798.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/377798.shtml
英文地址,請注明出處:http://en.pswp.cn/news/377798.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

用mstsc /console 強行“踢”掉其它在線的用戶

由于公司有很多windows服務器&#xff0c;而且有很大一部分不在國內&#xff0c;所以經常需要使用遠程桌面進行連接&#xff0c;這其中&#xff0c;就會經常遇到因為超出了最大連接數&#xff0c;而不能連接的事情&#xff0c;其中最頭痛的就是&#xff0c;在連接國外的服務器時…

set vector_Java Vector set()方法與示例

set vector向量類set()方法 (Vector Class set() method) set() method is available in java.util package. set()方法在java.util包中可用。 set() method is used to replace the old element with the given element (ele) when it exists otherwise it sets the given ele…

Android PreferenceActivity 使用

我想大家對于android的系統配置界面應該不會陌生吧&#xff0c;即便陌生&#xff0c;那么下面的界面應該似曾相識吧&#xff0c;假若還是不認識&#xff0c;那么也沒有關系&#xff0c;我們這一節主要就是介紹并講解android 中系統配置界面的使用&#xff0c;相信大家看完本節后…

Pandas(數據分析處理庫)---講解

本內容來自《跟著迪哥學Python數據分析與機器學習實戰》&#xff0c;該篇博客將其內容進行了整理&#xff0c;加上了自己的理解&#xff0c;所做小筆記。若有侵權&#xff0c;聯系立刪。 迪哥說以下的許多函數方法都不用死記硬背&#xff0c;多查API多看文檔&#xff0c;確實&a…

hdu 1141

地址&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1141 題意&#xff1a;atmel公司1960年發布4bits的處理器&#xff0c;每10年翻一番。給一個年份&#xff0c;問最近一次發布的處理器能運算的n!最大的n是多少。 mark&#xff1a;最大的處理器位數是2160年的4194304…

leetcode 78. 子集 思考分析

題目 給定一組不含重復元素的整數數組 nums&#xff0c;返回該數組所有可能的子集&#xff08;冪集&#xff09;。 說明&#xff1a;解集不能包含重復的子集。 思考分析 畫出解空間樹。 我們可以發現我們所需要的結果是解空間的所有結點。而我們之前組合問題和分割問題都是…

PHP checkdate()函數與示例

PHP checkdate()函數 (PHP checkdate() function) checkdate() function is used to check the valid Gregorian dates. It accepts the date and returns Boolean values (True/False) based on the date values. checkdate()函數用于檢查有效的公歷日期。 它接受日期&#xf…

設計模式讀書筆記-----備忘錄模式

個人比較喜歡玩單機游戲&#xff0c;什么仙劍、古劍、鬼泣、使命召喚、三國無雙等等一系列的游戲我都玩過(現在期待凡人修仙傳)&#xff0c;對于這些游戲除了劇情好、場面大、爽快之外&#xff0c;還可以隨時存檔&#xff0c;等到下次想玩了又可以從剛開始的位置玩起(貌似現在的…

【C++grammar】vector類和字符串字面量

C的vector類 用數組存放數據時&#xff0c;容量大小不可變&#xff0c;vector對象容量可自動增大。 vector的操作&#xff1a; 調用push_back函數時&#xff0c;vector對象的容量可能會增大。 觀察下列操作對vector的影響&#xff1a; #include <vector> #include <…

除去數組中的空字符元素array_filter

<?php$str1_arrayarray(電影,,http://www,,1654,);$str1_arrayarray_filter($str1_array);print_r($str1_array); ?>顯示結果&#xff1a;Array( [0] > 電影 [2] > http://www [4] > 1654) 轉載于:https://www.cnblogs.com/skillCoding/archive/20…

date.after方法_Java Date after()方法與示例

date.after方法日期類after()方法 (Date Class after() method) after() method is available in java.util package. after()方法在java.util包中可用。 after() method is used to check whether this date is after the given date (d) or not. after()方法用于檢查此日期是…

Matplotlib(數據可視化庫)---講解

本內容來自《跟著迪哥學Python數據分析與機器學習實戰》&#xff0c;該篇博客將其內容進行了整理&#xff0c;加上了自己的理解&#xff0c;所做小筆記。若有侵權&#xff0c;聯系立刪。 迪哥說以下的許多函數方法都不用死記硬背&#xff0c;多查API多看文檔&#xff0c;確實&a…

找min和max

看到的貌似是阿里的筆試題&#xff0c;題意是一組數&#xff0c;要找到min和max&#xff0c;同時要求時間復雜度&#xff08;比較次數&#xff09;小于2n&#xff08;2n的辦法都想得到&#xff09;。 別人的思路&#xff1a;n個數的數組里看作每兩個一組&#xff0c;若n是奇數&…

Shader Compiler 界面進展1

先從模仿Composer的界面開始. 目前的進展:不用不知道,雖然wxweidgets有很多界面工具如DialogBlocks(DB), 但仍然不好使. 我使用wxAui界面, DialogBlocks并不支持輸出其xrc格式, 我猜是wx本身就沒有解析wxAui的xrc格式.像wxAuiToolBar或其他wxToolBar, DB工具也不能獨立輸出xrc.…

leetcode 90. 子集 II 思考分析

與本題相關聯的題目解析&#xff1a; leetcode 78. 子集 思考分析 leetcode 40. 組合總和 II思考分析 題目 給定一個可能包含重復元素的整數數組 nums&#xff0c;返回該數組所有可能的子集&#xff08;冪集&#xff09;。 說明&#xff1a;解集不能包含重復的子集。 思考 …

java bitset_Java BitSet and()方法與示例

java bitsetBitSet類和()方法 (BitSet Class and() method) and() method is available in java.util package. and()方法在java.util包中可用。 and() method is used to perform logical AND between two Bitset. This bit set is updated so that every bit holds the value…

Redis-主從復制

一、Redis的Replication&#xff1a; 這里首先需要說明的是&#xff0c;在Redis中配置Master-Slave模式真是太簡單了。相信在閱讀完這篇Blog之后你也可以輕松做到。這里我們還是先列出一些理論性的知識&#xff0c;后面給出實際操作的案例。 下面的列表清楚的解釋了Redis…

.wav音樂文件轉換為.fft.npy頻譜格式文件

需要修改的地方 十個文件夾&#xff0c;每個文件夾下都有100首.au格式的音樂&#xff0c;這里舉個例子&#xff0c;那其中5個類別進行轉換 genre_list ["classical", "jazz", "country", "pop", "rock", "metal"…

WINDOWS編程筆記 2012.2.7

操作系統感知事件和傳遞事件是通過消息機制來實現的typedef struct tagMSG{ HWND hwnd; //窗口的句柄 UINT message; WPARAM wParam; //信息的附加參數 LPARAM lParam; DWORD time; //消息傳遞的時間 POINT pt; //消息投遞的時候&#xff0c;光標的位置}…

php 郵件驗證_PHP程序來驗證電子郵件地址

php 郵件驗證Suppose there is a form floating where every user has to fill his/her email ID. It might happen that due to typing error or any other problem user doesnt fill his/her mail ID correctly. Then at that point, the program should be such that it sho…