內存管理:
在C語言中,動態開辟空間可以用malloc,calloc,realloc這三個函數,下面先來復習一下這三者的區別
malloc和calloc都是用來開辟新空間,calloc在malloc的基礎上還會初始化該空間為0,用法也不同
? ?malloc(sizeof(int)*n)? ? ? ?calloc(sizeof(int),n)? ?
realloc時用來給已經動態開辟好的空間擴容的具體用法示例如下
int* newp = (int*)realloc(p,sizeof(int)*n)
C語言內存管理方式在C++中可以繼續使用,但有些地方就無能為力而且使用起來比較麻煩,因此C++又提出了自己的內存管理方式:通過new和delete操作符進行動態內存管理。
void Test()
{// 動態申請一個int類型的空間int* ptr4 = new int;// 動態申請一個int類型的空間并初始化為10int* ptr5 = new int(10);// 動態申請10個int類型的空間int* ptr6 = new int[3];delete ptr4;delete ptr5;delete[] ptr6;
}
可以發現new和我們之前用的函數都有點不一樣,這是因為它是操作符 ,所以導致了它獨特的使用方法。
要特別注意new int(10)不是申請10個int空間,而是申請一個int空間并初始化為10,new int[10]才是申請10個int空間,但申請數組就不能初始化了
那new和malloc有什么區別呢?
在回答這個問題之前,我們先來看另外一組操作符
operator new和operator delete
盡管他們有operator,但這和類的那些重載函數不一樣,,operator new 和operator delete是系統提供的全局函數,用法和malloc相似
void Test()
{// 動態申請一個int類型的空間int* ptr4 = (int*)operator new(sizeof(int));// 動態申請10個int類型的空間int* ptr6 = (int*)operator new(sizeof(int)*3);operator delete(ptr4);operator delete[](ptr6);
}
下面是operator new的底層實現
/*
operator new:該函數實際通過malloc來申請空間,當malloc申請空間成功時直接返回;申請空間失敗,
嘗試執行空間不足應對措施,如果改應對措施用戶設置了,則繼續申請,否則拋異常。
*/
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);
}
通過該函數的實現可以得知,operator new也是調用的malloc,只不過在這基礎上如果開辟失敗會拋異常
operator delete 和free沒區別,因為釋放空間失敗直接終止進程,operator delete只是為了和operator new成對出現
除此之外,operator new和operator delete也是可以重載的,如果你想在開辟空間的基礎上做別的事情,就可以重載operator new/delete
new,operator new,malloc
上面將了operator new和malloc的區別,是在調用malloc的基礎上可以拋異常。
而new和operator new的區別則是在operator new的基礎上會調用自定義類型的構造函數,這是因為new 的原理就是
1.調用operator new函數申請空間?
2.在申請的空間上執行構造函數
?delete也相似
1.在空間上執行析構函數
2. 調用operator delete函數釋放空間?
?那既然new是在operator new的基礎上調用構造函數,有沒有什么辦法可以直接在operator new 的基礎上自己去調用構造函數呢?
有的兄弟,有的,這個時候就需要請出定位new了
當我們已經通過malloc或operator new申請了空間后,如果想要調用該類的構造函數
Date *p = (Date*)operator new(sizeof(Date));
new(p) Date;
new后面的括號里面是指針名稱,后面跟類的名稱
正常來說是不能顯式調用構造函數的,構造函數都是在實例化類時隱式調用的,而定位new就算是一種顯式調用
總結:
new和malloc的區別
- ?new會調用構造函數,失敗拋異常,malloc失敗了返回0 ?
- ?malloc是一個函數,new是一個操作符
- malloc用法:參數傳字節數,返回值是void*;new后面跟申請對象的類型,返回值是類型的指針
malloc,operator new,new的關系
- malloc
- operator new -> malloc + 失敗拋異常
- new? ? ? ? ? ? ? ?-> operator new + 構造函數
讀者有沒有想過一個問題,為什么會有free,delete這樣的函數,有什么意義呢?
大家都知道free和delete都是用來釋放之前在堆中動態開辟的空間的,那如果不釋放有什么危害?
答案是會造成內存泄漏,雖說對小程序沒有什么危害,但對長期運行的程序,出現內存泄露危害很大,或者設備內存本身很小,也有危害,要防止可以用工具檢測或智能指針
?模板:
大家都知道,重載可以讓同一個函數名存在多個,根據參數的不同來選擇對應的函數,有些時候函數內部除了類型不同沒有任何的區別,這時用重載就會非常麻煩,就會用到模板(泛型編程)
模板分為函數模板和類模板
先來看下面一個Swap的函數模板
template<class T>//<typename T>也可以
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}
這是一個可以用于任何類型的Swap函數模板,其實就是在函數的上面一行寫上了template<class T>
之后不管調用Swap時是什么類型,都可以執行,但它執行的是這個函數模板嗎?
當然不是,函數模板是一個藍圖,它本身并不是函數,是編譯器用使用方式產生特定具體類型函數的模具。所以其實模板就是將本來應該我們做的重復的事情交給了編譯器。
也就是說模板本身并不會去執行,是在預處理階段生成對應的函數/類,真正去執行的是生成的函數/類
可以看到,三次調用的Swap的地址是不一樣的,所以是生成了三個不同參數的Swap,再分別調用
看上面這個代碼,可以看到模板的最后一行沒有加分號,但這并不影響編譯通過,這也證明了模板并不會參與編譯,只要沒有用到這個函數,預處理階段就不會生成相應的函數。
顯式實例化和隱式實例化
模板的實例化分為顯式和隱式,如Swap(a,b)就是隱式,?由編譯器自己去判斷應該調用什么類型的函數,也可以Swap<int>(a,b),這樣就是顯式實例化,指定編譯器必須要用int類型的函數。
如果a是int型,b是double型,Swap(a,b)也會報錯,因為不知道該生成那種函數,這時有兩種解決方法
- Swap(a,(int)b),這樣讓b也變成int類型,編譯器就知道該實例化生成一個int類型的Swap函數
- Swap<int>(a,b),直接顯式實例化也可以讓編譯器知道該實例化生成一個int類型的Swap函數
而類模板在實例化時必須顯式實例化,隱式實例化會報錯
并且函數模板和同名的函數可以共存
// 專門處理int的加法函數
int Add(int left, int right)
{return left + right;
}
// 通用加法函數
template <class T>
T Add(T left, T right)
{return left + right;
}
void Test()
{Add(1, 2); // 與非模板函數匹配,編譯器不需要特化Add<int>(1, 2); // 調用編譯器特化的Add版本
}
有人可能會問,第一個Add會調用函數模板生成的函數還是另一個同名函數呢?
答案是同名函數。對于非模板函數和同名函數模板,如果其他條件都相同,在調動時會優先調用非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好匹配的函數, 那么將選擇模板