內存分類
在C++中菜秦,內存分成5個區(qū),他們分別是堆尔店、棧褪尝、自由存儲區(qū)、全局/靜態(tài)存儲區(qū)和常量存儲區(qū)避诽。
- 棧:在執(zhí)行函數(shù)時沙庐,函數(shù)內局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結束時這些存儲單元自動被釋放棉安。棧內存分配運算內置于處理器的指令集中贡耽,效率很高鹊汛,但是分配的內存容量有限刁憋。
- 堆:就是那些由 new分配的內存塊滥嘴,他們的釋放編譯器不去管若皱,由我們的應用程序去控制尘颓,一般一個new就要對應一個 delete疤苹。如果程序員沒有釋放掉痰催,那么在程序結束后,操作系統(tǒng)會自動回收逸吵。
- 自由存儲區(qū):就是那些由malloc等分配的內存塊扫皱,他和堆是十分相似的捷绑,不過它是用free來結束自己的生命的粹污。
- 全局/靜態(tài)存儲區(qū):全局變量和靜態(tài)變量被分配到同一塊內存中壮吩,在以前的C語言中加缘,全局變量又分為初始化的和未初始化的拣宏,在C++里面沒有這個區(qū)分了勋乾,他們共同占用同一塊內存區(qū)嗡善。
- 常量存儲區(qū):這是一塊比較特殊的存儲區(qū)滤奈,他們里面存放的是常量蜒程,不允許修改伺帘。
堆棧區(qū)別
void f() { int* p=new int[5]; }
這條短短的一句話就包含了堆與棧伪嫁,看到new张咳,我們首先就應該想到脚猾,我們分配了一塊堆內存,那么指針p呢砰奕?他分配的是一塊棧內存军援,所以這句話的意思就是:在棧內存中存放了一個指向一塊堆內存的指針p称勋。在程序會先確定在堆中分配內存的大小赡鲜,然后調用operator new分配內存,然后返回這塊內存的首地址醉鳖,放入棧中哮内,
主要的區(qū)別由以下幾點:
- 管理方式不同
棧由編譯器自動釋放北发,堆必須由我們手動釋放琳拨,或者程序結束后系統(tǒng)回收狱庇。 - 空間大小不同
堆一般沒有限制(32位系統(tǒng)理論是4G)颜启,棧有限制很小 - 能否產生碎片不同
堆因為頻繁的new缰盏、delete導致地址不連續(xù)口猜,產生碎片降低程序效率暮的。棧因為數(shù)據(jù)結構設計淌实,先進后出拆祈,不存在此問題放坏。 - 生長方向不同
對于堆來講,生長方向是向上的钧敞,也就是向著內存地址增加的方向溉苛;對于棧來講愚战,它的生長方向是向下的寂玲,是向著內存地址減小的方向增長。 - 分配方式不同
堆都是動態(tài)分配的想许,沒有靜態(tài)分配的堆流纹。棧有2種分配方式:靜態(tài)分配和動態(tài)分配。靜態(tài)分配是編譯器完成的较雕,比如局部變量的分配挚币。動態(tài)分配由alloca函數(shù)進行分配妆毕,但是棧的動態(tài)分配和堆是不同的笛粘,他的動態(tài)分配是由編譯器進行釋放薪前,無需我們手工實現(xiàn)示括。 - 分配效率不同
棧是機器系統(tǒng)提供的數(shù)據(jù)結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址鳍侣,壓棧出棧都有專門的指令執(zhí)行倚聚,這就決定了棧的效率比較高秉沼。堆則是C/C++函數(shù)庫提供的唬复,它的機制是很復雜的敞咧,例如為了分配一塊內存休建,庫函數(shù)會按照一定的算法(具體的算法可以參考數(shù)據(jù)結構/操作系統(tǒng))在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內存碎片太多)茵烈,就有可能調用系統(tǒng)功能去增加程序數(shù)據(jù)段的內存空間呜投,這樣就有機會分到足夠大小的內存仑荐,然后進行返回粘招。顯然洒扎,堆的效率比棧要低得多逊笆。
內存錯誤的幾種姿勢及對策
- 內存分配未成功难裆,卻使用了它。需要使用assert(p!=NULL)或if (p!=NULL)進行防錯
- 內存分配雖然成功褂痰,但是尚未初始化就引用它缩歪。需要對分配的內存進行初始化匪蝙。
- 內存分配成功并且已經(jīng)初始化逛球,但操作越過了內存的邊界颤绕。
- 忘記了釋放內存奥务,造成內存泄露氯葬。需要配套使用malloc和free帚称,new和delete
- 釋放了內存卻繼續(xù)使用它世杀。
- 程序中的對象調用關系過于復雜肝集,實在難以搞清楚某個對象究竟是否已經(jīng)釋放了內存杏瞻,此時應該重新設計數(shù)據(jù)結構捞挥,從根本上解決對象管理的混亂局面砌函。
- 函數(shù)的return語句寫錯了讹俊,注意不要返回指向“棧內存”的“指針”或者“引用”仍劈,因為該內存在函數(shù)體結束時被自動銷毀贩疙。
- 使用free或delete釋放了內存后这溅,沒有將指針設置為NULL芍躏。導致產生“野指針”对竣。
野指針(也就是指向不可用內存區(qū)域的指針)
如何避免:
- 規(guī)則1:用malloc或new申請內存之后否纬,應該立即檢查指針值是否為NULL临燃。防止使用指針值為NULL的內存膜廊。
- 規(guī)則2:不要忘記為數(shù)組和動態(tài)內存賦初值爪瓜。防止將未被初始化的內存作為右值使用铆铆。
- 規(guī)則3:避免數(shù)組或指針的下標越界薄货,特別要當心發(fā)生“多1”或者“少1”操作谅猾。
- 規(guī)則4:動態(tài)內存的申請與釋放必須配對,防止內存泄漏贼涩。
- 規(guī)則5:用free或delete釋放了內存之后遥倦,立即將指針設置為NULL袒哥,防止產生“野指針”堡称。
指針和數(shù)組
- 修改內容
指針和數(shù)組對字符串的操作很像却紧,但是有區(qū)別晓殊,它們在內存中的存儲區(qū)域不同
如下,字符數(shù)組存儲在全局數(shù)據(jù)區(qū)或棧區(qū)介汹,內容可修改嘹承。而字符串存在常量區(qū)如庭,只讀不能改豪娜。
char a[] = “hello”;
a[0] = ‘X’;
cout << a << endl;
char *p = “world”; // 注意p指向常量字符串
p[0] = ‘X’; // 編譯器不能發(fā)現(xiàn)該錯誤
cout << p << endl;
- 內容復制和比較
不能對數(shù)組名進行直接復制與比較,應該用strcpy和strcmp
// 數(shù)組…
char a[] = "hello";
char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)
…
// 指針…
//tip:字符串是特殊情況卖擅,測試請使用int a[]={1惩阶,2断楷,3冬筒,4恐锣,5}
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // 不要用 p = a;因為p=a只是把a的地址賦給p,而不是內容舞痰;
if(strcmp(p, a) == 0) // 不要用 if (p == a)土榴,p==a也是比較的地址,非內容
…
- 計算內存容量
char a[] = "hello world";
char *p = a;
// 字符數(shù)組用sizeof得到數(shù)組的總容量响牛,注意玷禽,若a[10]則是10,動態(tài)指定的會加上一個字節(jié)的空字符
cout<< sizeof(a) << endl; // 12字節(jié)
//p是一個指針,sizeof(p)得到的是對sizeof(char *)的容量矢赁,不是p所指的內存容量。
cout<< sizeof(p) << endl; // 4字節(jié)
注意當數(shù)組作為函數(shù)的參數(shù)進行傳遞時贬丛,該數(shù)組自動退化為同類型的指針坯台。如下示例中,不論數(shù)組a的容量是多少瘫寝,sizeof(a)始終等于sizeof(char *)蜒蕾。
void Func(char a[100]){
cout<< sizeof(a) << endl; // 4字節(jié)而不是100字節(jié)
}
指針作為參數(shù)傳遞
如果函數(shù)的參數(shù)是一個指針,不要指望用該指針去申請動態(tài)內存焕阿。如下示例中咪啡,Test函數(shù)的語句GetMemory(str, 200)并沒有使str獲得期望的內存,
str依舊是NULL暮屡,為什么撤摸?
void GetMemory(char *p, int num){
p = (char *)malloc(sizeof(char) * num);
}
void Test(void){
char *str = NULL;
GetMemory(str, 100); // str 仍然為 NULL
strcpy(str, "hello"); // 運行錯誤
}
毛病出在函數(shù)GetMemory中。編譯器總是要為函數(shù)的每個參數(shù)制作臨時副本褒纲,指針參數(shù)p的副本是 _p准夷,編譯器使 _p=p。如果函數(shù)體內的程序修改了_p的內容莺掠,就導致參數(shù)p的內容作相應的修改衫嵌。這就是指針可以用作輸出參數(shù)的原因。在本例中彻秆,_p申請了新的內存楔绞,只是把 _p所指的內存地址改變了结闸,但是p絲毫未變。所以函數(shù)GetMemory并不能輸出任何東西酒朵。事實上桦锄,每執(zhí)行一次GetMemory就會泄露一塊內存,因為沒有用free釋放內存蔫耽。
如果非得要用指針參數(shù)去申請內存结耀,那么應該改用“指向指針的指針”,見示例:
void GetMemory2(char **p, int num){
*p = (char *)malloc(sizeof(char) * num);
}
void Test2(void){
char *str = NULL;
GetMemory2(&str, 100); // 注意參數(shù)是 &str匙铡,而不是str
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
由于“指向指針的指針”這個概念不容易理解饼记,我們可以用函數(shù)返回值來傳遞動態(tài)內存。這種方法更加簡單慰枕,見示例:
char *GetMemory3(int num){
char *p = (char *)malloc(sizeof(char) * num);
return p;
}
void Test3(void){
char *str = NULL;
str = GetMemory3(100);
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
用函數(shù)返回值來傳遞動態(tài)內存這種方法雖然好用具则,但是常常有人把return語句用錯了。這里強調不要用return語句返回指向“棧內存”的指針具帮,因為該內存在函數(shù)結束時自動消亡博肋,見示例:
char *GetString(void){
char p[] = "hello world";
return p; // 編譯器將提出警告
}
void Test4(void){
char *str = NULL;
str = GetString(); // str 的內容是垃圾
cout<< str << endl;
}
用調試器逐步跟蹤Test4,發(fā)現(xiàn)執(zhí)行str = GetString語句后str不再是NULL指針蜂厅,但是str的內容不是“hello world”而是垃圾匪凡。
如果把上述示例改寫成如下示例,會怎么樣掘猿?
char *GetString2(void){
char *p = "hello world";
return p;
}
void Test5(void){
char *str = NULL;
str = GetString2();
cout<< str << endl;
}
野指針
“野指針”不是NULL指針病游,是指向“垃圾”內存的指針。人們一般不會錯用NULL指針稠通,因為用if語句很容易判斷衬衬。但是“野指針”是很危險的,if語句對它不起作用改橘。 “野指針”的成因主要有三種:
- 指針變量沒有被初始化滋尉。任何指針變量剛被創(chuàng)建時不會自動成為NULL指針,它的缺省值是隨機的飞主,它會亂指一氣狮惜。所以,指針變量在創(chuàng)建的同時應當被初始化碌识,要么將指針設置為NULL碾篡,要么讓它指向合法的內存。例如:
char *p = NULL;
char *str = (char *) malloc(100);
- 指針p被free或者delete之后筏餐,沒有置為NULL开泽,讓人誤以為p是個合法的指針。
- 指針操作超越了變量的作用域范圍胖烛。這種情況讓人防不勝防眼姐,示例程序如下:
class A{
public:
void Func(void){ cout << “Func of class A” << endl; }
};
void Test(void){
A *p;
{
A a;
p = &a; // 注意 a 的生命期
}
p->Func(); // p是“野指針”
}
函數(shù)Test在執(zhí)行語句p->Func()時,對象a已經(jīng)消失佩番,而p是指向a的众旗,所以p就成了“野指針”。但奇怪的是我運行這個程序時居然沒有出錯趟畏,這可能與編譯器有關贡歧。
malloc/free和new/delete
malloc與free是C++/C語言的標準庫函數(shù),new/delete是C++的運算符赋秀。它們都可用于申請動態(tài)內存和釋放內存利朵。
對于非內部數(shù)據(jù)類型的對象而言,光用maloc/free無法滿足動態(tài)對象的要求猎莲。對象在創(chuàng)建的同時要自動執(zhí)行構造函數(shù)绍弟,對象在消亡之前要自動執(zhí)行析構函數(shù)。由于malloc/free是庫函數(shù)而不是運算符著洼,不在編譯器控制權限之內樟遣,不能夠把執(zhí)行構造函數(shù)和析構函數(shù)的任務強加于malloc/free。
因此C++語言需要一個能完成動態(tài)內存分配和初始化工作的運算符new身笤,以及一個能完成清理與釋放內存工作的運算符delete豹悬。注意new/delete不是庫函數(shù)。我們先看一看malloc/free和new/delete如何實現(xiàn)對象的動態(tài)內存管理液荸,見示例:
class Obj{
public :
Obj(void){ cout << “Initialization” << endl; }
~Obj(void){ cout << “Destroy” << endl; }
void Initialize(void){ cout << “Initialization” << endl; }
void Destroy(void){ cout << “Destroy” << endl; }
};
void UseMallocFree(void){
Obj *a = (obj *)malloc(sizeof(obj)); // 申請動態(tài)內存
a->Initialize(); // 初始化
//…
a->Destroy(); // 清除工作
free(a); // 釋放內存
}
void UseNewDelete(void){
Obj *a = new Obj; // 申請動態(tài)內存并且初始化
//…
delete a; // 清除并且釋放內存
}
類Obj的函數(shù)Initialize模擬了構造函數(shù)的功能瞻佛,函數(shù)Destroy模擬了析構函數(shù)的功能。函數(shù)UseMallocFree中娇钱,由于malloc/free不能執(zhí)行構造函數(shù)與析構函數(shù)伤柄,必須調用成員函數(shù)Initialize和Destroy來完成初始化與清除工作。函數(shù)UseNewDelete則簡單得多文搂。
所以我們不要企圖用malloc/free來完成動態(tài)對象的內存管理响迂,應該用new/delete。由于內部數(shù)據(jù)類型的“對象”沒有構造與析構的過程细疚,對它們而言malloc/free和new/delete是等價的蔗彤。
既然new/delete的功能完全覆蓋了malloc/free,為什么C++不把malloc/free淘汰出局呢疯兼?這是因為C++程序經(jīng)常要調用C函數(shù)然遏,而C程序只能用malloc/free管理動態(tài)內存。
如果用free釋放“new創(chuàng)建的動態(tài)對象”吧彪,那么該對象因無法執(zhí)行析構函數(shù)而可能導致程序出錯待侵。如果用delete釋放“malloc申請的動態(tài)內存”,結果也會導致程序出錯姨裸,但是該程序的可讀性很差秧倾。所以new/delete必須配對使用怨酝,malloc/free也一樣。
內存耗盡
如果在申請動態(tài)內存時找不到足夠大的內存塊那先,malloc和new將返回NULL指針农猬,宣告內存申請失敗。通常有三種方式處理“內存耗盡”問題售淡。
- 判斷指針是否為NULL斤葱,如果是則馬上用return語句終止本函數(shù)。例如:
void Func(void){
A *a = new A;
if(a == NULL)
return;
…
}
- 判斷指針是否為NULL揖闸,如果是則馬上用exit(1)終止整個程序的運行揍堕。例如:
void Func(void){
A *a = new A;
if(a == NULL){
cout << “Memory Exhausted” << endl;
exit(1);
}
…
}
- 為new和malloc設置異常處理函數(shù)。例如Visual C++可以用_set_new_hander函數(shù)為new設置用戶自己定義的異常處理函數(shù)汤纸,也可以讓malloc享用與new相同的異常處理函數(shù)衩茸。詳細內容請參考C++使用手冊。
上述 (1)贮泞、(2) 方式使用最普遍递瑰。如果一個函數(shù)內有多處需要申請動態(tài)內存,那么方式 (1) 就顯得力不從心(釋放內存很麻煩)隙畜,應該用方式 (2) 來處理抖部。
很多人不忍心用exit(1),問:“不編寫出錯處理程序议惰,讓操作系統(tǒng)自己解決行不行慎颗?”
不行。如果發(fā)生“內存耗盡”這樣的事情言询,一般說來應用程序已經(jīng)無藥可救俯萎。如果不用exit(1) 把壞程序殺死,它可能會害死操作系統(tǒng)运杭。道理如同:如果不把歹徒擊斃夫啊,歹徒在老死之前會犯下更多的罪。
有一個很重要的現(xiàn)象要告訴大家辆憔。對于32位以上的應用程序而言撇眯,無論怎樣使用malloc與new,幾乎不可能導致“內存耗盡”虱咧。對于32位以上的應用程序熊榛,“內存耗盡”錯誤處理程序毫無用處。這下可把Unix和Windows程序員們樂壞了:反正錯誤處理程序不起作用腕巡,我就不寫了玄坦,省了很多麻煩。
必須強調:不加錯誤處理將導致程序的質量很差,千萬不可因小失大煎楣。
void main(void){
float *p = NULL;
while(TRUE){
p = new float[1000000];
cout << “eat memory” << endl;
if(p==NULL)
exit(1);
}
}
malloc/free的使用要點
函數(shù)malloc的原型如下:
void * malloc(size_t size);
用malloc申請一塊長度為length的整數(shù)類型的內存豺总,程序如下:
int *p = (int *) malloc(sizeof(int) * length);
我們應當把注意力集中在兩個要素上:“類型轉換”和“sizeof”。
*malloc返回值的類型是void*择懂,所以在調用malloc時要顯式地進行類型轉換喻喳,將void\ *轉換成所需要的指針類型。
*malloc函數(shù)本身并不識別要申請的內存是什么類型休蟹,它只關心內存的總字節(jié)數(shù)沸枯。我們通常記不住int, float等數(shù)據(jù)類型的變量的確切字節(jié)數(shù)日矫。例如int變量在16位系統(tǒng)下是2個字節(jié)赂弓,在32位下是4個字節(jié);而float變量在16位系統(tǒng)下是4個字節(jié)哪轿,在32位下也是4個字節(jié)盈魁。最好用以下程序作一次測試:
cout << sizeof(char) << endl;
cout << sizeof(int) << endl;
cout << sizeof(unsigned int) << endl;
cout << sizeof(long) << endl;
cout << sizeof(unsigned long) << endl;
cout << sizeof(float) << endl;
cout << sizeof(double) << endl;
cout << sizeof(void *) << endl;
在malloc的“()”中使用sizeof運算符是良好的風格,但要當心有時我們會昏了頭窃诉,寫出 p = malloc(sizeof(p))這樣的程序來杨耙。
函數(shù)free的原型如下:
void free( void * memblock );
為什么free函數(shù)不象malloc函數(shù)那樣復雜呢?這是因為指針p的類型以及它所指的內存的容量事先都是知道的飘痛,語句free(p)能正確地釋放內存珊膜。如果p是NULL指針,那么free對p無論操作多少次都不會出問題宣脉。如果p不是NULL指針车柠,那么free對p連續(xù)操作兩次就會導致程序運行錯誤。
new/delete的使用要點
運算符new使用起來要比函數(shù)malloc簡單得多塑猖,例如:
int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];
這是因為new內置了sizeof竹祷、類型轉換和類型安全檢查功能。對于非內部數(shù)據(jù)類型的對象而言羊苟,new在創(chuàng)建動態(tài)對象的同時完成了初始化工作塑陵。如果對象有多個構造函數(shù),那么new的語句也可以有多種形式蜡励。例如:
class Obj{
public :
Obj(void); // 無參數(shù)的構造函數(shù)
Obj(int x); // 帶一個參數(shù)的構造函數(shù)
…
}
void Test(void){
Obj *a = new Obj;
Obj *b = new Obj(1); // 初值為1
…
delete a;
delete b;
}
如果用new創(chuàng)建對象數(shù)組令花,那么只能使用對象的無參數(shù)構造函數(shù)。例如:
Obj *objects = new Obj[100]; // 創(chuàng)建100個動態(tài)對象
不能寫成:
Obj *objects = new Obj[100](1);// 創(chuàng)建100個動態(tài)對象的同時賦初值1
在用delete釋放對象數(shù)組時凉倚,留意不要丟了符號‘[]’彭则。例如:
delete []objects; // 正確的用法
delete objects; // 錯誤的用法
指針和引用
指針指向一塊內存,它的內容是所指內存的地址占遥;引用是某塊內存的別名俯抖。
從現(xiàn)象上看,指針在運行時可以改變其所指向的值瓦胎,而引用一旦和某個對象綁定后就不再改變芬萍。這句話可以理解為:指針可以被重新賦值以指向另一個不同的對象尤揣。但是引用則總是指向在初始化時被指定的對象,以后不能改變柬祠,但是指定的對象其內容可以改變北戏。
從內存分配上看,程序為指針變量分配內存區(qū)域漫蛔,而不為引用分配內存區(qū)域嗜愈,因為引用聲明時必須初始化,從而指向一個已經(jīng)存在的對象莽龟。引用不能指向空值蠕嫁。
從編譯上看,程序在編譯時分別將指針和引用添加到符號表上毯盈,符號表上記錄的是變量名及變量所對應地址剃毒。指針變量在符號表上對應的地址值為指針變量的地址值,而引用在符號表上對應的地址值為引用對象的地址值搂赋。符號表生成后就不會再改赘阀,因此指針可以改變指向的對象(指針變量中的值可以改),而引用對象不能改脑奠。這是使用指針不安全而使用引用安全的主要原因基公。從某種意義上來說引用可以被認為是不能改變的指針。
不存在指向空值的引用這個事實宋欺,意味著使用引用的代碼效率比使用指針的要高轰豆。因為在使用引用之前不需要測試它的合法性。相反迄靠,指針則應該總是被測試秒咨,防止其為空。
因此如果你有一個變量是用于指向另一個對象掌挚,但是它可能為空雨席,這時你應該使用指針;如果變量總是指向一個對象吠式,你的設計不允許變量為空陡厘,這時你應該使用引用。
- 理論上特占,對于指針的級數(shù)沒有限制糙置,但是引用只能是一級。如下:
int** p1; // 合法是目。指向指針的指針
int*& p2; // 合法谤饭。指向指針的引用
int&* p3; // 非法。指向引用的指針是非法的
int&& p4; // 非法。指向引用的引用是非法的
指針是一個實體揉抵,而引用僅是個別名亡容;
引用使用時無需解引用(*),指針需要解引用冤今;
引用只能在定義時被初始化一次闺兢,之后不可變;指針可變戏罢;引用“從一而終
引用沒有 const屋谭,指針有 const,const 的指針不可變龟糕;
引用不能為空桐磁,指針可以為空;
“sizeof 引用”得到的是所指向的變量(對象)的大小翩蘸,而“sizeof 指針”得到的是指針本身(所指向的變量或對象的地址)的大兴狻淮逊;
typeid(T) == typeid(T&) 恒為真催首,sizeof(T) == sizeof(T&) 恒為真,但是當引用作為成員時泄鹏,其占用空間與指針相同(沒找到標準的規(guī)定)郎任。指針和引用的自增(++)運算意義不一樣;
常量指針(指向常量的指針备籽,值不能變舶治,但是地址可以變)
int i = 10;
const int *p = &i; //=》 int const *p = &i
*p = 20; //error
p = &num1 //success
常量引用(指向常量的引用)
int i = 10车猬;
const int &p = i;
p=20; //error
指針常量(表示指針本身是常量霉猛。在定義指針常量時必須初始化,地址不能變珠闰,但是值可以變)
int i = 10;
int j = 20惜浅;
int* const q = &i; //error,沒有初始化
int* const p = &i; //success
p = &num1; //error
*p = num1; //success
沒有引用常量的說法伏嗜,因為引用本身就是不可變的
常量指針常量(地址和值都不能改變)
int i =10坛悉;
const int* const p = &i;
指針和引用傳遞參數(shù)
1. 指針傳遞參數(shù)本質上是值傳遞的方式承绸,它所傳遞的是一個地址值裸影。
2. 值傳遞過程中,被調函數(shù)的形式參數(shù)作為被調函數(shù)的局部變量處理军熏,即在棧中開辟了內存空間以存放由主調函數(shù)放進來的實參的值轩猩,從而成為了實參的一個副本。
3. 值傳遞的特點是被調函數(shù)對形式參數(shù)的任何操作都是作為局部變量進行,不會影響主調函數(shù)的實參變量的值均践。
4. 引用傳遞過程中画饥,被調函數(shù)的形式參數(shù)也作為局部變量在棧中開辟了內存空間,但是這時存放的是由主調函數(shù)放進來的實參變量的地址浊猾。
5. 被調函數(shù)對形參的任何操作都被處理成間接尋址抖甘,即通過棧中存放的地址訪問主調函數(shù)中的實參變量。正因為如此葫慎,被調函數(shù)對形參做的任何操作都影響了主調函數(shù)中的實參變量衔彻。
6. 引用傳遞和指針傳遞是不同的,雖然它們都是在被調函數(shù)椡蛋欤空間上的一個局部變量凑耻,但是任何對于引用參數(shù)的處理都會通過一個間接尋址的方式操作到主調函數(shù)中的相關變量。而對于指針傳遞的參數(shù)配紫,如果改變被調函數(shù)中的指針地址碉就,它將影響不到主調函數(shù)的相關變量。如果想通過指針參數(shù)傳遞來改變主調函數(shù)中的相關變量废岂, 那就得使用指向指針的指針祖搓,或者指針引用。
7. 從概念上講湖苞。指針從本質上講就是存放變量地址的一個變量拯欧,在邏輯上是獨立的,它可以被改變财骨,包括其所指向的地址的改變和其指向的地址中所存放的數(shù)據(jù)的改變镐作。
8. 而引用是一個別名,它在邏輯上不是獨立的隆箩,它的存在具有依附性该贾,所以引用必須在一開始就被初始化,而且其引用的對象在其整個生命周期中是不能被改變的(自始至終只能依附于同一個變量)捌臊。
最后杨蛋,總結一下指針和引用的相同點和不同點:
相同點:
- 都是地址的概念;
- 指針指向一塊內存娃属,它的內容是所指內存的地址六荒;而引用則是某塊內存的別名。
不同點:
- 指針是一個實體矾端,而引用僅是個別名掏击;
- 引用只能在定義時被初始化一次,之后不可變秩铆;指針可變砚亭;引用“從一而終”灯变,指針可以“見異思遷”;
*引用沒有const捅膘,指針有const添祸,const的指針不可變;
具體指沒有int& const a這種形式寻仗,而const int& a是有的刃泌,前者指引用本身即別名不可以改變,這是當然的署尤,所以不需要這種形式耙替,后者指引用所指的值不可以改變)
* 引用不能為空,指針可以為空曹体;
* “sizeof 引用”得到的是所指向的變量(對象)的大小俗扇,而“sizeof 指針”得到的是指針本身的大小箕别;
* 指針和引用的自增(++)運算意義不一樣铜幽; - 引用是類型安全的,而指針不是 (引用比指針多了類型檢查)
一個有意思的例子
/*一個有意思的例子
常量不能修改串稀,const_cast去除常量修飾除抛,打印出來地址一樣,但值卻不一樣厨诸。
原因:編譯器優(yōu)化導致镶殷;加上volatile 修飾后發(fā)現(xiàn) a的地址為1禾酱?
*/
const int a = 1; //volatile const int a=1微酬,可以確保不讓編譯器優(yōu)化,每次去內存取值
int *p = const_cast<int*>(&a);
*p = 2;
cout << "value a="<< a << endl;
cout << "value *p=" <<*p << endl;
cout << "address a=" <<&a << endl;
cout << "address p=" <<p << endl;