用几个案例来解释值类型和引用类型
首先看值类型,案例如下:
案例一:
1 static void Main(string[] args) 2 { 3 int n = 10; 4 M1(n); 5 Console.WriteLine(n);//10 6 Console.ReadKey(); 7 } 8 9 private static void M1(int m) 10 { 11 m++; 12 }
解释:第一次声明变量n,会在栈中开辟一块空间,我们自己命名为n,赋值后,空间中就存了10.调用方法的时候,把n传给方法,相当于把n赋值给m,所以m变成10,然后执行方法内部的代码(m++),此时m=11。但是我们要输出的变量是n,所以结果仍然是10.
案例二:
1 static void Main(string[] args) 2 { 3 #region 案例一 4 //int n = 10; 5 //M1(n); 6 //Console.WriteLine(n);//10 7 #endregion 8 9 #region 案例一 10 Person p = new Person(); 11 p.Age = 100; 12 M2(p); 13 Console.WriteLine(p.Age);//101 14 #endregion 15 16 Console.ReadKey(); 17 } 18 19 private static void M2(Person p1) 20 { 21 p1.Age++; 22 }
解释:Person p=new Person();首先在堆中开辟一块空间,保存person对象,给Age赋值为100,这块堆的内存名称为x00x32,保存在栈中,对应的栈名称我们取名为p。当调用M2()方法的时候,我们重新声明了一个变量p1,并且把p赋给p1(变量都是值类型),等效于把栈中的值复制一份存入p1这块栈中,所以p,p1都指向x00x32这块内存,执行方法中的代码后,p.Age,p1.Age都为101。
案例三:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 #region 案例一 6 //int n = 10; 7 //M1(n); 8 //Console.WriteLine(n);//10 9 #endregion 10 11 #region 案例二 12 //Person p = new Person(); 13 //p.Age = 100; 14 //M2(p); 15 //Console.WriteLine(p.Age);//101 16 #endregion 17 18 #region 案例三 19 Person p = new Person(); 20 p.Age = 100; 21 M3(p); 22 Console.WriteLine(p.Age);// 23 #endregion 24 25 26 Console.ReadKey(); 27 } 28 29 private static void M3(Person p1) 30 { 31 p1.Age = 1000; 32 p1 = new Person(); 33 p1.Age = 200; 34 } 35 36 private static void M2(Person p1) 37 { 38 p1.Age++; 39 }
结果为1000.
案例四:
1 static void Main(string[] args) 2 { 3 #region 案例一 4 //int n = 10; 5 //M1(n); 6 //Console.WriteLine(n);//10 7 #endregion 8 9 #region 案例二 10 //Person p = new Person(); 11 //p.Age = 100; 12 //M2(p); 13 //Console.WriteLine(p.Age);//101 14 #endregion 15 16 #region 案例三 17 //Person p = new Person(); 18 //p.Age = 100; 19 //M3(p); 20 //Console.WriteLine(p.Age);//1000 21 #endregion 22 23 #region 案例四 24 Person p = new Person(); 25 p.Age = 100; 26 M4(p); 27 Console.WriteLine(p.Age);//100 28 #endregion 29 30 Console.ReadKey(); 31 } 32 33 private static void M4(Person p1) 34 { 35 p1 = new Person(); 36 p1.Age++; 37 }
Person p=new Person();在堆中开辟一块空间x001,Age=100,把堆的名称x001保存到栈中,栈中开辟的空间(实际的名称没显示)我们自己命名为p。调用方法时,又在栈中开辟了一块空间,我们自己命名为p1,然后把p1赋值给p,由于变量名称都是值类型,所以直接把p中x001拷贝一份给p1,这时候p,p1都指向x001这块堆空间。接下来执行方法中的代码的时候,又在堆中开辟了一块空间x002(类成员Age的初始默认值为0,p1.Age++后为1),然后赋值给p1,这时候,p1中保存堆名称变成x002了.但是我们输出的是p.Age,所以仍然是100.
案例五:
1 { 2 #region 案例五 3 string name = "科比"; 4 M5(name); 5 Console.WriteLine(name);//科比 6 #endregion 7 8 Console.ReadKey(); 9 } 10 11 private static void M5(string name1) 12 { 13 name1 = "乔丹"; 14 }
注意:变量都是值类型的,存在栈中 。
案例六:
1 { 2 #region 案例六 3 int[] arrInt = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; 4 M6(arrInt); 5 //?????????? 6 for (int i = 0; i < arrInt.Length; i++) 7 { 8 Console.WriteLine(arrInt[i]);//1,2,3,4,5,6,7,8 9 } 10 #endregion 11 12 Console.ReadKey(); 13 } 14 15 private static void M6(int[] arrInt1) 16 { 17 arrInt1 = new int[arrInt1.Length]; 18 for (int i = 0; i < arrInt1.Length; i++) 19 { 20 arrInt1[i] = arrInt1[i] * 2; 21 } 22 }
int[] arrInt = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };先在堆中开辟一块空间x001,并且初始化数组。栈中开辟的空间保存x001,我们自己把栈中空间命名为arrInt,调用方法的时候,声明了变量arrInt1,在栈中又开辟了一块空间x002,我们命名为arrInt1,然后把arrInt赋值给arrInt1,因为变量都是值类型,所以直接把栈的内容拷贝一份到arrInt1中,此时arrInt,arrInt2都指向x001这块内存。接下来执行方法中的代码。在M6方法中,先在堆中开辟了一块空间x002,指定了数组的长度,但是没有初始化,由于是int类型,所以数组的所有元素都是0,然后把x002保存到栈中,此时arrInt1就不再指向x001,而是指向了x002,最后操作数组arrInt1。我们需要输出的是arrInt,所以最后的值是1,2,3,4,5,6,7,8。
案例七:
1 { 2 #region 案例七 3 int[] arrInt = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; 4 M7(arrInt); 5 //?????????? 6 for (int i = 0; i < arrInt.Length; i++) 7 { 8 Console.WriteLine(arrInt[i]); 9 } 10 #endregion 11 12 Console.ReadKey(); 13 } 14 15 private static void M7(int[] arrInt1) 16 { 17 for (int i = 0; i < arrInt1.Length; i++) 18 { 19 arrInt1[i] = 100; 20 } 21 }
接下来我们把上面的方法全部改成引用传递,看看有什么异同。
案例一:
1 #region 案例一 2 int n = 10; 3 M1(ref n); 4 Console.WriteLine(n); 5 #endregion 6 7 private static void M1(ref int m) 8 { 9 m++; 10 }
int n=10;在栈中开辟一块空间x001,存入10,我们给这块空间的名称重新命名为n,值传递会把内存中的东西(不管保存的是内容还是地址)拷贝一份,引用传递传递的是电脑给内存分配的地址x001,所以值传递等效于我们给x001这个地址又重新起了个名字m, m,n都对应x001这个内存名称,x001对应分配的内存,但是m,n都不保存。所以m++后,n也会变。(至于m,n为什么不保存需要研究)。所以最终输出11.
把代码转换为IL语言后,发现内存中是不存在m,n变量的,只有内存地址,程序是根据内存地址读取数据的。
所以值传递传递的是栈中内容,对于值类型,栈中的类型就是栈中的数据,对于引用类型,栈中的内容就是对象的地址。引用传递传递的是栈本身的地址,多个变量名实际上指向的是同一个栈变量。
案例二:
1 #region 案例二 2 Person p = new Person(); 3 p.Age = 100; 4 M2(ref p); 5 Console.WriteLine(p.Age);//101 6 #endregion 7 8 private static void M2(ref Person p1) 9 { 10 p1.Age++; 11 }
结果是101;
案例三:
1 #region 案例三 2 Person p = new Person(); 3 p.Age = 100; 4 M3(ref p); 5 Console.WriteLine(p.Age);// 6 #endregion 7 8 private static void M3(ref Person p1) 9 { 10 p1.Age = 1000; 11 p1 = new Person(); 12 p1.Age = 200; 13 }
Person p=new Person();在堆中开辟一块空间x001,Age=100,保存在栈中,栈这块内存地址是y001,里面保存的内容是x001,我们自己命名为p,调用方法时,采用引用传递,只是给栈的地址换了个别名,改为p1,p,p1还是指向栈中y001这块内存。执行方法后,在堆中重新分配了块内存x002,并且赋值给p1,所以栈中x001这块内存中存的内容变成x002,然后改变Age=200,到这里实际上p,p1都指向了x002这块内存,所以最终的结果为200.
案例四:
1 #region 案例四 2 Person p = new Person(); 3 p.Age = 100; 4 M4(ref p); 5 Console.WriteLine(p.Age);// 6 #endregion 7 8 private static void M4(ref Person p1) 9 { 10 p1 = new Person(); 11 p1.Age++; 12 }
结果:1;
案例五:
1 #region 案例五 2 string name = "科比"; 3 M5(ref name); 4 Console.WriteLine(name);// 5 #endregion 6 7 private static void M5(ref string name1) 8 { 9 name1 = "乔丹"; 10 }
结果是:乔丹
案例六:
1 int[] arrInt = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; 2 M6(ref arrInt); 3 //?????????? 4 for (int i = 0; i < arrInt.Length; i++) 5 { 6 Console.WriteLine(arrInt[i]);// 7 } 8 9 private static void M6(ref int[] arrInt1) 10 { 11 arrInt1 = new int[arrInt1.Length]; 12 for (int i = 0; i < arrInt1.Length; i++) 13 { 14 arrInt1[i] = arrInt1[i] * 2; 15 } 16 }
结果全部是0;
案例七:
1 #region 案例七 2 int[] arrInt = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; 3 M7(ref arrInt); 4 //?????????? 5 for (int i = 0; i < arrInt.Length; i++) 6 { 7 Console.WriteLine(arrInt[i]); 8 } 9 #endregion 10 11 private static void M7(ref int[] arrInt1) 12 { 13 for (int i = 0; i < arrInt1.Length; i++) 14 { 15 arrInt1[i] = 100; 16 } 17 }
全部是100