前言
本系列文章承接C語言的學習,需要有C語言的基礎才能學會哦~
第8篇主要講的是有關于C++的內部類、匿名對象、對象拷貝時的編譯器優化和內存管理。
C++才起步,都很簡單!!
目錄
前言
內部類
性質
匿名對象
性質
※對象拷貝時的編譯器優化
內存管理
內存分區
?編輯
內核空間
棧
堆
靜態區/數據段
常量區/代碼段
虛擬進程地址空間
動態內存管理函數
C++內存管理
基本語法
operator new和operator delete函數
new和delete的原理
定位new表達式(placement-new)
※內存池(了解即可,后面會深度學)
內部類
一個類定義在另一個類里,這個類就是內部類。
//簡單代碼
class A
{public:private:class B{private:int _b;}
}
性質
①內部類默認是外部類的友元。
②本質是一種封裝方式。若類B實現出來是為了給類A使用,那么可以把B設置為A的內部類。
③內部類受訪問限定符限制,被privact和protect修飾的內部類只能被外部類使用。
匿名對象
沒有標識符標識的對象為匿名對象,反之為有名對象。
//有名對象,d1為其名
Date d1(1999,9,9);
//匿名對象
Date(2000,1,1);
性質
①匿名對象的生命周期只有一行代碼。也就是匿名對象構造后,下一步就會被析構(一次性筷子)。
②匿名對象具有常性,只能被const引用。
③被const引用后,匿名對象的生命周期被延長至與該引用同步。
※對象拷貝時的編譯器優化
現代編譯器會在不影響代碼正確性的情況下,提高效率,在底層進行優化實現。
不同的編譯器,優化方式不同,以下優化方式以VS2019的debug為例。
越新的編譯器,優化越好,甚至會有跨多行合并優化代碼。
例1:隱式類型轉換
A aa1 = 1;//隱式類型轉換
語法上:該隱式類型轉換先以1為參數構造臨時對象,在將臨時對象拷貝構造給aa1。
實際上:直接以1為參數構造新對象aa1。
從而減少對內存的使用,提高代碼運行效率。
例2:傳值傳參
void func(A aa)
{//·······
}
int main()
{func(1)return 0;
}
語法上:該傳值傳參先以1為參數構造臨時對象,在將臨時對象拷貝構造給aa,然后傳入func。
實際上:直接以1為參數構造對象aa傳入func。
從而減少對內存的使用,提高代碼運行效率。
例3:傳值返回
A f2()
{A aa;return aa
}int main()
{A aa2 = f2();return 0;
}
語法上:構造f2函數局部域的一個對象aa,然后返回aa時將aa拷貝構造為臨時對象,接著臨時對象在拷貝構造給aa2。(構造->拷貝->拷貝)。
實際上:構造f2函數局部域的一個對象aa,再直接拷貝給aa2。
內存管理
內存分區
內存分區主要分為四個區(還有其他不常用的區,這里省去):棧區、堆區、靜態區和常量區
內核空間
用戶代碼,不可以讀寫。
棧
局部變量,參數,返回值,對象,函數調用等等要在棧區開辟棧幀,主要存儲臨時的、局部的變量(棧區占比不大,M為單位)。棧向下增長,越后開辟的空間地址越小。
堆
malloc、new等動態內存管理開辟的空間,在堆區(占比較大,以G為單位)。堆向上增長,越后開辟的空間地址越大(可能會在動態內存管理時被打亂順序)。
靜態區/數據段
全局數據和靜態數據放在靜態區。
常量區/代碼段
存放常量和編譯完成的指令。
虛擬進程地址空間
地址空間是虛擬的,需要頁表等算法進行映射,這個部分知識涉及操作系統。
int a;
static int b = 1;
int main()
{int num[] = {1,2,3,4};static int c = 1;char arr[] = "abcdefg";const char* p1 = "abcdefg";char* p2 = (char*)malloc(sizeof(char) * 10);
}
如上代碼
a、b、c存放在靜態區
num、arr數組存放在棧區
p2存放在堆區
“abcdefg”存放在常量區
動態內存管理函數
C語言動態內存管理函數 malloc / calloc / realloc / free 依舊可以使用
tips:malloc / calloc / realloc的區別?------>calloc會在malloc的基礎上,初始化開辟空間;realloc重新分配更多的空間,也覆蓋malloc的功能。
C++內存管理
C++有新的方式進行內存管理,使用 new / delete 操作符進行內存管理。
基本語法
int main()
{//動態申請1個int類型空間int *ptr1 = new int;//動態申請1個int類型空間,并初始化為10int *ptr2 = new int(10);//釋放開辟的int類型空間delete ptr1;delete ptr2;//動態申請3個int類型的空間int ptr3 = new int[3];//動態申請3個int類型的空間,并依次初始化為1,2,3int ptr4 = new int[3]{1,2,3};//動態申請5個int類型的空間,并依次初始化為1,2,3,為指定初始化的空間,默認初始化為0int ptr5 = new int[5]{1,2,3};//釋放開辟的int類型數組delete[] ptr3;delete[] ptr4;delete[] ptr5;return 0;
}
以上為new和delete的使用,改為malloc和free基本沒有區別。
而new開辟自定義類型:
//malloc開辟
A* p1 = (A*)malloc(sizeof(A));
//new開辟
A* p2 = new A;
A* p3 = new A(10);
A* p4 = new A[10];//申請空間,構造10次
A* p5 = new A[2]{1,2};//申請空間,構造2次
A* p6 = new A[2]{p2,p3};//申請空間,構造2次
區別:
①new之于malloc,不只是申請空間,還會調用其默認構造函數。
②錯誤時不返回NULL,而是拋出異常。
③不需要手動填入申請空間大小
而delete釋放自定義類型:
delete p2;
delete p3;
delete[] p4;//申請空間,析構10次
delete[] p5;//申請空間,析構2次
delete[] p6;//申請空間,析構2次
區別:delete之于free,不只是釋放空間,還會調用其析構函數。
綜上,對于自定義類型,還是優先使用new和delete。
operator new和operator delete函數
這不是對操作符new和delete的重載,它們實際上是兩個全局函數。使用new和delete時,在底層會調用相應的全局函數。
在底層源碼中,new實際上是對malloc的再次擴展封裝。
▲malloc錯誤后會拋出一個nullptr,new會遇到malloc拋出的空指針后,首先執行用戶提供的應對措施,如果不提供,就拋出一個異常。(異常為后面的內容,了解即可)。
在底層源碼中,delete實際上是對free的再次擴展封裝。
▲進行錯誤檢查等處理后,再通過free釋放空間。
operator new和operator delete也可以直接調用。
new和delete的原理
調用new:①調用operator new函數,開辟空間。②若開辟自定義類型空間,最后依次調用其默認構造函數。
調用delete:①若是釋放自定義類型空間,首先依次調用其析構函數。②調用operator delete函數,釋放空間。
定位new表達式(placement-new)
作用是在已分配的原始內存空間調用構造函數初始化一個對象。
A *p1 = (A*)operator new(sizeof(A));
//new + 需構造的指針 + 對象類型 + 初始化參數
new(p1)A(10);
為什么要這樣做呢?因為不是new開辟的對象,不可以直接初始化,必須要用這種格式。
p1->A(10);//錯誤的
但是,析構可以直接調用。
p1->~A();
※內存池(了解即可,后面會深度學)
從堆區里一次性開辟出較大的內存空間作為內存池,通過數據結構管理起來,申請空間從內存池取出,可以減少頻繁申請堆區,因為在堆區只能依次申請空間,效率低。
?~~本文完結!!感謝觀看!!接下來更精彩!!歡迎來我博客做客~~?