今天再次学习C#语言,发现这门语言与java是如此的相似(当然对于java我也是略懂皮毛),他们都有引用类型,但是C#中却还有引用类型的ref(引用的引用),这又让我想起了c/c++中指针的指针,它们之间有着怎样的关系呢?在这里我根据一些资料和自己的见解来结合例子做一做梳理。
首先在java和C#里面有基本数据类型和引用数据类型,对于基本数据类型的ref和out这里不做解释,先看代码:
c#代码:
class Test
{
public Test()
{
this.value = 0;
}
public Test(int x)
{
this.value = x;
}
public int value;
}
class Program
{
static void changeValue(Test test)
{
test.value = 1122;
}
static void changeValue_ref(ref Test test)
{
test.value = 2233;
}
static void Main(string[] args)
{
Test test = new Test();
Console.WriteLine("[1]:{0}",test.value);
changeValue(test);
Console.WriteLine("[2]:{0}", test.value);
changeValue_ref(ref test);
Console.WriteLine("[3]:{0}", test.value);
Console.ReadLine();
}
}
会发现结果是:
以上结果说明:引用变量test做参数时,是按引用传递的,可以理解为C语言中changeValue(Test *p),就像changeValue(Test test),test是个句柄,是个指针,虽然说java和C#表面没有指针(当然C#里面是有指针的),却处处隐藏着指针。
下面用一张图表示changeValue(Test test)方法里面处理过程:
然后讨论对 changeValue_ref(ref Test test)的理解,我认为这里的test就是一个别名,而且关系到引用我们就该想到别名,深刻体会“别名”的意味,其实就是C/C++中的**p(指针的指针)。那么在c++实现以上功能,就是:
#include <iostream>
#include <stdlib.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
class Test
{
public:
Test()
{
this->value = 0;
}
Test(int x)
{
this->value = x;
}
int value;
};
void changeValue(Test *test)
{
test->value = 1122;
}
void changeValue_ref(Test **test)
{
(*test)->value = 2233;
}
int main(int argc, char** argv) {
Test *test = new Test();
cout<<"[0]"<<test->value<<endl;
changeValue(test);
cout<<"[1]"<<test->value<<endl;
changeValue_ref(&test);
cout<<"[2]"<<test->value<<endl;
delete test;
system("pause");
return 0;
}
结果跟上面C#代码运行结果一样,这下注意到了其实C#和java中处处隐藏着指针了吧。
这样以后,我们开始讨论下一个问题:在changeValue(Test test)和changeValue_ref中new出新的内存,改变这个新的内存中的数据,结果会怎么样呢?如果真正明白了上面的内存分析图,我想不难做出正确推论。好,先看代码:
c#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSharpRef
{
class Test
{
public Test()
{
this.value = 0;
}
public Test(int x)
{
this.value = x;
}
public int value;
}
class Program
{
static void changeValue(Test test)
{
test = new Test();
test.value = 1122;
}
static void changeValue_ref(ref Test test)
{
test = new Test();
test.value = 2233;
}
static void Main(string[] args)
{
Test test = new Test();
Console.WriteLine("[1]:{0}",test.value);
changeValue(test);
Console.WriteLine("[2]:{0}", test.value);
changeValue_ref(ref test);
Console.WriteLine("[3]:{0}", test.value);
Console.ReadLine();
}
}
}
你猜到结果了吗?
如果你分析出错误结果请再仔细研究上面的内存图,特别注意的是内存图为何会分成两边(左右两部分),而对于ref(别名)就不会分成左右两部分,因为只有一个变量,别名还是代表自己,而不用ref时,就会有拷贝,只不过拷贝出的变量里存的是原变量所指的地址,细细品味!
你可能要问,在c++中呢?
#include <iostream>
#include <stdlib.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
class Test
{
public:
Test()
{
this->value = 0;
}
Test(int x)
{
this->value = x;
}
int value;
};
void changeValue(Test *test)
{
test = new Test();
test->value = 1122;
}
void changeValue_ref(Test **test)
{
*test = new Test();
(*test)->value = 2233;
}
int main(int argc, char** argv) {
Test *test = new Test();
cout<<"[0]"<<test->value<<endl;
changeValue(test);
cout<<"[1]"<<test->value<<endl;
changeValue_ref(&test);
cout<<"[2]"<<test->value<<endl;
delete test;
system("pause");
return 0;
}
结果如下:
一样的结果,可是后果却不一样,这段代码会产生内存泄露,关于c/c++内存泄露问题,这里不做进一步讨论。
c#中还有一个out关键字引起了我的注意,感觉它跟ref相似,但也是有区别的,MS那帮精明的老头是不会搞两个功能一样的关键字的,这里我们来讨论一下ref与out的区别:
已有前辈总结的比较好了,我来引用一下吧:
1、ref传进去的参数必须在调用前初始化,out不必,即:
int i;
SomeMethod( ref i );//语法错误
SomeMethod( out i );//通过
2、ref传进去的参数在函数内部可以直接使用,而out不可:
public void SomeMethod(ref int i)
{
int j=i;//通过
//...
}
public void SomeMethod(out int i)
{
int j=i;//语法错误
}
3、ref传进去的参数在函数内部可以不被修改,但out必须在离开函数体前进行赋值。
ref在参数传递之前必须初始化;而out则在传递前不必初始化,且在 ... 值类型与引用类型之间的转换过程称为装箱与拆箱。