從C學C++(6)——構造函數和析構函數

從C學C++(6)——構造函數和析構函數

若無特殊說明,本博客所執行的C++標準均為C++11.

構造函數與析構函數

構造函數定義

  • 構造函數是特殊的成員函數,當創建類類型的新對象,系統自動會調用構造函數構造函數是為了保證對象的每個數據成員都被正確初始化。

構造函數的特點

  • 函數名和類名完全相同

  • 不能定義構造函數的類型(返回類型)(因為構造函數需要返回對象本身,所以不能指定返回類型),也不能使用void。

  • 通常情況下構造函數應聲明為公有函數,否則它不能像其他成員函數那樣被顯式地調用。

    (特殊情況,例如“單例模式”設計的時候,會將構造函數聲明為私有函數,以禁止外界定義一個對象)

  • 構造函數可以有任意類型和任意個數的參數,一個類可以有多個構造函數(構造函數可以重載

析構函數定義和特點

  • 函數名和比類名前面多了一個字符“~”
  • 沒有返回類型(和構造函數一樣)
  • 沒有參數,因此析構函數不能被重載

如果沒有定義構造函數和析構函數,默認會生成兩個空函數作為構造函數和析構函數。

class Test{
public:Test(){//默認構造函數就是這樣,空的,且沒有參數   }~Test(){//默認析構函數就是這樣,空的,且沒有參數 }
}

構造/析構函數和變量的生存期

當我們定義一個對象的時候,構造函數會被默認調用;當對象的生命周期結束的時候(一般是函數運行結束的時候,對全局對象來說是程序運行結束的時候),會默認調用析構函數進行對象的釋放。

當我們new一個對象的時候,構造函數會被默認調用;當delete對象的時候,會默認調用析構函數進行對象的釋放(而且通常需要程序員手動調用delete才能釋放該對象)。

這里需要注意的是,全局對象的構造是先于main函數執行的(這個我其實存有疑惑,特別是在單片機這樣的嵌入式設備的C++編程中,因為單片機的main函數入口前是匯編指令,負責聲明對堆棧大小和中斷向量表等操作,之后通過指令跳轉的main函數入口的,C++的全局變量的構造函數是如何插入到指令跳轉到main函數之前的呢?🤔)。

最后,析構函數是可以像普通成員函數一樣顯示調用的。

轉換構造函數和explicit

轉換構造函數

擁有單個參數的構造函數被編譯器用于隱式類型轉換的時候,稱為轉換構造函數。

因此,類的構造函數只有一個參數是非常危險的,因為編譯器可以使用這種構造函數把參數的類型隱式轉換為類類型。(可能在我們沒有注意到的地方調用了構造函數)。如下:

class Test{Test(int a){//可以什么也不做}
}int main(void){Test t(0); //此時,帶一個參數的構造函數,充當的是普通構造函數的功能t = 20; // 此時,編譯器會默認幫我們找到一個能夠用的構造函數(即一個參數的構造函數)進行隱性類型轉換;遵循以下步驟:// 1. 調用轉換構造函數將20這個整數轉換成類類型(生成一個臨時對象)// 2. 臨時對象賦值給t對象(調用的是=運算符成員函數)
}

初始化中的=不是賦值運算符

在初始化語句中的等號不是運算符。編譯器對這種表示方法有特殊的解釋,編譯器會將其等價于調用單個參數的構造函數構造對象,而不是賦值運算(調用=重載函數)

因為對編譯器來說,Test t = 10; 這樣的語句,是對象變量的初始化,直接調用單個參數的構造函數進行初始化就可以了(等價于 Test t(10););

而不需要像 Test t; t = 10; 中一樣經過先調用構造函數構造對象,后面那個賦值語句(注意這個不是初始化語句),會分兩步,先調用一個參數的構造函數做轉換構造函數構造一個臨時對象,再調用賦值構造函數(也就是=運算符的重載函數)將臨時對象賦值給我們賦值的對象。

這里順便說明等號運算符一般的重載形式,如下:

Test& Test::operator=(const Test& other){//需要的操作return *this; //返回對象本身
}

需要注意:

  • 返回對象的引用,一般我們會直接返回 *this , 因為這個重載函數是在 類似a=b 是調用的,其等價于 a = a.operator=(b) 這樣的調用,因此,能夠很明顯函數,=運算符重載函數是需要返回對象自身的(建議返回引用 return *this)
  • 接收參數必須是對象的引用,這里注意到,如果接收參數不是對象的引用的話,而是一個對象的話(參數值傳遞),因為a=b 等價于 a = a.operator=(b) ; 而值傳遞的參數決定了,這里需要有一個實參給形參傳值的操作,而實參給形參傳值的操作也就是 形參=實參 這樣的操作,那又會調用operator=(b) 函數,就會形成遞歸調用,死循環了。

explicit關鍵字

如果想要避免編譯器對類做默認類型轉換的話,我們可以給一個形參的構造函數前加上 explicit 這樣如果我們使用類似 Test t; t = 10; 這樣的操作時,編譯器會報錯,而不會使用一個參數的(轉換)構造函數和等號運算符函數做隱式的類型轉換。在這種情況下,對于Test 這個類對象,我們必須使用顯式的類型轉換。

構造函數和成員初始化

構造函數初始化列表

構造函數后可以使用: 跟一個構造函數初始化列表(成員變量(形參)),用于成員變量的初始化,注意,在構造函數體內執行的成員變量的操作是賦值操作,不是初始化操作。

Clock::Clock(int hour, int minute, int second) : m_hour(hour), m_minute(minute), m_second(second) {//初始化列表是初始化階段// 函數體內是普通計算階段// m_hour = hour;// m_minute = minute;  // m_second = second; //這部分是賦值操作而不是 初始化操作std::cout << "clock initialized: " << m_hour << ":" << m_minute << ":" << m_second << std::endl;
}

分清成員變量的初始化和賦值操作后,由之前的const變量引用變量 必須在初始化時賦初值(后期,const變量不可被賦值更改,而引用變量 更改則是對對象本身進行更改)可知。如果類內成員有const成員引用成員的話,只能在構造函數的初始化列表中完成初始化。

對象成員的初始化

如果一個類中含有其他類的對象作為成員變量(這里成為對象成員),如果我們沒有在這個類的初始化列表中提供這個對象成員的初始化的話,那么當這個類的對象定義時,會默認調用這個對象成員沒有任何參數的構造函數(即這個對象成員的默認構造函數);如果這個對象成員沒有提供這個函數的話,就和我們平時定義一個類對象(這個類的構造函數只有一個有參數的構造函數)而我們定義時沒有傳入任何參數一樣,會報錯找不到合適的構造函數。

對象成員的初始化

所以,如果一個類擁有對象成員(且這個對象成員沒有不帶任何參數的構造函數),在外類對象的初始化的時候必須在初始化列表中給出這個對象成員的初始化,不讓會報錯找不到對應的構造函數

拷貝構造函數

拷貝構造函數

  • 功能:使用一個已經存在的對象來初始化一個新的同一類型的對象
  • 聲明:只有一個參數并且參數為該類對象的引用,形如Clock::Clock(const Clock& other)
  • 如果類中沒有說明拷貝構造函數,則系統自動生成一個缺省拷貝構造函數(做逐成員賦值),作為該類的公有成員。

拷貝構造函數調用幾種情況

凡是需要用到對象的賦值操作的,都會有拷貝構造函數調用(如函數參數的實參到形參的值傳遞,函數返回值的賦值等待)

  • 當函數的形參是類的對象,調用函數時,進行形參與實參結合時使用。這時要在內存新建立一個局部對象,并把實參拷貝到新的對象中。理所當然也調用拷貝構造函數。
  • 當函數的返回值是類對象,函數執行完成返回調用者時使用。理由也是要建立一個臨時對象中,再返回調用者。為什么不直接用要返回的局部對象呢?因為局部對象在離開建立它的函數時就消亡了,不可能在返回調用函數后繼續生存,所以在處理這種情況時,編譯系統會在調用函數的表達式中創建一個無名臨時對象,該臨時對象的生存周期只在函數調用處的表達式中。所謂return對象,實際上是調用拷貝構造函數把該對象的值拷入臨時對象。如果返回的是變量,處理過程類似,只是不調用構造函數。

拷貝構造函數的幾種情況

深/淺拷貝和禁止拷貝

深/淺拷貝

**對于像成員變量中有在堆上分配的類,一般都需要自己實現拷貝構造函數,因為默認的拷貝構造函數是淺拷貝,僅僅將對成員變量的值拷貝而已。**但成員變量中有在堆上分配的類,每個對象都需要重新分配一個空間,不然,當對象自動銷毀時,由于不同對象內的指針指向同一個空間,當前面一個對象銷毀后,后一個對象內的指針便成了野指針,無法delete。

#pragma once
#include  <iostream>
#include <cstring>
class Str
{
private:char* m_str;size_t m_length;
public:Str(const char* str = ""): m_length(strlen(str)), m_str(new char[m_length + 1]){strcpy(m_str, str);std::cout << "Str(const char* str) called: " << m_str << std::endl;}Str(const Str& other): m_length(other.m_length), m_str(new char[other.m_length + 1]){strcpy(m_str, other.m_str);std::cout << "Str(const Str& other) called: " << m_str << std::endl;}~Str(){std::cout << "Str Decon called:" << m_str << std::endl;delete[] m_str;}};
class Empty { //在空類中測試禁止拷貝
public:Empty() {std::cout << "Empty constructor called." << std::endl;}~Empty() {std::cout << "Empty destructor called." << std::endl;}// int a; // 如果有成員變量,Empty類的大小會大于1字節,計算方式與結構體一樣
private: //需要將拷貝構造函數和賦值運算符聲明為私有,以禁止拷貝Empty(const Empty&); // 禁止拷貝構造函數Empty& operator=(const Empty&); // 禁止賦值運算符
};

禁止拷貝

通過將拷貝構造函數與=運算符聲明為私有,并且不提供它們的實現 ,可以使得,Str s2(S1); ,Str s2 = s1; , Str s2; s2 = s1 這樣的語句直接在編譯時報錯,幫助我們實現只有單個對象禁止拷貝的功能。需要注意上面三個語句編譯器所尋找的函數有一些不同。

  1. Str s2(S1); 找只有一個參數,且參數是Str 對象的構造函數,其實就是拷貝構造函數(這是我們人為對這個重載函數給的名字罷了)。
  2. Str s2 = s1; 找只有一個參數,且參數是Str 對象的構造函數,其實就是拷貝構造函數(這是我們人為對這個重載函數給的名字罷了),注意這里是初始化,所以是找拷貝構造函數,不是找=的重載函數。
  3. Str s2; s2 = s1 先找不帶任何參數的構造函數,第二個語句找=運算符的重載函數 operator=(const Str& other)

空類默認產生的成員

一個類(類型是沒有大小的,這里類的大小指的是這個類對象的大小,像sizeof(int) 得到的是int 變量的一個實例的大小一樣) 的大小只取決于其中的非static成員大小(計算規則和struct一樣),與以下都無關:

  • static成員(其實這些成員相當于全局變量,只是語法上編譯器層面把他歸類在類中,需要通過類域訪問)
  • 成員函數(不論static還是一般成員函數,包括各種構造析構函數,這些函數都是放在程序的代碼段,他們和類的大小沒有關系,那這些成員函數怎么區分對象的?依靠我們之前說過的隱藏參數this指針)

如果一個類沒有任何成員,是一個空的類,為了標識它的存在,編譯器會給它分配一個字節的空間。

測試代碼和結果:

深淺拷貝和空類

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

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

相關文章

清理 Windows C 盤該注意什么

C 盤空間不足會嚴重影響系統性能。 清理 C 盤文件時&#xff0c;首要原則是安全。錯誤地刪除系統文件會導致 Windows 無法啟動。下面我將按照 從最安全、最推薦到需要謹慎操作的順序&#xff0c;為你詳細列出可以清理的文件和文件夾&#xff0c;并提供操作方法。 第一梯隊&…

Python Selenium 滾動到特定元素

文章目錄 Python Selenium 滾動到特定元素?? **1. 使用 scrollIntoView() 方法&#xff08;最推薦&#xff09;**&#x1f5b1;? **2. 結合 ActionChains 移動鼠標&#xff08;模擬用戶行為&#xff09;**&#x1f9e9; **3. 使用坐標計算滾動&#xff08;精確控制像素&…

你寫的 Express 接口 404,可能是被“動態路由”吃掉了

本文首發在我的個人博客&#xff1a;你寫的 Express 接口 404&#xff0c;可能是被“動態路由”吃掉了 前情提要 最近參與公司的一個項目前端 React&#xff0c;后端用的 Express。目前我就做一些功能的新增或者修改。 對于 Express &#xff0c;本人沒有公司項目實戰經驗&…

【Java面試】你是怎么控制緩存的更新?

&#x1f504; 一、數據實時同步失效&#xff08;強一致性&#xff09; 原理&#xff1a;數據庫變更后立即失效或更新緩存&#xff0c;保證數據強一致。 實現方式&#xff1a; Cache Aside&#xff08;旁路緩存&#xff09;&#xff1a; 讀流程&#xff1a;讀緩存 → 未命中則…

react-嵌套路由 二級路由

什么是嵌套路由&#xff1f; 在一級路由中又內嵌了其他路由&#xff0c;這種關系就叫做嵌套路由&#xff0c;嵌套至一級路由內的路由又稱作二級路由 嵌套路由配置 實現步驟 配置二級路由 children嵌套 import Login from "../page/Login/index"; import Home from …

【CMake基礎入門教程】第八課:構建并導出可復用的 CMake 庫(支持 find_package() 查找)

很好&#xff01;我們進入 第八課&#xff1a;構建并導出可復用的 CMake 庫&#xff08;支持 find_package() 查找&#xff09;。 &#x1f3af; 本課目標 你將掌握&#xff1a; 如何構建一個庫并通過 install() 導出其配置&#xff1b; 如何讓別人在項目中使用 find_package…

Jenkins與Kubernetes深度整合實踐

采用的非jenkins-slave方式 jenkins配置&#xff1a; Jenkins添加k8s master節點的服務器信息 在Jenkins容器內部與k8s master節點設置免費登錄 # docker過濾查詢出運行的Jenkins服務 $ docker ps | grep jenkins# 進入Jenkins容器內部 $ docker exec -it jenkins-server /bi…

GraphQL API-1

簡介 判斷GraphQL方式 判斷一個網站是否使用了GraphQL API&#xff0c;可以通過以下幾種方法&#xff1a; 1. 檢查網絡請求 查看請求端點 GraphQL 通常使用單一端點&#xff0c;常見路徑如&#xff1a; /graphql/api/graphql/gql/query 觀察請求特征 POST 請求為主&…

推薦C++題目練習網站

LeetCode LeetCode是一個全球知名的編程練習平臺&#xff0c;提供大量C題目&#xff0c;涵蓋數據結構、算法、系統設計等。題目難度從簡單到困難&#xff0c;適合不同水平的學習者。平臺支持在線編寫代碼并即時運行測試&#xff0c;提供詳細的題目討論區和官方解答。 Codeforc…

Spring Cloud 微服務(服務注冊與發現原理深度解析)

&#x1f4cc; 摘要 在微服務架構中&#xff0c;服務注冊與發現是整個系統運行的基礎核心模塊。它決定了服務如何被定位、調用和管理。 本文將深入講解 Spring Cloud 中 Eureka 的服務注冊與發現機制&#xff0c;從底層原理到源碼分析&#xff0c;再到實際開發中的最佳實踐&a…

【Linux 設備模型框架 kobject 和 kset】

Linux 設備模型框架 kobject 和 kset 一、Linux 設備模型概述二、kobject 與 kset 的核心概念1. kobject2. kset3. 關鍵數據結構 三、kobject 與 kset 的實現源碼四、源碼解析與使用說明1. kset 的創建與初始化2. kobject 的創建與屬性3. sysfs 屬性操作4. 用戶空間訪問示例 五…

一起學前端之HTML------(1)HTML 介紹

HTML 介紹 HTML 即超文本標記語言&#xff08;HyperText Markup Language&#xff09;&#xff0c;它是構成網頁的基礎技術之一。HTML 借助各種標簽&#xff08;Tag&#xff09;對網頁的結構與內容加以描述。下面為你介紹其核心要點&#xff1a; 關鍵特性 標簽結構&#xff…

整體遷移法遷移 Docker 鏡像

docker添加了新的鏡像數據盤&#xff0c;數據盤遷移步驟 使用整體遷移法遷移 Docker 鏡像后&#xff0c;可以在確認遷移成功且新數據盤正常使用后&#xff0c;刪除舊數據目錄來釋放空間1。 # 停止 Docker 服務 sudo systemctl stop docker # 停止 socket 監聽器 sudo systemct…

智能IDE+高效數據采集,讓數據獲取接近0門檻

亮數據也有了自己的官方賬號&#xff0c;大家可以關注&#xff1a;https://brightdata.blog.csdn.net/ 現在正有福利&#xff0c;有興趣的伙伴可以訪問鏈接&#xff1a; https://www.bright.cn/products/web-scraper/?utm_sourcebrand&utm_campaignbrnd-mkt_cn_csdn_jhx…

GNSS位移監測站在大壩安全中的用處

一、實時監測大壩變形 整體位移監測 GNSS&#xff08;全球導航衛星系統&#xff09;位移監測站能夠實時、連續地獲取大壩在三維空間中的位置信息&#xff0c;包括水平位移和垂直位移。大壩在長期運行過程中&#xff0c;受到水壓力、溫度變化、地基沉降等多種因素的影響&#x…

數字圖像處理(一):從LED冬奧會、奧運會及春晚等等大屏,到手機小屏,快來挖一挖里面都有什么

數字圖像處理&#xff08;一&#xff09; 一、什么是圖像&#xff1a;圖像就是多維數組圖像的存儲每一個格子有自己的顏色、深淺如何訪問圖像&#xff1a;1.對于RGB圖像&#xff0c;共有R/G/B三個通道&#xff0c;通過代碼來看。圖像有單通道和多通道之分&#xff0c;訪問時只需…

關于漢語和英語哪個更先進、歷史更久的爭論

引言&#xff1a;熱議背后的思考? ? 在全球化浪潮的推動下&#xff0c;英語作為國際通用語言&#xff0c;在世界范圍內廣泛傳播&#xff0c;其在國際商務、科技交流、學術研究等領域占據著重要地位。而漢語&#xff0c;作為世界上使用人口最多的語言之一&#xff0c;承載著…

在不聯網的情況下,從可以聯網的計算機上拷貝過來的程序報錯:nu1301 無法加載源,https://api.nuget.org/v3/index.json

解決方法&#xff1a; 在聯網的計算機上&#xff0c;找到nuget文件&#xff0c;拷貝到&#xff0c;不能聯網的計算機的相應位置 設置加載這個nuget包&#xff0c;把nuget.org取消。 注意如果出現好多包都不能加載&#xff0c;可能是框架版本的問題&#xff0c;修改框架版本&am…

TCP 狀態流程及原理詳解:從連接建立到性能優化

一、TCP 協議概述與核心價值 TCP&#xff08;Transmission Control Protocol&#xff0c;傳輸控制協議&#xff09;是互聯網協議棧中的核心協議之一&#xff0c;為網絡通信提供可靠的、面向連接的數據傳輸服務。在當今復雜多變的網絡環境中&#xff0c;深入理解 TCP 協議的狀態…

【STM32 學習筆記】PWR電源控制

在電子設備中&#xff0c;待機&#xff08;Standby&#xff09;和睡眠&#xff08;Sleep&#xff09;是兩種不同的省電模式。 1. 待機模式&#xff08;Standby Mode&#xff09;&#xff1a;在待機模式下&#xff0c;設備仍然保持一定程度的活動&#xff0c;但大部分功能處于暫…