zhuzhu @ 2005-10-21 15:02
別人做的歸納,系統(tǒng)多了~~
一 :關(guān)于指針和堆的內(nèi)存分配 先來介紹一下指針 : 指針一種類型,理論上來說它包含其他變量的地址,因此有的書上也叫它:地址變量。既然指針是一個類型,是類型就有大小,在達內(nèi)的服務(wù)器上或者普通的PC機上,都是4個字節(jié)大小,里邊只是存儲了一個變量的地址而已。不管什么類型的指針,char * ,int * ,int (*) ,string * ,float * ,都是說明了本指針所指向的地址空間是什么類型而已,了解了這個基本上所有的問題都好象都變的合理了。 在C++中,申請和釋放堆中分配的存貯空間,分別使用new和delete的兩個運算符來完成: 指針類型 指針變量名=new 指針類型 (初始化); delete 指針名; 例如:1、 int *p=new int(0); 它與下列代碼序列大體等價: 2、int tmp=0, *p=&tmp; 區(qū)別:p所指向的變量是由庫操作符new()分配的,位于內(nèi)存的堆區(qū)中,并且該對象未命名。 下面是關(guān)于new 操作的說明?。骸〔糠忠?lt;<C++面向?qū)ο箝_發(fā)>> 1、new運算符返回的是一個指向所分配類型變量(對象)的指針。對所創(chuàng)建的變量或?qū)ο?,都是通過該指針來間接操作的,而動態(tài)創(chuàng)建的對象本身沒有名字。 2、一般定義變量和對象時要用標識符命名,稱命名對象,而動態(tài)的稱無名對象(請注意與棧區(qū)中的臨時對象的區(qū)別,兩者完全不同:生命期不同,操作方法不同,臨時變量對程序員是透明的)。 3、堆區(qū)是不會在分配時做自動初始化的(包括清零),所以必須用初始化式(initializer)來顯式初始化。new表達式的操作序列如下:從堆區(qū)分配對象,然后用括號中的值初始化該對象。 下面是從堆中申請數(shù)組 1、申請數(shù)組空間: 指針變量名=new 類型名[下標表達式]; 注意:“下標表達式”不是常量表達式,即它的值不必在編譯時確定,可以在運行時確定。這就是堆的一個非常顯著的特點,有的時候程序員本身都不知道要申請能夠多少內(nèi)存的時候,堆就變的格外有用。 2、釋放數(shù)組空間: delete [ ]指向該數(shù)組的指針變量名; 注意:方括號非常重要的,如果delete語句中少了方括號,因編譯器認為該指針是指向數(shù)組第一個元素的,會產(chǎn)生回收不徹底的問題(只回收了第一個元素所占空間),我們通常叫它“內(nèi)存泄露”,加了方括號后就轉(zhuǎn)化為指向數(shù)組的指針,回收整個數(shù)組。delete [ ]的方括號中不需要填數(shù)組元素數(shù),系統(tǒng)自知。即使寫了,編譯器也忽略。<<Think in c++>>上說過以前的delete []方括號中是必須添加個數(shù)的,后來由于很容易出錯,所以后來的版本就改進了這個缺陷。 下面是個例子,VC上編譯通過 #include<iostream> using namespace std; //#include <iostream.h> //for VC #include <string.h> void main(){ int n; char *p; cout<<"請輸入動態(tài)數(shù)組的元素個數(shù)"<<endl; cin>>n; //n在運行時確定,可輸入17 p=new char[n]; //申請17個字符(可裝8個漢字和一個結(jié)束符)的內(nèi)存空間strcpy(pc,“堆內(nèi)存的動態(tài)分配”);// cout<<p<<endl; delete []p;//釋放pc所指向的n個字符的內(nèi)存空間return ; } 通過指針使堆空間,編程中的幾個可能問題 1.動態(tài)分配失敗。返回一個空指針(NULL),表示發(fā)生了異常,堆資源不足,分配失敗。 data = new double [m]; //申請空間 if ((data ) == 0)…… //或者==NULL 2.指針刪除與堆空間釋放。刪除一個指針p(delete p;)實際意思是刪除了p所指的目標(變量或?qū)ο蟮龋?,釋放了它所占的堆空間,而不是刪除p本身,釋放堆空間后,p成了空懸指針,不能再通過p使用該空間,在重新給p賦值前,也不能再直接使用p。 3.內(nèi)存泄漏(memory leak)和重復(fù)釋放。new與delete 是配對使用的, delete只能釋放堆空間。如果new返回的指針值丟失,則所分配的堆空間無法回收,稱內(nèi)存泄漏,同一空間重復(fù)釋放也是危險的,因為該空間可能已另分配,而這個時候又去釋放的話,會導(dǎo)致一個很難查出來的運行時錯誤。所以必須妥善保存new返回的指針,以保證不發(fā)生內(nèi)存泄漏,也必須保證不會重復(fù)釋放堆內(nèi)存空間。 4.動態(tài)分配的變量或?qū)ο蟮纳?。無名變量的生命期并不依賴于建立它的作用域,比如在函數(shù)中建立的動態(tài)對象在函數(shù)返回后仍可使用。我們也稱堆空間為自由空間(free store)就是這個原因。但必須記住釋放該對象所占堆空間,并只能釋放一次,在函數(shù)內(nèi)建立,而在函數(shù)外釋放是一件很容易失控的事,往往會出錯,所以永遠不要在函數(shù)體內(nèi)申請空間,讓調(diào)用者釋放,這是一個很差的做法。你再怎么小心翼翼也可能會帶來錯誤。 類在堆中申請內(nèi)存 : 通過new建立的對象要調(diào)用構(gòu)造函數(shù),通過deletee刪除對象要調(diào)用析構(gòu)函數(shù)。 CGoods *pc; pc=new CGoods; //分配堆空間,并構(gòu)造一個無名對象 //的CGoods對象; ……. delete pc; //先析構(gòu),然后將內(nèi)存空間返回給堆; 堆對象的生命期并不依賴于建立它的作用域,所以除非程序結(jié)束,堆對象(無名對象)的生命期不會到期,并且需要顯式地用delete語句析構(gòu)堆對象,上面的堆對象在執(zhí)行delete語句時,C++自動調(diào)用其析構(gòu)函數(shù)。 正因為構(gòu)造函數(shù)可以有參數(shù),所以new后面類(class)類型也可以有參數(shù)。這些參數(shù)即構(gòu)造函數(shù)的參數(shù)。 但對創(chuàng)建數(shù)組,則無參數(shù),并只調(diào)用缺省的構(gòu)造函數(shù)。見下例類說明: class CGoods{ char Name[21]; int Amount; float Price; float Total_value; public: CGoods(){}; //缺省構(gòu)造函數(shù)。因已有其他構(gòu)造函數(shù),系統(tǒng)不會再自動生成缺省構(gòu)造,必須顯式聲明。 CGoods(char* name,int amount ,float price){ strcpy(Name,name); Amount=amount; Price=price; Total_value=price*amount; } ……};//類聲明結(jié)束 下面是調(diào)用機制 : void main(){ int n; CGoods *pc,*pc1,*pc2; pc=new CGoods(“hello”,10,118000); //調(diào)用三參數(shù)構(gòu)造函數(shù) pc1=new CGoods(); //調(diào)用缺省構(gòu)造函數(shù) cout<<”輸入商品類數(shù)組元素數(shù)”<<endl; cin>>n; pc2=new CGoods[n]; //動態(tài)建立數(shù)組,不能初始化,調(diào)用n次缺省構(gòu)造函數(shù) …… delete pc; delete pc1; delete []pc2; } 申請堆空間之后構(gòu)造函數(shù)運行; 釋放堆空間之前析構(gòu)函數(shù)運行; 再次強調(diào):由堆區(qū)創(chuàng)建對象數(shù)組,只能調(diào)用缺省的構(gòu)造函數(shù),不能調(diào)用其他任何構(gòu)造函數(shù)。如果沒有缺省的構(gòu)造函數(shù),則不能創(chuàng)建對象數(shù)組。 ---------------------下面我們再來看一下指針數(shù)組和數(shù)組指針――――――――――――― 如果你想了解指針最好理解以下的公式 : (1)int*ptr;//指針所指向的類型是int (2)char*ptr;//指針所指向的的類型是char (3)int**ptr;//指針所指向的的類型是int* (也就是一個int * 型指針) (4)int(*ptr)[3];//指針所指向的的類型是int()[3] //二維指針的聲明 (1)指針數(shù)組:一個數(shù)組里存放的都是同一個類型的指針,通常我們把他叫做指針數(shù)組。 比如 int * a[10];它里邊放了10個int * 型變量,由于它是一個數(shù)組,已經(jīng)在棧區(qū)分配了10個(int * )的空間,也就是32位機上是40個byte,每個空間都可以存放一個int型變量的地址,這個時候你可以為這個數(shù)組的每一個元素初始化,在,或者單獨做個循環(huán)去初始化它。 例子: int * a[2]={ new int(3),new int(4) }; //在棧區(qū)里聲明一個int * 數(shù)組,它的每一個元素都在堆區(qū)里申請了一個無名變量,并初始化他們?yōu)椋澈停矗⒁獯朔N聲明方式具有缺陷,VC下會報錯 例如?。? int * a[2]={new int[3],new int[3]}; delete a[0]; delet a[10]; 但是我不建議達內(nèi)的學(xué)生這么寫,可能會造成歧義,不是好的風(fēng)格,并且在VC中會報錯,應(yīng)該寫成如下?。? int * a[2]; a[0]= new int[3]; a[1]=new int[3]; delete a[0]; delet a[10]; 這樣申請內(nèi)存的風(fēng)格感覺比較符合大家的習(xí)慣;由于是數(shù)組,所以就不可以delete a;編譯會出警告.delete a[1]; 注意這里 是一個數(shù)組,不能delete [] ; ( 2 ) 數(shù)組指針 : 一個指向一維或者多維數(shù)組的指針; int * b=new int[10]; 指向一維數(shù)組的指針b ; 注意,這個時候釋放空間一定要delete [] ,否則會造成內(nèi)存泄露, b 就成為了空懸指針. int (*b2)[10]=new int[10][10]; 注意,這里的b2指向了一個二維int型數(shù)組的首地址. 注意:在這里,b2等效于二維數(shù)組名,但沒有指出其邊界,即最高維的元素數(shù)量,但是它的最低維數(shù)的元素數(shù)量必須要指定!就像指向字符的指針,即等效一個字符串,不要把指向字符的指針說成指向字符串的指針。這與數(shù)組的嵌套定義相一致。 int(*b3) [30] [20]; //三級指針――>指向三維數(shù)組的指針; int (*b2) [20]; //二級指針; b3=new int [1] [20] [30]; b2=new int [30] [20]; 兩個數(shù)組都是由600個整數(shù)組成,前者是只有一個元素的三維數(shù)組,每個元素為30行20列的二維數(shù)組,而另一個是有30個元素的二維數(shù)組,每個元素為20個元素的一維數(shù)組。 刪除這兩個動態(tài)數(shù)組可用下式: delete [] b3; //刪除(釋放)三維數(shù)組; delete [] b2; //刪除(釋放)二維數(shù)組; 再次重申:這里的b2的類型是int (*) ,這樣表示一個指向二維數(shù)組的指針。 b3表示一個指向(指向二維數(shù)組的指針)的指針,也就是三級指針. ( 3 ) 二級指針的指針 看下例 : int (**p)[2]=new (int(*)[3])[2]; p[0]=new int[2][2]; p[1]=new int[2][2]; p[2]=new int[2][2]; delete [] p[0]; delete [] p[1]; delete [] p[2]; delete [] p; 注意此地方的指針類型為int (*),碰到這種問題就把外邊的[2]先去掉,然后回頭先把int ** p=new int(*)[n]申請出來,然后再把外邊的[2]附加上去; p代表了一個指向二級指針的指針,在它申請空間的時候要注意指針的類型,那就是int (*)代表二級指針,而int (**)顧名思義就是代表指向二級指針的指針了。既然是指針要在堆里申請空間,那首先要定義它的范圍:(int(*)[n])[2],n 個這樣的二級指針,其中的每一個二級指針的最低維是2個元素.(因為要確定一個二級指針的話,它的最低維數(shù)是必須指定的,上邊已經(jīng)提到)。然后我們又分別為p[0],p[1],p[2]…在堆里分配了空間,尤其要注意的是:在釋放內(nèi)存的時候一定要為p[0],p[1],p[2],單獨delete[] ,否則又會造成內(nèi)存泄露,在delete[]p 的時候一定先delete p[0]; delete p[1],然后再把給p申請的空間釋放掉 delete [] p ……這樣會防止內(nèi)存泄露。 (3)指針的指針; int ** cc=new (int*)[10]; 聲明一個10個元素的數(shù)組,數(shù)組每個元素都是一個int *指針,每個元素還可以單獨申請空間,因為cc的類型是int*型的指針,所以你要在堆里申請的話就要用int *來申請; 看下邊的例子 (vc & GNU編譯器都已經(jīng)通過); int ** a= new int * [2]; //申請兩個int * 型的空間 a[1]=new int[3]; //為a的第二個元素又申請了3個int 型空間,a[1]指向了此空間首地址處 a[0]=new int[4]; ////為a的第一個元素又申請了4個int 型空間,a[0] 指向了此空間的首地址處 int * b; a[0][0]=0; a[0][1]=1; b=a[0]; delete [] a[0] //一定要先釋放a[0],a[1]的空間,否則會造成內(nèi)存泄露.; delete [] a[1]; delete [] a; b++; cout<<*b<<endl; //隨機數(shù) 注意?。阂驗閍 是在堆里申請的無名變量數(shù)組,所以在delete 的時候要用delete [] 來釋放內(nèi)存,但是a的每一個元素又單獨申請了空間,所以在delete [] a之前要先delete [] 掉 a[0],a[1],否則又會造成內(nèi)存泄露. (4) 指針數(shù)組?。? 我們再來看看第二種?。憾S指針數(shù)組 int *(*c)[3]=new int *[3][2]; 如果你對上邊的介紹的個種指針類型很熟悉的話,你一眼就能看出來c是個二級指針,只不過指向了一個二維int * 型的數(shù)組而已,也就是二維指針數(shù)組。 例子?。? int *(*b)[10]=new int*[2][10];// b[0][0]=new int[100]; b[0][1]=new int[100]; *b[0][0]=1; cout <<*b[0][0]<<endl; //打印結(jié)果為1 delete [] b[0][0]; delete [] b[0][1]; delete [] b; cout<<*b[0][0]<<endl; //打印隨機數(shù) 這里只為大家還是要注意內(nèi)存泄露的問題,在這里就不再多說了。 如果看了上邊的文章,大家估計就會很熟悉,這個b是一個二維指針,它指向了一個指針數(shù)組 第二種?。? int **d[2];表示一個擁有兩個元素數(shù)組,每一個元素都是int ** 型,這個指向指針的指針:) d不管怎樣變終究也是個數(shù)組,呵呵, 如果你讀懂了上邊的,那下邊的聲明就很簡單了: d[0]=new int *[10]; d[1]=new int * [10]; delete [] d[0]; delete [] d[1]; 具體的就不再多說了 :) 二?。骸『瘮?shù)指針 關(guān)于函數(shù)指針,我想在我們可能需要寫個函數(shù),這個函數(shù)體內(nèi)要調(diào)用另一個函數(shù),可是由于項目的進度有限,我們不知道要調(diào)用什么樣的函數(shù),這個時候可能就需要一個函數(shù)指針; int a();這個一個函數(shù)的聲明; ing (*b)();這是一個函數(shù)指針的聲明; 讓我們來分析一下,左邊圓括弧中的星號是函數(shù)指針聲明的關(guān)鍵。另外兩個元素是函數(shù)的返回類型(void)和由邊圓括弧中的入口參數(shù)(本例中參數(shù)是空)。注意本例中還沒有創(chuàng)建指針變量-只是聲明了變量類型。目前可以用這個變量類型來創(chuàng)建類型定義名及用sizeof表達式獲得函數(shù)指針的大?。? unsigned psize = sizeof (int (*) ()); 獲得函數(shù)指針的大小 // 為函數(shù)指針聲明類型定義 typedef int (*PFUNC) (); PFUNC是一個函數(shù)指針,它指向的函數(shù)沒有輸入?yún)?shù),返回int。使用這個類型定義名可以隱藏復(fù)雜的函數(shù)指針語法,就我本人強烈建議我們大內(nèi)弟子使用這種方式來定義; 下面是一個例子,一個簡單函數(shù)指針的回調(diào)(在GNU編譯器上通過,在VC上需要改變一個頭文件就OK了) #include<iostream> //GNU 編譯器 g++ 實現(xiàn) using namespace std; /* //vc 的實現(xiàn) #include "stdafx.h" #include <iostream.h> */ #define DF(F) int F(){ cout<<"this is in function "<<#F<<endl;\ return 0; \ } //聲明定義DF(F)替代 int F();函數(shù); DF(a); DF(b); DF(c); DF(d); DF(e); DF(f); DF(g); DF(h); DF(i); //聲明定義函數(shù) a b c d e f g h i // int (*pfunc)(); //一個簡單函數(shù)指針的聲明 typedef int(*FUNC)(); //一個函數(shù)指針類型的聲明 FUNC ff[] = {a,b,c,d,e,f,g,h,i}; //聲明一個函數(shù)指針數(shù)組,并初始化為以上聲明的a,b,c,d,e,f,g,h,i函數(shù) FUNC func3(FUNC vv){ //定義函數(shù)func3,傳入一個函數(shù)指針,并且返回一個同樣類型的函數(shù)指針 vv(); return vv; } /*FUNC func4(int (*vv)()){ //func3的另一種實現(xiàn) vv(); return vv; }*/ int main(){ for(int i=0;i<sizeof(ff)/sizeof (FUNC);i++){ //循環(huán)調(diào)用函數(shù)指針 FUNC r=func3(ff[ i ]); cout<<r()<<endl; //輸出返回值,只是返回了0 } return 0; } 到目前為止,我們只討論了函數(shù)指針及回調(diào)而沒有去注意ANSI C/C++的編譯器規(guī)范。許多編譯器有幾種調(diào)用規(guī)范。如在Visual C++中,可以在函數(shù)類型前加_cdecl,_stdcall或者_pascal來表示其調(diào)用規(guī)范(默認為_cdecl)。C++ Builder也支持_fastcall調(diào)用規(guī)范。調(diào)用規(guī)范影響編譯器產(chǎn)生的給定函數(shù)名,參數(shù)傳遞的順序(從右到左或從左到右),堆棧清理責(zé)任(調(diào)用者或者被調(diào)用者)以及參數(shù)傳遞機制(堆棧,CPU寄存器等)。 好了,先到此為止吧,寫這篇文章耗費了基本上快半天的時間了,很多事情還沒有做,等改天有時間再回來整理,所有的源程序都放在openlab3服務(wù)器上我的目錄下lib/cpp下,大家可以去拿。不知道的登陸openlab3 然后cd ~chengx/lib/cpp就可以看到了。 還有很復(fù)雜的聲明可能也是一種挑戰(zhàn) 比如<<Think in c++>>里的 int (*(*f4())[10]();的聲明,f4是一個返回指針的函數(shù),該指針指向了含有10個函數(shù)指針的數(shù)組,這些函數(shù)返回整形值;不是這個函數(shù)有特別之處,而是Bruce Eckel 說的“從右到左的辨認規(guī)則”是一種很好的方法,值得我們?nèi)W(xué)習(xí),感謝他:) 最后我想應(yīng)該跟大家說一下,寫程序應(yīng)該就象JERRY所說的:簡單就是美;我們應(yīng)該遵循一個原則 : KISS (Keep It Simple,Stupid ,盡量保持程序簡單 出自 :《Practical C programming》),把自己的程序盡量的簡單明了,這是個非常非常好的習(xí)慣。 |
|
來自: bluecrystal > 《VC》