参数

  当函数接受参数时,就必须指定以下内容:

  • 函数在其定义中指定接受的参数列表,以及这些参数的类型。
  • 在每个函数调用中提供匹配的实参列表。
  仔细阅读 C#规范会发现行參( parameter )和实参( argument )之间存在一些细微的区别:行參是函数定义的一部分,而实参则由调用代码传递给函数。但是,这两个术语通常被简单地称为参数,似乎没有人对此感到十分不满。

  示例代码如下所示,其中可以有任意数量的参数,每个参数都有一个类型和一个名称:

  1. static <returnType> <FunctionName>(<paramType> <paramName>, ... )
  2. {
  3. ...
  4. return <returnValue>;
  5. }

  参数用逗号分隔开。每个参数都在函数的代码中用作一个变量。例如,下面是一个简单的函数,带有两个 double 参数,并返回它们的乘积:

  1. static double Product (double param1, double param2)
  2. {
  3. return param1 * parma2;
  4. }
  下面看一个较复杂的示例。  把下列代码添加到 Program.cs 中:

  1. class Program
    {
    static int MaxValue(int[] intArray)
    {
    int maxVal = intArray[0];
    for (int i = 1; i < intArray.Length; i++)
    {
    if(intArray[i] > maxVal)
    maxVal = intArray[i];
    }
    return maxVal;
    }

    static void Main(string[] args)
    {
    int[] myArray = { 1, 8, 3, 6, 2, 5, 9, 3, 0, 2 };
    int maxVal = MaxValue(myArray);
    Console.WriteLine("The maximum value in myArray is {0}", maxVal);
    Console.ReadKey();
    }
    }


  示例的说明  这段代码包含一个函数,它执行的任务就是本章引言中示例函数所完成的任务。该函数以一个整数数组作为参数,并返回该数组中的最大值。该函数的定义如下所示:

  1. static int MaxValue(int[] intArray)
    {
    int maxVal = intArray[0];
    for (int i = 1; i < intArray.Length; i++)
    {
    if (intArray[i] > maxVal)
    maxVal = intArray[i];
    }
    return maxVal;
    }


  函数 MaxValue() 定义了一个参数,即 int 数组 intArray,它还有一个 int 类型的返回值。最大值的计算是很简单的。局部整型变量 maxVal 初始化为数组中的第一个值,然后把这个值与数组中后面的每个元素依次进行比较。如果一个元素的值比 maxVal 大,就用这个值代替当前的 maxVal 值。循环结束时,maxVal 就包含数组中的最大值,用 return 语句返回。  Main() 中的代码声明并初始化一个简单的整数数组,用于 MaxValue() 函数:

  1. int[] myArray = { 1, 8, 3, 6, 2, 5, 9, 3, 0, 2 };


  调用 MaxValue(),把一个值赋给 int 变量 maxVal

  1. int maxVal = MaxValue(myArray);


  接着,使用 Console.WriteLine() 把这个值写到屏幕上:

  1. Console.WriteLine("The maximum value in myArray is {0}", maxVal);


- - -

  1. 参数匹配

  在调用函数时,必须使提供的参数与函数定义中指定的参数完全匹配,这意味着要匹配参数的类型、个数和顺序。例如,下面的函数:

  1. static void MyFunction(string myString, double myDouble)
  2. {
  3. ...
  4. }

  不能使用下面的代码调用:

  1. MyFunction(2.6, "Hello");

  这里试图把一个 double 值作为第一个参数传递,把 string 值作为第二个参数传递,参数顺序与函数声明中定义的顺序不匹配。

  也不能使用下面的代码:

  1. MyFunction("Hello");
  这里仅传送一个 string 参数,而该函数需要两个参数。使用上述两个函数调用都会产生编译错误,因为编译器要求必须匹配所有函数的签名。  使用函数的名称和参数来定义函数的签名。

  再回顾这个示例,MaxValue() 只能用于获取整数数组中的最大 int 值。如果用下面的代码替换 Main() 中的代码:

  1. static void Main(string[] args)
  2. {
  3. double[] myArray = { 1.3, 8.9, 3.3, 6.5, 2.7, 5.3 };
  4. double maxVal = MaxValue(myArray);
  5. Console.WriteLine("The maximum value in myArray is {0}", maxVal);
  6. Console.ReadKey();
  7. }

  就不能编译这段代码,因为参数类型是错误的。在本章后面的 “重载函数” 一节将介绍解决这个问题的一个有效技术。


  2. 参数数组

  C#允许为函数指定一个(只能指定一个)特殊参数,这个参数必须是函数定义中的最后一个参数,称为参数数组。参数数组允许用个数不定的参数调用函数,可使用 params 关键字定义它们。

  参数数组可以简化代码,因为在调用代码中不必传递数组,而是传递同类型的几个参数,这些参数会被放在可在函数中使用的一个数组中。

  定义使用参数数组的函数时,需要使用下列代码:

  1. static <returnType> <FunctionName>(<p1Type> <p1Name>, ..., params <type>[] <name>)
  2. {
  3. ...
  4. return <returnValue>;
  5. }

  使用下面的代码可以调用该函数。

  1. <FunctionName>(<p1>, ..., <val1>, <val2>, ...)

  其中 <val1><val2> 等都是 <type> 类型的值,用于初始化 <name> 数组。可以指定的参数个数几乎不受限制,但它们都必须是 <type> 类型。甚至根本不必指定参数。

  这一点使参数数组特别适合于函数提供一些额外的信息,供它们在处理过程中使用。例如,假定有一个函数 GetWord(),它的第一个参数是一个 string 值,并返回字符串中的第一个单词。

  1. string firstWord = GetWord("This is a sentence.");

  其中 firstWord 被赋予字符串 This

  可在 GetWord() 中添加一个 params 参数,以根据其索引选择另一个要返回的单词:

  1. string firstWord = GetWord("This is a sentence.", 2);

  假定第一个单词计数为 1,则 firstWord 就被赋予字符串 is

  也可以在第 3 个参数中限制返回的字符个数,同样通过 params 参数来实现:

  1. string firstWord = GetWord("This is a sentence.", 4, 3);

  此时 firstWord 被赋予字符串 sen

  下面的示例定义并使用带有 params 类型参数的函数。  把下述代码添加到 Program.cs 中:

  1. class Program
    {
    static int SumVals(params int[] vals)
    {
    int sum = 0;
    foreach (int val in vals)
    {
    sum += val;
    }
    return sum;
    }

    static void Main(string[] args)
    {
    int sum = SumVals(1, 5, 2, 9, 8);
    Console.WriteLine("Summed Values = {0}", sum);
    Console.ReadKey();
    }
    }


  示例的说明  这个示例用关键字 params 定义函数 sumVals(),该函数可以接受任意个 int 参数(但不接受其他类型的参数):

  1. static int SumVals(params int[] vals) { }


  这个函数对 vals 数组中的值进行迭代,将这些值加在一起,返回其结果。  在 Main() 中,用 5 个整型参数调用函数 SumVals()

  1. int sum = SumVals(1, 5, 2, 9, 8);


  也可以用 0、1、2 或 100 个整型参数调用这个函数—参数的数量不受限制。  C# 4 中引入了指定函数参数的新方式,包括用一种可读性更好的方式来包含可选参数。第 14 章将介绍这些方法,从该章中可以了解到自问世以来,C#语言发生了怎样的变迁。

  3. 引用参数和值参数

  本章迄今定义的所有函数都带有值参数。其含义是,在使用参数时,是把一个值传递给函数使用的一个变量。对函数中此变量的任何修改都不影响函数调用中指定的参数。例如,下面的函数使传递过来的参数值加倍,并显示出来:

  1. static void ShowDouble(int val)
  2. {
  3. val *= 2;
  4. Console.WriteLine("val doubled = {0}", val);
  5. }

  参数 val 在这个函数中被加倍,如果按一下方式调用它:

  1. int myNumber = 5;
  2. Console.WriteLine("myNumber = {0}", myNumber);
  3. ShowDouble(myNumber);
  4. Console.WriteLine("myNumber = {0}", myNumber);

  输出到控制台上的文本如下所示:

  1. myNumber = 5
  2. val doubled = 10
  3. myNumber = 5

  把 myNumber 作为一个参数,调用 ShowDouble() 并不影响 Main()myNumber 大的值,即使把 myNumber 赋值给 val 后将 val 加倍,myNumber 的值也不变。

  这很不错,但如果要改变 myNumber 的值,就会有问题。可以使用一个为 myNumber 返回新值的函数:

  1. static int DoubleNum(int val)
  2. {
  3. val *= 2;
  4. return val;
  5. }

  并使用下面的代码调用它:

  1. int myNumber = 5;
  2. Console.WriteLine("myNumber = {0}", myNumber);
  3. myNumber = DoubleNum(myNumber);
  4. Console.WriteLine("myNumber = {0}", myNumber);

  但这段代码一点也不直观,且不能改变用作参数的多个变量值(因为函数只有一个返回值)。

  此时可以通过 “引用”传递参数。即函数处理的变量与函数调用中使用的变量相同,而不仅仅是值相同的变量。因此,对这个变量进行的任何改变都会影响用作参数的变量值。为此,只需使用 ref 关键字指定参数:

  1. static void ShowDouble(ref int val)
  2. {
  3. val *= 2;
  4. Console.WriteLine("val doubled = {0}", val);
  5. }

  在函数调用中再次指定它(这是必须的,因为 ref 参数是函数签名的一部分):

  1. int myNumber = 5;
  2. Console.WriteLine("myNumber = {0}", myNumber);
  3. ShowDouble(ref myNumber);
  4. Console.WriteLine("myNumber = {0}", myNumber);

  输出到控制台的文本如下所示:

  1. myNumber = 5
  2. val doubled = 10
  3. myNumber = 10

  这次,myNumberShowDouble() 修改了。

  用作 ref 参数的变量有两个限制。首先,函数可能会改变引用参数的值,所以必须在函数调用中使用 “非常量” 变量。所以,下面的代码是非法的:

  1. const int myNumber = 5;
  2. Console.WriteLine("myNumber = {0}", myNumber);
  3. ShowDouble(ref myNumber);
  4. Console.WriteLine("myNumber = {0}", myNumber);

  其次,必须使用初始化过的变量。C#不允许假定 ref 参数在使用它的函数中初始化,下面的代码也是非法的:

  1. int myNumber; ShowDouble(ref myNumber);
  2. Console.WriteLine("myNumber = {0}", myNumber);

  4. 输出参数

  除了按引用传递值外,还可以使用 out 关键字,指定所给的参数是一个输出参数。 out 关键字的使用方式与 ref 关键字相同(在函数定义和函数调用中用作参数的修饰符)。实际上,它的执行方式与引用参数完全一样,因为在函数执行完毕后,该参数的值将返回给函数调用中使用的变量。但是,二者存在一些重要区别:

  • 把未赋值的变量用作 ref 参数是非法的,但可以把未赋值的变量用作 out 参数。* 另外,在函数使用 out 参数时,必须把它看成是尚未赋值。
      即调用代码可以把已赋值的变量用作 out 参数,但存储在该变量中的值会在函数执行时丢失。

  例如,考虑前面返回数组中最大值的 MaxValue() 函数,略微修改该函数,获取数组中最大值的元素索引。为简单起见,如果数组中有多个元素的值都是这个最大值,只提取第一个最大值的索引。为此,修改函数,添加一个 out 参数,如下所示:

  1. static int MaxValue(int[] intArray, out int maxIndex)
  2. {
  3. int maxVal = intArray[0];
  4. maxIndex = 0;
  5. for (int i = 1; i < intArray.Length; i++)
  6. {
  7. if (intArray[i] > maxVal)
  8. {
  9. maxVal = intArray[i];
  10. maxIndex = i;
  11. }
  12. }
  13. return maxVal;
  14. }

  可以采用以下方式使用该函数:

  1. int[] myArray = { 1, 8, 3, 6, 2, 5 9, 3, 0, 2 };
  2. int maxIndex;
  3. Console.WriteLine("The maximum value in myArray is {0}", MaxValue(myArray, out maxIndex));
  4. Console.WriteLine("The first occurrence of this value is at element {0}", maxIndex + 1);

  结果如下:

  1. The maximum value in myArray is 9
  2. The first occurrence of this value is at element 7

  注意 ⚠️,必须在函数调用中使用 out 关键字,就像 ref 关键字一样。

  在屏幕上显示结果时,给返回的 maxIndex 的值加上 1。这样可以使索引更容易读懂,因此数组的第一个元素记为元素 1,而不是元素 0。