C++——動態內存分配new--delete


一、靜態內存分配與動態內存分配

靜態內存分配:全局或局部變量(對象),編譯器在編譯時都可以根據變量或對象的類型知道所需內存空間的大小。從而系統在適當的時候為他們分配內存空間

動態內存分配:有些操作對象只有在程序運行時才能確定,這樣編譯器在編譯時就無法為他們預定存儲空間,只能在程序運行時,系統根據運行時的要求進行內存分配稱為動態內存分配。動態分配都在自由存儲區中進行。

  1. 動態內存分配 指針變量名=new 類型名(初始化式);delete 指針名;動態分配的一般都是無名對象,自由存儲區不會在分配時做初始化(包括清零)所以必須用初始化式來顯示初始化。
    • 1 int *pi=new int(0);
      2 delete pi;
      3 //釋放的是pi所指向的目標的內存空間,也就是撤銷了該目標。但是指針pi本身並沒有撤銷,指針所占用的內存空間並沒有釋放。
  2. 數組動態分配:
  • 1 指針變量名=new 類型名[下標表達式];
    2 delete[] 指向該數組的指針變量名;
    3 //兩式中的方括號必須配對使用。如果delete語句中少了方括號,因編譯器認為該指針指是指向數組第一個元素的指針,會產生回收不徹底的問題(只回收了數組第一個元素所占用的內存空間)

     

    3.自由存儲區對象與構造函數:

  • 類對象動態建立與刪除過程
    //
    對於CGoods類,動態建立內存與釋放內存 CGoods *pc; pc=new CGoods;//分配自由存儲空間,並構建一個無名的CGoods對象 ...... delete CGoods;//先析構,C++自動調用商品類的析構函數,再將內存空間返回給自由存儲區
  • 類對象初始化:

new后面類(class)類型可以有參數這些參數即構造函數的參數。但對創建數組,則無參數,只能調用默認的構造函數。




 

二、類的封裝:

  封裝的更高境界是在該類對象中的一切都是完備的,自給自足的,不僅有數據和對數據的操作,還包括資源的動態安排與釋放。在需要時可以無條件地安全的使用。標准string類模板就是典型的例子。這樣的類對象,作為另一個類的成員對象使用時就不會出現任何問題。這表明聚合實現了完善的封裝。

  有關string類會專門寫一篇文章總結。




 

三、new/delete與malloc/free的區別

  1、兩者之間的區別:

  • new/delete是C++中的操作符,而malloc/delete是標准庫函數
  • 對於非內建數據類型只使用malloc是無法完成動態對象要求的一般在創建對象時需要調用構造函數,在對象釋放時需要自動調用析構函數。而malloc是庫函數而不是運算符,不在編譯器控制范圍內,不能夠自動調用構造函數與析構函數。而new與delete在為對象申請分配內存空間時,可以自動調用構造函數完成對象的初始化,delete也可以自動調用析構函數。malloc只是做一件事,只是為變量分配內存空間free只是釋放變量的內存。
  • new返回的是指定類型的指針,並可以自動計算所申請內存的大小。而malloc需要我們計算申請的內存的大小,並在返回時強制轉換為實際類型的指針。

  2、new、delete運行機制

  • operator new與operatoe delete:這兩個是C++標准庫函數,原型分別如下:
    • 1 void *operator new(size_t);//allocation an obhject
      2 void *operator delete(void*;//free an object)
      3 
      4 void *operator new[](size_t);//allocaton an array
      5 void *operator delete[](void*);//free an arry

       這兩個函數與C語言中的malloc/free函數類似,都是用來申請和釋內存的,並且operator new申請內存后不對內存進行初始化,直接返回申請內存的指針。可以直接在程序中使用這幾個函數。

  3、通過實例分析new /delete的實現機制:new/delete

    • //不使用C++的內置數據類型,自定義一個類A;
      class A
      {
      public:
          A(int v):var(v)
      {
      fopen_s(&file,"test","r");
      }
      ~A()
      {
      fclose(file);
      }
      private:
      int var;
      FILE *file;
      }
      //類A有兩個私有數據成員,一個構造函數和一個析構函數,構造函數中初始化私有變量var以及打開一個文件,析構函數關閉文件
      class *pa=new A(10);
      delete pa;
    • 動態內存分配的過程:
      • 首先:調用上文提到的operator new 標准庫函數,傳入的參數為class A的大小,這里為8個字節,一個int類型,一個指針分別都是四個字節,函數的返回值是所分配內存地址的起始值。
      • 上邊分配的內存是未初始化的,第二步就是在這一塊原始的內存上對類對象進行初始化,調用相應的構造函數這里是A::A(10);這個函數,然后對這塊內存進行初始化,然后file指向打開的文件。
      • 最后一步是返回分配並初始化好了的對象的指針,
    • 動態內存釋放的過程:delete pa;
      • 首先調用pa所指向的對象的析構函數對打開的文件進行關閉
      • 通過上文提到的operator delete標准庫函數來釋放該對象的內存,傳入的函數的參數是pa的值,即內存的首地址。

  4、動態申請和釋放一個數組的機制。new[]/delete[]

    • 1 string *psa =new string[10];//array of 10empty strings
      2 int *pia=new int[10];//arry of 10 uninitialized ints
    • 申請一個數組時用到了new[];第一個數組時string類型,分配的保存對象的內存空間后,調用string類型的默認構造函數依次初始化數組中的每個元素。
    • 第二個申請的是具有內置類型的數組,分配了存儲10個int對象的內存空間,但並沒有初始化。
  • 1 //釋放內存空間:
    2 delete[] psa;
    3 delete[] pia;
    4 //注意這里的[]不能漏掉
    • 對於string類型數組:首先對10個string類型對象分別調用析構函數然后在釋放掉為對象分配的所有內存對象
      • 如何知道psa所指向的對象的數組的大小,怎么知道調用幾次析構函數
        • 這就需要在new[]一個對象時,保存數組的維度,C++的做法是在分配數組空間時多分配4個字節的大小,專門保存數組的大小,在delete[]時就可以取出這個保存的數,就知道需要調用幾次析構函數了。
    • 對於int類型數組:因為是內置數據類型不存在析構函數,所以直接釋放掉為10個int 類型所分配的內存空間。

  • class A *p=new A[3];
  • 首先調用庫函數operator new[]分配足夠的內存大小,需要注意的是需要多出四個字節用來存放數組的大小,
  • 然后在剛分配的內存上調用構造函數初始化對象
  • 最后返回數組對象的指針,而不是分配內存空間的首地址,因為首地址上存放的是4個字節的數組的大小,返回的地址即在內存首地址+4的地址。
    • 在delete[]時傳入operator delete[]的參數便不是數組對象的指針,而是p的值減4

  5、為什么new[]/delete[]要配對使用

  • class A *pAa = new class A[3];
    delete pAa;
    
      • delete pAa;做了兩件事:
        • 調用一次pAa所指向的對象的析構函數;
        • 調用operator delete(pAa);釋放內存;
      • 第一點:這里只對數組的第一個元素調用了析構函數,后邊兩個對象沒有調用析構函數,在銷毀數組對象時少調用了析構函數,會造成內存泄漏。
      • 第二點:直接釋放pAa所指向的內存空間,會造成嚴重的段錯誤,因未分配的內存地址的首地址是pAa所指向的地址-4,應該傳入的參數是&pAa-4。



 

四、new/delete運算符的重載:

  1、new/delete在C++中也是運算符,可以重載

     new:

  • 先開辟內存空間
  • 再調用類的構造函數

開辟內存空間的部分可以被重載

     delete:

  • 先調用類的析構函數
  • 再釋放內存空間

釋放內存空間的部分可以被重載

  2、為什么要重載new/delete運算符

  有時候在實現內存池的時候需要重載他們。頻繁的使用new/delete,會造成內存碎片,內存不足等問題,影響程序的正常執行所以一次開辟一個適當大的空間,每當需要對象的時候,不再需要去開辟內存空間,只需要調用構造函數即可。

  new/delete可以有多種重載方式,但是new函數的第一個參數一定是size_t類型。分別是new單個對象,new對象的數組。詳見下邊示例代碼:

  • class String{
    public:
      String(const char* str = ""){
      cout << "Create" << endl;
        if(NULL == str){
          data = new char[1];
          data[0] = '\0';
        }
        else{
          data = new char[strlen(str) + 1];
          strcpy(data, str);
        }
      }
      ~String(){
      cout << "Free" << endl;
        delete []data;
        data = NULL;
      }
    private:
      char* data = NULL;
    };
    //重載方式1
    void* operator new(size_t sz){
      cout << "in operator new" << endl;
      void* o = malloc(sz);
      return o;
    }
    void operator delete(void *o){
      cout << "in operator delete" << endl;
      free(o);
    }
    //重載方式2
    void* operator new[](size_t sz){
      cout << "in operator new[]" << endl;
      void* o = malloc(sz);
      return o;
    }
    void operator delete[](void *o){
      cout << "in operator delete[]" << endl;
      free(o);
    }
    
    //重載方式3
    //第一個參數size_t即使不適用,也必須有                          
    void* operator new(size_t sz, String* s, int pos){
      return s + pos;
    }
    int main(){
      String *s = new String("abc");
      delete s;
    
      String *sr = new String[3];
      delete []sr;
    
      //開辟內存池,但是還沒有調用過池里對象的構造方法                  
      String *ar = (String*)operator new(sizeof(String) * 2);
      //調用池里第一個對象的構造方法,不再開辟空間
      new(ar, 0)String("first0");
      //調用池里第二個對象的構造方法 ,不再開辟空間 
      new(ar, 1)String("first1");
      //調用池里第一個對象的析構方法,注意不會釋放到內存
      (&ar[0])->~String();
      //調用池里第二個對象的析構方法,注意不會釋放到內存
      (&ar[1])->~String();
      //下面語句執行前,內存池里的對象可以反復利用 
      operator delete(ar);
    
    }

     

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM