c# 函数与函数重载
本章的学习重点:
◆ 函数的概念
◆ 函数的语法
◆ 函数的调用及执行过程
4.1函数概念
在数学领域,我们经常会听到函数的概念,比如三角函数中的正弦函数,余弦函数,正切函数,余切函数,这些对于我们来说,再熟悉不过了,只要输入相对应的变量就可以计算出结果。格式为y=f(x)。x称为自变元,y称为因变元,f称为函数名。
今天,在计算机领域,也继承了这种思维方式,把一段经常需要使用的代码封装起来,在需要使用时可以直接调用,并且返回结果。格式为y=f(x)。x称为参数,y称为返回值,f称为函数名。同时,也再一次说明了计算机与数学的互补关系。
4.1.1函数的概念
函数就是一段准备好的代码,它可以接受参数,处理逻辑,然后返回某些数据。我们通常使用函数来实现某个单一功能,通过对多个函数的组织和调用来实现整个程序的功能。函数相当于把一个复杂的较大的程序,分解为单一的较小的程序模块。所以,有时我们也把函数成为子程序。
在c#语言中,存在两种函数,一种是运行时提供的标准库函数,称为内置函数。这种函数不需要用户定义就可以直接使用。比如,console.write(),console.read()等函数。微软公司在开发.net平台的时候已经把我们在编写程序时经常需要使用的函数,以最优的方式编写好了,并以类库的方式提供给用户使用。用户使用时只需按内置函数给出的函数名和相关规则直接调用即可。
另一种是用户自定义的函数,称为自定义函数。尽管内置函数能为程序设计提供方便,使程序的质量和效率得以提高,但其数量毕竟有限,不能完全满足用户的特殊需求。因此,c#语言允许用户按c#语言的函数语法自己设计函数,来实现更个性化的功能。
作者心得:
函数这一节很重要,可以说一个程序的优劣集中体现在函数上。如果函数使用的恰当,可以让程序看起来有条理,容易看懂。如果函数使用的乱七八糟,或者是没有使用函数,程序就会显得很乱,不仅让别人无法查看,就连自己也容易晕头转向。可以这样说,如果超过100行的程序中没有使用函数,那么这个程序一定很罗嗦(有些绝对,但也是事实)。
4.1.2函数的语法
上一节,我们讲到了内置函数和自定义函数,内置函数比较简单只要知道函数的作用以及调用规则就可以顺利的使用了。内置函数的内部实现和自定义函数的实现是一样的,所以,我们只要理解了自定义函数,自然就明白了内置函数的使用方法。下面我们向大家介绍自定义函数的语法,简称函数的语法。
一个函数包括函数头和语句体两个部分,语句体比较容易理解,就是指函数的内部实现。函数头由三个部分组成:返回值类型,函数名,参数表。格式如下:
返回值类型 函数名(参数表)
{
语句体;
}
返回值类型用来定义函数返回的数据是什么类型的,可以是前面说到的某个内置数据类型、或者是自定义数据类型,在语句体中使用return关键字来返回数据。如果没有返回值,使用void关键字来代替具体的返回值类型。
函数名用来代表函数的标识,所以在程序中必须是唯一的,不能重复定义。它也必须遵循我们在讲述变量时所讲的标识符命名规则。
参数表用来向语句体传送外部数据,可以没有参数,也可以有多个参数。在函数调用的时候,参数可以是值类型,也可以是引用类型。语句体对值类型参数的修改不会影响函数外部的值类型数据。但是语句体对引用类型参数的修改会影响函数外部的引用类型数据。
语句体可以申明局部变量,并且可以编写代码实现功能的逻辑。
我们在前面其实已经接触过函数了,例如:console.writeline()函数,我们并不知道它的内部是什么,我们只要会使用它即可。下面我们来看一个范例:
using system;
namespace microsoft.example
{
public class testfunction
{
static int add(int a, int b, int c) //定义add函数
{
return a + b + c; //返回值
}
static void main(string[] args)
{
int a = 1; //定义int类型的变量a
int b = 2; //定义int类型的变量b
int c = 3; //定义int类型的变量c
int result = add(a, b, c); //调用add函数
console.writeline("结果为:" + result);
}
}
}
上述代码中,第6行定义了一个add函数,其中int是它的返回值类型,add是函数名。这个函数需要输入三个整数a b c作为参数表。
第8行是函数的语句体,用来实现函数的功能,这里实现的是三个整数相加。然后使用return关键字把结果返回调用函数的语句中。这里需要注意了,return关键字返回的类型要与函数名前面的回返值类型一致。
最后的输出结果是:
结果为:6
作者心得:
函数名的命名可大有学问,我们需要记住的第一条就是名称要有真实意义。看着函数名就知道,函数的作用是什么,实现什么功能。在c#语言中一般使用动宾格式来命名,而不是主宾格式。例如:getage()函数。
4.1.3函数的调用及执行过程
上一节向大家讲述了如何定义一个函数,这一节将向大家讲述如何调用一个已经定义了的函数,以及函数调用的相关知识。
函数的调用格式:
函数名(参数表);
需要说明一下,函数名必须与函数定义的函数名一致,这个估计大家不会做错。参数表也是一样,必须与函数定义的参数表一致,类型,个数,顺序都要一致。当多个参数时,使用逗号相互隔开。作为参数表中的参数可以是常量,有值的变量或者运算表达式。
在c#语言中,函数调用根据在程序中出现的位置来分,可以有以下三种调用方式:
1、以函数调用语句的形式调用。当函数调用不要求返回值时,可由函数调用加上分号构成来实现,即该函数调用作为一个独立的语句使用。
例如:test();
test()函数调用相当于执行一段相关程序。
2、函数调用作为一个运算对象直接出现在一个表达式中。例如:
k=sin(x)*cos(y);
该赋值语句包含两个函数调用,每个函数调用都是表达式的一个运算对象。因此要求函数应带回一个确定的值参加表达式的运算,这种表达式称为函数表达式。
3、将一个函数调用的返回值作为另一个函数调用的实参。例如:k=sin(cos(x));
下面我们来看一个范例:
using system;
namespace microsoft.example
{
public class testtransfer
{
static int add(int a, int b, int c) //定义add函数
{
int sum; //定义一个int变量sum
sum = a * (b + c); //进行计算
console.writeline("a的值为:" + a);
console.writeline("b的值为:" + b);
console.writeline("c的值为:" + c);
return sum; //返回值
}
static void main(string[] args)
{
int j, sum; //定义两个int类型的变量
console.write("请输入j的值:");
int.tryparse(console.readline(), out j);
sum = add(j, ++j, ++j); //调用add函数
console.writeline("计算结果sum是:" + sum);
console.write("请输入j的值:");
int.tryparse(console.readline(), out j);
sum = add(++j, ++j, j); //调用add函数
console.writeline("计算结果sum是:" + sum);
}
}
}
上述代码中,当输入j=1,调用函数add(j,++j,++j)时,按从左到右的顺序,先计算表达式的值,相
当于add(1,2,3)函数调用,函数计算返回值 1*(2+3)=5。
当再次输入j=1,调用函数add(++j,++j,j)时,按从左到右的顺序,先计算表达式的值,相当于add(2,3,3)函数调用,函数计算返回值 2*(3+3)=12。
这里需要注意的是,c#对于参数为表达式是,使用的是从左到右的计算顺序。其他语言可能有所不同,比如c语言使用的是从右到左的计算顺序。
最后的输出结果是:
请输入j的值:1
a的值为:1
b的值为:2
c的值为:3
计算结果sum是:5
请输入j的值:1
a的值为:2
b的值为:3
c的值为:3
计算结果sum是:12
本章的学习重点:
◆ 函数重载的概念
◆ 函数重载的优点
4.3函数重载
函数重载(overload)为程序开发提供了极大的便利,尤其是对于我们这些类库的使用者——可以从复杂的类型转换中解放出来,只要以我们自己认为最方便的方式调用方法即可。本节主要从重载的语义角度来讨论与使用重载相关的一些基本条件。
4.3.1函数重载的概念
函数重载是指多个函数实现可以同时使用一个函数名,只是函数的参数表不同而已。打个比方,使用重载函数我们可以定义多个加法函数来求两个数之和。其中,一个函数实现两个int类型之和,另外一个实现两个float类型之和,再另外一个实现两个decimal之和。每种实现对应一个函数体,这些函数的名字相同,但是函数的参数类型不同,这就是函数重载的概念。
刚才我们说函数重载时多个函数使用同一个函数名。那么,c#语言的编译器就需要确定用户调用的是哪一个函数,即采用哪个函数实现。确定函数实现时,要求从函数参数的个数和类型上来区分。这就是说,进行函数重载时,要求同名函数在参数个数上不同,或者参数类型上不同。否则,将无法实现重载。
函数重载的格式如下:
void add(int a,int b)
{}
void add(int a,int b,int c)
{}
char add(char a,char b,char c)
{}
上述格式中,定义了3个add函数,第一个add()函数对应的是两个int类型求和的函数实现,而后边一个add()函数对应的是三个int类型求和的函数实现,最后一个add()函数对应的是三个char类型求和的函数实现,这便是函数的重载。
下面我们来看一个范例:
using system;
namespace microsoft.example
{
public class testreturnvalue
{
static int max(int x, int y) //定义int版本的max函数
{
int z;
z = x > y ? x : y; //比较x,y的大小
return z;
}
static double max(double x, double y) //定义double版本的max函数
{
double z;
z = x > y ? x : y; //比较x,y的大小
return z;
}
static void main(string[] args)
{
int intx = 50; //定义一个int变量
int inty = 43; //定义一个int变量
console.writeline("比较两个整数的大小:" + intx + "; " + inty);
int intresult = max(intx, inty); //调用int版本的max函数
console.writeline("结果是:" + intresult);
double doublex = 50.5; //定义一个double变量
double doubley = 43.3; //定义一个double变量
console.writeline("比较两个整数的大小:" + doublex + "; " + doubley);
double doubleresult = max(doublex, doubley); //调用int版本的max函数
console.writeline("结果是:" + doubleresult);
}
}
}
上述代码中,第6行我们定义了一个int版本的max函数,这个函数只接收两个int类型的参数,然后返回int类型的数据。第12行我们定义了一个double版本的max函数,这个函数只接收两个double类型的参数,然后返回double类型的数据。这两个函数使用了max的函数名字,但是,它们能共同存在,这就是函数重载。
最后的输出结果是:
比较两个整数的大小:50; 43
结果是:50
比较两个整数的大小:50.5; 43.3
结果是:50.5
4.3.2函数重载的优点
从上面的范例中,我们可以知道,虽然不同的重载在形式上是多个独立的函数,但在语义上它们代表的是同一个函数——准确地说,它们执行的是同样的操作。之所以函数要提供多个重载,其主要目的是方便调用者。具体地说来包括以下一些优点:
1、支持多种数据类型
前面关于max函数的范例即是说明了这一点。我们要提供的是“取最大值”的功能,并不是“对整型数取绝对值”的功能,因此将输入参数限制为整型是不合适的。提供重载可以使得用户不必操心有关数据类型转换的工作,各个重载函数会负责针对不同的数据类型进行处理,也减少了记忆。
2、支持多种数据提供方式
这是比支持多种数据类型更为宽泛而方便的方式。int类型与double类型之间可以直接转换,但很多情况却没有那么简单。例如xmldocument类提供了load方法用于装入一个xml文档,那么如何指定这个文档呢?如果这个文档还没有被打开,那么指定它的路径及文件名是最方便的。如果这个文档已经打开,那么也许可以通过一个stream对象对其进行访问是最方便的。对于类库的使用者来说,这些可能都是存在的,如果xmldocument强制使用一种方式,无疑会给用户造成不便,此时最好的办法就是提供一组重载:
xmldocument.load(string)
xmldocument.load(stream)
这里的string、stream之间是不存在类型转换关系的,重载函数要做的额外工作并不像之前那个最大值函数那么简单。但是对于函数的使用者来说,它们的意义是类似的。
3、为复杂的参数提供默认值,简化调用
这也是非常常见的情况。有些函数的功能非常强大,但其带来的负面影响就是函数调用者需要考虑太多的参数设置。事实上,大多数情况下,使用者真正关心的只是其中的一两个参数,剩余的参数都集中在某些相同的设置值上。如果能为这些高级特性提供合理的默认值的话,函数调用者的工作量就会大大降低。例如file的create方法用于创建一个文件,它支持很多丰富的设置参数:
public void create(
string path;
int buffersize;
fileoptions options;
filesecurity filesecurity;
}
这么多设置参数,我们第一次创建文件的时候肯定会不知所措,按我们的理解,只要指定文件地址就够了。就像下面这样:
public void create(
string path;
}
事实上create确实提供了这样一个简单版本的重载,确实在大多数情况下,用户只需要一些简单的功能,那些复杂的特性被使用的频率相对较低。如果每次都要调用者设置这么多参数的话,显然增加了很多不必要的劳动。如果提供一个简单版本的重载,为这些高级特性提供合理的默认值的话,可以在很大程度上使代码变得更加简洁有序。前面这个重载函数,或许就是通过下面的方式来实现的:
public void create (string path)
{
create (
path;
0
fileoptions.none
null
);
}
可以清楚地看到,如果没有这些重载来帮助设置默认参数值的话,用户需要多编写多少代码去完成一个本应该非常简单的操作