您的位置:首页 > 编程语言 > C#

关于C#值类型,引用类型,值传递,引用传递

2012-11-30 11:22 489 查看
说到参数传递,必须得弄清值类型和引用类型:
(为了容易表达,我暂且命名存放在堆中的内容为堆中对象,存放在栈上的内容为栈中对象。)
值类型存放在栈中,直接访问。如果有:int a=0;int b=a;就产生了两个栈中对象。
引用类型需要在堆中显式分配,且不能直接访问,需要在栈中分配一个栈中对象(C++叫指针,C#叫引用)指向其堆中对象。
如果:
StringBuilder strb = new StringBuilder();
StringBuilder strb2 = strb;
则在堆中只有一个堆中对象,只是栈中有两个栈中对象指向堆中对象。
可以看出:每个变量都是一个栈中对象。不管是值类型还是引用类型,只是值类型的栈中对象就是其内容,而引用类型的栈中对象只是一个指向堆中对象的地址。

判断是值类型还是引用类型:

int a1 = 10;
StringBuilder strb1 = new StringBuilder("ABC");

int a2 = a1;
StringBuilder strb2 = strb1;

bool bl1 = object.ReferenceEquals(a1,a2); //false为值类型(因为值类型要装箱)
bool bl2 = object.ReferenceEquals(strb1,strb2); //true为引用类型

参数传递分值传递和引用传递两种。
通常,在没有显式指出ref和out时都是值传递。

值传递:传的是对象的值拷贝。(即函数内参数对象是调用时传递对象的栈中对象的拷贝。)
引用传递:传的是栈中对象的地址。(即函数内参数对象与调用时传递对象完全是同一栈中对象。)
现在用例子来说明传值跟传地址的不同:

private void button2_Click(object sender, System.EventArgs e)
{
StringBuilder strb1 = new StringBuilder();
StringBuilder strb2 = new StringBuilder();
Test1(strb1);
Test2(ref strb2);
string str1 = strb1.ToString(); //str1值:"A"
string str2 = strb2.ToString(); //str2值:"BC"
}

void Test1(StringBuilder strb)
{
//strb和strb1是两个栈中对象,但指向相同的地址,这个操作是改变堆中对象
strb.Append("A");

//这里将strb指向一个新的堆中对象,所以后面的操作与strb1指向的栈中对象无关
strb = new StringBuilder("B");
strb.Append("C");
}

void Test2(ref StringBuilder strb)
{
//这里的strb和strb2是同一个栈中对象,所以改变strb的值使其指向另一个对象也等于改变strb2
strb = new StringBuilder("B");
strb.Append("C");
}

转自http://www.cnblogs.com/greatandforever/archive/2008/07/08/1238180.html

class myclass
{
public int val;
}
class test
{
public void change(ref myclass mc1, myclass mc2)
{
mc1 = new myclass();
mc1.val = 999;
//mc2 = new myclass();
mc2.val = 999;
}
}
class Program
{
static void Main(string[] args)
{
test tc = new test();
myclass m1 = new myclass();
m1.val = 1;
myclass m2 = new myclass();
m2.val = 1;
tc.change(ref m1, m2);
Console.WriteLine("m1.val={0},m2.val={1}", m1.val, m2.val);
}
}
如上代码,如果类test中的注释行被注释掉,则输出为:999,999
如果注释行的代码不被注释而起作用,那么输出为:999,1
原因就是mc2是通过值传递的引用。
二、ref和out
ref和out关键字都是指明参数是引用类型的,其区别是:
使用ref,则参数必须在使用前初始化,而在方法内被声明为ref的参数可以被修改,也可以不被修改;
使用out,则参数在使用前可以不初始化,但是在方法内部(方法返回之前)必须对声明为out的参数赋值。当参数是引用类型时(比如对象),这种“赋值”不是指某些字段、属性赋值,而是对对象赋值。
三、方法重载
重载的最低要求是参数列表不同(包括参数次序不同),而返回类型、访问修饰符不同都不是重载。
使用ref或out来区分的,视为重载,但是不能同时使用二者:
public void read(string s)
{
Console.WriteLine(s);
}
//Either of following two is ok,but not both
public void read(ref string s)
{
Console.WriteLine(s);
}
public void read(out string s)
{
Console.WriteLine(s);
}
四、重载构造函数
一旦重载了构造函数,编译器将不再提供默认构造函数,可以自己定义一个无参数的构造函数。
五、继承与重载
当联合使用继承和重载时,C#的做法类似于java,而不是C++。C++的重载概念仅限于同一个类中的方法,而C#中派生类和基类的方法仍然被认为是继承。
六、方法隐藏
类似于C++,如果在基类声明一个方法,派生类中重写该方法并使用new关键字,则隐藏基类方法。(new是默认的,可省略。)
class Employee
{
public void Calpay()
{
Console.WriteLine("Employee.Calpay");
}

public virtual void Work()
{
Console.WriteLine("Employee.Work");
}

public virtual void Funs()
{
Console.WriteLine("Employee.Funs");
}

}
class SalariedEmployee:Employee
{

public new void Callpay()//new 隐藏父类方法
{
Console.WriteLine("SalariedEmployee.Calpay");
}
public new void Work()//new 隐藏父类方法
{
Console.WriteLine("SalariedEmployee.Work");
}
public override void Funs()//override 重写父类方法
{
Console.WriteLine("SalariedEmployee.Funs");
}

}
class Program
{
static void Main(string[] args)
{

Employee e = new Employee();
e.Callpay();
e.Work();
e.Funs();
SalariedEmployee se = new SalariedEmployee();
se.Callpay();
se.Work();
se.Funs();
Employee em = new SalariedEmployee();//子类实例化父类,可以调用父类被隐藏的方法
em.Callpay();
em.Work();
em.Funs();
Console.ReadLine();

}
}

输出为:



七、多态、覆盖
与C++类似,基类方法声明时间关键字virtual,派生类想要覆盖则在声明时加关键字override。
覆盖的、重定义的方法的方分级别必须与它重定义的虚函数级别一致(不低于),虚函数不能声明为private。
new和virtual可以同时使用,不过此时是为这个虚函数建立了一个新的级别。
如果从构造函数调用虚函数,C#类似于java,而不是C++,调用最早派生的重定义的方法。(此时派生类的对象有可能还没有完全构造好。)
八、静态方法
与C++类似,使用static关键字。
静态方法可以访问类中所有静态成员,但不能访问普通实例成员。反过来,非静态方法都可以访问静态成员和非静态成员。
构造函数中不能通过this访问静态成员。
C#的静态方法只能通过 “类名.方法” 的形式访问。
九、静态构造函数
可以声明静态构造函数:不能有参数,不能有访问修饰符,只能访问静态成员。将先调用静态构造函数,再调用非静态。不构造对象,直接调用静态方法或成员也会触发调用静态构造函数。
出处/article/4354955.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: