C++類和對象:構造函數、析構函數、拷貝構造函數

引言

? ? ? ? 介紹:C++類和對象:構造函數、析構函數、拷貝構造函數

? ? ? ?_涂色_博主主頁

? ? ? ? C++基礎專欄

?一、類的默認成員函數

? ? ? ? 先認識一下類中的默認成員函數:

????????默認成員函數就是用戶沒有顯式實現,編譯器會自動生成的成員函數稱為默認成員函數。?個類,不寫的情況下編譯器會默認生成以下 6 個默認成員函數,需要注意的是這6個中最重要的是前4個,最后兩個取地址重載不重要,我們稍微了解?下即可。其次就是C++11以后還會增加兩個默認成員函數, 移動構造和移動賦值,這個我們后賣面再講解。

從兩個方面去學習:

? 第一:我們不寫時,編譯器默認生成的函數的行為是什么,是否滿足我們的需求。

? 第二:編譯器默認生成的函數不滿足我們的需求,我們需要自己實現,那么如何自己實現?

整體思維圖:?

二、構造函數

????????構造函數是特殊的成員函數,需要注意的是,構造函數雖然名稱叫構造,但是構造函數的主要任務并不是開空間創建對象(我們常使?的局部對象是棧幀創建時,空間就開好了),?是對象實例化時初始化 對象。構造函數的本質是要替代Date類中寫的Init函數的功能,構造函數自動調用的 特點就完美的替代的了Init。

使用默認構造函數創建d1:

使用默認構造函數創建d2,然后用Init函數初始化想要的初始值:

#include<iostream>
using namespace std;class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}private:int _year;int _month;int _day;
};
int main()
{//使用默認構造函數創建d1Date d1;d1.Print();//使用默認構造函數創建d2,然后用Init函數初始化想要的初始值Date d2;d2.Init(2025, 1, 1);d2.Print();return 0;
}

?

?可以看出編譯器自己默認的構造函數不滿足初始化,所以咱們可以自己寫構造函數,這時,編譯器就調用咱們寫的構造函數。

說明:C++把類型分成內置類型(基本類型)和自定義類型。內置類型就是語言提供的原生數據類型, 如:int / char / double / 指針等,自定義類型就是我們使用 class / struct等關鍵字自己定義的類型。

構造函數的特點:

1. 函數名與類名相同。

2. 無返回值。(返回值啥都不需要給,也不需要寫void,不要糾結,C++規定如此)

3. 對象實例化時系統會自動調用對應的構造函數。

4. 構造函數可以重載。

5. 如果類中沒有顯式定義構造函數,則C++編譯器會自動生成一個無參的默認構造函數,一旦用戶顯式定義,編譯器將不再生成。

6. 無參構造函數、全缺省構造函數、我們不寫構造時編譯器默認生成的構造函數,都叫做默認構造函數。但是這三個函數有且只有?個存在,不能同時存在。無參構造函數和全缺省構造函數雖然構成函數重載,但是調用時會存在歧義。要注意很多同學會認為默認構造函數是編譯器默認生成那個叫默認構造,實際上無參構造函數、全缺省構造函數也是默認構造總結一下就是不傳實參就可以調用的構造就叫默認構造。

7. 我們不寫,編譯器默認生成的構造,對內置類型成員變量的初始化沒有要求,也就是說是否初始 化是不確定的,看編譯器。對于自定義類型成員變量,要求調用這個成員變量的默認構造函數初始 化。如果這個成員變量,沒有默認構造函數,那么就會報錯,我們要初始化這個成員變量,需要用初始化列表才能解決,初始化列表,后面來講解。

#include<iostream>
using namespace std;class Date
{
public://void Init(int year, int month, int day)//{//	_year = year;//	_month = month;//	_day = day;//}//1. 無參構造// 注意:如果通過?參構造函數創建對象時,對象后?不?跟括號,否則編譯器?法// 區分這?是函數聲明還是實例化對象Date(){_year = 1;_month = 1;_day = 1;}//2. 帶參構造函數 Date(int year, int month, int day){_year = year;_month = month;_day = day;}//3.全缺省構造函數/*Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}*/void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}private:int _year;int _month;int _day;
};
int main()
{Date d1(2025, 1, 1);d1.Print();return 0;
}

?

對第7點的解釋: 看下面代碼

#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public:Stack(int n = 4){cout << "Stack(int n = 4)" << endl;_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申請空間失敗");return;}_capacity = n;_top = 0;}~Stack() //這個是析構函數,下面會講{cout << "~Stack()" << endl;free(_a);_a = nullptr;_capacity = _top = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};
// 兩個棧實現一個隊列
class Myqueue
{
private:// 自定義類型// 編譯器默認生成的MyQueue的構造函數調用了Stack的構造,完成了兩個成員的初始化Stack _pushst;Stack _popst;
};int main()
{Myqueue q;Stack st;return 0;
}

三、析構函數

????????析構函數與構造函數功能相反,析構函數不是完成對? 對象本身的銷毀,比如局部對象是存在棧幀的,函數結束棧幀銷毀,他就釋放了,不需要我們管,C++規定對象在銷毀時會自動調用析構函數,完成對象中資源的清理釋放工作。析構函數的功能類比我們之前 Stack 實現的 Destroy 功能,而像Date沒有 Destroy,其實就是沒有資源需要釋放,所以嚴格說Date是不需要析構函數的。

析構函數的特點:

1. 析構函數名是在類名前加上字符~。

2. 無參數無返回值。(這里跟構造類似,也不需要加void)

3. 一個類只能有一個析構函數。若未顯式定義,系統會自動動生成默認的析構函數。

4. 對象生命周期結束時,系統會自動調用析構函數。

5. 跟構造函數類似,我們不寫編譯器自動生成的析構函數對內置類型成員不做處理,自定類型成員會調用他的析構函數。

6. 還需要注意的是我們顯示寫析構函數,對于自定義類型成員也會調用他的析構,也就是說自定義類型成員無論什么情況都會自動調用析構函數。

7. 如果類中沒有申請資源時,析構函數可以不寫,直接使用編譯器生成的默認析構函數,如Date;如果默認生成的析構就可以用,也就不需要顯示寫析構,如MyQueue;但是有資源申請時,?定要自己寫析構,否則會造成資源泄漏,如Stack。

8. 一個局部域的多個對象,C++規定:后定義的先析構。

#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public:Stack(int n = 4){cout << "Stack(int n = 4)" << endl;_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申請空間失敗");return;}_capacity = n;_top = 0;}~Stack() {cout << "~Stack()" << endl;free(_a);_a = nullptr;_capacity = _top = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};
// 兩個棧實現一個隊列
class Myqueue
{
private:// 自定義類型// 編譯器默認生成的MyQueue的構造函數調用了Stack的構造,完成了兩個成員的初始化Stack _pushst;Stack _popst;
};int main()
{Myqueue q; //調用兩次Stack的構造和析構Stack st;  //調用一次Stack的構造和析構return 0;
}

四、拷貝構造函數

????????如果一個構造函數的第一個參數是自身類類型的引用,且任何額外的參數都有默認值,則此構造函數叫做拷貝構造函數,也就是說拷貝構造是一個特殊的構造函數。

拷貝構造的特點:

1. 拷貝構造函數是構造函數的?個重載。

2. 拷貝構造函數的第一個參數必須是類類型對象的引用,使用傳值方式編譯器直接報錯,因為語法邏輯上會引發無窮遞歸調用。拷貝構造函數也可以多個參數,但是第?個參數必須是類類型對象的引用,后面的參數必須有缺省值(一般就一個參數)。

通過代碼來理解一下:

#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1){cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}~Date()// 析構函數{cout << "~Date()" << endl;}
private:int _year;int _month;int _day;
};
void Func(Date a)
{//......
}
int main()
{Date d1;d1.Print();//比如這里應該函數Func(),需要將d1傳過去Func(d1);//這里傳d1的時候,就需要拷貝構造函數,將d1拷貝給形參a//假如拷貝構造函數的實現是:/*Date(const Date  d){_year = d._year;_month = d._month;_day = d._day;}*///拷貝函數又需要使用拷貝構造函數,將d1拷貝給形參d,這里又需要調用拷貝構造函數,\所以拷貝構造函數的參數是形參的話,就會陷入死循環,一直調用拷貝構造函數//所以,使用引用,可以杜絕這樣一直調用拷貝構造函數。//正確使用拷貝構造函數:Date d1(2025, 4, 24);  // 構造Date d2(d1);           // 拷貝構造Date d4 = d1;          // 拷貝構造
}

3. C++規定:自定義類型對象進行拷貝行為必須調用拷貝構造,所以這里自定義類型傳值傳參 和 傳值返回都會調用拷貝構造完成。

4. 若未顯式定義拷貝構造,編譯器會自動生成拷貝構造函數。自動生成的拷貝構造對內置類型成 員變量會完成值拷貝 / 淺拷貝(一個字節一個字節的拷貝),對自定義類型成員變量會調用他的拷貝構造。

5. 像Date這樣的類成員變量全是內置類型且沒有指向什么資源,編譯器自動生成的拷貝構造就可以完成需要的拷貝,所以不需要我們顯示實現拷貝構造。

????????像Stack這樣的類,雖然也都是內置類型,但是 _a 指向了資源,編譯器自動生成的拷貝構造完成的值拷貝 / 淺拷貝不符合我們的需求,所以需要我們自己實現深拷貝 (對指向的資源也進行拷貝)。

????????像MyQueue這樣的類型內部主要是自定義類型 Stack成員,編譯器自動生成的拷貝構造會調用Stack的拷貝構造,也不需要我們顯示實現 MyQueue的拷貝構造。

這里還有一個小技巧,如果一個類顯示實現了析構并釋放資源,那么他就需要顯示寫拷貝構造,否則就不需要。

看代碼感受一下:

#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申請空間失敗");return;}_capacity = n;_top = 0;}Stack(const Stack& s){cout << "Stack(Stack& s)" << endl;_a = (STDataType*)malloc(sizeof(STDataType) * s._capacity);if (nullptr == _a){perror("malloc申請空間失敗");return;}memcpy(_a, s._a, sizeof(STDataType) * s._top);_top = s._top;_capacity = s._capacity;}void Push(const STDataType& x){// 擴容_a[_top] = x;_top++;}int Top(){return _a[_top-1];}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};
Stack& Fun()
{Stack s;s.Push(1);s.Push(2);s.Push(3);s.Push(4);return s;
}class MyQueue
{
private:Stack _pushst;Stack _popst;
};int main()
{int main()
{Stack st1;st1.Push(1);st1.Push(2);// Stack不顯?實現拷?構造,??動?成的拷?構造完成淺拷?// 會導致st1和st2??的_a指針指向同?塊資源,析構時會析構兩次,程序崩潰Stack st2 = st1;MyQueue mq1;// MyQueue?動?成的拷?構造,會?動調?Stack拷?構造完成pushst / popst// 的拷?,只要Stack拷?構造??實現了深拷?,他就沒問題MyQueue mq2 = mq1;return 0;
}
}

6. 傳值返回會產生一個臨時對象調用拷貝構造,傳值引用返回,返回的是返回對象的別名(引用),沒有產生拷貝。但是如果返回對象是一個當前函數局部域的局部對象,函數結束就銷毀了,那么使用引用返回是有問題的,這時的引用相當于?個野引用,類似一個野指針一樣。傳引用返回可以減少拷貝,但是一定要確保返回對象,在當前函數結束后還在,才能用引用返回。

來看個例子感受一下:

#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申請空間失敗");return;}_capacity = n;_top = 0;}Stack(const Stack& s){cout << "Stack(Stack& s)" << endl;_a = (STDataType*)malloc(sizeof(STDataType) * s._capacity);if (nullptr == _a){perror("malloc申請空間失敗");return;}memcpy(_a, s._a, sizeof(STDataType) * s._top);_top = s._top;_capacity = s._capacity;}void Push(const STDataType& x){// 擴容_a[_top] = x;_top++;}int Top(){return _a[_top-1];}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};
Stack& Fun()
{Stack s;s.Push(1);s.Push(2);s.Push(3);s.Push(4);return s;
}int main()
{cout << Fun().Top() << endl;//問:還能返回這個棧頂的元素嗎?//答:不可以了,因為返回的是s的別名,出了Fun函數,s就析構銷毀了,所以程序會崩潰//那塊地方已經被還給內存了。return 0;
}

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

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

相關文章

CTF - PWN之ORW記錄

CTF - Pwn之ORW記錄https://mp.weixin.qq.com/s/uiRtqCSopn6U6NqyKJ8I7Q

RabbitMQ 中的六大工作模式介紹與使用

文章目錄 簡單隊列&#xff08;Simple Queue&#xff09;模式配置類定義消費者定義發送消息測試消費 工作隊列&#xff08;Work Queues&#xff09;模式配置類定義消費者定義發送消息測試消費負載均衡調優 發布/訂閱&#xff08;Publish/Subscribe&#xff09;模式配置類定義消…

民宿管理系統6

普通管理員管理&#xff1a; 新增普通管理員&#xff1a; 前端效果&#xff1a; 前端代碼&#xff1a; <body> <div class"layui-fluid"><div class"layui-row"><div class"layui-form"><div class"layui-f…

vue3+ant design vue + Sortable實現多級表格列拖動

1、最近遇到個需求&#xff0c;需使用vue3ant design vue Sortable實現多級表格的表頭允許用戶拖拽移動。即當用戶拖拽一級表頭時&#xff0c;其對應的子級表頭及數據應同步移動&#xff0c;并且只允許一級非固定表頭允許拖拽。 2、代碼 <a-table:data-source"rowDat…

第五十四篇 AI與數據分析

一、AI數據分析就像做菜 想象你在廚房做一道新菜&#xff0c;AI數據分析的流程其實非常相似&#xff1a; 買菜&#xff08;獲取數據&#xff09; 去市場挑選新鮮蔬菜 從Excel/數據庫獲取數據例&#xff1a;pd.read_csv(超市銷售表.csv) 洗菜切菜&#xff08;清洗數據&#x…

差分OPA verilogaA 模型

做電路設計&#xff0c;需要提前用理想模型如VerilogA模型做驗證。這里分享一個由ahdlib庫里單端opamp改造而來的差分opamp。參考何樂年的《模擬集成電路設計與仿真》10.4節423頁&#xff1b; 描述的小信號模型如上。 VerilogA 用到了SRI/C&#xff0c;GBWgm/C,gaingm*r1等概念…

Solidity語言基礎:區塊鏈智能合約開發入門指南

一、Solidity概述 Solidity是以太坊生態系統中最重要的智能合約編程語言&#xff0c;由Gavin Wood于2014年提出。作為面向合約的高級語言&#xff0c;它結合了JavaScript、Python和C的語法特點&#xff0c;專為在以太坊虛擬機&#xff08;EVM&#xff09;上運行而設計。 核心…

開啟智能Kubernetes管理新時代:kubectl-ai讓操作更簡單!

在如今的科技世界中,Kubernetes 已經成為容器編排領域的標桿,幾乎所有現代應用的基礎設施都離不開它。然而,面對復雜的集群管理和日常運維,許多開發者常常感到無所適從。今天,我們將為大家介紹一款結合了人工智能的強大工具——kubectl-ai。它不僅能幫助開發者更加順暢地與…

2003-2020年高鐵站開通時間數據

2003-2020年高鐵站開通時間數據 1、時間&#xff1a;2003-2020年 2、來源&#xff1a;Chinese High-speed Rail and Airline Database&#xff0c;CRAD 3、指標&#xff1a;高鐵站名稱、開通時間、所在省份、所在城市、所屬線路名稱 4、說明&#xff1a; Hsrwsnm[高鐵站名…

神經網絡—感知器、多層感知器

文章目錄 前言一、生物神經元與感知器的類比二、感知器1、簡單感知器2、多層感知器&#xff08;1&#xff09;多層感知機結構 3、神經網絡結構 總結1、感知器的局限性如何突破感知器的局限性&#xff1f; 2、感知器的應用 前言 感知器&#xff08;Perceptron&#xff09;是神經…

避免數據丟失:在存儲測試數據之前,要做好Redis持久化

Redis提供兩種持久化方法&#xff1a; 一、RDB&#xff08;Redis DataBase&#xff09;&#xff1a; 1、周期性保存內存快照到磁盤&#xff08;生成 dump.rdb 文件&#xff09;。 2、配置save 相關參數。 我本地的配置如下&#xff1a; &#xff08;1&#xff09;配置文件re…

Oracle EBS FORM快捷鍵與觸發器的關系與使用

Oracle EBS FORM快捷鍵與觸發器的引用 1、快捷鍵作用&#xff0c;可以看文檔fmrweb.res 2、文件內容&#xff1a;標識了快捷鍵的作用 9 : 0 : “Tab” : 1 : “Next Field” 9 : 1 : “ShiftTab” : 2 : “Previous Field” 116 : 0 : “F5” : 3 : “Clear Field” 38 : 0 :…

Java 24:重構數字信任邊界 —— 后量子時代的智能安全防御體系構建

引言 在量子計算陰影與 AI 驅動攻擊交織的網絡安全新紀元&#xff0c;Java 平臺正經歷著自誕生以來最深刻的安全架構革新。作為企業級應用的核心基礎設施&#xff0c;Java 24 不僅延續了 “一次編寫&#xff0c;處處運行” 的跨平臺基因&#xff0c;更以后量子密碼學引擎、動態…

【故障定位系列】容器CPU問題引起的故障如何快速排查

原文地址&#xff1a;https://mp.weixin.qq.com/s/0VlIjbeEdPZUbLD389disA 當生產環境中的容器CPU出現異常時&#xff0c;可能會引發上層業務出現一系列問題&#xff0c;比如業務請求緩慢、網頁卡頓甚至崩潰等&#xff0c;如果沒有一個有效的故障定位方法&#xff0c;運維人員很…

DeepSeek 智能客服應用指南:構建、策略與成效升級

目錄 一、引言二、DeepSeek 與智能客服的相遇2.1 DeepSeek 的技術亮點2.2 智能客服&#xff1a;企業不可或缺的 “數字助手”2.3 DeepSeek 如何適配智能客服需求 三、基于 DeepSeek 搭建智能客服系統3.1 需求分析3.2 技術選型3.3 系統架構設計3.4 搭建步驟實操 四、基于 DeepSe…

JVM運行時數據區域(Run-Time Data Areas)的解析

# JVM運行時數據區域(Run-Time Data Areas)的解析 歡迎來到我的博客&#xff1a;TWind的博客 我的CSDN:&#xff1a;Thanwind-CSDN博客 我的掘金&#xff1a;Thanwinde 的個人主頁 本文參考于&#xff1a;深入理解Java虛擬機&#xff1a;JVM高級特性與最佳實踐 本文的JVM均…

【特別版】Kubernetes集群安裝(1master,2node)

Kubernetes集群安裝 虛擬機準備 按照三臺虛擬機標準&#xff0c;一臺控制節點兩臺工作節點 主機名ip配置要求master-k8s192.168.242.102GB或更多RAM&#xff0c;2個CPU或更多CPU&#xff0c;硬盤30GB或更多node01-k8s192.168.242.112GB或更多RAM&#xff0c;2個CPU或更多CPU…

Excel點擊單元格內容消失

Excel點擊單元格內容消失 前言一、原因說明二、解決方案1.菜單欄中找到“審閱”&#xff0c;選擇“撤銷工作表保護”2.輸入密碼3.解除成功 前言 Excel想要編輯單元格內容時&#xff0c;無論是單擊還是雙擊單元格內容都莫名其妙的消失了 一、原因說明 單擊或者雙擊Excel中單元…

最優化方法Python計算:有約束優化應用——線性Lasso回歸預測器

實際應用中&#xff0c;特征維度 n n n通常遠大于樣本容量 m m m&#xff08; n ? m n\ll m n?m&#xff09;&#xff0c;這種高維小樣本場景下特征數據可能含有對標簽數據 y i y_i yi?的取值不必要的成分&#xff0c;甚至是噪聲。此時&#xff0c;我們希望回歸模型中的優化…

如何為APP應用程序選擇合適的服務器

搭建一個成功的APP應用程序&#xff0c;服務器選擇是至關重要的決策之一。合適的服務器不僅能確保應用流暢運行&#xff0c;還能節省成本并保障安全性。本文將為您詳細解析如何為APP選擇最佳服務器方案。 一、了解您的APP需求 在選擇服務器前&#xff0c;首先需要明確您的應用…