在阐明它们之间的区别与联系之前(当然了普通对象的参数传递也会参与分析之中因为普通的对象参数传递与两者也是极为相似的)我先要补充点额外的东西, 因为这些有助于对问题的理解。一个是对象变量和值变量在内存中的存储,另外一个要说是的就是C/C++指针了。
对象变量和值变量在内存中的存储
1. 函数内值变量或值参数在内存中的存储
2. 函数内对象变量或对象参数在内存中的存储
C/C++指针
如果你有C++基础,我们再来看一下C++里面两种类型的指针: 指向对象的常指针和指向常对象的指针,这听上去有点拗口。
class Student
{
public:
Student()
{
_age = 0;
_score = 0.0;
}
void setAge(int age)
{
_age = age;
}
public:
int _age ;
float _score ;
};
/*
这就是指向常对象的指针, 意思是你不能通过这个指针改变对象中的值,
但是你可以使这个指针指向其它对象s->age = 343编译错误,不能通过这
个指改变其值s=&obj2,编译正确可以指向其它对象。
*/
Student const *s1;
/*
这个是指向对象的常指针意思是你不能修改这个指针的值,就是不能再使它指向
同类型的其它对象, 但是你可以通过该指针来改变对象中的值,s2=&obj2;
编译错误,不能指向其它对象了,s2->age =33;正确可修改其中的值
*/
Student * const s2=&obj1;
所以当我们的一个参数是这两种类型的话,一个则可以保证在函数内部可以修改传过来的指针所指向的对象的值,但不能改变所指对象,另一个则可以保证在函数内部我不能改变你所指向对象字段的值。这些都是为了保证程序的键壮性。还有这些原生态的C++是可以写在C#语言中的哦.
C#参数面前的ref 或out修饰符
在C#编程中参数面前可以加上ref或out修饰符,可以让函数改变它的值,它们分别有一定的规则如果不遵循这个规则,程序在编译时则不能通过。
Ref修饰传参: 所修饰的变量必须在调用前初始化或赋值,函数内可以初始化也可以不用初始化。
Out修饰传参: 所修饰的变量必须在所调用的函数内初始化或赋值。函数外可以初始化也可以不用初始化。
也有人总结ref和out不同时,说Ref 有进有出,out 只出不进,函数可以通过out返回多个值…..至于这个规则,大家都明白。
如果我的参数是一个对象那么我不加out或ref也能修改对象中字段值,因为对象本身传的就是地址嘛。这好像out 或ref是多余的哈. 可是不要忘了C#中还有值类型。要在函数内修改外部变量的值,不传地址还真难办到!这似乎给了我们一个ref或out存在的理由!要传地址用ref或out其中一个就可以了为什么还要区分ref和out。这就牵扯到如果传递的参数是对象的问题。
如果参数是对象传递的是对象的地址,如果我在参数面前加上ref或out传递的又是什么?
这个时候传递的则对象地址的地址也就是C/C++中的二级指针,画个图。
这个时候我们看一下对象传参时out规则:
所修饰的变量必须在所调用的函数内初始化或赋值。函数外可以初始化也可以不用初始化。
必须在函数内初始化!这就出故事了,意思是你必须在函数内重新new一个对象出来并赋值给它,这样导致函数外面的对象变量指向了另一个对象,在函数内修改对象属性的值,是修改新new出来的对象。在C#编程我们一般传参时都没有保存原来那个对象的地址了,这在C/C++中这样的操作会造成野指针也就是内存泄露,当然了C#中我们有垃圾收集器,不用怕。现在用图说明一下
在这种规则之下是不是与C/C++指向常对象的指针有异曲同工之妙:不能通过这个指针改变对象中的值,但是你可以使这个指针指向其它对象。
而对于ref规则似乎要宽泛很多,在函数内可以初始化也可以不重新初始化,意思是可以让它在函数内指向另一个对象也可以不指向另一个对象。不指向另一个对象则是对原对象属性的修改。如果ref规则变成只能在函数外初始化不能在函数内初始化则演变成了C/C++中的指向对象的常指针了。可是微软并没有要求得这么严格。希望这篇文章加深了你对ref和out的理解.