一、在講堆棧之前,我們先看看值類型和引用類型:
1,我們看看值類型與引用類型的存儲方式:
引用類型:引用類型存儲在堆中。類型實例化的時候,會在堆中開辟一部分空間存儲類的實例。類對象的引用還是存儲在棧中。
值類型:值類型總是分配在它聲明的地方,做為局部變量時,存儲在棧上;類對象的字段時,則跟隨此類存儲在堆中。
什麼是堆什麼是棧我們後面解釋。
圖1-1
2,我們再看看引用類型與值類型的區別:
①引用類型和值類型都繼承自Systerm.Object類。不同之處,幾乎所有的引用類型都是直接從Systerm.Object繼承,而值類型則是繼承Systerm.Object的子類Systerm.ValueType類。
②我們在給引用類型的變量賦值的時候,其實只是賦值了對象的引用;而給值類型變量賦值的時候是創建了一個副本(副本不明白?說通俗點,就是克隆了一個變量)。
文字不夠形象?我們上代碼看看
圖1-2
3,我們再看看引用類型和值類型的內存分配情況(我們對著代碼與圖看)
圖1-3
圖1-4
看了圖1-3和圖1-4之後,你們可能問我了:你是怎麼知道變量在棧中的地址的。嘿嘿,等下教你用c#玩指針
從上面兩張圖我們可以看出:
①棧的結構是後進先出,也就是說:變量j的生命周期在變量s之前結束,變量s的生命周期在變量i之前結束,
②棧地址從高往底分配
③類型的引用也存儲在棧中
二、對於堆和棧的詳細介紹,我們往下看。
1,有人老是搞不明白堆和棧的叫法。我來解釋下:
堆:在c裡面叫堆,在c#裡面其實叫托管堆。為什麼叫托管堆,我們往下看。
棧:就是堆棧,因為和堆一起叫著別扭,就簡稱棧了。
2,托管堆:
托管堆不同於堆,它是由CLR(公共語言運行庫(Common Language Runtime))管理,當堆中滿了之後,會自動清理堆中的垃圾。所以,做為.net開發,我們不需要關心內存釋放的問題。
3,有人老是搞不清楚內存堆棧與數據結構堆棧,我們來看看什麼是內存堆棧,什麼是數據結構堆棧
①數據結構堆棧:是一種後進先出的數據結構,它是一個概念,圖4-1中可以看出,棧是一種後進先出的數據結構。
②內存堆棧:存在內存中的兩個存儲區(堆區,棧區)。
棧區:存放函數的參數、局部變量、返回數據等值,由編譯器自動釋放
堆區:存放著引用類型的對象,由CLR釋放
三、最後我們用c#玩一玩指針
1,首先右鍵項目-->屬性-->生成-->勾選允許不安全代碼
2,在寫不安全代碼的時候需要加上unsafe
//標記類 unsafe public class Student { //標記字段 unsafe int* pAge; //標記方法 unsafe void getType(int* a) { //標記代碼段 unsafe { int* pAbc; //聲明指針語法 } } }
3,指針的語法
unsafe { int* pWidth, pHeight; double* pResult; byte*[] pByte; //&:表示“取地址”,並把一個值數據類型轉換為指針,例如,int轉換為*int。這個運算稱為【尋址運算符】 //*:表示“獲取地址內容”,把一個指針轉換為一個值數據類型(例如:*float轉換為float)。這個運算符被 //稱為“間接尋址運算符”(有時稱“取消引用運算符”) int a = 10;//聲明一個值類型,給它賦值10 int* pA, pB;//聲明2個指針 pA = &a;//取出值類型a的地址,賦值給指針pA pB = pA;//把指針pA的地址賦值給pB *pB = 20;//獲取pB指向的地址內容,並賦值20 Console.WriteLine(a);//輸出20 }
4,將指針強制轉化為整數類型(只能轉化為uing、long、ulong類型)
int a = 10; int* pA, pB; pA = &a; uint address = (uint)pA;//將指針地址強制轉換為整數類型 pB = (int*)address;//將整數類型強制轉換為指針 *pB = 20;//指針指向a Console.WriteLine(a);//輸出20
5,指針類型的強制裝換
int b = 10; int* pIa; double* pDa; pIa = &b; pDa = (double*)pIa;//將int*強制轉換成double*
6,void指針(不指向任何數據類型的指針)
int c = 10; int* pAA; pAA = &c; void* pVa = (void*)pAA;//轉換viod指針
7,指針算數的運算(不允許對void指針進行運算)
int d = 10; int* pId;//如果地址為100000 pId = &d; pId++;//結果:100004(int類型的大小為4個字節)
8,結構指針:指針成員訪問運算符
①指針不能只想任何引用類型
②結構裡面不能包含引用類型
MyStruct* pStruct; MyStruct ms = new MyStruct(); pStruct = &ms; //--取地址內容賦值為10 (*pStruct).X = 10; pStruct->X = 10; //--取地址賦值給pIs int* pIs1 = &(ms.X); int* pIs2 = &(pStruct->X); //結構 struct MyStruct { public int X = 1; public int Y = 2; }
9,類成員指針
Person p = new Person(); fixed (int* pIp1 = &(p.X), pIp2 = &(p.Y)) { }//pIp1和pIp2的生命周期 //類 class Person { public int X; public int Y; }
10,有趣的實驗
unsafe { //有趣的問題:為什麼b的值改變了 //回答:因為i的地址指向了b int a = 10; int b = 20; int* i; i = &a;//將a的地址賦值給指針i i -= 1;//將i的地址-1(相當於向下移動4個字節(int類型的大小為4個字節)) *i = 30;//取出i指針指向的內容賦值為30 Console.WriteLine(b);//輸出30 }