C++的new / delete 與 C語言的malloc/realloc/calloc / free 的講解

?????????在C語言中我們通常會使用malloc/realloc/calloc來動態開辟的空間,malloc是只會開辟你提供的空間大小,并不會初始化內容;calloc不但會開辟空間,還會初始化;realloc是專門來擴容的,當你第一次開辟的空間不夠用的時候,就要使用realloc;如果你第一次使用realloc的時候,前面沒有開辟過空間,那么realloc的行為會跟malloc一樣,之后再發揮realloc自己的行為。而我們的C++是面向對象的編程,當開辟空間失敗了malloc只會返回一個空指針,我們還需要自己來判斷。所以在C++這里就將malloc升級成為了new,new在開辟空間失敗的時候會拋出異常,這跟我們面向對象的理念是一致的。

? ? ? ? 其實我在C語言階段學習動態開辟空間的時候,是有點迷糊的,就是不知道如何去開辟,給誰開辟?現在想想其實不難理解,假設我們要開辟10個int類型的空間大小,我們有兩種辦法,一種就是直接定義一個int類型的數組:int arr[10],但是這樣的空間是定長的空間,我們無法對定長數組進行擴容。第二種就是用到動態開辟的空間了,我們肯定是先要malloc10個int類型的空間,但是我們應該如何取到這段空間呢?用指針變量接收是吧,但是為什么是指針變量呢?我們繼續講解,因為我們先開辟了一段空間,正常情況下是無法取到這段空間的,只有拿到這段空間的地址,才可以訪問這段空間。這也就是為什么我們會用指針變量來接收malloc開辟的空間了。

????????下面是malloc的函數聲明:通過malloc的返回值也不難看出要用指針接收。所以我們要開辟空間,一種就是直接定義類型,比如int a、int arr[10]、char ch等等,一種就是用malloc/realloc/calloc開辟空間,用指針接收。

一、介紹

1. malloc、realloc、calloc的介紹

1. malloc只是動態開辟空間,并沒有初始化這段空間的內容;

2. realloc是用來擴容的,如果沒有預先開辟空間,直接使用realloc,realloc的作用相當于malloc;

3. calloc相當于是malloc的加強版,不僅可以開辟空間,也順便初始化我們的內容;

4. 面對內置類型可以直接開辟空間,但是對于自定義類型的話,比如我們的棧,則無法一步到位,因為棧里面還有數組空間的開辟,需要兩層開辟。

????????下面就是棧的舉例:因為我們僅僅是用的malloc來開辟一個棧的空間,所以棧內部的數組空間還需要開辟,如果我們要是寫一個構造函數去初始化,在主函數中是無法調用的,也就完成不了對數組空間的開辟,不過在了解定位new之后是可以調用的,我們這里使用寫一個成員函數Init來完成對數組空間的開辟,這樣我們在主函數中也可以調用。然后其實這樣的方法有點麻煩,而且在malloc失敗的時候只是返回空指針,判斷條件也需要我們自己去寫,這很不滿足面向對象的要求,所以C++基于這樣的原因創造了new這個操作符。

#include <iostream>
using namespace std;
class Stack
{
public:void Init(int capacity){_a = (int*)malloc(sizeof(int) * capacity);if(_a == nullptr){perror("malloc fail");exit(-1);}_size = 0;_capacity = capacity;}
private:int *_a;int _size;int _capacity;
};
int main()
{Stack *st = (Stack*)malloc(sizeof(Stack));st->Init(4);return 0;
}

2. free的介紹

????????free專門釋放動態開辟的空間,如果釋放的不是動態開辟的空間,就會報錯,所以這里一定要多加注意!!!

????????一般就是free( ),括號里面就是指向那段空間的指針就行。

3. new、operator new的介紹(內置類型和自定義類型)

????????new是在C++中才有的操作符,因為C++兼容C語言,所以在C++中可以寫C,但是在C中寫不了C++。new也是用來動態開辟空間的,也可以初始化,即可以開辟內置類型的空間,也可以開辟自定義類型的空間,這兩種類型都可以初始化;

1. new對內置類型開辟空間:直接開辟

int *a = new int;      //單純開辟空間
int *b = new int(3);   //開辟空間并初始化int *arr1 = new int[5]; //單純開辟5個整型的空間
int *arr2 = new int[5]{1, 2, 3, 4, 5}; //開辟5個整型空間并初始化

2. new對自定義類型開辟空間:第一步:開辟這個自定義類型需要的空間;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 第二步:調用這個自定義類型的構造函數;

所以我們上面對于棧的開辟,可以改善為下面這樣:

#include <iostream>
using namespace std;
class Stack
{
public:Stack(int capacity = 4, int size = 0, int num = 0): _a(new int[capacity]{0}), _size(size), _capacity(capacity){cout << "Stack(int capacity = 4)" << endl; //方便觀察確實調用了構造函數}private:int *_a;int _size;int _capacity;
};
int main()
{Stack *st = new Stack;//Stack *st = new Stack(4, 0, 0);這種就是對于顯式構造函數的寫法,或者是你想傳入的初始化內容return 0;
}

3. 那operator new 又是什么呢?

????????相信大家對這個并不熟悉,所以這部分知識了解即可,operator new 是一種全局函數,對malloc進行了封裝,也就是讓malloc函數更加面向對象,而不是面向過程了。因為我們在C語言的時候,malloc開辟空間失敗,會返回個空指針,我們會根據空指針去找過程中的錯誤,然后C++是面向對象編程,所以就必須知道是哪個對象出錯了,new出錯就報new。所以operator new就是對malloc進行了一個包裝,底層還是通過malloc實現,只不過在出錯的時候,operator new是拋異常,不是返回空指針了。下面就是operator new這個函數的定義:

所以operator new 還是通過malloc實現的,看不懂下面的也沒關系,記住operator new 開辟空間失敗拋異常,malloc失敗是返回NULL;

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytesvoid *p;while ((p = malloc(size)) == 0){if (_callnewh(size) == 0){// report no memory// 如果申請內存失敗了,這里會拋出bad_alloc 類型異常static const std::bad_alloc nomem;_RAISE(nomem);}}return (p);
}

4. delete、operator delete的介紹

delete是用來釋放動態開辟的空間,如果釋放的不是動態開辟的空間會報錯;

他跟free的區別是在自定義類型這里,free僅僅釋放他括號里指向的空間;

delete在釋放自定義類型的空間時候,會做兩件事:

1. 先調用自定義類型的析構函數;

2. 再去調用operator delete函數;

    int *a = new int;char *ch = new char;double *d = new double[5];delete a;delete ch;delete []d;

而operator delete 我們會在new、delete針對數組這里拿出來講解

二、new、delete的原理

1. new對于自定義類型:

1. new 會先調用 operator new 開自定義類型的空間;

2. 在申請的空間上,再去調用自定義類型的構造函數?;

這里解答一下為什么new步調用malloc,而是調用的operator new

因為還是C++是面向對象編程,所以我們不使用malloc這套判斷錯誤的方式,而是選擇operator new 這個函數去封裝一下new,再讓new來調用自己,一切都是為了面向對象,所以new的產生原因也是這樣的,因為面向對象。

2. delete對于自定義類型

1. 在空間上執行析構函數,完成對象中資源的清理工作
2. 調用operator delete函數釋放對象的空間

需要注意的就是為什么delete要先調用析構函數

因為我們要知道,再對于棧這樣的數據結構,他里面數組的空間是被棧空間的指針指著的,如果先釋放棧的空間,就相當于把數組的地址釋放了,然后再去調用析構函數就會找不到要析構的位置了,所以就會出錯;

基于這樣的原因一定要先析構,再釋放自定義類型的空間;

3.?new、delete對于數組、自定義類型的數組(重點,難點)

????????這里其實又涉及到一些不一樣的知識,我們知道,在new一個數組的時候,我們是告訴他開辟多少個元素的空間的,如下圖,我們是知道開5個空間的大小的,那在釋放的時候,delete又是如何知道需要釋放多大的空間的呢?我們保持這個問題,繼續往下看。

#include <iostream>
using namespace std;
class Stack
{
public:Stack(int capacity = 4, int size = 0, int num = 0): _a(new int[capacity]{0}), _size(size), _capacity(capacity){cout << "Stack(int capacity = 4)" << endl; //方便觀察確實調用了構造函數}private:int *_a;int _size;int _capacity;
};
int main()
{Stack *st = new Stack[5];delete []st;return 0;
}

其實,是因為在開辟空間之前,我們多開辟了一個int的空間,這個空間就是用來存放數組的個數的,相當于是圖中這樣開辟的空間:

????????所以delete面對數組這樣的空間,他先通過st指針指向的位置,先調用多次析構函數,連續釋放完5個_a指針指向的空間之后,返回到a位置處,再用通過調用operator delete釋放自定義類型數組的這段空間。

????????換句話說delete 的[ ] 是識別這個類型是數組,然后知道數組會多開辟4個字節的空間,進而從多開辟的空間的地址處釋放。而單獨的delete就不會認為這個類型是數組類型,也就不會尋找這段空間,直接在數組首元素這里釋放空間,當然一定要注意自定義類型,還需要調用析構函數。

new --> 內置類型數組

new --> operator new[ ]?函數? --> operator new 函數 --> malloc??

這里要知道的是,上面都是調用了一次,因為開辟的空間是提前算好的,開辟一次就行

delete --> 內置類型數組

delete --> operator delete[ ] 函數? --> operator delete?函數 --> free??

new --> 自定義類型數組

1.?new --> operator new[ ] 函數? --> operator new 函數 --> malloc??

2. 每一個自定義類型再調用構造函數

開辟的空間都是一次性開辟好的,所以只需要new一次,而每個自定義類型都需要調用構造函數,所以是多個。

delete --> 自定義類型數組

1. 每一個自定義類型先調用自己的析構函數

2. delete?--> operator delete[ ] 函數? --> operator delete?函數 --> free

三、面試題:new/delete 和?malloc/free 的 異同?

共同點:
都是從堆上申請空間,并且需要用戶手動釋放。
不同點:
1. malloc和free是函數,new和delete是操作符;
2. malloc申請的空間不會初始化,new可以初始化;
3. malloc申請空間時,需要手動計算空間大小并傳遞,new只需在其后跟上空間的類型即可, 如果是多個對象,[ ]中指定對象個數即可;
4. malloc的返回值為void*, 在使用時必須強轉,new不需要,因為new后跟的是空間的類型
5. malloc申請空間失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new需
要捕獲異常;
6. 申請自定義類型對象時,malloc/free只會開辟空間,不會調用構造函數與析構函數,而new在申請空間后會調用構造函數完成對象的初始化,delete在釋放空間前會調用析構函數完成空間中資源的清理;

四、練習題

1. C++中,類ClassA的構造函數和析構函數的執行次數分別為( )

ClassA *p = new ClassA[5];delete p;

A.5,1

B.1,1

C.5,5

D.程序可能崩潰

答案及解析:D

大家可以參考下面這個圖:

????????delete p這就相當于先調用b位置的析構函數,然后直接在b位置釋放了。因為是delete p;并不是delete [ ]p;所以delete并不知道說這是一個數組,并不會跳到最前面來釋放,釋放的位置不對,導致了程序的崩潰;

new和delete一定要匹配,避免未定義行為;

new —— delete

new [ ] —— delete[ ]

malloc —— free

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

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

相關文章

目標檢測YOLO實戰應用案例100講-基于YOLO的小目標檢測改進算法(續)

目錄 3.3基于混合注意力的多尺度特征融合改進方法 3.3.1整體網絡架構 3.3.2特征金字塔的構建

Vue 2.0源碼分析-實例掛載的實現

Vue 中我們是通過 $mount 實例方法去掛載 vm 的&#xff0c;$mount 方法在多個文件中都有定義&#xff0c;如 src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js、src/platform/weex/runtime/index.js。因為 $mount 這個方法的實現是和平臺…

Python 使用tkinter復刻Windows記事本UI和菜單功能(三)

上一篇&#xff1a;Python 使用tkinter復刻Windows記事本UI和菜單功能&#xff08;二&#xff09;-CSDN博客 下一篇&#xff1a;敬請耐心等待&#xff0c;如發現BUG以及建議&#xff0c;請在評論區發表&#xff0c;謝謝&#xff01; 本文章完成了記事本的新建、保存、另存、打…

【技巧】前端開發技巧 增加前端的請求緩存 提高開發效率

定義變量 /*** 開發緩存 開關* 說明* 方便開發使用 提升開發效率* true 打開緩存* false 關閉緩存 這里上線的時候必須改為* type {boolean}*/ const cacheFlag true/*** 排除某個url 方便開發時的數據實時生效* 這里根據開發到哪個功能 實時變更&#xff0c; 比如開…

京東數據分析(京東大數據):2023年10月京東手機行業品牌銷售排行榜

鯨參謀監測的京東平臺10月份手機市場銷售數據已出爐&#xff01; 根據鯨參謀平臺的數據顯示&#xff0c;今年10月份&#xff0c;京東平臺手機行業的銷量約340萬&#xff0c;環比增長約11%&#xff0c;同比則下滑約2%&#xff1b;銷售額為108億&#xff0c;環比增長約17%&#x…

請你說一下Vue中v-if和v-for的優先級誰更高

v-if 與 v-for簡介 v-ifv-forv-if & v-for使用 v-if 與 v-for優先級比較 vue2 中&#xff0c;v-for的優先級高于v-if 例子進行分析 vue3 v-if 具有比 v-for 更高的優先級 例子進行分析 總結 在vue2中&#xff0c;v-for的優先級高于v-if在vue3中&#xff0c;v-if的優先級高…

RubyMine 2023:提升Rails/Ruby開發效率的強大利器

在Rails/Ruby開發領域&#xff0c;JetBrains RubyMine一直以其強大的功能和優秀的性能而備受開發者的青睞。現如今&#xff0c;我們迎來了全新的RubyMine 2023版本&#xff0c;它將為開發者們帶來更高效的開發體驗和無可比擬的工具支持。 首先&#xff0c;RubyMine 2023提供了…

Java-使用poi-tl根據word模板動態生成word

作者wangsz&#xff0c;想寫一些關于word的工具&#xff0c;所以就寫了這篇文章 1.首先&#xff0c;先導入所需要的依賴&#xff08;poi相關依賴即可&#xff09; <!-- POI --><dependency><groupId>org.apache.poi</groupId><artifactId>poi&l…

【libGDX】使用Mesh繪制立方體

1 前言 本文主要介紹使用 Mesh 繪制立方體&#xff0c;讀者如果對 Mesh 不太熟悉&#xff0c;請回顧以下內容&#xff1a; 使用Mesh繪制三角形使用Mesh繪制矩形使用Mesh繪制圓形 在繪制立方體的過程中&#xff0c;主要用到了 MVP &#xff08;Model View Projection&#xff0…

目標檢測YOLO系列從入門到精通技術詳解100篇-【目標檢測】計算機視覺(最終篇)

目錄 知識儲備 KITTI數據集 1.KITTI數據集概述 2.數據采集平臺 3.Dataset詳述 算法原理

GIT無效的源路徑/URL

ssh-add /Users/haijunyan/.ssh/id_rsa ssh-add -K /Users/haijunyan/.ssh/id_rsa

windows11上enable WSL

Windows電腦上要配置linux&#xff08;這里指ubuntu&#xff09;開發環境&#xff0c;主要有三種方式&#xff1a; 1&#xff09;在windows上裝個虛擬機&#xff08;比如vmware&#xff09;。缺點是vmware加載ubuntu后系統會變慢很多&#xff0c;而且需要通過samba來實現window…

git clone -mirror 和 git clone 的區別

目錄 前言兩則區別git clone --mirrorgit clone 獲取到的文件有什么不同瘦身倉庫如何選擇結語開源項目 前言 Git是一款強大的版本控制系統&#xff0c;通過Git可以方便地管理代碼的版本和協作開發。在使用Git時&#xff0c;常見的操作之一就是通過git clone命令將遠程倉庫克隆…

【vue2】axios請求與axios攔截器的使用詳解

&#x1f973;博 主&#xff1a;初映CY的前說(前端領域) &#x1f31e;個人信條&#xff1a;想要變成得到&#xff0c;中間還有做到&#xff01; &#x1f918;本文核心&#xff1a;當我們在路由跳轉前與后我們可實現觸發的操作 【前言】ajax是一種在javaScript代碼中發請…

低代碼開發與IT開發的區別

目錄 一、含義不同 二、開發門檻不同 三、兩者之間的區別 1、從技術特征來看 2、從目標開發者來看 四、低代碼平臺使用感受&#xff1f; &#xff08;1&#xff09;自定義模塊&#xff0c;滿足不同的業務需求 &#xff08;2&#xff09;工作流引擎&#xff0c;簡化復雜流程的管…

機器學習實戰-第4章 基于概率論的分類方法: 樸素貝葉斯

樸素貝葉斯 概述 貝葉斯分類是一類分類算法的總稱,這類算法均以貝葉斯定理為基礎,故統稱為貝葉斯分類。本章首先介紹貝葉斯分類算法的基礎——貝葉斯定理。最后,我們通過實例來討論貝葉斯分類的中最簡單的一種: 樸素貝葉斯分類。 貝葉斯理論 & 條件概率 貝葉斯理論 …

linux網絡之網絡層與數據鏈路層

文章目錄 一、網絡層 1.IP協議 2.IP協議頭格式 3.網段劃分 4.特殊ip地址 5.IP地址的數量限制 6.私有ip和公網IP 7.路由 二、數據鏈路層 1.以太網 2.以太網幀格式 3.MAC地址 4.對比理解MAC地址和IP地址 5.MTU 6.ARP協議 ARP協議的工作流程 ARP數據報的格式 7.DNS 8.ICMP協議 9.N…

839 - Not so Mobile (UVA)

題目鏈接如下&#xff1a; Online Judge 這道題劉汝佳的解法極其簡潔&#xff0c;用了20來行就解決了問題。膜拜…… 他的解法如下&#xff1a;天平&#xff08;UVa839紫書p157&#xff09;_天平 uva 839_falldeep的博客-CSDN博客 我寫了兩個&#xff08;都很冗長&#xff…

淺談電氣設備的絕緣在線監測與狀態維修探究

賈麗麗 安科瑞電氣股份有限公司 上海嘉定 201801 摘要&#xff1a;在線監測是控制好電氣設備絕緣的重要方式&#xff0c;為電力系統穩定奠定重要基礎。在線監測電氣設備時&#xff0c;要利用檢測技術促進電力系統運行效率提升&#xff0c;讓電氣設備在具體工作過程中發揮更大作…

升級jdk17過程中,原來的jdk8下的webservice客戶端怎樣處理

背景&#xff1a;之前jdk8環境下&#xff0c;使用的cxf框架&#xff0c;而且是動態加載解析作為客戶端。大家一直相處的很愉快。但是最近升級jdk17&#xff0c;發現cxf不好用了。網上百度&#xff0c;大部分都是說升級cxf版本&#xff0c;并且添加jaxb的相關依賴就可以了。但是…