问题是什么
刚接触C#不久,对于值类型和引用类型的区别还是有点糊涂。后来看了一个不能工作的Swap()(下面有示例)方法之后,才发现,其实引用类型相当于C/C++中的指针——因为有声明:“string aStr = null;”是正确的。为了让自己以后可以追溯一下,所以把今天的理解记录下来,写在这里。
如何理解
C#中关于引用类型的定义是这样的:
“……值类型与引用类型的基本区别是他们在内存中的存储方式。……引用类型变量的地址存放在栈上,但实际的对象存放在堆上。……”
在上一篇文章里我说过,指针其实是一个ulong,从上面的描述中可以看出,引用类型其实也是一个ulong(地址),也就是说,“Object obj;”中,obj被分成两部分,一部分是其地址,另一部分是真正的Object实例。这两部分紧密联系在一起,形成一个引用类型对象。
引用类型的定义暗示我们,C#中引用类型相当于C++的指针,只不过C#语言本身作了一些工作,将指针和其指向的对象紧密地联系在一起了。只不过我们无法通过强制类型转换来获取这个值——也许有,但是我比较菜,不知道,呵呵。另外,引用类型的定义还暗示我们,C/C++函数调用时以传值方式传递参数的规则,在C#中同样有效。最简单的例子:
// C# code. Non dereferencing.
public void Swap(T lhs, T rhs)
{
T temp = lhs;
lhs = rhs;
rhs = temp;
}
调用该函数结果是什么样子呢?运行一下下面的代码就知道结果:传进去的引用类型的对象根本就没有改变(以string为例):
string a = "aaaa";
string b = "bbbb";
Swap(a, b);
System.Console.WriteLine("a = " + a + ", b = " + b);
输出结果:a = aaaa, b = bbbb
因为实际上是对地址进行操作,传入的参数就是地址,因此可以将其翻译成C++(方便起见,使用int类型):
// C++ code. Non dereferencing.
template
void Swap(T * lhs, T * rhs)
{
T * ptemp = lhs;
lhs = rhs;
rhs = ptemp;
}
这个函数也不能达到交换实参的目的,因为此处操作的是地址,与上一个例子一样。也就是说,C/C++和C#中函数调用时以传值方式传递参数。
那么为什么不是与C++的引用相似呢?可以看以下代码:
// C++ code
template
void Swap(T& lhs, T& rhs)
{
int temp = lhs;
lhs = rhs;
lhs = temp;
}
对这个函数的调用将正确的交换两个实参。
C#要想通过函数调用来修改引用类型的对象,有两种方式:
1、dereference该引用以获取实际对象,即改变存放在堆上的对象本身,而不是存放在栈上的地址(没有进行dereference),这与C++的指针是相同的,也是前两个例子说明的问题。用deference的话,而且T实现了拷贝自身的方法,那么实现如下:
// C# code. Dereferencing.
// NOTE: T must have CopyTo() method.public void Swap
(T lhs, T rhs)
{
T temp = new T();
lhs.CopyTo(temp);
rhs.CopyTo(lhs);
temp.CopyTo(rhs);
}
翻译成C++,就是如下代码:
// C++ code. Dereferencing.
// NOTE: T must have operator "=" overloaded.
template
void Swap(T * lhs, T * rhs)
{
int temp = *lhs;
*lhs = *rhs;
*rhs = temp;
}
2、使用ref修饰符(从另一个角度说明C#引用的行为更像C++的指针),如下:
//可以工作的Swap
// NOTE: T doesn't need to have CopyTo() method implemented.
// If you don't need to modify the reference itself, "ref" is not needed.
private void Swap(ref T lhs, ref T rhs)
{
T temp = lhs;
lhs = rhs;
rhs = temp;
}
原因很简单,使用了ref,将其翻译成C++就是:
/ C++ code. Dereferencing.
// NOTE: T doesn't need to have operator "=" overloaded.template
void Swap(T **lhs, T **rhs)
{
T * temp = *lhs;
*lhs = *rhs;
*rhs = temp;
}
至于这里面涉及到的应该使用引用还是指针的问题,那就是另外一个话题了。关于指针本身,可以参考“指针是通往地狱的捷径”。
PS:由于对C#水平实在太浅,理解难免出现偏差,任何错误,请大虾指正!上述代码均为合法代码。
参考:
Programming C#, 3rd edition.
Copyleft (C) 2007-2009 raof01.
本文可以用于除商业外的所有用途。此处“用途”包括(但不限于)拷贝/翻译(部分或全部),不包括根据本文描述来产生代码及思想。若用于非商业,请保留此
权利声明,并标明文章原始地址和作者信息;若要用于商业,请与作者联系(raof01@gmail.com),否则作者将使用法律来保证权利。
阅读(15927) | 评论(25) | 转发(0) |