C++六大默認成員函數

C++六大默認成員函數

  • 默認構造函數
  • 默認析構函數
    • RAII技術
      • RAII的核心思想
      • 優點
      • 示例
      • 應用場景
  • 默認拷貝構造
    • 深拷貝和淺拷貝
  • 默認拷貝賦值運算符
  • 移動構造函數(C++11起)
  • 默認移動賦值運算符(C++11起)
  • 取地址及const取地址操作符重載
      • 取地址操作符重載
      • 常量取地址操作符重載
      • 示例代碼
      • 輸出結果
      • 注意事項
  • 擴展:前置++和后置++重載
      • 重載規則

C++中的六大默認成員函數是編譯器在特定條件下自動生成的成員函數,用于管理對象的生命周期和資源操作。它們分別是:

默認構造函數

  • 作用:初始化對象,當類沒有顯式定義任何構造函數時生成。

  • 生成條件:用戶未定義任何構造函數。

  • 注意:若類有其他構造函數(如帶參數的構造函數),需顯式使用 = default 聲明默認構造函數。

class Person
{
public://Person()//{//} 不寫的話默認自動生成void GetAge(){std::cout << _age << std::endl;}
private:int _age;
};int main()
{Person p;p.GetAge();
}

其特征如下:

  1. 函數名與類名相同
  2. 無返回值。
  3. 對象實例化時編譯器自動調用對應的構造函數。
  4. 構造函數可以重載
  5. 如果類中沒有顯式定義構造函數,則C++編譯器會自動生成一個無參的默認構造函數,一旦用戶顯式定義編譯器將不再生成
  6. 編譯器生成默認的構造函數會對自定類型成員調用的它的默認成員
    函數。C++11 中針對內置類型成員不初始化的缺陷,又打了補丁,即:內置類型成員變量在類中聲明時可以給默認值。

默認析構函數

  • 作用:釋放對象資源,默認析構函數調用成員變量的析構函數。

  • 生成條件:用戶未定義析構函數。

  • 注意:若類管理動態資源(如堆內存),需自定義析構函數以避免內存泄漏

class Person
{
public://Person()//{//} 不寫的話默認自動生成void GetAge(){std::cout << _age << std::endl;}~Person(){}
private:int _age;
};int main()
{Person p;p.GetAge();
}

RAII技術

RAII(Resource Acquisition Is Initialization,資源獲取即初始化)是C++中一種管理資源的編程技術。它通過將資源的生命周期與對象的生命周期綁定在一起,利用C++的構造函數和析構函數來自動管理資源,從而避免了手動分配和釋放資源可能帶來的問題,如內存泄漏、資源未正確釋放等。

RAII的核心思想

  • 資源在對象構造時獲取:當一個對象被創建時,它的構造函數負責獲取所需的資源(例如,動態內存分配、文件打開、網絡連接等)。
  • 資源在對象銷毀時釋放:當對象離開作用域或被顯式刪除時,其析構函數會自動釋放之前獲取的資源。

優點

  1. 異常安全性:由于資源管理由構造和析構函數自動處理,即使程序中拋出了異常,也能確保資源得到正確釋放。
  2. 簡化代碼:開發者不需要手動跟蹤每個資源的狀態,并且可以在不使用顯式的try-finally塊的情況下保證資源的釋放。
  3. 防止資源泄露:只要對象被正確地創建并最終銷毀,資源就會被正確釋放。

示例

以下是一個簡單的例子,展示了如何使用RAII來管理動態分配的內存:

#include <iostream>class ResourceHandler {
private:int* data;
public:// 構造函數:資源獲取ResourceHandler() {data = new int(10); // 分配資源std::cout << "Resource acquired." << std::endl;}// 析構函數:資源釋放~ResourceHandler() {delete data; // 釋放資源std::cout << "Resource released." << std::endl;}void showData() const {std::cout << "Data: " << *data << std::endl;}
};void useResource() {ResourceHandler handler;handler.showData();// 不需要手動釋放資源,handler離開作用域時會自動調用析構函數
}int main() {useResource();return 0;
}

在這個例子中,ResourceHandler類負責管理一個整數類型的動態分配內存。構造函數在對象創建時分配資源,而析構函數在對象銷毀時釋放這些資源。這樣就確保了無論函數useResource如何退出(正常結束或因異常退出),資源都會被正確釋放。

應用場景

RAII不僅限于內存管理,還可以應用于其他資源類型,如文件句柄、網絡套接字、數據庫連接等。標準庫中的智能指針(如std::unique_ptrstd::shared_ptr)、鎖機制(如std::lock_guardstd::unique_lock)都是RAII原則的實際應用案例。通過使用這些工具,可以有效地減少資源管理錯誤,提高代碼的安全性和可靠性。

默認拷貝構造

  • 聲明形式:ClassName(const ClassName&)

  • 作用:通過已有對象初始化新對象,默認執行淺拷貝。

  • 生成條件:用戶未定義拷貝構造函數。

  • 注意:若類包含指針或動態資源,需自定義深拷貝防止重復釋放。

class Person
{
public:Person(){}Person(const Person& person){this->_age = person._age;}~Person(){}void GetAge(){std::cout << _age << std::endl;}
private:int _age;
};int main()
{Person p;p.GetAge();
}

深拷貝和淺拷貝

class Stack
{
public://初始化Stack(){_array = new int[20];}//默認生成拷貝構造//析構~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(s1);
}

這里我沒有寫實際的拷貝構造函數,這里s2調用的默認的拷貝構造,所以s2_array的地址就是s1中_array的地址,這就叫淺拷貝:
在這里插入圖片描述
這樣代碼就會有問題,因為一個地址會被析構兩次:
在這里插入圖片描述正確的方法應該是給s2的array開辟一塊新的空間:

class Stack
{
public://初始化Stack(){_array = new int[20];}Stack(const Stack& st){_array = new int[10];_size = st._size;_capacity = st._capacity;}//析構~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(s1);
}

在這里插入圖片描述這樣的拷貝我們稱為深拷貝,再次運行程序:
在這里插入圖片描述

默認拷貝賦值運算符

  • 聲明形式:ClassName& operator=(const ClassName&)

  • 作用:將已有對象的值賦給另一個對象,默認淺拷貝。

  • 生成條件:用戶未定義拷貝賦值運算符。

  • 注意:需處理自賦值問題,并在資源管理時實現深拷貝。

class Stack
{
public://初始化Stack(){_array = new int[20];}Stack(const Stack& st){_array = new int[10];_size = st._size;_capacity = st._capacity;}Stack& operator=(const Stack& st){if (this != &st){_array = new int[10];_size = st._size;_capacity = st._capacity;}return *this;}//析構~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2;s2 = s1;
}

在這里插入圖片描述

移動構造函數(C++11起)

  • 聲明形式:ClassName(ClassName&&)

  • 作用:通過右值引用“竊取”資源,避免深拷貝開銷。

  • 生成條件:用戶未定義拷貝操作、移動操作或析構函數。

  • 注意:移動后源對象應處于有效但未定義狀態(如空指針)。

class Stack
{
public://初始化Stack(){_array = new int[20];}Stack(const Stack& st){_array = new int[10];_size = st._size;_capacity = st._capacity;}Stack& operator=(const Stack& st){if (this != &st){_array = new int[10];_size = st._size;_capacity = st._capacity;}return *this;}void swap(Stack& st){std::swap(_array, st._array);std::swap(_size, st._size);std::swap(_capacity, st._capacity);}//移動構造函數Stack(Stack&& st):_array(nullptr), _size(0), _capacity(0){swap(st);}//析構~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(std::move(s1));
}

在這里插入圖片描述

默認移動賦值運算符(C++11起)

  • 聲明形式:ClassName& operator=(ClassName&&)

  • 作用:通過右值引用轉移資源所有權。

  • 生成條件:同移動構造函數。

  • 注意:需正確處理自移動賦值。

class Stack
{
public://初始化Stack(){_array = new int[20];}Stack(const Stack& st){_array = new int[10];_size = st._size;_capacity = st._capacity;}Stack& operator=(const Stack& st){if (this != &st){_array = new int[10];_size = st._size;_capacity = st._capacity;}return *this;}void swap(Stack& st){std::swap(_array, st._array);std::swap(_size, st._size);std::swap(_capacity, st._capacity);}//移動構造函數Stack(Stack&& st):_array(nullptr), _size(0), _capacity(0){swap(st);}//移動復制構造Stack& operator=(Stack&& st){swap(st);st._array = nullptr;st._size = 0;st._capacity = 0;return *this;}//析構~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2;s2 = std::move(s1);
}

在C++中,前置++和后置++運算符可以通過成員函數或非成員函數的形式進行重載。兩者的主要區別在于參數列表和返回值:

  • 前置++:增加對象的值,并返回增加后的對象引用。
  • 后置++:首先保存當前對象的狀態,然后增加對象的值,最后返回之前保存的對象的副本。

取地址及const取地址操作符重載

在C++中,取地址操作符(&)和常量取地址操作符(const &)通常不需要顯式地重載,因為編譯器提供了默認的實現,它們分別返回對象或常量對象的內存地址。然而,在某些特定情況下,你可能想要自定義這些操作符的行為。

取地址操作符重載

當你重載取地址操作符時,你通常是為了改變其默認行為,例如返回一個代理對象的地址而不是原始對象的地址。不過這種情況非常少見,大多數時候并不需要這樣做。

常量取地址操作符重載

類似地,重載常量版本的取地址操作符也是為了提供特定的行為,但同樣,這并不是常見的需求。

示例代碼

盡管不常見,這里還是給出如何重載這兩種操作符的基本示例:

#include <iostream>class MyClass {
private:int value;
public:MyClass(int val) : value(val) {}// 重載取地址操作符int* operator&() {std::cout << "非const取地址操作符被調用" << std::endl;return &value;}// 重載const取地址操作符const int* operator&() const {std::cout << "const取地址操作符被調用" << std::endl;return &value;}
};int main() {MyClass obj(10);const MyClass constObj(20);int* addr = &obj;       // 調用非const版本const int* constAddr = &constObj; // 調用const版本std::cout << "*addr: " << *addr << std::endl;std::cout << "*constAddr: " << *constAddr << std::endl;return 0;
}

輸出結果

非const取地址操作符被調用
const取地址操作符被調用
*addr: 10
*constAddr: 20

在這個例子中,我們為MyClass類重載了取地址操作符和常量取地址操作符。當通過非常量對象調用operator&()時,會調用非常量版本的操作符,并打印一條消息。而當通過常量對象調用operator&()時,則調用常量版本的操作符,并打印另一條不同的消息。

注意事項

  • 謹慎使用:一般情況下,不需要也不建議重載這兩個操作符,除非有特別的需求。這是因為它們改變了標準語義,可能會導致混淆或者不可預期的行為。
  • 保持一致性:如果你決定重載這些操作符,請確保它們的行為符合邏輯且一致,避免引入錯誤。
  • 理解限制:需要注意的是,即使你重載了取地址操作符,也無法阻止使用內置的取地址操作來獲取對象的實際地址。例如,&obj總是可以獲得obj的實際地址,除非你完全隱藏了對象的訪問方式。

總的來說,重載取地址操作符和常量取地址操作符是一種高級技巧,適用于特定場景下的特殊需求。在大多數日常編程任務中,這種重載是不必要的。

擴展:前置++和后置++重載

重載規則

  • 前置++只需要一個參數(即調用該運算符的對象本身),并且通常返回一個指向修改后的對象的引用。
  • 后置++需要兩個參數:第一個是調用該運算符的對象本身,第二個是一個int類型的占位參數,用于區分前置和后置形式。后置++返回的是操作前對象的一個副本(通常是通過值返回)。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;class Count
{
public://重載后置++Count operator++(){++_count;return *this;}//后置++Count operator++(int){Count temp = *this;++_count;return temp;}int getCount() const {return _count;}
private:int _count = 0;
};int main()
{Count myCounter;std::cout << "Initial count: " << myCounter.getCount() << std::endl;++myCounter; // 調用前置++std::cout << "After prefix increment: " << myCounter.getCount() << std::endl;Count d;d = myCounter++; // 調用后置++std::cout << "After postfix increment: " << d.getCount() << std::endl;return 0;
}

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

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

相關文章

Java 2024年面試總結(持續更新)

目錄 最近趁著金三銀四面了五六家公司吧&#xff0c;也整理了一些問題供大家參考一下&#xff08;適合經驗三年左右的&#xff09;。 面試問題&#xff08;答案是我自己總結的&#xff0c;不一定正確&#xff09;&#xff1a; 總結&#xff1a; 最近趁著金三銀四面了五六家公…

防火墻的安全策略

1.VLAN 2屬于辦公區;VLAN 3屬于生產區&#xff0c;創建時間段 [FW]ip address-set BG type object [FW-object-address-set-BG]address 192.168.1.0 mask 25 [FW]ip address-set SC type object [FW-object-address-set-SC]address 192.168.1.129 mask 25 [FW]ip address-se…

工作流項目BPMN.JS_Question梳理

工作流項目 想了解如果候選人熟悉工作流技術、bpmn.js和Flowable工作流引擎&#xff0c;面試官會對哪些信息感興趣。我需要分析這個問題&#xff0c;并給出一個全面而結構化的回答。 首先&#xff0c;用戶可能希望了解作為前端面試官&#xff0c;應該關注候選人哪些方面的知識和…

windows下搭建鴻蒙OS應用開發環境

一、前言 HUAWEI DevEco Studio 是華為推出的一款集成開發環境&#xff08;IDE&#xff09;&#xff0c;主要用于開發基于華為鴻蒙操作系統&#xff08;HarmonyOS&#xff09;的應用。作為華為開發者工具的核心之一&#xff0c;DevEco Studio 提供了一個多功能的開發平臺&…

MacBook Pro(M1芯片)Qt環境配置

MacBook Pro&#xff08;M1芯片&#xff09;Qt環境配置 1、準備 試圖寫一個跨平臺的桌面應用&#xff0c;此時想到了使用Qt&#xff0c;于是開始了搭建開發環境&#xff5e; 在M1芯片的電腦上安裝&#xff0c;使用brew工具比較方便 Apple Silicon&#xff08;ARM/M1&#xf…

Sqlserver DBCC Check 遇到Msg 3853報錯涉及sys.columns和sys.objects信息不匹配的解決方法

對數據庫CacheDBMSIntl執行DBCC checkcatalog(‘CacheDBMSIntl’)時遇到報錯如下 Msg 3853, Level 16, State 1, Line 7 Attribute (object_id1071830442) of row (object_id1071830442,column_id1) in sys.columns does not have a matching row (object_id1071830442) in sy…

VUE之組件通信(二)

1、v-model v-model的底層原理&#xff1a;是:value值和input事件的結合 $event到底是啥&#xff1f;啥時候能.target 對于原生事件&#xff0c;$event就是事件對象 &#xff0c;能.target對應自定義事件&#xff0c;$event就是觸發事件時&#xff0c;所傳遞的數據&#xff…

P2036 [COCI 2008/2009 #2] PERKET(dfs)

#include<bits/stdc.h> using namespace std;int n; int a[15],b[15]; int ansINT_MAX; // 初始化最小差值為一個很大的數&#xff0c;保證能找到最小值void dfs(int i,int s,int k){if(in){ // 當遍歷完所有元素時if(s1&&k0) return;int difabs(s-k);ans mi…

論文解讀:《基于TinyML毫米波雷達的座艙檢測、定位與分類》

摘要 本文提出了一種實時的座艙檢測、定位和分類解決方案&#xff0c;采用毫米波&#xff08;mmWave&#xff09;雷達系統芯片&#xff08;SoC&#xff09;&#xff0c;CapterahCAL60S344-AE&#xff0c;支持微型機器學習&#xff08;TinyML&#xff09;。提出了波束距離-多普勒…

ORB-SLAM2源碼學習:KeyFrame.cc④: void KeyFrame::UpdateBestCovisibles更新最佳共視

前言 在添加新連接之后就要重新對所有的共視關鍵幀和權重的那兩個列表重新進行降序排列&#xff0c;這樣非常容易知道列表的第一位就是最佳共視關鍵幀和權重。 總的來說就是只要權重發生了變化就要調用這個函數來修改mvpOrderedConnectedKeyFrames共視關鍵幀和mvOrderedWeigh…

尚硅谷課程【筆記】——大數據之Shell【一】

課程視頻&#xff1a;【【尚硅谷】Shell腳本從入門到實戰】 一、Shell概述 為什么要學習Shell&#xff1f; 1&#xff09;需要看懂運維人員的Shell程序 2&#xff09;偶爾編寫一些簡單的Shell程序來管理集群、提高開發效率 什么是Shell&#xff1f; 1&#xff09;Shell是一…

ES6 對象擴展:對象簡寫,對象屬性 表達式,擴展運算符 ...,Object.assign,Object.is,用法和應用場景

1. 對象屬性簡寫 1.1 基本語法 // 傳統寫法 const name John; const age 25; const user {name: name,age: age };// ES6 簡寫語法 const user {name,age };1.2 實際應用場景 // 1. 函數返回對象 function createUser(name, age, email) {return {name,age,email}; }// …

【2025】camunda API接口介紹以及REST接口使用(3)

前言 在前面的兩篇文章我們介紹了Camunda的web端和camunda-modeler的使用。這篇文章主要介紹camunda結合springboot進行使用&#xff0c;以及相關api介紹。 該專欄主要為介紹camunda的學習和使用 &#x1f345;【2024】Camunda常用功能基本詳細介紹和使用-下&#xff08;1&…

Java進階學習之路

Java進階之路 提示&#xff1a;這里可以添加系列文章的所有文章的目錄&#xff0c;目錄需要自己手動添加 提示&#xff1a;寫完文章后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 目錄 Java進階之路前言一、Java入門 Java基礎 1、Java概述 1.1 什…

JAVA安全—反射機制攻擊鏈類對象成員變量方法構造方法

前言 還是JAVA安全&#xff0c;哎&#xff0c;真的講不完&#xff0c;太多啦。 今天主要是講一下JAVA中的反射機制&#xff0c;因為反序列化的利用基本都是要用到這個反射機制&#xff0c;還有一些攻擊鏈條的構造&#xff0c;也會用到&#xff0c;所以就講一下。 什么是反射…

TfidfVectorizer

TF-IDF / Term Frequency - Inverse Document Frequency 作用&#xff1a;是自然語言處理NLP中常用的文本特征提取工具&#xff0c;用于將文本數據轉換為數據向量。 核心思想&#xff1a;是通過統計詞頻和逆文檔頻率來量化詞語在文本中的重要性。 T F ? I D F ( t , d ) T F…

DeepSeek-R1 論文解讀:強化學習如何 “煉” 出超強推理模型?

深度解析DeepSeek-R1&#xff1a;強化學習驅動大語言模型推理能力新突破 論文鏈接&#xff1a;DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning 在大語言模型&#xff08;LLMs&#xff09;飛速發展的當下&#xff0c;提升模型推理能力成…

【數據結構】循環鏈表

循環鏈表 單鏈表局限性單向循環鏈表判斷鏈表是否有環思路code 找到鏈表入口思路代碼結構與邏輯 code 單鏈表局限性 單鏈表作為一種基本的數據結構&#xff0c;雖然在很多場景下都非常有用&#xff0c;但它也存在一些局限性&#xff1a; 單向訪問&#xff1a;由于每個節點僅包含…

ip屬地是手機號還是手機位置?一文理清

在數字化和網絡化的今天&#xff0c;IP屬地這一概念逐漸成為了人們關注的焦點。特別是在社交媒體和在線平臺上&#xff0c;IP屬地的顯示往往讓人聯想到用戶的地理位置。然而&#xff0c;關于IP屬地到底與手機號還是手機位置有關&#xff0c;卻存在著不少誤解和混淆。本文將深入…

【嵌入】基于nomic-embed-text-v1.5和HuggingFaceEmbeddings實現

測試代碼 model_name = /media/zhangbin/DATA/DataCache/nomic-ai/nomic-embed-text-v1.5import osos.environ[HF_HOME] = /media/zhangbin/DATA/DataCache/#os.environ["TRANSFORMERS_CACHE"] = "/media/zhangbin/DATA/DataCache/" # 確保目錄結構正確 if…