在上一章中?C++ 語法之 指針的一些應用說明-CSDN博客
我們了解了指針變量,int *p;取變量a的地址這些。
那么函數同樣也有個地址,直接輸出函數名就可以得到地址,如下:
#include<iostream>
using namespace std;
void fun()
{int a = 5;}
int main()
{cout << "fun函數地址:" << fun;
}
返回函數地址:
這個就是系統為這個函數代碼分配的內存空間的首地址。
既然有這個內存地址,那相對的,跟變量指針一樣,也有函數指針,我們要怎么定義函數指針變量呢?
像int *p; char *cptr;?可以看到,定義指針變量需要提前進行類型區分。
所以函數指針,也是如此,必須說明這個函數的返回值,以及參數類型,幾個參數。
如下定義:
void (*pFun)();
如果fun函數是這樣
void fun(int a);
則對應的函數指針:
void (*pFun)(int);
你們可以在編譯器嘗試一下,是必須對應一致的,函數指針不能指向定義不一樣的函數。
就像int *p指針不能指向char變量地址一樣,只能是int類型的。
現在我們來一個例子學習一下:
?
#include<iostream>
using namespace std;
void (*pFun)();
void fun1()
{cout << "我是fun2";
}void fun()
{cout << "我是fun\n";pFun = fun1; //在這里 函數指針 指向另一個函數 pFun;
}int main()
{pFun = &fun; //為了便于閱讀用&fun, 事實上直接pFun=fun也可以,下面加*號同理 可省略(*pFun)();(*pFun)();
}
結果:?
上面的例子說明了,pFun指針指向fun,就是fun函數,指向fun1就是fun1函數。?
其實跟變量指針一個道理。
函數的生命周期?
我們知道在函數內正常定義的變量叫局部變量,函數執行完了,這些變量的內存空間就被釋放了。
也就是函數每執行一次,比如有個int a=5;就會給a分配內存空間,執行完了,就會釋放。
這種由系統分配,系統釋放的內存空間,就是內存中的棧空間。
也就是說,這種變量在棧中申請空間。由系統管理釋放。
而堆中,是由程序員自己申請的內存空間:
比如C的malloc函數(需free釋放)
C++的new?
例:?int* ptr = new int; (需delete釋放)
像這些,申請的空間,系統不會幫你自動釋放,所以就需要你自己手動釋放,否則,這塊空間即使不使用了,也會被程序一直占用。它并沒有棧中內存空間的功能,自動釋放。
這個內存堆棧空間是系統定義的,而物理上內存并沒有此種劃分,需要明白。
下面我們用一些例子來證明:
#include <iostream>using namespace std;
int i = 0;
void fun() {int a = 5;cout << "第" << i << "次地址:" << &a << endl;
}int main() {for (; i < 4; i++) {fun();}return 0;
}
每次調用fun函數,輸出局部變量a的地址,理論上應該是不同的地址,因為每次函數調用完之后a變量在棧中的空間會被釋放。
但是結果:
地址是一樣的,這是系統優化分配的原因,因為這個地址被釋放了,下次分配還可以找同樣的地址。這是合法的,就像在磁盤刪掉一個文件,然后再存儲,還是原來的位置。
所以由于這種現象,上面這個代碼并不能證明變量被重新分配內存空間。
我們要怎么做,在函數執行期間,調用其它函數干擾棧空間分配,就像磁盤刪除文件,然后復制大量其它文件,這樣再粘貼的文件位置就會不一樣?
比如下面這個:
#include <iostream>using namespace std;
int i = 0;
void fun() {int a = 5;cout << "第" << i << "次地址:" << &a << endl;
}
void other()
{int b = 5;int c = 6;int bc[56] = { 0 };
}
int main() {for (; i < 4; i++) {fun(); other();//調用一下其它函數,里面申請棧空間,打亂分配。}return 0;
}
并沒有用,原因是other里的也是局部變量,執行完后,同樣變量占用的棧空間也被釋放了。所以跟原來的還是一樣。?
那我們想個方法,不被釋放,看下面的代碼:
#include <iostream>using namespace std;
int i = 0;
void fun(int sum) {int a = 5;cout << "第" << sum << "次地址:" << &a << endl;
}int main() {for (; i < 4; i++) {fun(i);int b = 5; //定義 b變量,然后再調用fun(11) 此時b變量和fun(11)同時在for的作用域中fun(11);int c = 6;fun(22);}return 0;
}
很遺憾,還是沒有效果,所有的a變量地址都是一樣的。
我分析可能是進入for作用域一次性的分配好了(有待驗證)。
從這個現象可以看出,雖然每次執行函數時其中的局部變量,都是重新分配,但系統遵循著某一種優化規則,使得每次分配的地址盡可能一樣。
由于方向上的問題,這個規則就不深入研究了。
好了,通過直觀的查看地址方法已失敗,實驗起來比較困難。
我們可以從側面來驗證,有兩個方法,第一個通過值的變化來驗證,如果內存空間被釋放了,那么的它的值如果沒有保留,那可以證明函數執行完,局部變量已經被釋放。
取值驗證:
#include <iostream>using namespace std;
int i = 0;
int* ptr;
void fun(int sum) {int a = 5;ptr = &a; //將a變量的地址存到全局指針變量ptr中 以便在函數外訪問cout << "\n fun函數內a值:" << *ptr;
}
int main() {for (; i < 2; i++) {fun(i); cout << "\n函數外的a值:" << *ptr;}return 0;
}
結果:
可以看到,同樣是取*ptr的值,函數外已經變了,說明系統沒有為變量a留有內存空間來保存值了,函數執行完就被釋放了。?
第二個通過遞歸調用函數的方法強制驗證,這樣的地址絕對不能相同,比如說遞歸調用4次函數。
局部變量a肯定是不同的地址,如果每一次都重新分配空間的話。
為什么,因為在遞歸未完成時,所有的局部變量都不會被釋放。因為所有的函數都沒執行完。
它想復用上一次變量A的地址是不可能的。
這個方法是反向證明,證明每次是重新分配空間的。然后就可以佐證,即然每次執行重新分配空間,那么執行完了也應當是釋放空間的。
遞歸調用驗證:
#include <iostream>using namespace std;void fun(int sum) {int a = 5;if (sum == 0) return; //用sum來控制 遞歸調用fun函數,防止無限循環調用else{cout << "\nsum=" << sum << "時,a的地址:" << &a;sum = sum - 1;fun(sum);//遞歸調用fun}}
int main() {fun(4);return 0;
}
結果:
可以看到,每次a變量地址是不同的,四個不同的地址。?
說了局部變量,這里有個有趣的點,有沒有一種變量,我不想每次函數執行,重新分配和釋放,是一直存在的,有,就是在函數內被static修飾的變量,這種變量跟全局變量一樣,它的空間不是在堆棧中,而是靜態內存空間中,從整個程序開始分配,運行期間一直存在,到程序結束才釋放。
?代碼示例:
using namespace std;
int g = 5;
void fun() {static int s = 5; //靜態變量int b = 10;int c = 10;s++;cout << "\n靜態s變量值:" << s << "------地址:" << &s << "-----全局變量g地址:" << &g;cout << "\n局部變量b地址:" << &b << "---局部變量c地址:" << &c;}
int main() {for(int i=0;i<3;i++)fun();return 0;
}
運行結果:
?1.從地址分配來看,可以證明,全局變量g和靜態變量s?的地址相近,說明它們在同一塊內存區域(靜態存儲區)。有著相同的特性。
2.而局部變量b,c又是另一塊內存區域(動態存儲區),即棧中。所以它們的地址很接近,只是后幾位不同。
3.可以看到靜態變量s的值在增長,說明并沒有被釋放,而開頭一句static int s=5;靜態變量在定義時賦值只會初始化一次。
?4.另:還記得我們在文章開頭取了一個函數地址嗎,那么這個屬于什么區域呢?這個是代碼區,因為函數的執行代碼是存儲在程序代碼區。
這就是一個函數的內存分布區域,不是所有的內容都是一起的。