最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++動態(tài)內存分配(new/new[]和delete/delete[])詳解

 更新時間:2017年05月22日 15:20:37   投稿:lqh  
這篇文章主要介紹了C++動態(tài)內存分配(new/new[]和delete/delete[])詳解的相關資料,需要的朋友可以參考下

C++動態(tài)內存分配(new/new[]和delete/delete[])詳解

為了解決這個普通的編程問題,在運行時能創(chuàng)建和銷毀對象是基本的要求。當然,C已提供了動態(tài)內存分配函數malloc( )和free( ),以及malloc( )的變種(realloc:改變分配內存的大小,calloc:指針指向內存前初始化),這些函數在運行時從堆中(也稱自由內存)分配存儲單元,但是運用這些庫函數需要計算需要開辟內存的大小,容易出現錯誤。

     那么通常我們在C語言中我們開辟內存的方式如下:

(void*)malloc(sizeof(void)); 

然而,在C+ +中這些函數不能很好地運行。構造函數不允許通過向對象傳遞內存地址來初始化它。如果那么做了,我們可能

  • 忘記了。則對象初始化在C + +中難以保證。
  • 期望某事發(fā)生,但結果是在給對象初始化之前意外地對對象作了某種改變。
  • 把錯誤規(guī)模的對象傳遞給了它。

當然,即使我們把每件事都做得很正確,修改我們的程序的人也容易犯同樣的錯誤。不正確的初始化是編程出錯的主要原因,所以在堆上創(chuàng)建對象時,確保構造函數調用是特別重要的。

C+ +是如何保證正確的初始化和清理并允許我們在堆上動態(tài)創(chuàng)建對象的呢?

    答案是使動態(tài)對象創(chuàng)建成為語言的核心。malloc( )和free( )是庫函數,因此不在編譯器控制范圍之內。如果我們有一個能完成動態(tài)內存分配及初始化工作的運算符和另一個能完成清理及釋放內存工作的運算符,編譯器就可以保證所有對象的構造函數和析構函數都會被調用。

    若使用原始的動態(tài)內存開辟方式就會顯得很繁瑣,具體代碼如下:

#include<cstdlib> 
#include<cstring> 
#include<iostream> 
using namespace std; 
class Obj 
{ 
 int i,j,k; 
 enum {sz=100}; 
 char buf[sz]; 
public: 
  void initialize() 
  { 
    cout<<"initialize"<<endl; 
    i=k=j=0; 
    memset(buf,0,sz); 
  } 
  void destroy() const 
  { 
   cout<<"destroying Obj"<<endl; 
  } 
}; 
int main() 
{ 
  Obj* obj=(Obj*)malloc(sizeof(Obj)); 
   if(obj!=0) 
  obj->initialize(); 
  obj->destroy(); 
  free(obj); 
 return 0; 
}

     在上面這行代碼中,我們可以看到使用malloc( )為對象分配內存:obj* Obj = (obj*)malloc(sizeof(obj)) ;
這里用戶必須決定對象的長度(這也是程序出錯原因之一)。因為它是一塊內存而不是一個對象,所以malloc( )返回一個void*.C++不允許將一個void* 賦予任何指針,所以必須映射。因為malloc( )可能找不到可分配的內存(在這種情況下它返回 0),所以必須檢查返回的指針以確信內存分配成功。

但最壞的是:Obj->initialize( ) ;用戶在使用對象之前必須記住對它初始化。注意構造函數沒有被使用,因為構造函數不能被顯式地調用—而是當對象創(chuàng)建時由編譯器調用。這里的問題是現在用戶可能在使用對象時忘記執(zhí)行初始化,因此這也是引入程序缺陷的主要來源。許多程序設計者發(fā)現 C的動態(tài)內存分配函數太復雜,令人混淆。所以, C程序設計者常常在靜態(tài)內存區(qū)域使用虛擬內存機制分配很大的變量數組以避免使用動態(tài)內存分配。因為C++能讓一般的程序員安全使用庫函數而不費力,所以應當避免使用 C的動態(tài)內存方法。C++中的解決方案是把創(chuàng)建一個對象所需的所有動作都結合在一個稱為new的運算符里。當用new(new的表達式)創(chuàng)建一個對象時,它就在堆里為對象分配內存并為這塊內存調用構造函數。

    因此,如果我們寫出下面的表達式foo *fp = new foo(1,2) ; 在運行時等價于調用malloc(sizeof(foo)),并使用(1,2)作為參數表來為

foo調用構造函數,返回值作為this指針的結果地址。在該指針被賦給 fp之前,它是不定的、未初始化的對象— 在這之前我們甚至不能觸及它。它自動地被賦予正確的 foo類型,所以不必進行映射。缺省的new還檢查以確信在傳遞地址給構造函數之前內存分配是成功的,所以我們不必顯式地確定調用是否成功。在本章后面,我們將會發(fā)現,如果沒有可供分配的內存會發(fā)生什么事情。我們可以為類使用任何可用的構造函數而寫一個 ne w表達式。如果構造函數沒有參數,可以寫沒有構造函數參數表的new表達式:

foo *fp = new foo ;我們已經注意到了,在堆里創(chuàng)建對象的過程變得簡單了—只是一個簡單的表達式 ,它帶有內置的長度計算、類型轉換和安全檢查。這樣在堆里創(chuàng)建一個對象和在棧里創(chuàng)建一個對象一樣容易。

new表達式的反面是delete表達式。delete表達式首先調用析構函數,然后釋放內存(經常是調用free( ))。正如new表達式返回一個指向對象的指針一樣,delete表達式需要一個對象的地址。delete fp ;上面的表達式清除了早先創(chuàng)建的動態(tài)分配的對象foo。delete只用于刪除由new創(chuàng)建的對象。如果用malloc( )(或calloc( )或realloc( ))創(chuàng)建一個對象,然后用delete刪除它,這個行為是未定義的。因為大多數缺省的new和delete實現機制都使

用了malloc( )和free( ),所以我們很可能會沒有調用析構函數就釋放了內存。如果正在刪除的對象指針是 0,將不發(fā)生任何事情。為此,建議在刪除指針后立即把指針賦值為0以免對它刪除兩次。對一個對象刪除兩次一定不是一件好事,這會引起問題。

當創(chuàng)建一個new表達式時有兩件事發(fā)生。首先,使用運算符new分配內存,然后調用構造函數。在delete表達式里,調用析構函數,然后使用運算符 delete釋放內存。我們永遠無法控制構造函數和析構函數的調用(否則我們可能意外地攪亂它們),但可以改變內存分配函數運算  符new和delete。被new和delete使用的內存分配系統(tǒng)是為通用目的而設計的。但在特殊的情形下,它不能滿足我們的需要。改變分配系統(tǒng)的原因是考慮效率:我們也許要創(chuàng)建和銷毀一個特定的類的非常多的對象以至于這個運算變成了速度的瓶頸。 C++允許重載new和delete來實現我們自己的存儲分配方案,所以可以像這樣處理問題。   

  另外一個問題是堆碎片:分配不同大小的內存可能造成在堆上產生很多碎片,以至于很快用完內存。也就是內存可能還有,但由于是碎片,找不到足夠大的內存滿足我們的需要。通過為特定類創(chuàng)建我們自己的內存分配器,可以確保這種情況不會發(fā)生。 
在嵌入和實時系統(tǒng)里,程序可能必須在有限的資源情況下運行很長時間。這樣的系統(tǒng)也可能要求分配內存花費相同的時間且不允許出現堆內存耗盡或出現很多碎片的情況。由客戶定制的內存分配器是一種解決辦法,否則程序設計者在這種情況下要避免使用new和delete,從而失去了C + +很有價值的優(yōu)點。 

當重載運算符new和delete時,記住只改變原有的內存分配方法是很重要的。編譯器將用new代替缺省的版本去分配內存,然后為那個內存調用構造函數。所以,雖然編譯器遇到new 時會分配內存并調用構造函數,但當我們重載new時,可以改變的只是內存分配部分。(delete 也有相似的限制。)

當重載運算符new時,也可以替換它用完內存時的行為,所以必須在運算符new里決定做什么:返回0、寫一個調用new - handler的循環(huán)、再試著分配或用一個 bad_alloc異常處理重載new和delete與重載任何其他運算符一樣。但可以選擇重載全局內存分配函數,或為特定的類使用特定的分配函數

    當全局版本的new和delete不能滿足整個系統(tǒng)時,對其重載是很極端的方法。如果重載全局版本,那么缺省版本就完全不能被訪問—甚至在這個重載定義里也不能調用它們。

重載的ne w 必須有一個size_t 參數。這個參數由編譯器產生并傳遞給我們,它是要分配內存的對象的長度。必須返回一個指向等于這個長度(或大于這個長度,如果我們有這樣做的原因)的對象的指針,或如果沒有找到存儲單元(在這種情況下,構造函數不被調用),返回一個0。然而如果找不到存儲單元,不能僅僅返回0,還應該調用new-handler或進行異常處理,通知這里存在問題。

運算符new的返回值是一個void *,而不是指向任何特定類型的指針。它所做的是分配內存,而不是完成一個對象的建立—直到構造函數調用了才完成對象的創(chuàng)建,這是由編譯器所確保的動作,不在我們的控制范圍內。

運算符delete接受一個指向由運算符new分配的內存的void *。它是一個void *因為它是在調用析構函數后得到的指針。析構函數從存儲單元里移去對象。運算符 delete的返回類型是void。

下面提供了一個如何重載全局new和delete的簡單的例子:

#include <stdlib.h> 
 
void * operator new(size_t sz) 
{ 
  printf("operator new:%d bytes\n",sz); 
  void* m=malloc(sz); 
  if(!m) puts("out of memory"); 
  return 0; 
} 
void operator delete(void* m) 
{ 
puts("operator delete"); 
free(m); 
} 
class s 
{ 
  int i[100]; 
public: 
  s(){puts("s::s()");} 
  ~s(){puts("s::~s()");} 
}; 
int main() 
{ 
 puts("creating & destorying an int "); 
 int* p=new int(47); 
 delete p; 
 puts("creating & destorying an s"); 
 s* S=new s; 
 delete S; 
 puts("creating & destorying an s[3]"); 
 s* SA=new s[3]; 
 delete [] SA; 
}

    這里可以看到重載new和delete的一般形式。為了實現內存分配器,使用了標準 C庫函數 malloc( )和free( )(可能缺省的new和delete也使用這些函數)。它們還打印出了它們正在做什么的信息。注意,這里使用 printf( )和puts( )而不是i o s t r e a m s。當創(chuàng)建了一個i o s t r e a m對象時(像全局的c i n、c o u t和c e r r),它們調用new去分配內存。用printf( )不會進入死鎖狀態(tài),因為它不調用new來初始化本身。

在main( )里,創(chuàng)建內部數據類型的對象以證明在這種情況下重載的new和delete也被調用。然后創(chuàng)建一個類型s的單個對象,接著創(chuàng)建一個數組。對于數組,我們可以看到需要額外的內存用于存放數組對象數量的信息。在所有情況里,都是使用全局重載版本的new和delete。

      為一個類重載new和delete時,不必明說是 static,我們仍是在創(chuàng)建 static成員函數。它的語法也和重載任何其他運算符一樣。當編譯器看到使用new創(chuàng)建類對象時,它選擇成員版本運算符new而不是全局版本的new。但全局版本的new和delete為所有其他類型對象使用(除非它們有自己的new和delete)。 

        如果為一個類重載了運算符new和delete,那么無論何時創(chuàng)建這個類的一個對象都將調用這些運算符。但如果為這些對象創(chuàng)建一個數組時,將調用全局運算符new( )立即為這個數組分配足夠的內存。全局運算符 delete( )被調用來釋放這塊內存??梢酝ㄟ^為那個類重載數組版本的運算符new [ ]和delete [ ]來控制對象數組的內存分配。這里提供了一個顯示兩個不同版本被調用的例子: 這里,全局版本的new和delete被調用,除了加入了跟蹤信息以外,它們和未重載版本new 和delete的效果是一樣的。當然,我們可以在重載的new和delete里使用想要的內存分配方案。

         可以看到除了加了一個括號外,數組版本的new和delete與單個對象版本是一樣的。在這兩種情況下,要傳遞分配的對象內存大小。傳遞給數組版本的內存大小是整個數組的大小。應該記住重載運算符new唯一需要做的是返回指向一個足夠大的內存的指針。雖然我們可以初始化那塊內存,但通常這是構造函數的工作,構造函數將被編譯器自動調用。 

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

相關文章

  • c++連接mysql數據庫的兩種方法(ADO連接和mysql api連接)

    c++連接mysql數據庫的兩種方法(ADO連接和mysql api連接)

    現在正做一個接口,通過不同的連接字符串操作不同的數據庫。要用到mysql數據庫,C++連接mysql有2種方法:利用ADO連接、利用mysql自己的api函數進行連接,下面看看如何用吧
    2013-12-12
  • 詳解C++圖搜索算法之雙端隊列廣搜

    詳解C++圖搜索算法之雙端隊列廣搜

    這篇文章主要為大家介紹一下C++圖搜索算法中的雙端隊列廣搜,文中通過例題詳細介紹了雙端隊列廣搜的使用方法,感興趣的可以了解一下
    2022-06-06
  • C語言 文件的打開與關閉詳解及示例代碼

    C語言 文件的打開與關閉詳解及示例代碼

    本文主要介紹C語言 文件的基礎知識,這里整理了相關資料及示例代碼,有興趣的小伙伴可以參考下
    2016-08-08
  • C++簡單又輕松的講解類和對象中友元函數

    C++簡單又輕松的講解類和對象中友元函數

    采用類的機制后實現了數據的隱藏與封裝,類的數據成員一般定義為私有成員,成員函數一般定義為公有的,依此提供類與外界間的通信接口。但是,有時需要定義一些函數,這些函數不是類的一部分,但又需要頻繁地訪問類的數據成員,這時可以將這些函數定義為該類的友元函數
    2022-06-06
  • C++詳解鏈棧的實現

    C++詳解鏈棧的實現

    今天我們學習的是鏈棧,也就是說棧的鏈式結構,我們運用順序鏈的方式來實現。首先呢,鏈棧是不存在存儲空間滿的情況的,所以可以說它是個無底洞,然而我們之前學的順序棧是有額定空間的
    2022-06-06
  • C++實現二分法求連續(xù)一元函數根

    C++實現二分法求連續(xù)一元函數根

    這篇文章主要為大家詳細介紹了C++實現二分法求連續(xù)一元函數根,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • C++實現簡易的五子棋小游戲

    C++實現簡易的五子棋小游戲

    這篇文章主要為大家詳細介紹了C++實現簡易的五子棋小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • VS2019中CMake項目如何指定c++語言標準

    VS2019中CMake項目如何指定c++語言標準

    這篇文章主要介紹了VS2019中CMake項目如何指定c++語言標準,需要的朋友可以參考下
    2020-02-02
  • C++中vector的常用接口詳析說明

    C++中vector的常用接口詳析說明

    vector類我們可以將其看作是一個能夠動態(tài)擴容的數組,下面這篇文章主要給大家介紹了關于?C++?vector常用接口的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-08-08
  • 貪吃蛇C語言代碼實現(難度可選)

    貪吃蛇C語言代碼實現(難度可選)

    這篇文章主要為大家詳細介紹了貪吃蛇C語言代碼實現,游戲難度可供選擇,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-01-01

最新評論

华亭县| 济源市| 民丰县| 怀安县| 图木舒克市| 三台县| 体育| 武陟县| 安仁县| 巫溪县| 苏尼特右旗| 鲁山县| 香港| 阆中市| 齐齐哈尔市| 阳城县| 商河县| 陇西县| 汨罗市| 同德县| 高清| 山东省| 淅川县| 诏安县| 正安县| 察隅县| 东乌| 肇庆市| 凉城县| 沛县| 赤城县| 乌鲁木齐市| 安西县| 巴彦淖尔市| 札达县| 武川县| 景宁| 兴城市| 榆林市| 和硕县| 化州市|