最近有人问到 ref 关键字的正确用法,下面我们来举例说明。其实要更好的理解 ref 关键字,结合 C++ 代码更加容易一些。另外在开始我们的例子之前,需要提前说明几点:
- C# 中的数据有两种类型:引用类型(reference types)和值类型(value types)。 简单类型(包括int, long, double等)和结构(structs)都是值类型,而其他的类都是引用类型。 简单类型在传值的时候会做复制操作,而引用类型只是传递引用,就像 C++ 中的指针一样。
- 注意 structs 在 C# 和 C++ 中的区别。在 C++ 中, structs 和类基本相同(except that the default inheritance and default access are public rather than private)。 而在 C# 中,structs 和类有很大的区别。其中最大的区别(我个人觉得,同时也是容易忽略的一个地方)可能就是它是值类型,而不是引用类型。
下面这段代码是 MSDN 中的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // cs_ref.cs using System; public class MyClass { public static void TestRef(ref char i) { // The value of i will be changed in the calling method i = 'b'; } public static void TestNoRef(char i) { // The value of i will be unchanged in the calling method i = 'c'; } // This method passes a variable as a ref parameter; the value of the // variable is changed after control passes back to this method. // The same variable is passed as a value parameter; the value of the // variable is unchanged after control is passed back to this method. public static void Main() { char i = 'a'; // variable must be initialized TestRef(ref i); // the arg must be passed as ref Console.WriteLine(i); TestNoRef(i); Console.WriteLine(i); } } |
大家很容易看出输出结果是:
1 2 | b b |
那么如果把这个例子做一些新的改动,将值类型(这里用的是 char)改成引用类型,程序运行又是什么效果呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // ---------------------------------------- // MyClass definition public class MyClass { public int Value; } // ---------------------------------------- // Tester methods public static void TestRef(ref MyClass m) { m.Value = 10; } public static void TestNoRef(MyClass m) { m.Value = 20; } public static void TestCreateRef(ref MyClass m) { m = new MyClass(); m.Value = 100; } public static void TestCreateNoRef(MyClass m) { m = new MyClass(); m.Value = 200; } public static void Main() { MyClass m = new MyClass(); m.Value = 1; TestRef(ref m); Console.WriteLine(m.Value); TestNoRef(m); Console.WriteLine(m.Value); TestCreateRef(ref m); Console.WriteLine(m.Value); TestCreateNoRef(m); Console.WriteLine(m.Value); } |
大家能马上给出正确的答案么?如果能,那看来你对 ref 的用法了解得还是非常不错的。其实如果大家对 C++ 比较熟悉的话,把这段代码换成 C++ 的就好理解的多了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | // ---------------------------------------- // MyClass definition #pragma once class MyClass { public: int Value; }; typedef MyClass* MyClassPtr; // ---------------------------------------- // Tester methods void TestRef(char* i) { *i = 'b'; } void TestNoRef(char i) { i = 'c'; } void TestRef(MyClassPtr* m) { (*m)->Value = 10; } void TestNoRef(MyClassPtr m) { m->Value = 20; } void TestCreateRef(MyClassPtr* m) { delete (*m); *m = new MyClass(); (*m)->Value = 100; } void TestCreateNoRef(MyClassPtr m) { m = new MyClass(); m->Value = 200; } int main(int argc, char* argv[]) { char c = 'a'; TestRef(&c); printf("%c\n", c); // output: b TestNoRef(c); printf("%c\n", c); // output: b MyClassPtr m = new MyClass; m->Value = 1; TestRef(&m); printf("%d\n", m->Value); TestNoRef(m); printf("%d\n", m->Value); TestCreateRef(&m); printf("%d\n", m->Value); TestCreateNoRef(m); printf("%d\n", m->Value); delete m; return 0; } |
这两段分别用 C# 和 C++ 实现的代码的输出结果都是一样的。后面用 MyClass 测试的输出结果是:
1 2 3 4 | 10 20 100 100 |
具体的原因相信经过大家的分析应该会很清楚的。另外如果大家有兴趣可以用 structs 再试试,也可以同时对 structs 在 C++ 和 C# 中的区别有进一步的认识。
» 下一篇:正确理解 C# 中的 ref 关键字 (续)
回复 引用
回复 引用 查看
有ref 或out 修饰符的引用类型参数的方法(使用new)创建一个对象之后,指向新对象的指针会被返回给调用代码。
楼主既然写出来了就应该详细一些,就这么一些代码而没有任何解释,是为了显摆还是作什么。。。
回复 引用
回复 引用
骗人的吧!!!
回复 引用 查看
回复 引用 查看
这个一楼最搞笑了。
回复 引用 查看
我觉得aaa有道理
出了string的特殊点,其他的引用类型都没必要用ref吧
回复 引用
加ref相当于指针的指针
很基本的概念啊。。
回复 引用 查看
C# 中的引用和 C++ 的指针就是差不多的吧!有什么区别么?请不吝赐教哦,呵呵!
回复 引用
引用必须有实体,指针可以是空指针,等等还有好多区别。
另外dotnet的对象是放在托管堆里面的,有自动垃圾回收装置。不要简单地想象成C++中的指针
回复 引用 查看
呵呵,Cpp中指针是可以参与运算的,例如自增(++),C#中的引用能进行自增吗?
回复 引用 查看
要这么算起来,引用和指针的差别就大了去了!在C++中能 delete,在C#中可以么?在C++中能直接指定指针的值,在C#中可以么?C#和C++的内存管理不一样,这两个当然有很大的区别。
我仅仅只是单指这篇文章中对引用和指针的用法的差别!
回复 引用
善用google,在博客园里应该就有好多讲ref的blog
回复 引用 查看
回复 引用
System.Int32, System.Int64, System.Double 等都定义在 mscorlib.dll 中,都是 struct。
不过我觉得直接定义 int, long, double 什么的不会 box 成 System.Int32, System.Int64, System.Double 吧,就是一些简单的值吧
回复 引用
楼上真是。。。
int, long, double 分别是 System.Int32, System.Int64, System.Double的别名,既然你都说了它们是struct,那又哪来的boxing呢。
回复 引用
@aaa
我觉得看看代码就应该会比较明白的,没有什么比代码更能说明问题的了。不过看来晚上我再补上一篇吧。
有什么不对的地方,也请大家多多指出来啊
回复 引用
假如一个方法的参数加上ref关键字,那么在调用该方法之前必须首先初始化该参数,被调用方法可以任意选择读取或修改该参数的值。
对于引用类型参数,传递给方法的就是这个参数的地址,所以一般不需要使用ref关键字(除了用new对这个参数重新赋值)。
实际上不明白的就是new一个引用类型的实例,它返回指向该引用类型实例在托管堆的地址的指针,那么在不使用ref关键字的情况下为什么不能直接改变这个引用类型参数呢,难道这个引用类型参数(它本身保存指向托管堆中的内存地址)在堆栈中也有一个自己的地址吗?
回复 引用
文章不错。
回复 引用 查看
在这里讨论讨论又明白一些东西了!原来对 boxing 和 unboxing 这些东西就没仔细注意过。看了 aaa 的回复,再查了些文章,总算明白了些
这里面有个例子:
struct Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(10, 10);
object box = p;
p.x = 20;
Console.Write(((Point)box).x);
最后输出结果是 10
回复 引用 查看
public static void TestCreateNoRef(MyClass m)
{
m = new MyClass();
m.Value = 200;
}
这个方法的参数m本身仅包含一个指向托管堆的指针,而不包含其在堆栈中的地址,所以没有办法new MyClass用所返回的指针改变m现在的指针(因为不知道m这个参数在堆栈中的地址),所以new MyClass之后应该重新在堆栈中创建一个指向这个new MyClass的指针。而加上ref关键字之后,被调用方法除了可以得到m所保存的指针之外,还可以得到m在堆栈中的地址。个人感觉是这样,不知道对不对
boxing即从值类型-》引用类型 : 从托管堆为新生成引用类型对象分配内存(包括要boxing的值类型的大小以及指针)空间,将值类型实例字段从堆栈拷贝到新分配的内存中,返回托管堆中新分配对象的地址。
楼上的输出之所以不是20就是因为box仅拷贝了p的值
struct Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(10, 10);
object box = p; //对p进行装箱,box指向已装箱对象
p=(point)box; //对box进行拆箱,并将字段从托管堆拷贝到堆栈上
回复 引用
回复 引用 查看
明白这个,就很容易区分了。
回复 引用
回复 引用 查看
回复 引用
另外,指针和引用是不同的。
概念还是不要混淆的好。
回复 引用 查看
在托管情况下,c#所谓指针和引用不是同样的吗
回复 引用
回复 引用
int 只是System.Int32的别名而已,事实上就是System.Int32.
回复 引用
支持。
回复 引用 查看
相当于指向指针的指针啦....
回复 引用
回复 引用 查看
回复 引用
回复 引用