首页 > 代码库 > 方法(1)- 参数传递

方法(1)- 参数传递

按值传递

c#的函数默认只能有0或者1个返回值,而输入参数可以有任意个。默认情况,参数都是按值传递的,即在方法内部改变,而方法外部看不到任何改变。不过,这要看参数本身是值类型还是引用类型。如果是值类型的话,那么相信大家都知道,方法内部怎么改,外面都看不见,因为传进去的是一个值的副本。而如果参数本身是引用类型,那么传进去的就是一个引用。(亦即按值传递传递的是栈上的对象)此时如果在方法内改变引用类型的值,相当于把引用指向的堆上的对象改了,自然而然的,所有指向该堆上的对象的引用将全部受到影响。

class Program    {        static void Main(string[] args)        {            rectangle a = new rectangle("info", 10, 20);            rectangle b = a;            rectangle c = a;            change(a, 999);            Console.WriteLine(string.Format("A: chang is {0} and kuan is {1}, info: {2}", a._chang, a._kuan, a._recinfo.infostring));            Console.WriteLine(string.Format("B: chang is {0} and kuan is {1}, info: {2}", b._chang, b._kuan, b._recinfo.infostring));                        Console.WriteLine(string.Format("C: chang is {0} and kuan is {1}, info: {2}", c._chang, c._kuan, c._recinfo.infostring));                     Console.ReadKey();        }        public static void change(rectangle r, double chang)        {            r._chang = chang;            //没有意义,引用类型按值传递,不能改变引用指针的指向            r = new rectangle("newinfo", 1, 1);        }    }    class rectangle    {        public shapeinfo _recinfo;        public double _chang;        public double _kuan;        public rectangle(string recinfo, double chang, double kuan)        {            _recinfo = new shapeinfo(recinfo);            _chang = chang;            _kuan = kuan;        }    }

该例子中我们通过方法change修改了对象a的字段chang的值,但a,b,c三个对象全部受到了影响,这是因为这三个对象在栈上的引用的指针全部指向一个地方。下面的一个草图可能可以令大家加深一点印象。当我修改的时候,我修改的是堆上的字段chang的值,显然指向他的所有对象都有牵连。(这是对于具有可变性的引用类型来说的,对于具有不变性,或者不具有可变性的引用类型 - 字符串来说,我们无法修改它的值,也就是说我们做不到操作他堆上空间的值这件事,我们通过另一种方法进行修改,大家可以先思考一下这是什么方法 - 对了,如果我不能修改你,我可以另辟一块空间出来嘛,然后我把新的值放进去,然后把你的指针指向改成指到新的空间,不就可以了么,这正是修改字符串的时候发生的全部事情!)

技术分享

所以字符串是特例,字符串虽然是引用类型,但它具有不变性,也就是说一经赋值就不能改变(堆上的空间)了。每次当改变某个字符串的值的时候,实际上是在堆上新开辟了一块内存,然后把新的值储存在那里,然后改变栈上引用的指针的指向,令其指向了新的内存。例如(例子来源:http://www.cnblogs.com/zhili/archive/2013/06/11/ParameterPass.html)。所以说多次操作字符串将会对系统性能产生不好的影响:

  1. 开辟大量新的空间,占用过多堆上的空间
  2. 会开辟新的空间,然后指针指向也改变了。那么旧的那块空间呢,回收了么,显然没有,因为旧的那块空间也是字符串类型的,是一个引用类型,而引用类型是要等待垃圾收集器回收的,所以一时半会回收不了,这也影响性能

一个解决方法是使用StringBuilder,因为其具有可变性,于是上面两个问题都没有了。

 

class Program    {        static void Main(string[] args)        {             string str = "old string";            ChangeStr(str);            Console.WriteLine(str);                    }                private static void ChangeStr(string oldStr)        {            oldStr = "New string";            Console.WriteLine(oldStr);        }}

技术分享

这就是引用类型的按值传递,传递的是栈上的引用,而指针的指向不能被改变。那么如何可以改变指针的指向呢?那就要借助按引用传递了。

 

按引用传递(ref关键字)

默认方法是按值传递的,如果要按引用传递,必须借助ref关键字。先说说值类型。如果是以ref传入值类型的话,可以预见,外部的值类型也会发生变化(此时值类型就拥有了一个类似引用类型的行为)。显而易见,如果传进去的引用指向null,那么在方法内部操作相当于对null进行操作,编译器将给出NullException。所以传入的引用必须已经初始化了一个值。例如下面的例子:

static void Main(string[] args)        {            string a = "123";            string b = "abc";            //这里也要显式的加上ref            swap(ref a, ref b);            Console.WriteLine(string.Format("a= {0}, b={1}", a, b));            Console.ReadKey();        }        //值类型的按引用传递,不会为值类型装箱        public static void swap(ref string a, ref string b)        {            string temp = a;            a = b;            b = temp;        }

这个简单的例子演示了按引用传递两个值类型,当方法运行完之后,外部的两个字符串也发生了变化。要注意的是,当值类型按引用传递时,不会对值类型装箱。传进去的仅仅是地址而已。对引用类型按引用传递时,传进去的也是地址(或者是指针),此时我们可以操作这个地址,也就是改变指针的指向。我们也可以和按值传递时候的行为一样,改变引用类型字段的值。

        static void Main(string[] args)        {            rectangle a = new rectangle("info", 10, 20);            rectangle b = a;            rectangle c = a;            change(ref a);            Console.WriteLine(string.Format("A: chang is {0} and kuan is {1}, info: {2}", a._chang, a._kuan, a._recinfo.infostring));            Console.WriteLine(string.Format("B: chang is {0} and kuan is {1}, info: {2}", b._chang, b._kuan, b._recinfo.infostring));                        Console.WriteLine(string.Format("C: chang is {0} and kuan is {1}, info: {2}", c._chang, c._kuan, c._recinfo.infostring));                     Console.ReadKey();        }        public static void change(ref rectangle r)        {            //引用类型按引用传递,更改指针的指向使其指向一个新的对象            r = new rectangle("newinfo", 1, 1);        }

注意如果不是按引用传递的话,方法内部的

r = new rectangle("newinfo", 1, 1);

没有任何意义。

 

方法(1)- 参数传递