原创 参数

2009-11-9 21:49 1542 12 12 分类: 软件与OS

我们之前一直是通过方法的return语句来返回数据。本节描述了如何通过方法参数来返回数据(而且方法参数的数量是可变的)。



初学者主题:匹配调用者变量与参数名


在前面一些代码清单中,我们故意使调用者中的变量名与目标方法中的参数名相匹配。这种匹配纯粹是为了增强可读性,名称是否匹配与方法调用的行为是完全没有关系的。


4.5.1 值参数


参数默认是传值(pass by value)的。换言之,变量的栈数据会完整地复制到目标参数中。例如在代码清单4-11中,在调用Combine()的时候,Main()使用的每个变量都会复制到Combine()方法的参数中。输出4-5展示了这个代码清单的结果。


代码清单4-11 以传值方式来传递变量


class Program


{


  static void Main()


  {


      // ...


      string fullName;


      string driveLetter = "C:";


      string folderPath  = "Data";


      string fileName  = "index.html";


      fullName = Combine(driveLetter, folderPath, fileName);


      Console.WriteLine(fullName);


      // ...


  }


  static string Combine(


      string driveLetter, string folderPath, string fileName)


  {


      string path;


      path = string.Format("{1}{0}{2}{0}{3}",


          System.IO.Path.DirectorySeparatorChar,


          driveLetter, folderPath, fileName);


      return path;


  }



}


输出4-5


C:\Data\index.html


Combine()方法返回之前,即使将null值赋给driveLetter、folderPath和fileName等变量,Main()中对应的变量仍会保持它们的初始值不变,因为在调用一个方法时,只是将变量的值复制了一份给方法。调用栈在一次调用的末尾“展开”的时候,当初复制的那个副本会被丢弃。



高级主题:引用类型与值类型的对比


出于本节的目的,传递的参数是值类型还是引用类型并不重要。重要的是目标方法是否能将一个新值赋给调用者的初始变量。由于会生成一个副本,所以不可能重新对调用者的副本进行赋值。


更具体地说,引用类型的变量包含实际数据所在的内存地址。假如以传值方式来传递一个引用类型的变量,地址会从调用者复制给方法参数。其结果就是,目标方法不能更新调用者变量的地址值。另一方面,假如方法参数是一个值类型,值本身会复制到参数中,更改参数不影响调用者的原始变量。


4.5.2 引用参数(ref)


来看看代码清单4-12的例子,它调用一个方法来交换两个值,输出4-6展示了结果。


代码清单4-12 以传引用的方式来传递变量


class Program


{


  static void Main()


  {


      // ...


      string first = "first";


      string second = "second";


      Swap(ref first, ref second);                            


      System.Console.WriteLine(


          @"first = " "{0}"", second = ""{1}""",


          first, second);


      // ...


  }



  static void Swap(ref string first, ref string second)          


  {


      string temp = first;


      first = second;


      second = temp;


  }



}


输出4-6


first = "second", second = "first"


赋给first和second的值被成功地交换,即使Swap()方法无任何返回值。为此,我们要以传引用(pass by reference)的方式来传递变量。将这个例子的Swap()调用与代码清单4-11的Combine()调用进行比较。不难发现,二者最明显的区别就是本例在参数的数据类型之前使用了关键字ref。这个关键字使参数以传引用的方式进行传递,使被调用的方法可以用新值来更新原始调用者的变量。


如果调用方法将参数指定为ref,那么调用者在调用这个方法的时候,需要在准备传递的变量之前添加一个ref关键字。这样一来,调用者就显式地指定了目标方法可以对它接收到的任何ref参数进行重新赋值。除此之外,调用者应该对传引用的变量进行初始化,因为目标方法可能直接从ref参数读取数据而不先对它们进行赋值。例如在代码清单4-12中,我们一开始就将first的值赋给temp——假定通过first传递的变量已经由调用者进行了初始化。


4.5.3 输出参数(out)


除了将参数单向传入一个方法(传值),或者同时将参数传入和传出一个方法(传引用)之外,还可以将数据从一个方法内部单向传出方法。为此,代码需要使用关键字out来修饰参数类型。例如代码清单4-13的GetPhoneButton()方法,它能返回与一个字符对应的电话按键。


代码清单4-13 仅传出的变量


class ConvertToPhoneNumber


{


  static int Main(string[] args)


  {


      char button;


      if(args.Length == 0)


      {


          Console.WriteLine(


              "ConvertToPhoneNumber.exe <phrase>");


          Console.WriteLine(


              "'_' indicates no standard phone button");


          return 1;


      }


      foreach(string word in args)


      {


          foreach(char character in word)


          {


              if(GetPhoneButton(character, out button))         


              {


                  Console.Write(button);


              }


              else


              {


                  Console.Write('_');


              }


         }


     }


     Console.WriteLine();


     return 0;


 }


 static bool GetPhoneButton(char character, out char button)       


 {


      bool success = true;


      switch( char.ToLower(character) )


      {


        case '1':


            button = '1';


            break;


        case '2': case 'a': case 'b': case 'c':


            button = '2';


            break;


        case '3': case 'd': case 'e': case 'f':


            button = '3';


            break;


        case '4': case 'g': case 'h': case 'i':


            button = '4';


            break;


        case '5': case 'j': case 'k': case 'l':


            button = '5';


            break;


        case '6': case 'm': case 'n': case 'o':


            button = '6';


            break;


        case '7': case 'p': case 'q': case 'r': case 's':


            button = '7';


            break;


        case '8': case 't': case 'u': case 'v':


            button = '8';


            break;


        case '9': case 'w': case 'x': case 'y': case 'z':


            button = '9';


            break;


        case '*':


            button = '*';


            break;


        case '0':


            button = '0';


            break;


        case '#':


            button = '#';


            break;


        case ' ':


            button = ' ';


            break;


        case '-':


            button = '-';


            break;


        default:


              // Set the button to indicate an invalid value


            button = '_';


            success = false;


            break;


      }


      return success;


  }



}


输出4-7展示了代码清单4-13的结果。


输出4-7


>ConvertToPhoneNumber.exe CSharpIsGood


274277474663


在这个例子中,假如能成功判断与一个字符对应的电话按键,GetPhoneButton()方法就返回true。方法还会使用被声明为out的button参数来返回对应的按钮。


如果一个参数被标记为out,编译器就会核实方法内的所有代码路径是否都设置了该参数。例如,假定在某个代码执行路径中,没有对button进行赋值,编译器就会报告一个错误,指出代码没有对button进行初始化。在代码清单4-13中,方法最后将"_"值赋给button,因为即使它无法判断正确的电话按键,也必须对button进行赋值。


4.5.4 参数数组(params)


在迄今为止讲到的例子中,参数数量都由方法声明进行了固定。然而,我们有时希望参数的数量是可变的。以代码清单4-11的Combine()方法为例。在那个方法中,传递了驱动器号、文件夹路径以及文件名(driveLetter、folderPath、fileName)等参数。假如路径中的文件夹数量大于1,调用者希望将额外的文件夹联结起来,以构成一个完整的路径,那么应该如何编写代码呢?也许最好的办法就是为文件夹传递一个字符串数组。然而,这会使调用代码变得稍微复杂一些,因为需要事先构造好一个数组,再将这个数组作为参数来传递。


为了简化编码,C#提供了一个特殊的关键字,它允许在调用一个方法时提供数量可变的参数,而不是由方法事先固定好参数的数量。在具体讨论这种方法声明之前,先观察一下代码清单4-14的Main()方法中的调用代码。


代码清单4-14 传递一个长度可变的参数列表


using System.IO;


class PathEx


{


  static void Main()


  {


      string fullName;


      // ...


      // Call Combine() with four parameters                                 


     fullName = Combine(                                                              


         Directory.GetCurrentDirectory(),                                                


         "bin", "config", "index.html");                                              


     Console.WriteLine(fullName);


     // ...


     // Call Combine() with only three parameters                               


     fullName = Combine(                                                              


         Environment.SystemDirectory,                                                


         "Temp", "index.html");                                               


      Console.WriteLine(fullName);


      // ...


      // Call Combine() with an array                                                 


      fullName = Combine(                                                              


          new string[] {                                                            


              "C:\", "Data",                                                       


              "HomeDir", "index.html"} );                                                   


      Console.WriteLine(fullName);


      // ...


  }


  static string Combine(params string[] paths)              


  {


      string result = string.Empty;


      foreach (string path in paths)


      {


          result = System.IO.Path.Combine(result, path);


      }


      return result;


  }



}


输出4-8展示了代码清单4-14的结果。


输出4-8


C:\Data\mark\bin\config\index.html


C:\WINDOWS\system32\Temp\index.html


C:\Data\HomeDir\index.html


在第一个Combine()调用中,我们指定了4个参数。第二个调用只指定了3个参数。在最后一个调用中,参数用一个数组来传递。换言之,Combine()方法可接受数量可变的参数,不管这些参数是以逗号分隔的,还是整体作为一个数组来传递的。


为了获得这样的效果,Combine()方法需要:


(1) 在方法声明的最后一个参数之前,添加一个params关键字;


(2) 将最后一个参数声明为一个数组。


像这样声明了一个参数数组(parameter array)之后,就可以将每个参数作为参数数组的一个成员来访问了。在Combine()方法的实现中,我们遍历paths数组的每个元素,并调用System.IO. Path.Combine()。这个方法能自动合并一个路径中的各个部分,并能正确地使用平台特有的目录分隔符。注意,PathEx.Combine()完全等价于Path.Combine(),只是PathEx.Combine()能处理数量可变的参数,而非只能处理两个。


参数数组有以下一些值得注意的特征。


l    参数数组不一定是方法声明中的唯一参数。但是,参数数组必须是方法声明中的最后一个参数。由于只有最后一个参数才可能是参数数组,所以方法最多只能有一个参数数组。


l    调用者可以为参数数组指定零个参数,这会造成包含零个数据项的一个数组。


l    参数数组是类型安全的——类型必须匹配于数组指定的类型。


l    调用者可以显式地提供一个数组,而不是以逗号分隔的参数列表。最终生成的CIL代码是一样的。


l    假如目标方法的实现要求一个最起码的参数数量,请在方法声明中显式指定必须提供的参数。这样一来,假如要求的参数遗失了,就会强迫编译器报错,而不是依赖于运行时错误处理。例如,使用int Max(int first, params int[] operads)而不是int Max(params int[] operads),确保至少有一个值传给Max()。


使用参数数组,我们可以将相同类型的、数量可变的多个参数传给一个方法。本章后面的4.7节讨论了如何支持不同类型的、数量可变的参数。

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
12
关闭 站长推荐上一条 /3 下一条