<From C++ To C#>进军到函数
看来简单的代码实现已经不在话下,那我们再来研究一下函数。
函数就是把一段相关内容的代码整合在一起,赋予一个函数名,函数也可能会有参数和返回值。这个跟C++中的函数是一致的,并且C#中的函数也可以根据参数的不同进行重载。
那么差别在哪里呢?
一个我们要强调的是,在C#中不存在全局函数。使用Main函数的时候我们知道,所有的函数都是属于某个具体的类,哪怕是Main这样的函数也是如此。
另一个就是参数的修饰。回忆一下C 或 C++中的函数参数,我们花了很长时间研究值传递和引用传递(指针传递)的区别。
//值传递 void fun1(string s); //引用/指针传递 void fun2(string * ps);
传参的时候必然要进行copy,值传递的时候系统把值copy到一个局部变量中,局部变量在函数结束后会被释放,所以函数内部对该局部变量的任何修改都不会对调用函数的地方产生影响。
引用传递也会进行copy,系统会把传递的地址copy到一个局部变量中,也就是说,现在,局部变量和外部变量都是指向同一块内存地址进行操作,对该局部变量的操作直接影响外部变量。
那么C#是如何区分的呢。
首先,在C#中没有指针符号。
string str = new string('a',10);
这句话创建了一个由10个a组成的字符串对象,返回的其实是一个指向这块内存地址的指针,只不过C#不再用*来标明了。
再试一下
Object o = new Object();
StringBuilder sb = new StringBuilder("123");
确实如此。
那没有*符号如果来区别值传递和引用传递呢?C#引入了一些参数的修饰符。
//值传递 void fun1(string s); //引用/指针 传递 void fun2(ref string s); //引用/指针 传递 void fun2(out string s);
值传递还是老样子,引用传递C#分出了两种修饰符,ref就是我们常常用的引用传递,调用者赋初值,函数内部可以对他进行改变。out更强调这个值是输出参数,也就是强制要求函数内部必须对这个值进行改变,否则编译无法通过。
另外,调用这些函数的时候,参数的修饰符也同样要加上。
我们举个例子来试试吧。先试试看值类型,int
//值传递 static void Test1(int i) { i=1; } //引用传递 static void Test2(ref int i) { i=2; } //输出参数 static void Test3(out int i) { i=3; } static void Main(string[] args) { int i = 0; Test1(i); Console.Write("{0},", i); Test2(ref i); Console.Write("{0},", i); Test3(out i); Console.Write("{0}", i); } 函数的输出结果是0,2,3
正如我们前面所说,值传递的时候i=1不能影响外部变量,所以i打印还是0,而其他两种引用传递,对内容的修改会直接影响外部数据,所以打印出来分别是2,3
那再来试试看引用类型StringBuilder吧。
static void Test1(StringBuilder sb) {
sb.Append("pass by value");
} static void Test2(ref StringBuilder sb) {
sb.Append("ref");
sb = new StringBuilder("value in Test");
} static void Test3(out StringBuilder sb) { //sb.Append("out");//out参数必须赋值,所以这句无法编译通过 sb = new StringBuilder("value in Test"); } static void Main(string[] args) { StringBuilder sb = new StringBuilder("value in Main. "); Test1(sb); Console.WriteLine( sb); Test2(ref sb); Console.WriteLine( sb); Test3(out sb); Console.WriteLine( sb); } 运行的结果是
value in Main.pass by value
value in Test
value in Test
我们来分析一下:第一句 StringBuilder sb = new StringBuilder("value in Main. "); 创建了一个sb对象。前面说了,其实sb是一个指向sb对象的指针,只不过C#中不用*明确标明了。这样的话,虽然void Test1(StringBuilder sb)是用的值传递的方式,但是sb本身就已经是一个指针,所以对sb的任何修改操作都会体现到外部对象上。因此
sb.Append("pass by value"); 可以实现。
推论下来,第二和第三个函数都是引用传递,而传递的内容又已经是一个指针,相当与函数传递的是指针的指针。这样我们不仅可以对sb指向的内容做修改,甚至可以对sb对象本身重新定义。这也就是第二第三个函数调用结果的原因。