文章目錄
- 動態申請記憶體操作符 new
- 釋放記憶體操作符delete
- 保持平衡
動靜態記憶體配置
靜態記憶體配置:編譯器在處理常式原始碼時(即編譯時間)分配。
動態記憶體分配:程式執行時調用運行時刻庫函數來分配。
靜態與動態記憶體分配的兩個主要區別:
- 靜態對象是有名字的變數,我們直接對其進行操作。而動態對象是沒有名字的變數,我們通過指標間接地對它進行操作。
- 靜態對象的分配與釋放由編譯器自動處理。程式員需要理解這一點,但不需要做任何事情。相反,動態對象地分配與釋放,必須由程式員顯示地管理,相對來說比較容易出錯 。
new/delete
在C語言中進行記憶體的動態分配與釋放,我們使用malloc()和free()函數。C++中不再使用C語言中的malloc()和free()函數進行記憶體的動態分配與釋放。因為,malloc()函數在運行時從自由記憶體中分配儲存單元。在C++中建立對象時會發生兩件事情:(1)為對象分配記憶體;(2)調用建構函式來初始化那個記憶體。而建構函式不允許向它傳遞記憶體位址來進行初始化。
在C++中使用new和delete來進行記憶體的動態分配與釋放。new會觸發類對象的建構函式,delete會觸發類對象的解構函式。
動態申請記憶體操作符 new
文法格式:類型名T *指標變數名P = new 類型名T(初值列表);
功能:在程式執行期間,申請用於存放T類型對象的連續的、未命名的記憶體空間,並依初值列表賦以初值。
結果值:成功:T類型的指標,指向新分配的記憶體。失敗:0(NULL)
new運算子是一個一元運算子。它隱含地產生一個函數來調用函數operator new(),從這個函數返回被分配記憶體對象的指標。
在new運算式中,可以在類型名後使用空圓括弧:new 資料類型T();功能:將初始化為資料類型T的預設值。 若new 資料類型T,沒有在T後加(),則表明沒有初始化資料類型T的預設值
new 資料類型T;與new 資料類型T();的區別
eg:int *ptrInt1 = new int; // Value is unknown
int *ptrInt1 = new int(); // Value is zero
說明:第一種情況下,在空閑儲存區中建立了一個int類型對象,但其值未知。在第二種情況下,在空閑儲存區中建立了一個int類型對象並初始化為int在全域範圍中的值(預設值)。而所有的全域基本類型在預設情況下的 初始值為0,故第二種情況在建立了對象後將其值初始化為0。
釋放記憶體操作符delete
文法格式:delete 指標變數名P;delete [ ] 指標變數名P;
功能:釋放指標P所指向的記憶體。P必須是new操作的傳回值。delete運算式首先調用解構函式,然後釋放記憶體(經常調用free( ))。若正在刪除的指標是0,則將不發生任何事情,是安全的。故開發人員經常在刪除指標後立即把指標賦值為0以免對它刪除兩次(對一個對象刪除兩次可能會產生某些問題)。
eg:delete pi;注意這時釋放了pi所指的目標的記憶體空間,也就是撤銷了該目標,稱動態記憶體釋放(dynamic memory
deallocation),但指標pi本身並沒有撤銷,該指標所佔記憶體空間並未釋放。 刪除一個指標p(delete p;)實際意思是刪除了p所指的目標(變數或對象等),釋放了它所佔的自由儲存區空間,而不是刪除p本身,p成了空懸指標。空懸指標是程式錯誤的一個根源)。建議這時將p置空(NULL)。
如何釋放單個對象的空閑空間
文法格式:delete 指標變數名P;指標變數名P = NULL;
功能:釋放指標P所指向的記憶體。P必須是new操作的傳回值。
刪除基於0的指標(null 指標)總是一種安全的操作。
如何為對象數組分配空閑空間
文法格式:資料類型T *指標變數名P = new 資料類型T[元素個數];
功能:在空閑空間指派至數組,指標P指向數組的第一個元素。如果在方括弧中的運算式的值為0,則就會分配沒有元素的數組,new運算式返回的指標是非0值,並且與其他指向任何對象的指標都不同。如果這個值在運行期間被確定是無效的,則結果不可預知。
保持平衡
涉及到new以及delete的主要規則相當簡單,使用delete來平衡每一個new。如果在new語句中使用了方括弧(即分配了對象數組),必須在delete後使用方括弧。無論何時使用new,一定要確保使用正確的delete格式。
淺拷貝與深拷貝
淺拷貝:實現對象間資料元素的一一對應複製。
深拷貝:當被複製的對象資料成員是指標類型時,不是複製該指標成員本身,而是將指標所指的對象進行複製。
深拷貝執行個體
// test.cpp : 定義控制台應用程式的進入點。 #include "stdafx.h"#include <iostream>using namespace std;// string類,深度複製執行個體class student{char *pName; //為了示範深複製,不用string類public:student(); //預設建構函式student(char *pname); //帶參數建構函式student(student &s); //複製(拷貝)建構函式~student(); //解構函式student & operator=(student &s); //複製賦值操作符}; student::student(){cout<<"Constructor";pName=NULL;cout<<"預設"<<endl;}student::student(char *pname){cout<<"Constructor";if(pName=new char[strlen(pname)+1])strcpy(pName,pname);cout<<pName<<endl;}student::~student(){cout<<"Destructor"<<pName<<endl;// if(pName) // pName[0]='\0';//===>>*pName='\0';//間接的把不是指標的變數置為空白('')。當然,此處沒必要也不需要判斷來間接設定其指向的變數為空白delete[] pName;//釋放字串} student::student(student &s){cout<<"Copy Constructor";if(s.pName){if(pName=new char[strlen(s.pName)+1])strcpy(pName,s.pName);}else pName=NULL;cout<<pName<<endl;}student & student::operator=(student &s){cout<<"Copy Assign operator";delete[] pName;//如原來已指派,應先撤銷,再重分配。先刪除自己分配的空間if(s.pName)//否則會發生記憶體流失{ if(pName=new char[strlen(s.pName)+1])strcpy(pName,s.pName);}else pName=NULL;cout<<pName<<endl;return *this;//this是一個指標,它的範圍在類的內部。而函數返回的是對象,所以 return *this;}int main(void){student s1("範英明"),s2("沈俊"),s3;student s4=s1;s3=s2;return 0;//加斷點調試,你回傳現堆變數刪除釋放(由系統自動釋放)的順序與其建立的順序相同。}void test(){int val=66;int *pval = &val;int **ppval = &pval;cout<<"val="<<val<<'\n'<<"**ppval="<<**ppval<<'\n';//輸出: 66 66**ppval=18;cout<<"val="<<val<<'\n'<<"**ppval="<<**ppval<<endl; //輸出: 18 18}對象成員的另一種訪問方式
指 針,聲明形式:類名 *對象指標名;eg:
Point A(5, 10);
Piont *ptr = NULL;
ptr = &A;
通過指標訪問對象成員,對象指標名->成員名,ptr->getx() 相當於 (*ptr).getx();
指標與引用的優劣
- 指標是個變數,可以把它再賦值成其它的地址(水性楊花) 。然而建立引用時必須進行初始化並且決不會再指向其他不同的變數(從一而終) 。
- 有null 指標,無Null 參考。
- 由於指標也是變數,所以可以有指標變數的引用。eg:int * a = NULL; int * & p = a;// (表示 int * 的引用p 初始化為 a) int b = 5; p = &b ; // p 是a 的一個別名,是一個指標
- 引用本身不是一種資料類型,所以沒有引用的引用,也沒有引用的指標。eg:int a; int & r = a; // (表示 int * 的引用p 初始化為 a) int & * p = & r ; // 錯, 企圖定義一個引用的指標由於指標也是變數,所以可以有指標變數的引用
- 使用引用和指標都可以使函數返回多個值。
- 在參數傳遞方面:引用比指標直觀,可讀性強,盡量使用引用代替指標,以下兩條除外。a,如果必須傳遞數組,使用指標加元素個數。b,如果參數可能無效(null),使用指標,並判斷(p == null)。如果輸入參數以值傳遞的方式傳遞對象,則宜改用“const &”方式來傳遞,這樣可以省去臨時對象的構造和析構過程,從而提高效率。
- 傳回值方面:如果返回動態分配的對象或記憶體,必須使用指標,引用可能引起記憶體流失。 operator = 的傳回值必須是 Cxxx &
- 可以使用指標或引用的時候,除了傳遞參數和傳回值,其它地方都應該是指標更好一些。
- 必須使用引用的地方:operator = 的傳回值必須是 Cxxx &;運算子多載的時候:operator + (const Cxxx& lhs, const Cxxx & rhs); 不能用指標。如果想重載[ ]並且想讓他和數組用法完全一致,那麼返回的類型必須是參考型別,此時對於類或結構體應注意對象是否需要深度拷貝。
執行個體
#include "stdafx.h"#include <iostream>using namespace std;void GetMemory1(char* p,int num){p=new char[num];}//如果非得要用指標參數去申請記憶體,那麼應該改用“指向指標的指標”void GetMemory2(char** p,int num){*p=new char[num];}//或者改用“指向指標的引用”void GetMemory3(char* &p,int num){p=new char[num]; }//或者改用函數傳回值類傳遞動態記憶體char* GetMemory4(int num){char *p=new char[num]; return p;//必須放回的是堆記憶體儲存區,靜態全域儲存區,或者返回唯讀常量儲存區}char* GetMemory5(){char p[]="hello world";//數組p是棧記憶體,沒有用new,"hello world"給數組p賦值,數組p是被分配連續的記憶體空間 return p;//return語句返回不能是指向"棧記憶體"的指標,因為該記憶體在函數結束時自動消亡}char* GetMemory6(){char* p="hello world";//"hello world"是字串常量,位於常量儲存區,它在程式生命期內恒定不變。無論什麼時候調用GetMemory6,它返回的始終是同一個“唯讀”的記憶體塊。return p;}void test(){// char* str=NULL;// GetMemory1(str,100);//str仍然為NULL// strcpy(str,"hello");//運行有誤// cout<<str<<endl;// delete [] str; // str=NULL;// char* str=NULL;// GetMemory2(&str,100);// strcpy(str,"hello");// cout<<str<<endl;// delete [] str; // str=NULL;// char* str=NULL;// GetMemory3(str,100);// cout<<str<<endl;// strcpy(str,"hello");// cout<<str<<endl;// delete [] str; // str=NULL;// char* str=NULL;// str=GetMemory4(100);// strcpy(str,"hello");// cout<<str<<endl;// delete [] str; // str=NULL;// char* str=NULL;// str=GetMemory5();//GetMemory5()返回後str不再是NULL指標,但str的內容不是"hello world",而是垃圾資訊,可以是亂碼// strcpy(str,"hello");// cout<<str<<endl; char* str=NULL;str=GetMemory6();//GetMemory6()返回的是一個"唯讀"的記憶體塊cout<<str<<endl; //輸出"hello world"strcpy(str,"hello");cout<<str<<endl; }int _tmain(int argc, _TCHAR* argv[]){test();return 0;}那些總結
- 指標p指向常量字串“world”(位於靜態儲存區,內容為world\0),常量字串的內容是不可以被修改的。
- return語句不可返回指向“棧記憶體”的“指標”或者“引用”,因為該記憶體在函數體結束時被自動銷毀。