1 类类型的数据传递
C#和C++ 一样,有值传递和引用传递。例如下面的代码
Person p1 = new Person();
p1.Name = “张三”;
Person p2 = p1;
p2.Name = “李四”;
Console.WriteLine(p1.Name);
把对象p1赋值给p2, 此时p2修改Name属性,那么p1的Name属性也被修改了,类是 引用类型,一个对象的修改可能会涉及到其它对象的修改。但是对于string类型又不同,string具有不可变性,例如下面的代码
//字符串的不可变性
string str1 = “abc”;
string str2 = str1;
str2 = “dfg”;
Console.WriteLine(“str1 = {0}”, str1);
Console.WriteLine(“str2 = {0}”, str2);
str1赋值给str2,修改str2的值,并不会 修改str1的值,这是string和其它类的区别,string赋值时会重新在堆上分配空间,之前的值依然存在。
2 函数引用传参
(1)基本数据类型
public static void TestNum(ref int num) //相当于C++的引用传参
{
num += 10;
}
如果在函数参数的前面加了ref关键字,那么此时就和C++的引用传参是一样的,区别是C#在调用时, 实参前面也要加 ref修饰,不然会有语法错误。
(2)类类型做形参
public static void TestClass(Person p) //相当于C++的引用传参
{
p.Name = “赵六”;
}
此时传入对象,会修改对象的属性。
3 返回ref类型的数组项
例如下面的代码
using System;
namespace ref返回
{
class Program
{
public static ref int Test(int[] array)
{
return ref array[0];
}
static void Main(string[] args)
{
int[] array = { 5, 80 };
ref int tmp = ref Test(array);
Console.WriteLine("{0}, {1}", array[0], array[1]);
tmp = 0; //修改这个引用后,数组对应的元素值也被修改了
Console.WriteLine("{0}, {1}", array[0], array[1]);
Console.ReadKey();
}
}
}
函数Test返回的是ref类型的数组项,当调用Test函数后,如果外部修改返回值,对应的数组项也会被修改。
对于值类型它在进行值得传递的时候传递都是它的数值,但是引用类型传递的都是传递引用,对于常见的引用类型,比如:
xxx a=new xxx();
当我们将a赋值到另一个xxx的对象(比如对象b),那么我们在利用对象b去操纵与利用a去操纵是一样的,因为它们指向的是同一块堆空间。
引用类型:自定义类,数组,字符串,接口,集合
值类型:int,double,float,char,bool,decimal,enum,struct
值类型的值传递
值类型存储的地方全部都在栈上。
比如int a=10;
此时我们输入这样的语句int b=a,那么会在栈上开辟一块空间去存储a的值,然后b就是指向该栈空间。所以说值类型是值传递,因为引用类型的传递时将栈上的指针进行传递,所以传递的仅仅只是指向堆空间的指针,所以时引用传递。
String特殊的引用传递
因为String字符串有着不变性,如果我们去修改一个字符串,它的本质时开辟其他的堆空间,然后将之前的引用切断然后指向新开辟的堆空间,这就使得频繁修改字符串会导致空间的浪费。我们来看下面的例子:
String a=”aaa“
String b=a
虽然String是引用类型,但是因为修改字符串的本质都是新开辟堆空间,然后修改引用,那么上面的语句执行的本质是在堆上开辟一个新空间,然后将b指向新开辟的空间,里面的值是“aaa”。
ref和out
因为值类型在作为参数传入的时候,传递的是一个新的栈空间,那么传入后,无论在方法的内部如何修改传入的参数,都无法改变外部的值类型变量,所以我们可以使用ref和out参数去修饰,使得传入的参数不是就的栈空间,而是值对象指向的那块栈空间,这样在方法内部修改,操作后就可以影响外部参数。
out强调传入的是一个没有初始化的值类型。
而ref强调传入的是一个初始化后的值类型。