本节介绍如何把函数添加到应用程序中,以及如何在代码中使用(调用)它们。首先从基础知识开始,看看不交换任何数据的简单函数以及调用它们的代码,然后介绍更高级的函数用法。
首先看一个示例。
试试看:定义和使用基本函数
(1) 在目录C:\BegVCSharp\Chapter6下创建一个新控制台应用程序Ch06Ex01。
(2) 把下述代码添加到Program.cs中:
class Program
{
static void Write()
{
Console.WriteLine("Text output from function.");
}
static void Main(string[] args)
{
Write();
Console.ReadKey();
}
}
(3) 执行代码,结果如图6-1所示。

图 6-1
示例的说明
下面的4行代码定义了函数Write():
static void Write()
{
Console.WriteLine("Text output from function.");
}
这些代码把一些文本输出到控制台窗口中。但此时这些并不重要,我们更关心定义和使用函数的机制。
函数定义由以下几部分组成:
● 两个关键字:static和void
● 函数名后跟圆括号,如Write()
● 一个要执行的代码块,放在花括号中
注意:
函数名一般采用PascalCasing形式来编写。
定义Write()函数的代码非常类似于应用程序中的其他代码:
static void Main(string[] args)
{
...
}
这是因为,到目前为止我们编写的所有代码(除了类型定义之外)都是函数的一部分。函数Main()是(如自动生成的代码所述)控制台应用程序的入口点函数。当执行一个C#应用程序时,就会调用它包含的入口点函数,这个函数执行完后,应用程序就终止了。所有的C#可执行代码都必须有一个入口点。
Main()函数和Write()函数的惟一区别(除了它们包含的代码)是函数名Main后面的圆括号中还有一些代码,这是指定参数的方式,详见后面的内容。
如上所述,Main()函数和Write()函数都是使用关键字static和void定义的。关键字static与面向对象的概念相关,本书在后面讨论。现在只需记住,在本节的应用程序中所使用的所有函数都必须使用这个关键字。
而void解释起来就要简单得多。这个关键字表明函数没有返回值。在本章的后面,将讨论函数有返回值时需要编写什么代码。
继续下去,调用函数的代码如下所示:
Write();
输入函数名,后跟空括号即可。在程序执行到这行代码时,就会运行Write()函数中的代码。
注意:
在定义函数和调用函数时,必须使用圆括号。如果删除它们,代码就不能编译。
6.1.1 返回值
通过函数进行数据交换的最简单方式是利用返回值。有返回值的函数会计算这个值,其方式与在表达式中使用变量计算它们包含的值完全相同。与变量一样,返回值也有数据类型。
例如,有一个函数getString(),其返回值是一个字符串,可以在代码中使用该函数,如下所示:
string myString;
myString = getString();
另外,还有一个函数getVal(),它返回一个double值,可以在数学表达式中使用它。
double myVal;
double multipler = 5.3;
myVal = getVal() * multiplier;
当函数返回一个值时,可以用下面两种方式修改函数:
● 在函数声明中指定返回值的类型,但不使用关键字void。
● 使用return关键字结束函数的执行,把返回值传送给调用代码。
在代码的术语中,控制台应用程序中的下述代码看起来像是前面见过的函数类型:
static <returnType> <functionName>()
{
...
return <returnValue>;
}
这里惟一的限制是<returnValue>必须是一个值,其类型可以是<returnType>,也可以隐式转换为该类型。但是,<returnType>可以是任何类型,包括前面介绍的较复杂的类型。
这段代码可以很简单:
static double getVal()
{
return 3.2;
}
但是,返回值通常是函数执行的一些处理的结果,如上所示,返回值是使用const变量得到的。
在执行到return语句时,程序会立即返回调用代码。这个语句后面的代码都不会执行。但是,这并不意味着return语句只能放在函数体的最后一行。可以在前边的代码里使用return,也可能在执行了分支逻辑之后使用。把return语句放在for循环中、if块中,或其他结构中,会使该结构立即终止,函数也立即终止。例如:
static double getVal()
{
double checkVal;
// checkVal assigned a value through some logic.
if (checkVal < 5)
return 4.7;
return 3.2;
}
根据checkVal的值,将返回两个值中的一个。
这里惟一的限制是return语句必须在函数的闭合花括号 } 之前处理。下面的代码是不合法的:
static double getVal()
{
double checkVal;
// checkVal assigned a value through some logic.
if (checkVal < 5)
return 4.7;
}
如果 checkVal>= 5,就不会执行到 return语句,这是不允许的。所有的处理路径都必须执行到 return语句。在大多数情况下,编译器会检查是否执行到 return语句,如果没有,就给出一个错误“并不是所有的处理路径都返回一个值”。
最后要注意的是,return可以用在通过void关键字声明的函数中(没有返回值)。如果这么做,函数就会立即终止。以这种方式使用return语句时,在return关键字和其后的分号之间提供返回值是错误的。
6.1.2 参数
当函数接受参数时,就必须指定下述内容:
● 函数在其定义中指定接受的参数列表,以及这些参数的类型。
● 在每个函数调用中匹配的参数列表。
这涉及到下述代码:
static <returnType> <functionName>(<paramType> <paramName>, ...)
{
...
return <returnValue>;
}
其中可以有任意多个参数,每个参数都有一个类型和一个名称。参数用逗号分隔开。每个参数都在函数的代码中用作一个变量。
例如,下面是一个简单的函数,带有两个参数,并返回它们的乘积:
static double product(double param1, double param2)
{
return param1 * param2;
}
下面看一个比较复杂的示例。
(1) 在目录C:\BegVCSharp\Chapter6下创建一个新控制台应用程序Ch06Ex02。
(2) 把下述代码添加到Program.cs中:
{
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();
}
}
(3) 执行代码,结果如图6-2所示。

图 6-2
示例的说明
这段代码包含一个函数,它执行的任务就是本章引言中示例函数所完成的任务。该函数的参数是一个整数数组,返回该数组中的最大值。该函数的定义如下所示:
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()函数:
int[] myArray = {1, 8, 3, 6, 2, 5, 9, 3, 0, 2};
调用MaxValue(),把一个值赋给int变量maxVal:
int maxVal = MaxValue(myArray);
接着,使用Console.WriteLine()把这个值写到屏幕上:
Console.WriteLine("The maximum value in myArray is {0}", maxVal);
1. 参数匹配
在调用函数时,必须使参数与函数定义中指定的参数完全匹配,这意味着要匹配参数的类型、个数和顺序。例如,下面的函数:
static void myFunction(string myString, double myDouble)
{
...
}
不能使用下面的代码调用:
myFunction (2.6, "Hello");
这里试图把一个double值作为第一个参数传递,把string值作为第二个参数传递,参数的顺序与函数声明中定义的顺序不匹配。
也不能使用下面的代码:
myFunction("Hello");
这里仅传送了一个string参数,而该函数需要两个参数。
使用上述两个函数调用都会产生编译错误,因为编译器要求必须匹配函数的签名。
再回过头来看看这个示例,MaxValue()只能用于获取整数数组中的最大值int。如果用下面的代码替换Main()中的代码:
static void Main(string[] args)
{
double[] myArray = {1.3, 8.9, 3.3, 6.5, 2.7, 5.3};
double maxVal = MaxValue(myArray);
Console.WriteLine("The maximum value in myArray is {0}", maxVal);
Console.ReadKey();
}
这段代码就不能编译,因为参数类型是错误的。
在本章后面的“重载函数”一节将介绍解决这个问题的一个有效技术。
2. 参数数组
C#允许为函数指定一个(只能指定一个)特定的参数,这个参数必须是函数定义中的最后一个参数,称为参数数组。参数数组可以使用个数不定的参数调用函数,它可以使用params关键字来定义。
参数数组可以简化代码,因为不必从调用代码中传递数组,而是传递可在函数中使用的一个数组中相同类型的几个参数。
定义使用参数数组的函数时,需要使用下述代码:
static <returnType> <functionName>(<p1Type> <p1Name>, ... ,
params <type>[] <name>)
{
...
return <returnValue>;
}
使用下面的代码可以调用该函数。
<functionName>(<p1>, ... , <val1>, <val2>, ...)
其中<val1>, <val2>等都是类型为<type>的值,用于初始化<name>数组。在可以指定的参数个数方面没有限制。甚至可以根本不指定参数。惟一的限制是它们都必须是<type>类型。
这一点使参数数组特别适合于为在处理过程中要使用的函数指定其他信息。例如,假定有一个函数GetWord(),它的第一个参数是一个string值,并返回字符串中的第一个单词。
string firstWord = GetWord("This is a sentence.");
其中firstWord被赋予字符串This。
可以在GetWord()中添加一个params参数,以根据其下标选择另一个要返回的单词:
string firstWord = GetWord("This is a sentence.", 2);
假定第一个单词计数为1,则firstWord就被赋予字符串is。
也可以在第3个参数中限制返回的字符个数,同样通过params参数来实现:
string firstWord = GetWord("This is a sentence.", 4, 3);
其中firstWord被赋予字符串sen。
下面看一个完整的示例。这个示例定义并使用带有params类型参数的函数。
试试看:通过函数交换数据(2)
(1) 在目录C:\BegVCSharp\Chapter6下创建一个控制台应用程序Ch06Ex03。
(2) 把下述代码添加到Program.cs中:
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();
}
}
(3) 执行代码,结果如图6-3所示。

图 6-3
示例的说明
在这个示例中,函数sumVals()是用关键字params定义的,可以接受任意个int参数(或不接受任何参数):
static int SumVals(params int[] vals)
{
...
}
这个函数对vals数组中的值进行迭代,把这些值加在一起,返回其结果。
在Main()中,用5个整型参数调用这个函数:
int sum = SumVals (1, 5, 2, 9, 8);
也可以用0、1、2或100个整型参数调用这个函数—— 参数的个数没有限制。
3. 引用参数和值参数
到目前为止,本章定义的所有函数都带有值参数。其含义是,在使用参数时,是把一个值传递给函数使用的一个变量。对函数中此变量的任何修改都不影响函数调用中指定的参数。例如,下面的函数使传递过来的参数值加倍,并显示出来:
static void showDouble(int val)
{
val *= 2;
Console.WriteLine("val doubled = {0}", val);
}
参数val在这个函数中被加倍,如果以下面的方式调用它:
Console.WriteLine("myNumber = {0}", myNumber);
showDouble(myNumber);
Console.WriteLine("myNumber = {0}", myNumber);
输出到控制台上的文本如下所示:
myNumber = 5
val doubled = 10
myNumber = 5
把myNumber作为一个参数,调用showDouble()并不影响Main()中myNumber的值,即使分配给val的参数被加倍,myNumber的值也不变。
这很不错,但如果要改变myNumber的值,就会有问题。可以使用一个给myNumber返回新值的函数:
static void DoubleNum(int val)
{
val *= 2;
return val;
}
并使用下面的代码调用它:
int myNumber = 5;
Console.WriteLine("myNumber = {0}", myNumber);
myNumber = DoubleNum(myNumber);
Console.WriteLine("myNumber = {0}", myNumber);
但这段代码一点也不直观,且不能改变用作参数的多个变量值(因为函数只有一个返回值)。
此时可以通过引用传递参数。即函数处理的变量与函数调用中使用的变量相同,而不仅仅是值相同的变量。因此,对这个变量进行的任何改变都会影响用作参数的变量值。为此,只需使用ref关键字指定参数:
static void showDouble(ref int val)
{
val *= 2;
Console.WriteLine("val doubled = {0}", val);
}
在函数调用中(这是必须的,因为ref参数是函数签名的一部分):
Console.WriteLine("myNumber = {0}", myNumber);
Console.WriteLine("myNumber = {0}", myNumber);
输出到控制台上的文本如下所示:
myNumber = 5
val doubled = 10
myNumber = 10
这次,myNumber has被showDouble()修改了。
用作ref参数的变量有两个限制。首先,函数可能会改变引用参数的值,所以必须在函数调用中使用变量。所以,下面的代码是非法的:
const int myNumber = 5;
Console.WriteLine("myNumber = {0}", myNumber);
showDouble(ref myNumber);
Console.WriteLine("myNumber = {0}", myNumber);
其次,必须使用初始化过的变量。C#不允许假定ref参数在使用它的函数中初始化,下面的代码也是非法的:
int myNumber;
showDouble(ref myNumber);
Console.WriteLine("myNumber = {0}", myNumber);
4. 输出参数
除了根据引用传递值之外,还可以使用out关键字,指定所给的参数是一个输出参数。out关键字的使用方式与ref关键字相同(在函数定义和函数调用中用作参数的修饰符)。实际上,它的执行方式与引用参数完全一样,因为在函数执行完毕后,该参数的值将返回给函数调用中使用的变量。但是,这里有一些重要区别。
● 把未赋值的变量用作ref参数是非法的,但可以把未赋值的变量用作out参数。
● 另外,在函数使用out参数时,该参数必须看作是还未赋值。即调用代码可以把已赋值的变量用作out参数,存储在该变量中的值会在函数执行时丢失。
例如,考虑前面返回数组中最大值的MaxValue()函数,略微修改该函数,获取数组中最大值的元素下标,为了简单起见,如果数组中有多个元素的值都是这个最大值,只提取第一个最大值的下标。为此,修改函数,添加一个输出参数,如下所示:
static int MaxValue(int[] intArray, out int maxIndex)
{
int maxVal = intArray[0];
maxIndex = 0;
for (int i = 1; i < intArray.Length; i++)
{
if (intArray[i] > maxVal)
{
maxVal = intArray[i];
maxIndex = i;
}
}
return maxVal;
}
可以用下述方式使用该函数:
int[] myArray = {1, 8, 3, 6, 2, 5, 9, 3, 0, 2};
int maxIndex;
Console.WriteLine("The maximum value in myArray is {0}",
MaxValue(myArray, out maxIndex));
Console.WriteLine("The first occurrence of this value is at element {0}",
maxIndex + 1);
结果是:
The maximum value in myArray is 9
The first occurrence of this value is at element 7
注意,必须在函数调用中使用out关键字,就像ref关键字一样。
注意:
在屏幕上显示结果时,给返回的maxIndex的值加上1。这样可以使下标更容易读懂,因此数组的第一个元素指元素1,而不是元素0。





