C# 堆与栈
一、在讲堆栈之前,我们先看看值类型和引用类型:
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//标记类 unsafe public class Student { //标记字段 unsafe int * pAge; //标记方法 unsafe void getType( int * a) { //标记代码段 unsafe { int * pAbc; //声明指针语法 } } } |
3,指针的语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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类型)
1
2
3
4
5
6
7
|
int a = 10; int * pA, pB; pA = &a; uint address = ( uint )pA; //将指针地址强制转换为整数类型 pB = ( int *)address; //将整数类型强制转换为指针 *pB = 20; //指针指向a Console.WriteLine(a); //输出20 |
5,指针类型的强制装换
1
2
3
4
5
|
int b = 10; int * pIa; double * pDa; pIa = &b; pDa = ( double *)pIa; //将int*强制转换成double* |
6,void指针(不指向任何数据类型的指针)
1
2
3
4
|
int c = 10; int * pAA; pAA = &c; void * pVa = ( void *)pAA; //转换viod指针 |
7,指针算数的运算(不允许对void指针进行运算)
1
2
3
4
|
int d = 10; int * pId; //如果地址为100000 pId = &d; pId++; //结果:100004(int类型的大小为4个字节) |
8,结构指针:指针成员访问运算符
①指针不能只想任何引用类型
②结构里面不能包含引用类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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,类成员指针
1
2
3
4
5
6
7
8
9
10
|
Person p = new Person(); fixed ( int * pIp1 = &(p.X), pIp2 = &(p.Y)) { } //pIp1和pIp2的生命周期 //类 class Person { public int X; public int Y; } |
10,有趣的实验
1
2
3
4
5
6
7
8
9
10
11
12
|
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 } |