詳解C++中動(dòng)態(tài)內(nèi)存管理和泛型編程
一、C/C++內(nèi)存區(qū)域劃分
1. 棧又叫堆棧--非靜態(tài)局部變量/函數(shù)參數(shù)/返回值等等,棧是向下增長(zhǎng)的。
2. 內(nèi)存映射段是高效的I/O映射方式,用于裝載一個(gè)共享的動(dòng)態(tài)內(nèi)存庫(kù)。用戶可使用系統(tǒng)接口創(chuàng)建共享共享內(nèi)存,做進(jìn)程間通信。
3. 堆用于程序運(yùn)行時(shí)動(dòng)態(tài)內(nèi)存分配,堆是可以上增長(zhǎng)的。
4. 數(shù)據(jù)段--存儲(chǔ)全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù)。
5. 代碼段--可執(zhí)行的代碼/只讀常量。
二、常見(jiàn)變量存儲(chǔ)區(qū)域
int globalVar = 1;//全局變量中在靜態(tài)區(qū) static int staticGlobalVar = 1;//靜態(tài)區(qū) void Test() { static int staticVar = 1;//靜態(tài)區(qū) int localVar = 1;//棧區(qū) int num1[10] = { 1, 2, 3, 4 };//棧區(qū) char char2[] = "abcd";//棧區(qū),*char2在棧區(qū) const char* pChar3 = "abcd";//指針在棧區(qū),*pchar3在常量區(qū) int* ptr1 = (int*)malloc(sizeof(int) * 4);//指針在棧區(qū),*ptr1在堆區(qū) int* ptr2 = (int*)calloc(4, sizeof(int));///棧區(qū) int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);//棧區(qū) free(ptr1); free(ptr3); }
三、new和delete
1、new和delete的使用方式
int main() { int* p1 = new int;//在堆區(qū)申請(qǐng)一個(gè)int大小的空間,不會(huì)初始化 int* p2 = new int(0);//申請(qǐng)并初始化為0 delete p1; delete p2; int* p3 = new int[10];//在堆區(qū)申請(qǐng)一塊10個(gè)int大小的空間,未初始化 int* p4 = new int[10]{ 1,2,3,4 };//初始化為{1,2,3,4,0,0,0,0,0,0} delete[] p3; delete[] p4; return 0; }
注意:申請(qǐng)和釋放單個(gè)元素的空間,使用new和delete操作符,申請(qǐng)和釋放連續(xù)的空間,使用new[]和delete[],一定要匹配起來(lái)使用。
2、new、delete和malloc、free的區(qū)別
1、對(duì)于內(nèi)置類型,沒(méi)有區(qū)別。
2、new和delete是C++的關(guān)鍵字/操作符,而malloc和free是C語(yǔ)言的庫(kù)函數(shù)。
3、對(duì)于自定義類型,相比于malloc和free,new和delete會(huì)額外調(diào)用類中的構(gòu)造函數(shù)和析構(gòu)函數(shù)。
4、malloc的返回值是void*,使用時(shí)需要強(qiáng)轉(zhuǎn),new后邊跟的是空間的類型,所以new不需要強(qiáng)轉(zhuǎn)。
5、malloc失敗返回空指針,需要判空;new失敗拋異常,需要捕獲異常。
3、new的原理
new等于operator new()+構(gòu)造函數(shù)。operator new()不是new運(yùn)算符的重載,因?yàn)閰?shù)沒(méi)有自定義類型。它是一個(gè)庫(kù)里的全局函數(shù)。
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { // try to allocate size bytes void *p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { // report no memory // 如果申請(qǐng)內(nèi)存失敗了,這里會(huì)拋出bad_alloc 類型異常 static const std::bad_alloc nomem; _RAISE(nomem); } return (p); }
從底層代碼可以看出operator new()是對(duì)malloc的封裝,如果malloc失敗,將會(huì)拋出異常。
4、delete的原理
delete等于operator delete()+析構(gòu)函數(shù)
//operator delete: 該函數(shù)最終是通過(guò)free來(lái)釋放空間的 void operator delete(void *pUserData) { _CrtMemBlockHeader * pHead; RTCCALLBACK(_RTC_Free_hook, (pUserData, 0)); if (pUserData == NULL) return; _mlock(_HEAP_LOCK); /* block other threads */ __TRY /* get a pointer to memory block header */ pHead = pHdr(pUserData); /* verify block type */ _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)); _free_dbg( pUserData, pHead->nBlockUse );//調(diào)用free() __FINALLY _munlock(_HEAP_LOCK); /* release other threads */ __END_TRY_FINALLY return; } //free的實(shí)現(xiàn) #define free(p) _free_dbg(p, _NORMAL_BLOCK)
從底層代碼可以看出operator delete()調(diào)用了free。
所以針對(duì)內(nèi)置類型或無(wú)資源的類對(duì)象delete時(shí),使用delete和free效果相同。但對(duì)于有資源需要釋放的對(duì)象時(shí),直接使用free雖然釋放了對(duì)象的空間,但對(duì)象內(nèi)部的資源還未被清理,導(dǎo)致內(nèi)存泄漏!這種情況必須使用delete。
5、new T[N]原理
1、new T[N]調(diào)用operator new[]
2、operator new[]調(diào)用operator new完成N個(gè)對(duì)象空間的開(kāi)辟。
3、調(diào)用N次構(gòu)造函數(shù)完成N個(gè)對(duì)象的初始化。
6、delete[]原理
1、調(diào)用N次析構(gòu)函數(shù)完成N個(gè)對(duì)象資源的清理工作。
2、調(diào)用operator delete[]
3、operator delete[]調(diào)用operator delete完成整段空間的釋放。
四、定位new
1、定位new的概念
對(duì)于一個(gè)類,我們可以顯式的去調(diào)用類的析構(gòu)函數(shù),但是不能顯式調(diào)用構(gòu)造函數(shù),那么使用定位new,就可以顯式調(diào)用類的構(gòu)造函數(shù),對(duì)一塊空間重新初始化。
2、定位new的使用格式
new (指針)類名或者new (指針) type(初始化列表)
int main() { Date d1; new(&d1)Date;//new (指針)類名 Date* p = new Date[4]{ {2022,10,15},{2023,11,8} }; new(p)Date[4];//new (指針) type(初始化列表) delete[] p; return 0; }
上述代碼一共調(diào)用了10次構(gòu)造函數(shù),經(jīng)過(guò)定位new的處理,d1和p所代表的空間已經(jīng)被重新初始化了。
3、定位new的使用場(chǎng)景
一般不會(huì)像上邊代碼一樣,對(duì)一塊已有對(duì)象數(shù)據(jù)的空間重新初始化。定位new表達(dá)式在實(shí)際中一般是配合內(nèi)存池使用。因?yàn)閮?nèi)存池分配出的內(nèi)存沒(méi)有初始化,對(duì)于自定義類型的對(duì)象,可以使用定位new對(duì)這些沒(méi)有被初始化的內(nèi)存顯式調(diào)用類的構(gòu)造函數(shù)初始化。
五、泛型編程
泛型編程:編寫與類型無(wú)關(guān)的通用代碼,是代碼復(fù)用的一種手段。模板是泛型編程的基礎(chǔ)。
模板分為函數(shù)模板和類模板
六、函數(shù)模板
1、函數(shù)模板的使用
template<typename T> void Swap(T& a, T& b) { T tmp = a; a = b; b = tmp; } int main() { int a = 10, b = 5; double m = 2.3, n = 4.9; Swap(a, b); Swap(m, n); return 0; }
兩個(gè)Swap調(diào)用的不是模板,而是模板生成的實(shí)例化函數(shù),像上述代碼中,模板會(huì)生成int和double類型的兩種實(shí)例化函數(shù)。
2、不同類型形參傳參時(shí)的處理
2.1傳參時(shí)強(qiáng)轉(zhuǎn)(對(duì)應(yīng)形參需要const修飾)
template<typename T> T Add(const T& a,const T& b)//const接收常性實(shí)參 { return a + b; } int main() { int a = 10, b = 5; double m = 2.3, n = 4.9; Add(a, (int)m);//強(qiáng)轉(zhuǎn),臨時(shí)變量傳參,具有常性 return 0; }
使用強(qiáng)制類型轉(zhuǎn)換在推演的時(shí)候?qū)⑿螀⑥D(zhuǎn)換成同一類型。
2.2顯式實(shí)例化(傳參時(shí)隱式類型轉(zhuǎn),對(duì)應(yīng)形參需要const修飾)
template<typename T> T Add(const T& a, const T& b)//需要使用const接收 { return a + b; } int main() { int a = 10, b = 5; double m = 2.3, n = 4.9; Add<int>(a, m);//顯式實(shí)例化,m發(fā)生隱式類型轉(zhuǎn)換 return 0; }
顯式實(shí)例化編譯器不再去推演T的類型,而是直接使用尖括號(hào)內(nèi)的類型實(shí)例化對(duì)應(yīng)函數(shù)。
2.3使用多個(gè)模板
template<typename T1,class T2>//可以寫typename也可以寫class T1 Add(const T1& a, const T2& b) { return a + b; } int main() { int a = 10, b = 5; double m = 2.3, n = 4.9; Add(a, m);//Add<int,double>(a,m);多個(gè)模板的手動(dòng)推演 return 0; }
3、模板和實(shí)例可以同時(shí)存在,編譯器會(huì)優(yōu)先調(diào)用實(shí)例
template<typename T>//可以寫typename也可以寫class T Add(const T& a, const T& b) { return a + b; } int Add(const int& a, const int& b) { return a + b; } int main() { int a = 10, b = 5; double m = 2.3, n = 4.9; Add(a, m);//調(diào)用已有實(shí)例 Add<int>(a, m);//調(diào)用模板生成的實(shí)例 return 0; }
1、模板和普通函數(shù)的函數(shù)名修飾規(guī)則是不一樣的。
2、模板和實(shí)例可以同時(shí)存在,編譯器會(huì)優(yōu)先調(diào)用實(shí)例。如果想使用模板生成的實(shí)例,必須使用尖括號(hào)指定類型。
3、如果模板可以生成更加匹配的版本,編譯器將會(huì)生成這個(gè)匹配版本而不是使用那個(gè)已有但不太匹配的實(shí)例。
六、類模板
1、對(duì)象定義時(shí)需要顯式實(shí)例化
int main() { Stack<double> st1; // double st1.Push(1.1); Stack<int> st2; // int st2.Push(1); return 0; }
函數(shù)模板可以通過(guò)傳參確定T的類型,但是類模板編譯器無(wú)法推演,必須要在對(duì)象定義時(shí)顯式實(shí)例化類型。
模板參數(shù)不同,他們就是不同的類型。st1和st2屬于不同的類定義出的兩個(gè)對(duì)象。所以不能有st1=st2,因?yàn)樗麄儾皇峭粋€(gè)類,除非針對(duì)這種賦值,自己寫一個(gè)賦值重載。
2、為什么stl被稱為模板
類模板和函數(shù)模板不同,需要在實(shí)例化的時(shí)候在類名后加上<類型>。
類模板不是真正的類,而實(shí)例化出來(lái)的才是真正的類。
// Vector是類模板,Vector<int>才是類型 Vector<int> s1; Vector<double> s2;
以上就是詳解C++中動(dòng)態(tài)內(nèi)存管理和泛型編程的詳細(xì)內(nèi)容,更多關(guān)于C++動(dòng)態(tài)內(nèi)存管理 泛型編程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- C++ 多態(tài)性虛函數(shù)和動(dòng)態(tài)綁定學(xué)習(xí)筆記
- 關(guān)于C++虛函數(shù)與靜態(tài)、動(dòng)態(tài)綁定的問(wèn)題
- C++實(shí)現(xiàn)動(dòng)態(tài)綁定代碼分享
- 深入理解C++的動(dòng)態(tài)綁定與靜態(tài)綁定的應(yīng)用詳解
- 詳解C++動(dòng)態(tài)內(nèi)存管理
- C++內(nèi)存管理面經(jīng)
- C++圖文并茂分析講解內(nèi)存管理
- 一文詳解C++中動(dòng)態(tài)內(nèi)存管理
- C語(yǔ)言與C++中內(nèi)存管理詳解
- 一起來(lái)學(xué)習(xí)C++的動(dòng)態(tài)內(nèi)存管理
- C++中動(dòng)態(tài)綁定和內(nèi)存管理的實(shí)現(xiàn)
相關(guān)文章
C++動(dòng)態(tài)規(guī)劃算法實(shí)現(xiàn)矩陣鏈乘法
動(dòng)態(tài)規(guī)劃算法通常用于求解具有某種最優(yōu)性質(zhì)的問(wèn)題。在這類問(wèn)題中,可能會(huì)有許多可行解。每一個(gè)解都對(duì)應(yīng)于一個(gè)值,我們希望找到具有最優(yōu)值的解2022-06-06基于C++ bitset常用函數(shù)及運(yùn)算符(詳解)
下面小編就為大家?guī)?lái)一篇基于C++ bitset常用函數(shù)及運(yùn)算符(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11C語(yǔ)言sizeof和strlen區(qū)別小結(jié)
C語(yǔ)言中的sizeof和strlen是兩個(gè)常用的操作符/函數(shù),但它們的功能和用途有很大的區(qū)別,本文就詳細(xì)的來(lái)介紹一下C語(yǔ)言sizeof和strlen區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01C語(yǔ)言手撕一個(gè)Hash表(HashTable)實(shí)例代碼
哈希表(HashTable)是一種非常重要的數(shù)據(jù)結(jié)構(gòu),它可以在常量時(shí)間內(nèi)進(jìn)行插入、查找和刪除操作,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言手撕一個(gè)Hash表(HashTable)的相關(guān)資料,需要的朋友可以參考下2023-03-03C語(yǔ)言靜態(tài)版通訊錄的設(shè)計(jì)與實(shí)現(xiàn)
靜態(tài)版通訊錄是一種簡(jiǎn)單的通訊錄實(shí)現(xiàn)方式,通過(guò)定義固定的數(shù)組大小來(lái)存儲(chǔ)聯(lián)系人信息。該方法不支持動(dòng)態(tài)增刪聯(lián)系人,但具有實(shí)現(xiàn)簡(jiǎn)單、易于理解的優(yōu)點(diǎn)。在程序設(shè)計(jì)中,需注意數(shù)組邊界溢出等問(wèn)題2023-04-04Clion(CMake工具)中引入第三方庫(kù)的詳細(xì)方法
這篇文章主要介紹了Clion(CMake工具)中引入第三方庫(kù)的詳細(xì)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02