【C++面向對象】封裝(上):探尋構造函數的幽微之境

?

每文一詩? 💪🏼

? ? ? ?我本將心向明月,奈何明月照溝渠? —— 元/高明《琵琶記》

? ? ? ? 譯文:我本是以真誠的心來對待你,就像明月一樣純潔無瑕;然而,你卻像溝渠里的污水一樣,對這份心意無動于衷,甚至于不屑一顧。


如果本文對你有所幫助,那能否支持一下老弟呢,嘻嘻🥰

??個人主頁 點擊??

封裝

封裝作為C++面向對象的三大特性之一

封裝將數據和操作數據的代碼組合成一個獨立的單元,也就是類。類的成員可以有不同的訪問權限,如公有(public)、私有(private)和受保護(protected),以此來控制外部對這些成員的訪問。

類的結構

class 類名{ 訪問權限: 屬性 / 行為 };

  • class類名:指的是類的名稱 這個名稱可以隨意命名例如class hunman
  • 訪問權限指的是:如公有(public)、私有(private)和受保護(protected)
  • 屬性和行為是指的在這個類當中所定義的變量和方法。

?例如這里定義了一個人類

#include <iostream>
class hunman
{
private:std::string name;
public:hunman();~hunman();
protected:std::string ID_card;
};

struct和class區別

struct默認是公共權限,class默認是私有權限

也就是說在sttruct中定義的變量和方法,可以在外部用該對象直接訪問;

而對于Class中定義的變量和方法,如果不指定其權限,默認是無法在外部通過其對象訪問的。

構造函數和析構函數

????????在C++中,構造函數:是指在這個類當中進行對變量的初始化操作,主要作用在于創建對象時為對象的成員屬性賦值,構造函數由編譯器自動調用,無須手動調用。即在該類的對象被實例化后,構造函數會被立即調用。

????????在C++中,析構函數:主要作用在于對象銷毀前系統自動調用,執行一些清理工作。例如,當程序中有使用動態內存,即使用new操作符,那么可以在析構函數中進行delete,即內存釋放。

構造函數的分類

構造函數的語法是:類名(){}?? 名稱和類名相同,可以重載

  • 無參構造函數:human(){}
  • 有參構造函數:human(std::string name){}
  • 拷貝構造函數:human(const human& h){}

解釋:

  • 無參構造函數:是指這個類當中的構造函數不傳入任何參數
  • 有參構造函數:是指這個類當中的構造函數傳入參數,通常時將傳入的參數賦值給類當中的成員變量
  • 拷貝構造函數:傳入的參數是和個類的對象,為什么要傳入const +引用的形式呢?,是因為我們不想傳入的對象被修改,并且以引用的方式傳遞,這個可以避免被傳入的對象占用的內存非常大時,對其的拷貝,使用引用本質上是在使用指針,避免內存的拷貝,提高程序的效率。

構造函數調用規則

  • 括號法
  • 顯示法
  • 隱式轉換法

?形式1:類名 變量名? hunman p? 調用函數:無參構造函數,析構函數

代碼

#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval){height = heightval;std::cout<<"有參構造函數"<<std::endl;}//拷貝構造函數hunman(const hunman& h){height = h.height;std::cout<<"拷貝構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl;       }float height;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{hunman h;/* code */return 0;
}

?輸出

??形式2:類名() 或者 類名 變量名() hunman(1.80) 或者hunman h(1.80);調用函數:有參構造函數,析構函數

代碼

#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval){height = heightval;std::cout<<"有參構造函數"<<std::endl;}//拷貝構造函數hunman(const hunman& h){height = h.height;std::cout<<"拷貝構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl;       }float height;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{// hunman h;hunman(1.80);/* code */return 0;
}

輸出

??形式2:類名 變量名 = 類名(參數)? hunman h1 = hunman(1.80);? 調用函數:有參構造函數,析構函數

代碼

#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval){height = heightval;std::cout<<"有參構造函數"<<std::endl;}//拷貝構造函數hunman(const hunman& h){height = h.height;std::cout<<"拷貝構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl;       }float height;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{// hunman h;// hunman(1.80);hunman h1 = hunman(1.80);// hunman h2 = hunman(h1);/* code */return 0;
}

輸出

??形式2:類名 變量名 = 參數 hunman h1 = 1.80;調用函數:有參構造函數,析構函數

代碼

#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval){height = heightval;std::cout<<"有參構造函數"<<std::endl;}//拷貝構造函數hunman(const hunman& h){height = h.height;std::cout<<"拷貝構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl;       }float height;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{// hunman h;// hunman(1.80);// hunman h1 = hunman(1.80);// hunman h2 = hunman(h1);hunman h1  = 1.80;/* code */return 0;
}

輸出:

默認情況下,c++編譯器至少給一個類添加3個函數
1.默認構造函數(無參,函數體為空)
2.默認析構函數(無參,函數體為空)
3.默認拷貝構造函數,對屬性進行值拷貝
構造函數調用規則如下:
如果用戶定義有參構造函數,c++不在提供默認無參構造,但是會提供默認拷貝構造
如果用戶定義拷貝構造函數,c++不會再提供其他構造函數

拷貝構造函數

拷貝構造函數的調用

當將一個當前類的對象作為參數傳入構造函數中后,就會調用拷貝構造函數

    hunman h;hunman h2 = hunman(h);

?

?這里為什么析構函數調用了兩次呢?

很好理解,因為你實例化了兩個對象,分別是h,h2。

深拷貝與淺拷貝

這個問題時對于拷貝構造函數的經典問題

淺拷貝:

????????是指在定義類時,沒有在類中顯式的構建拷貝構造函數,將一個類的對象作為參數傳入類的構造函數中,編譯器會把原對象中棧區的變量的值和堆區指針的值復制,而不復制一個指針指向的值

導致的問題:

????????在類執行完成,調用析構函數函數時,并且析構函數中有使用delete對指針進行釋放時,會給兩個對象中的指針分別釋放,而兩個對象中指針的值時相同的,就比如說第一個對象中指針的值是0x123,另一個對象中指針的值也是0x123,根據棧區先進后出的原則,后被創建的對象會先進行釋放對象中的指針0x123,釋放之后,另一個對象中的指針0x123也會被釋放,但是這時就會報錯,因為0x123這個指針已經被釋放過了。

代碼演示 (先看一下沒有構建拷貝構造函數時,棧區和堆區變量是否被復制)

#include <iostream>
class hunman
{
private:std::string name;
public://無參構造函數hunman(){std::cout<<"無參構造函數"<<std::endl;}//有參構造函數hunman(float heightval,int ageval){height = heightval;age = new int(ageval);std::cout<<"有參構造函數"<<std::endl;}//析構函數~hunman(){std::cout<<"析構函數"<<std::endl;       }float height;int *age;
protected:std::string ID_card;
};int main(int argc, char const *argv[])
{hunman h(1.80,23);hunman h2 = hunman(h);std::cout<<"第一個對象中的height的值:"<<h.height<<"  第二個對象中的height的值:"<<h2.height<<std::endl;std::cout<<"第一個對象中的age的值:"<<h.age<<"  第二個對象中的age的值:"<<h2.age<<std::endl;return 0;
}

輸出

解析:

????????這段代碼中比沒有構建拷貝構造函數,而這時編譯器會默認構建一個,剛才說過棧區和堆區變量會被復制,根據輸出可以直觀的看到,第一個對象中的棧區變量height和堆區中的age變量所存儲的值是相同的。

但是上段代碼有些問題,因為我們既然創建了一個指針變量,即動態分配內存,那么就應該手動釋放這塊內存,即使用delete。

在析構函數中添加

 ~hunman(){if(age != nullptr){delete age;age = nullptr;}std::cout<<"析構函數"<<std::endl;       }

?但是執行后出現問題

????????通過圖中我們可以看到,第一個析構函數已經成功執行,但是第二個析構函數執行時卻發生的報錯free(): double free detected in tcache 2

????????這個錯誤提示表明你試圖對同一塊已經釋放的內存進行了多次釋放操作,也就是所謂的 “雙重釋放” 問題

這個雙重釋放也很好理解,因為在第二個對象對0x123這個地址釋放后,原對象再次對這個地址釋放是沒有用的,因為他已經被釋放過了。

注:

?hunman h(1.80,23);原對象h(先進后出,后釋放)
?hunman h2 = hunman(h);第二個對象h2(先進后出,先釋放)

?解決方法:

使用深拷貝

????????深拷貝是指在復制對象時,不僅復制對象的基本數據類型的值,還會為對象中的指針成員分配新的內存空間,并將原指針所指向的內存內容復制到新的內存空間中。這樣,原對象和拷貝對象的指針成員會指向不同的內存地址。

代碼,顯式構建拷貝構造函數

    // 拷貝構造函數hunman(const hunman& h){height = h.height;age = new int(*h.age);std::cout<<"拷貝構造函數"<<"地址:"<<std::endl;}

?在拷貝構造函數中對棧區的變量重新復制,并且對堆區的指針重新分配內存,使其和原對象中的指針的值不是一個地址,這樣第二個對象釋放一個地址,而另一個對象釋放里一個地址,這樣互不干涉,程序就不會崩潰了。

由圖可知,兩個對象當中的age的值即地址是不一樣的,完美解決!

注:在顯式的構建拷貝構造函數時,在函數中應該對需要復制的值手動賦值,因為在沒有構建拷貝茍造函數時,編譯器會幫你把所有的變量復制,但是當你顯式構建是,就沒了,所以需要手動復制。

構造函數初始化列表

使用構造函數初始化列表可以幫助我們快速的初始化成員變量

語法:類名(參數1,參數2,...): 成員變量1(參數1),成員變量2(參數2),...

#include <iostream>
class hunman
{
public://構造函數初始化列表hunman(int a,float b,std::string c): age(a),height(b),name(c){std::cout<<"age:"<<age<<std::endl;std::cout<<"height:"<<height<<std::endl;std::cout<<"name:"<<name<<std::endl;}int age;float height;std::string name;
};int main(int argc, char const *argv[])
{hunman(21,1.80,"rqtz");return 0;
}

類對象作為類成員

當有另一個類a的對象作為類h的成員變量時候,構造函數和析構函數的調用順序

構造函數順序 :先a后h

析構函數順序:先h后a

代碼

#include <iostream>
class animal
{public:animal(){std::cout<<"animal無參構造函數"<<std::endl;}~animal(){std::cout<<"animal析構函數"<<std::endl;}};
class hunman
{
public://無參構造函數hunman(){std::cout<<"hunman無參構造函數"<<std::endl;}~hunman(){std::cout<<"hunman析構函數"<<std::endl;}animal a;
};int main(int argc, char const *argv[])
{hunman h;return 0;
}

?

靜態成員

在類中使用 static關鍵字所修飾的變量和函數叫做類的靜態成員

  • 靜態成員不屬于任何對象
  • 該類的任何對象共用一分數據,共享一塊內存
  • 該類的任何對象都可以修改靜態成員的值,并且該值會被更新
  • 靜態成員的初始化需要在類外,使用類名加作用域的方式初始化
#include <iostream>class hunman
{
public://無參構造函數hunman(){// std::cout<<"hunman無參構造函數"<<std::endl;}~hunman(){// std::cout<<"hunman析構函數"<<std::endl;}static void func(){std::cout<<"靜態成員函數"<<std::endl;}static int a;
};
//類外初始化
int hunman::a = 1;
int main(int argc, char const *argv[])
{hunman h;std::cout<<h.a<<std::endl;//其他對象改變靜態變量的值班hunman h2;h2.a = 10;//在用原對象訪問時,值已經更新std::cout<<h.a<<std::endl;//通過類名加作用域訪問靜態變量和靜態成員函數std::cout<<hunman::a<<std::endl;hunman::func();//該類的對象靜態變量時統一快內存std::cout<<&h2.a<<std::endl;std::cout<<&hunman::a<<std::endl;return 0;
}

輸出:

?

?

??🔥🔥個人主頁 🔥🔥

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

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

相關文章

JavaScript性能優化(下)

1. 使用適當的算法和邏輯 JavaScript性能優化是一個復雜而重要的話題&#xff0c;尤其是在構建大型應用時。通過使用適當的算法和邏輯&#xff0c;可以顯著提高代碼的效率和響應速度。以下是一些關鍵策略和實踐&#xff0c;用于優化JavaScript性能&#xff1a; 1.1. 采用適當…

螞蟻 Flink 實時計算編譯任務 Koupleless 架構改造

張馮君&#xff08;遠遠&#xff09; Koupleless PMC 螞蟻集團技術工程師 就職于螞蟻集團中間件團隊&#xff0c;參與維護與建設螞蟻 SOFAArk 和 Koupleless 開源項目、內部 SOFAServerless 產品的研發和實踐。 本文 3488 字&#xff0c;預計閱讀 11 分鐘 業務背景 基于開源 A…

使用pycharm社區版調試DIFY后端python代碼

目錄 背景 前置條件 DIFY使用的框架 API服務調試配置步驟&#xff08;基于tag為0.15.3的版本&#xff09; 1.配置.env文件 2.關閉docker里面的docker-api-1服務 3.使用DOCKER啟動本地環境需要用到的中間件&#xff0c;并暴露端口 注意事項一&#xff1a; 注意事項二&#xff1a…

從 macos 切換到 windows 上安裝的工具類軟件

起因 用了很多年的macos, 已經習慣了macos上的操作, 期望能在windows上獲得類似的體驗, 于是花了一些時間來找windows上相對應的軟件. 截圖軟件 snipaste?????? windows和macos都有的軟件, 截圖非常好用 文件同步軟件 oneDrive: 嘗試了不同的同步軟件, 還是微軟在各…

MySQL體系架構(一)

1.1.MySQL的分支與變種 MySQL變種有好幾個,主要有三個久經考驗的主流變種:Percona Server,MariaDB和 Drizzle。它們都有活躍的用戶社區和一些商業支持,均由獨立的服務供應商支持。同時還有幾個優秀的開源關系數據庫,值得我們了解一下。 1.1.1.Drizzle Drizzle是真正的M…

【項目實訓項目博客】prompt初版實踐

通過對camel技術的理解&#xff0c;我們向其中添加了市場營銷角色的prompt 初版設計如下&#xff1a; chatchainconfig.json { "chain": [ { "phase": "DemandAnalysis", "phaseType": "SimplePhase", "max_turn_step…

[Bond的雜貨鋪] CKS 證書也到貨咯

最近比較忙&#xff0c;忘記寫Blog了&#xff1a;&#xff09; 一年前黑五去官網蹲了一手Cyber Monday&#xff0c;買了英文考試券bundle&#xff0c;當時只考了cka,后來cks差點都忘記了。將近一年后&#xff0c;無意中收到官方的提醒郵件&#xff0c;說考試券本已過期&#x…

【回眸】Linux 內核 (十五) 之 多線程編程 上

前言 進程和線程 區別 線程API 1.創建線程 2.線程退出 3.線程等待 4.線程脫離 5. 線程ID獲取及比較 6.創建及銷毀互斥鎖 7.創建及銷毀條件變量 8. 等待 9.觸發 多線程編程 后記 前言 高產的幾天。 進程和線程 區別 進程——資源分配的最小單位&#xff0c;線…

127.0.0.1本地環回地址(Loopback Address)

127.0.0.1 是計算機網絡中的一個特殊IPv4地址&#xff0c;稱為本地環回地址&#xff08;Loopback Address&#xff09;&#xff0c;主要用于以下用途&#xff1a; 1. 基本定義 本地主機&#xff08;Localhost&#xff09;&#xff1a;該地址始終指向當前正在使用的計算機本身&a…

S7-1200 PLC熱電偶和熱電阻模擬量模塊

熱電偶和熱電阻模擬量模塊 S7-1200 PLC有專用用于對溫度進行采集的熱電偶模塊SM1231 TC和SM 1231RTD。熱電偶模塊有4AI和8AI兩種&#xff0c;下面以SM1231 TC 4AI為例看一下接線圖。 該模塊一共有4個通道&#xff0c;每個通道有兩個接線端子&#xff0c;比如0&#xff0c;0-。…

深度了解向量引論

今天去研究了一個基本數學原理 這個其實需要證明 今天推導了一下這個公式&#xff0c;感覺收獲挺大 下面是手工推導過程

Feign修仙指南:聲明式HTTP請求的優雅之道

各位在微服務世界摸爬滾打的道友們&#xff01;今天要解鎖的是Spring Cloud的絕世神通——Feign&#xff01;這貨堪稱HTTP界的"言出法隨"&#xff0c;只需定義接口&#xff0c;就能自動生成HTTP請求代碼&#xff01;從此告別手動拼裝URL的苦日子&#xff0c;讓你的代…

UDP學習筆記(四)UDP 為什么大小不能超過 64KB?

&#x1f310; UDP 為什么大小不能超過 64KB&#xff1f;TCP 有這個限制嗎&#xff1f; 在進行網絡編程或者調試網絡協議時&#xff0c;我們常常會看到一個說法&#xff1a; “UDP 最大只能發送 64KB 數據。” 這到底是怎么回事&#xff1f;這 64KB 是怎么來的&#xff1f;TCP…

LabVIEW 中串口設備與采集卡的同步精度

在 LabVIEW 項目開發中&#xff0c;常涉及多種設備協同工作&#xff0c;如通過串口設備采集溫度&#xff0c;利用采集卡&#xff08;如 NI 6251&#xff09;采集壓力。此時&#xff0c;設備間的同步精度至關重要&#xff0c;它直接影響系統數據的準確性與可靠性。下面&#xff…

DP_AUX輔助通道介紹

DisplayPort&#xff08;簡稱DP&#xff09;是一個由PC及芯片制造商聯盟開發&#xff0c;視頻電子標準協會&#xff08;VESA&#xff09;標準化的數字式視頻接口標準。該接口免認證、免授權金&#xff0c;主要用于視頻源與顯示器等設備的連接&#xff0c;并也支持攜帶音頻、USB…

[GESP202312 五級] 平均分配

文章目錄 題目描述輸入格式輸出格式輸入輸出樣例 #1輸入 #1輸出 #1 輸入輸出樣例 #2輸入 #2輸出 #2 提交鏈接提示解析參考代碼 題目描述 小楊認為&#xff0c;所有大于等于 a a a 的完全平方數都是他的超級幸運數。 小楊還認為&#xff0c;所有超級幸運數的倍數都是他的幸運…

[Mysql]buffersize修改

1、找到my.cnf文件位置 ps -ef|grep mysqld 2、編輯my.cnf cd /etc/my.cnf.d vim my.cnf 一般修改為內存的50%~70% 3、重啟服務 systemctl restart mysqld

清晰易懂的 Apollo 配置中心安裝與使用教程

Apollo 是攜程開源的分布式配置管理平臺&#xff0c;支持配置實時推送、版本管理、權限控制等功能。本教程將手把手教你完成 Apollo 核心組件安裝、基礎配置管理及避坑指南&#xff0c;助你快速掌握企業級配置管理能力。 一、環境準備&#xff08;關鍵依賴&#xff09; 1. 基礎…

PyTorch池化層詳解:原理、實現與示例

池化層&#xff08;Pooling Layer&#xff09;是卷積神經網絡中的重要組成部分&#xff0c;主要用于降低特征圖的空間維度、減少計算量并增強模型的平移不變性。本文將通過PyTorch代碼演示池化層的實現原理&#xff0c;并詳細講解最大池化、平均池化、填充&#xff08;Padding&…

如何構建并優化提示詞?

提示詞是一個小白最容易上手大模型的方式&#xff0c;提示詞就是你告訴大模型應該如何去完成一項工作的系統性的命令&#xff0c;所以寫一個好的提示詞是比較關鍵的&#xff0c;那么如何寫好一個提示詞呢&#xff1f; 要寫好提示詞&#xff0c;其實就像我們要把一些命令清晰地傳…