【C++】類的默認成員函數(上)

在這里插入圖片描述
🔥博客主頁 小羊失眠啦.
🎥系列專欄《C語言》 《數據結構》 《C++》 《Linux》 《Cpolar》
??感謝大家點贊👍收藏?評論??


在這里插入圖片描述

文章目錄

  • 一、默認成員函數
  • 二、構造函數
    • 構造函數的概念及特性
  • 三、析構函數
    • 析構函數的特性
  • 四、拷貝構造函數
    • 拷貝構造函數的特性

一、默認成員函數

上一章中我們談到,如果一個類中什么成員也沒有,那么這個類就叫作空類。其實這么說是不太嚴謹的,因為一個類不可能什么都沒有

當我們定義好一個類,不做任何處理時,編譯器會自動生成以下6個默認成員函數

  • 默認成員函數:如果用戶沒有手動實現,則編譯器會自動生成的成員函數。

在這里插入圖片描述

  • 構造函數:主要完成初始化工作;
  • 析構函數:主要完成清理工作;
  • 拷貝構造:使用一個同類的對象初始化創建一個對象;
  • 賦值重載:把一個對象賦值給另一個對象;
  • 取地址重載普通對象取地址操作;
  • 取地址重載(const):const對象取地址操作;

本章我們將學習四個默認成員函數——構造函數析構函數——拷貝構造賦值重載


二、構造函數

在C語言階段,我們實現的數據結構時,有一件事很苦惱,就是每當創建一個stack對象(之前叫作定義一個stack類型的變量)后,首先得調用它的專屬初始化函數StackInit來初始化對象。

typedef int dataOfStackType;typedef struct stack
{dataOfStackType* a;int top;int capacity;
}stack;void StackInit(stack* ps);
//...int main(){stack s;StackInit(&s);//...return 0;}

這不免讓人覺得有點麻煩。在C++中,構造函數為我們很好的解決了這一問題。

構造函數的概念及特性

構造函數是一個特殊的成員函數。構造函數雖然叫作構造,但是其主要作用并不是開辟空間創建對象,而是初始化對象

構造函數之所以特殊,是因為相比于其它成員函數,它具有如下特性

  1. 函數名與類名相同
  2. 無返回值
  3. 對象實例化時,編譯器自動調用對應的構造函數
  4. 構造函數可以重載

舉例

class Date
{
public://無參的構造函數Date(){};//帶參的構造函數Date(int year,int month,int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1;//調用無參構造函數(自動調用)Date d2(2023, 3, 29);//調用帶參構造函數(自動調用)
}

特別注意

  • 創建對象時編譯器會自動調用構造函數,若是調用無參構造函數,則無需在對象后面使用()。否則會產生歧義:編譯器無法確定你是在聲明函數還是在創建對象

錯誤示例

//錯位示例
Date d3();
  1. 如果類中沒有顯式定義構造函數,則C++編譯器會自動生成一個無參的默認構造函數,一旦用戶顯式定義編譯器將不再生成
class Date
{
public://若用戶沒有顯示定義,則編譯器自動生成。/*Date(int year,int month,int day){_year = year;_month = month;_day = day;}*/private:int _year;int _month;int _day;
};
  1. 默認生成構造函數,對內置類型成員不作處理;對自定義類型成員,會調用它的默認構造函數
  • C++把類型分成內置類型(基本類型)和自定義類型。內置類型就是語言提供的數據類型,如:int、char、double…,自定義類型就是我們使用class、struct、union等自己定義的類型。

舉例

默認構造函數對內置類型

class Date
{
public://此處不對構造函數做顯示定義,測試默認構造函數/*Date(){}*/void print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};
void TestDate1()
{Date d1;d1.print();
}

在這里插入圖片描述

  • 如圖所示,默認構造函數的確未對內置類型做處理。

默認構造函數對自定義類型

class stack
{
public://此處對stack構造函數做顯示定義stack(){cout <<"stack()" << endl;_a = nullptr;_top = _capacity = 0;}
private:int* _a;int _top;int _capacity;
};class queue
{
public://此處不對queue構造函數做顯示定義,測試默認構造函數/*queue(){}*/
private://自定義類型成員stack _s;
};void TestQueue()
{queue q;
}

在這里插入圖片描述

  • 如圖所示,在創建queue對象時,默認構造函數對自定義成員_s做了處理,調用了它的默認構造函數stack()

這一波蜜汁操作讓很多C++使用者感到困惑與不滿,為什么要針對內置類型和自定義類型做不同的處理呢?終于,在C++11中針對內置類型成員不初始化的缺陷,又打了補丁,即:

  1. 內置類型成員變量在類中聲明時可以給默認值

舉例

class Date
{
public:
//...void print(){cout << _year << "-" << _month << "-" << _day << endl;}
private://使用默認值int _year = 0;int _month = 0;int _day = 0;
};
void TestDate2()
{Date d2;d2.print();
}

在這里插入圖片描述

  • 默認值:若不對成員變量做處理,則使用默認值。
  1. 無參的構造函數和全缺省的構造函數都稱為默認構造函數,并且默認構造函數只能有一個

舉例

class Date
{
public://無參的默認構造函數//Date()//{//}//全缺省的默認構造函數Date(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year = 0;int _month = 0;int _day = 0;
};

默認構造函數:

  1. 無參的構造函數
  2. 全缺省的構造函數
  3. C++編譯器生成的無參的構造函數

即三種必須要有一種,如果沒有默認的構造函數(寫的構造函數不是無參的,也不是全缺省的)就會報錯


三、析構函數

析構函數構造函數的特性相似,但功能有恰好相反。構造函數是用來初始化對象的,析構函數是用來銷毀對象的。

  • 需要注意的是,析構函數并不是對對象本身進行銷毀(因為局部對象出了作用域會自行銷毀,由編譯器來完成),而是在對象銷毀時會自動調用析構函數,對對象內部的資源做清理(例如stack _s中的int* a)。

同樣,有了析構函數,我們再也不用擔心創建對象(或定義變量)后由于忘記釋放內存而造成內存泄漏了。

舉例

class Stack
{
public:Stack(){//...}void Push(int x){//...}bool Empty(){// ...}int Top(){//...}void Destory(){//...}
private:// 成員變量int* _a;int _top;int _capacity;
};void TestStack()
{Stack s;st.Push(1);st.Push(2);//過去需要手動釋放st.Destroy();
}

析構函數的特性

  1. 析構函數名是在類名前加上字符 ~
  2. 無參數
  3. 無返回值
  4. 一個類只能有一個析構函數。若未顯式定義,系統會自動生成默認的析構函數
  5. 析構函數不能重載

舉例

class Date
{
public:Date(){cout << "Date()" << endl;}~Date(){cout << "~Date()" << endl;}
private:int _year = 0;int _month = 0;int _day = 0;
};void TestDate3()
{Date d3;//d3生命周期結束時自動調用構造函數
}

在這里插入圖片描述

  1. 編譯器生成的默認析構函數,對自定類型成員調用它的析構函數

舉例

class stack
{
public://此處對stack構造函數做顯示定義stack(){cout <<"stack()" << endl;_a = nullptr;_top = _capacity = 0;}~stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}
private:int* _a;int _top;int _capacity;
};
class queue
{
public://此處不對queue構造函數做顯示定義,測試默認構造函數/*queue(){}*/
private://自定義類型成員stack _s;
};void TestQueue1()
{queue q;
}

在這里插入圖片描述

  • 這里可能有小伙伴會好奇:為什么析構函數不像構造函數那樣區分內置類型與自定義類型呢
    答案是:因為內置類型壓根不需要我們擔心清理工作,在其生命周期結束時會自動銷毀。而自定義類型需要擔心,因為自定義類型里可能含有申請資源(例如:malloc申請內存須手動釋放)。
  1. 如果類中沒有申請資源時,析構函數可以不寫,直接使用編譯器生成的默認析構函數,比如Date類;有資源申請時,一定要寫,否則會造成資源泄漏,比如stack類。

四、拷貝構造函數

同樣,拷貝構造函數也屬于6個默認成員函數,而且拷貝構造函數構造函數的一種重載形式

  • 拷貝構造函數的功能就如同它的名字——拷貝。我們可以用一個已存在的對象來創建一個與已存在對象一模一樣的新的對象

舉例

class Date
{
public://構造函數Date(){cout << "Date()" << endl;}//拷貝構造函數Date(const Date& d){cout << "Date()" << endl;_year = d._year;_month = d._month;_day = d._day;}//析構函數~Date(){cout << "~Date()" << endl;}
private:int _year = 0;int _month = 0;int _day = 0;
};void TestDate()
{Date d1;//調用拷貝構造創建對象Date d2(d1);
}

在這里插入圖片描述

拷貝構造函數的特性

拷貝構造函數作為特殊的成員函數同樣也有異于常人的特性:

  1. 拷貝構造函數是構造函數的重載
  2. 拷貝構造函數的參數只有一個且必須是類類型對象的引用。若使用傳值的方式,則編譯器會報錯,因為理論上這會引發無窮遞歸

錯誤示例

class Date
{
public://錯誤示例//如果這樣寫,編譯器就會直接報錯,但我們現在假設如果編譯器不會檢查,//這樣的程序執行起來會發生什么Date(const Date d){_year = d._year;_month = d._month;_day = d._day;}
private:int _year = 0;int _month = 0;int _day = 0;
};void TestDate()
{Date d1;//調用拷貝構造創建對象Date d2(d1);
}
  • 當拷貝構造函數的參數采用傳值的方式時,創建對象d2,會調用它的拷貝構造函數d1會作為實參傳遞給形參d。不巧的是,實參傳遞給形參本身又是一個拷貝,會再次調用形參的拷貝構造函數…如此便會引發無窮的遞歸。

在這里插入圖片描述

  1. 若未顯式定義,編譯器會生成默認的拷貝構造函數。 默認的拷貝構造函數對象按內存存儲按字節序完成拷貝,這種拷貝叫做淺拷貝或者值拷貝

舉例

class Date
{
public://構造函數Date(int year = 0, int month = 0, int day = 0){//cout << "Date()" << endl;_year = year;_month = month;_day = day;}//未顯式定義拷貝構造函數/*Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}*/void print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year = 0;int _month = 0;int _day = 0;
};void TestDate()
{Date d1(2023, 3, 31);//調用拷貝構造創建對象Date d2(d1);d2.print();
}

在這里插入圖片描述

  • 有的小伙伴可能會有疑問:編譯器默認生成的拷貝構造函數貌似可以很好的完成任務,那么還需要我們手動來實現嗎?
    答案是:當然需要。Date類只是一個較為簡單的類且類成員都是內置類型,可以不需要。但是當類中含有自定義類型時,編譯器可就辦不了事兒了。
  1. 類中如果沒有涉及資源申請時,拷貝構造函數寫不寫都可以;一旦涉及到資源申請時,則拷貝構造函數是一定要寫的,否則就是淺拷貝

錯誤示例

class stack
{
public:stack(int defaultCapacity=10){_a = (int*)malloc(sizeof(int)*defaultCapacity);if (_a == nullptr){perror("malloc fail");exit(-1);}_top =  0;_capacity = defaultCapacity;}~stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}void push(int n){_a[_top++] = n;}void print(){for (int i = 0; i < _top; i++){cout << _a[i] << " ";}cout << endl;}
private:int* _a;int _top;int _capacity;
};void TestStack()
{stack s1;s1.push(1);s1.push(2);s1.push(3);s1.push(4);s1.print();stack s2(s1);s2.print();s2.push(5);s2.push(6);s2.push(7);s2.push(8);s2.print();
}

在這里插入圖片描述

如圖所示,這段程序的運行結果是程序崩潰了,且通過觀察發現,是在第二次析構時出現了錯誤。其實出現錯誤的原因是在第二次析構時對野指針進行free了。

一個小tip

  • 多個對象進行析構的順序如同一樣,先創建的對象后析構,后創建的對象先析構

為什么會出現對野指針進行free呢?

  • 原因是,對象s1與對象s2中的成員_a,指向的是同一塊空間。在s2析構完成后,這塊空間已經被釋放,此時的s1._a就是野指針。這就是淺拷貝導致的后果。

理解淺拷貝

編譯器默認生成的拷貝構造函數是按字節序拷貝的,在創建s2對象時,僅僅是把s1._a的值賦值給s2._a并沒有重新開辟一塊與s1._a所指向的空間大小相同內容相同的空間。我們把前者的拷貝方式稱為淺拷貝后者稱為深拷貝

在這里插入圖片描述

當開啟監視窗口來觀察這一過程,我們可以看到s2在進行push時,s1的內容也在跟著改變,且s1._a=s2._a

在這里插入圖片描述

正確的做法

class stack
{
public:stack(int defaultCapacity=10){_a = (int*)malloc(sizeof(int)*defaultCapacity);if (_a == nullptr){perror("malloc fail");exit(-1);}_top =  0;_capacity = defaultCapacity;}//用戶自己定義拷貝構造函數stack(const stack& s){_a= (int*)malloc(sizeof(int) * s._capacity);if (_a == nullptr){perror("malloc fail");exit(-1);}memcpy(_a, s._a, sizeof(int) * s._capacity);_top = s._top;_capacity = s._capacity;}~stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}void push(int n){_a[_top++] = n;}void print(){for (int i = 0; i < _top; i++){cout << _a[i] << " ";}cout << endl;}
private:int* _a;int _top;int _capacity;
};
  1. 拷貝構造函數典型調用場景
  • 使用已存在對象創建新對象;
  • 函數參數類型為類類型對象;
  • 函數返回值類型為類類型對象。

為了提高程序效率,一般對象傳參時,盡量使用引用類型,返回時根據實際場景,能用引用盡量使用引用

在這里插入圖片描述

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

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

相關文章

續簡單學生管理系統、包裝類--day18

Day18 一、刪除的思考題 思考題&#xff1a;刪除功能可以省略第一步嗎&#xff1f;不可以 有第一步判斷學生信息合法性&#xff0c;如果信息不合法會直接結束返回 如果沒有第一步&#xff0c;將會在第二步判斷是否有該學生query循環匹配查找&#xff0c;數據量大情況&#xff…

藍橋杯倒計時 43天 - 前綴和,單調棧

最大數組和 算法思路&#xff1a;利用前綴和化簡 for 循環將 n^2 簡化成 nn&#xff0c;以空間換時間。枚舉每個 m&#xff0c;m是刪除最小兩個數&#xff0c;那k-m就是刪除最大數&#xff0c;m<k&#xff0c;求和最大的值。暴力就是枚舉 m-O(n)&#xff0c;計算前 n-(k-m)的…

PCSA時鐘控制集成之時鐘門控級別

這一部分描述了&#xff1a; ? 時鐘門控的級別。 ? 實現最大效果的時鐘門位置。 ? 實現有效和高效時鐘門控的集成方法。 時鐘樹是由時鐘緩沖器構建的&#xff0c;這些緩沖器在時鐘源&#xff08;時鐘輸入或PLL&#xff09;與時鐘終端&#xff08;寄存器或RAM&#xff09…

ULN2003(COM的作用)

單路內部電路原理圖 三極管多級放大電路&#xff0c;最大可達到500ma&#xff1b; 典型應用&#xff1a; ULN2003屬于灌電流驅動方式&#xff1b;輸入與輸出電平反向&#xff0c;下拉4K電阻&#xff0c;為解決單片機上電IO瞬間不穩定&#xff1b; COM端 1.可懸空&#xff1a…

Java面題總結7

spring事務什么時候會失效 1:發生自調用&#xff0c;類里面使用this調用本類的方法&#xff08;&#xff09;此時這個this對象不是代理類&#xff0c;而是UserService對象本身 2&#xff1a;方法不是public 3&#xff1a;數據庫不支持事務 4&#xff1a;沒有被spring管理 …

git標簽操作

一.標簽管理 1.理解標簽 標簽 tag &#xff0c;可以簡單的理解為是對某次 commit 的?個標識&#xff0c;相當于起了?個別名,當我們需要回退到某個重要版本時&#xff0c;直接使?標簽就能很快定位到 2.創建標簽 ?先&#xff0c;切換到需要打標簽的分?上,然后&#xf…

經典目標檢測網絡Yolo——原理部分

目標檢測問題 分為兩個子問題: 找到圖片中哪些位置、哪些區域含有目標對象識別這些區域中的目標對象是什么基于CNN的目標檢測算法能夠很好的解決第二個問題,在一張圖片僅含一個對象,且該對象占據了整張圖片絕大部分面積時,基于CNN的對象識別算法具有很高的準確率。 一種定…

操作系統(1)——學習導論(Ⅱ)

目錄 小程一言專欄鏈接: [link](http://t.csdnimg.cn/6grrU) 學習導論&#xff08;Ⅱ&#xff09;操作系統-賞前人佳作大型操作系統大型操作系統的一些特點和功能舉例 服務器操作系統服務器操作系統特點和功能舉例 多處理器操作系統舉例 個人計算機操作系統舉例 掌上計算機操作…

什么是ACID屬性。在MySQL中,如何使用事務?給出一個使用事務的示例,并解釋其工作原理。

解釋什么是ACID屬性 ACID是數據庫事務正確執行的四個基本要素的縮寫&#xff0c;包括原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔離性&#xff08;Isolation&#xff09;和持久性&#xff08;Durability&#xff09;。這四個屬性…

設計模式:什么是設計模式?①

一、什么是設計模式&#xff1f; 1. 是一類程序設計思想 2. 是在大量實踐過程中摸索總結出的標準經驗提煉 3. 具有多樣性和豐富性&#xff0c;不同情況應用的思想不同 二、設計模式的好處 1. 代碼生產力和效率的提升 2. 讓代碼表現更為規整&#xff0c;簡潔。閱讀維護管理的成本…

【競技寶】DOTA2-夢幻聯賽S22:AR命懸一線 XG確定晉級淘汰賽

北京時間2024年2月28日&#xff0c;DOTA2夢幻聯賽S22的比賽在昨日進入小組賽第三個比賽日&#xff0c;本次夢幻聯賽共有AR、XG、IG三支中國區的隊伍參賽&#xff0c;那么經過三日激烈的比賽之后&#xff0c;目前三支隊伍的積分情況以及晉級形勢如何呢&#xff1f; XG XG是小組…

貪心(基礎算法)--- 區間選點

905. 區間選點 思路 &#xff08;貪心&#xff09;O(nlogn) 根據右端點排序 將區間按右端點排序 遍歷區間&#xff0c;如果當前區間左端點不包含在前一個區間中&#xff0c;則選取新區間&#xff0c;所選點個數加1&#xff0c;更新當前區間右端點。如果包含&#xff0c;則跳…

常見的算法

查找算法 基本查找 package MyApi.search;public class a01BasicSearchdemo01 {public static void main(String[] args) { int[] arr{131,127,147,81,103,23,7,79}; int number82;System.out.println(BasicSearch(arr,number));}public static boolean BasicSearch(int[] ar…

Java二叉樹(1)

&#x1f435;本篇文章將對二叉樹的相關概念、性質和遍歷等知識進行講解 一、什么是樹 在講二叉樹之前&#xff0c;先了解一下什么是樹&#xff1a;樹是一種非線性結構&#xff0c;其由許多節點和子節點組成&#xff0c;整體形狀如一顆倒掛的樹&#xff0c;比如下圖&#xff1…

給nginx部署https及自簽名ssl證書

一、生成服務器root證書 openssl genrsa -out root.key 2048 openssl req -new -key root.key -out root.csr#Country Name (2 letter code) [XX]:---> CN#Country Name (2 letter code) [XX]:---> CN#State or Province Name (full name) []:---> Shanghai#Locality…

多層感知機 + 代碼實現 - 動手學深度學習v2 | 李沐動手學深度學習課程筆記

感知機 感知機≈二分類問題 感知機和其他問題的對比 訓練感知機 如果小于等于零&#xff0c;說明預測錯啦 &#xff0c;其實就是同號為正&#xff0c;異號為負 舉個分類的例子 增加樣本&#xff0c;改變分類線 繼續分類 感知機的收斂定理 XOR問題 XOR問題其實就是第1、3象限數…

【踩坑】一條指令解決torch_scatter等安裝報錯安裝不上問題

轉載請注明出處&#xff1a;小鋒學長生活大爆炸[xfxuezhang.cn] 目錄 背景說明 (推薦方法)解決方法一&#xff1a;使用conda安裝。 解決方法二&#xff1a;指定pip的網站。 解決方法三&#xff1a;直接去下載whl文件。 (終極方法)解決方法四&#xff1a;配置MSVC 特殊情況…

Linux系統運維腳本:掃描主機上多個端?狀態

目 錄 一、要求 二、解決方案 &#xff08;一&#xff09;解決思路 &#xff08;二&#xff09;方案 三、腳本程序實現 &#xff08;一&#xff09;腳本代碼和解釋 1、腳本代碼 2、代碼解釋 &#xff08;二&#xff09;腳本驗證 1、腳本編輯 2、給予執…

構建 ESLint 內存泄露檢測插件入門:提升代碼質量與防范運行時風險

前言 本文目的是介紹如何創建開發一個自定義規則 ESLint 插件。利用其能力,檢測一些代碼中可能存在的內存泄露并及時進行提示,避免潛在的后期影響。 本文實現其中一部分功能–檢測事件監聽器的使用是否存在內存泄露為例來演示基本的 ESLint 自定義規則插件開發的過程。用以…

nginx筆記整理

目錄 一.Nginx基礎介紹 二.nginx安裝配置 三.Nginx配置文件 3.1nginx主配置文件(/etc/nginx/nginx.conf) 3.2默認的網站配置文件(/etc/nginx/conf.d/default.conf) 四.創建新的虛擬主機 五.Nginx日志 5.1nginx日志格式 5.2查看日志 5.3日志緩存(了解) 5.4日志輪轉(/…