1、第六章 函数,本章内容,概述函数的定义与调用 局部变量和全局变量及其作用域 变量的生存期 Main()函数 委托简介,概述,使用函数的好处: 程序结构清晰,可读性好。 减少重复编码的工作量。 可多人共同编制一个大程序,缩短程序设计周期,提高程序设计和调试的效率。,4,【例】求若干个数的阶乘。,class Class1 static int f (int x) /* 函数定义 */ int num=1;for(int i=1;i=x;i+)num*=i;return num;static void Main(string args ) int a,b,result1,result2;string
2、 str1,str2;Console.WriteLine(“nEnter two integer number:“);str1=Console.ReadLine(); str2=Console.ReadLine();a=int.Parse(str1);b=int.Parse(str2);result1= f(a); result2=f(b); Console.WriteLine(“nThe Results are:“+result1+“and“+result2); ,5,在一个C#程序中,有且仅有一个主函数Main。C#程序的执行总是从Main函数开始,调用其它函数后最终回到Main函数,在M
3、ain函数中结束整个程序的运行。,6,函数的种类,从函数定义形式分: 有参函数: 在主调(用)函数和被调(用)函数之间通过参数进行数据传递, 如f (int x) 无参函数: 如ReadLine(); 在调用无参函数时,主调函数不需要将数据传递给无参函数。,从使用的角度看: 标准函数(库函数) 库函数是由系统提供的。如ReadLine()、WriteLine(string value)等。在程序中可以直接调用它们。 用户自定义函数。 如:前例中的f(int x)函数。,7,例:无参函数的定义与调用。,class Class1 static void welcome ( ) string str
4、;str=“*n“; str=“ Welcome to China n“;str=“*n“;static void Main(string args) welcome( ); ,8,函数的定义,函数定义的一般形式,属性 修饰符 返回值类型 函数名(形参列表) 说明语句执行语句 ,例如:求两个数的最大值。 int max(int x,int y) int z;z = x y ? x : y;return( z ); ,“形参列表”是可选的,多个参数以逗号分隔。每个参数都需指定参数名和数据类型(即使是相同类型的多个方法参数)。,即使未提供任何参数,括号也不能省略。 在方法定义中省略返回值类型是语法
5、错误。如方法不返回值,必须将其返回值类型设为 void。 在函数调用中,参数值的数量、类型和顺序必须与方法定义中的参数完全对应。如果方法是没有返回值的,方法的调用只能作为一条语句;如果方法有返回值,方法的调用相当于一个同类型的数据,可以作为表达式或表达式的一部分参与运算。,10,static int sum100( ) int i,t=0;for (i=1; i=100; i+)t+=i;return (t); static void Main(string args ) int s;s=sum100( );Console.WriteLine(s); ,程序输出结果: 5050,static
6、int sum ( int x ) int i,t=0;for (i=1; i=x; i+)t+=i;return (t); static void Main(string args ) int s;s=sum (100);Console.WriteLine(s); ,例:求1100的累加和。,思考:两个程序有何不同,程序输出结果: 5050,?,11,1.函数的返回值 函数的返回值是通过return语句带回到主调函数的,功能:终止函数的运行,返回主调函数,若有返回值,将返回值带回主调函数。,说明: 若函数没有返回值,return之后不能有返回值,或可以省略return语句。 return语句
7、中的表达式类型一般应和函数的类型一致。,函数的返回值与函数参数,return 语句格式:,return (表达式); 或 return 表达式 ; 或 return;,12,static void swap(int x, int y) int z;z=x; x=y; y=z; Console.Write(“nx=“+x+ “,y=“+y); static void Main(string args) int a= 10,b=20;swap(a,b); Console.Write(“na=“+a+“,b=“+b); ,函数参数与函数的返回值,1函数的形式参数与实际参数,形式参数(形参),实际参数
8、(实参),例:编一程序,将主函数中的两个变量的值传递给swap函数中的两个形参,交换两个形参的值。,13,有关形参和实参的说明:, 当函数被调用时才给形参分配内存单元。调用结束,所占内存被释放。 实参可以是常量、变量或表达式,但要求它们有确定的值。 实参与形参类型要一致,字符型与整型可以兼容。 实参与形参的个数必须相等。在函数调用时,实参的值赋给与之相对应的形参。 (即:必须使参数与函数定义中指定的参数完全匹配。),C#中函数的参数有4中类型:1、值参数:不附加任何修饰符。2、引用参数:以ref修饰符声明。3、输出参数:以out修饰符声明,可返回一 个或多个值给调用者。4、数组参数:以para
9、ms修饰符声明。,值参数,一个值参数相当于一个局部变量,只是它的初始值来自该函数调用所提供的相应参数。允许函数将新值赋给值参数。,例:值参数传递 using System; class SquareApp static int CalcSquare(int nSideLength)return nSideLength * nSideLength;static void Main()Console.WriteLine(“the square of the circle is:0n“,CalcSquare(25).ToString(); 程序运行的结果如图所示。,引用参数,引用参数并不创建新的存储
10、位置。相反,引用参数表示的存储位置恰是在方法调用中作为参数给出的那个变量所表示的存储位置。当利用引用参数向方法传递形参时,编译程序将把实际值在内存中的地址传递给方法。引用参数以ref修饰符声明。,18,例:引用参数传递 using System; class SquareApp static void CalcSquare(ref int nOne4All)nOne4All *= nOne4All;static void Main()int nSquaredRef = 25; / 一定要初始化CalcSquare(ref nSquaredRef);Console.WriteLine(“the
11、square of the circle is:0n“,nSquaredRef.ToString(); ,输出参数,如果想要一个函数返回多个值,可以用输出参数来处理.输出参数由out关键字标识,既它与普通形参相比只多了个out修饰,如: static void myMethod(out int x,out int y,int z) 与引用参数的区别在于:调用方法前无需对输出参数进行初始化。输出型参数用于传递方法返回的数据,20,例:输出参数传递using System; class SquareApp static void CalcSquare(int nSideLength, out in
12、t nSquared)nSquared = nSideLength * nSideLength;static void Main()int nSquared; / 不必初始化CalcSquare(25, out nSquared);Console.WriteLine(“the square of the circle is:0n“, nSquared.ToString(); ,如果形参表中包含了数组型参数,那么它必须在参数表中位于最后,而且必须是一维数组类型。另外,数组型参数不可能将params修饰符与ref和out修饰符组合起来使用。,数组参数,using System; class Squ
13、areApp static void CalcSquare(params int args)foreach (int i in args)int radius = i;int square = radius * radius;Console.WriteLine(“the square of the circle is:0n“, square);static void Main()int radiusarray = 5, 15, 25 ;CalcSquare(radiusarray); 程序说明:数组radiusarray作为值参数传递,数组中的每个元素都当作半径来计算圆的面积。,例:数组参数传
14、递。,局部变量和全局变量及其作用域,1.局部变量及其作用域,变量的作用域:变量在程序中可以被使用的范围。 根据变量的作用域可以将变量分为局部变量和全局变量。,局部变量(内部变量):在函数内或复合语句内定义的变量以及形参。 作用域:函数内或复合语句内。,问题:一个变量在程序的哪个函数中都能使用吗?,2.全局变量及其作用域 全局变量(外部变量):在函数外部定义的变量。 作用域:整个类。如在其作用域内的函数或分程序中定义了同名局部变量,则在局部变量的作用域内,同名全局变量暂时不起作用。,例:变量作用域,using System; class Program static string myStrin
15、g; /静态全局变量static void Write()string myString = “String defined in Write()”; /局部变量Console.WriteLine(“Now in Write()“);Console.WriteLine(“Local myString = 0“, myString);Console.WriteLine(“Global myString = 0“,Program.myString);static void Main(string args)string myString = “String defined in Main()“;
16、/局部变量Program.myString = “Global string“;Write();Console.WriteLine(“nNow in Main()“);Console.WriteLine(“Local myString = 0“, myString);Console.WriteLine(“Global myString = 0“, Program.myString);,25,变量的生存期,变量在内存中占据存储空间的时间。,26,局部变量 内存分配 调用函数或程序段时为其分配存储单元,函数或程序段执行结束,所占内存空间即刻释放。 变量的初值 定义变量时若没赋初值,变量的初值不确定
17、;如果赋初值则每次函数被调用时执行一次赋值操作。 生存期 在函数或分程序执行期间。 作用域 变量所在的函数内或程序段内从定义到函数或程度段结束。,27,静态全局变量 内存分配 编译时即分配内存,程序运行结束释放该单元。 静态变量的初值 若定义时未赋初值,在编译时,系统自动赋初值为0;若定义时赋初值,则仅在编译时赋初值一次,程序运行后不再给变量赋初值 。 生存期 整个程序的执行期间。 作用域 最小范围为整个类.,28,函数的重载,class Class1 static void Main(String args)int i=5;double j=5.5;string str1= “ welcom
18、e to c# “;Console.WriteLine(i);Console.WriteLine(j);Console.WriteLine(str1);Console.WriteLine(“0,20 “,str1); ,29,函数重载实际上是函数名重载,即支持多个不同的函数采用同一名字。 例如:int abs(int n)return(n0?-n:n;float abs(float f)if(f0)f-f; return f; double abs(double d)if(d0)return-d;return d; 三个函数都是求绝对值,采用同一个函数名,更符合人们的习惯 . 例如在程序中经常
19、出现这样的情况:对若干种不同的数据类型求和,虽然数据本身差别很大(例如整数求和,向量求和,矩阵求和),具体的求和操作差别也很大,但完成不同求和操作的函数却可以取相同的名字(例如sum,add 等)。,30,函数名的重载并不是为了节省标识符(标识符的数量是足够的),而是为了方便程序员的使用,这一点很重要。实现函数的重载必须满足下列条件之一: 参数表中对应的参数类型不同; 参数表中参数个数不同;,31,例如: void print(int); /整型void print(point); /类point 的对象 int sum(int ,int);int sum(int ,int ,int); in
20、t get(int n,float a ); int get(int n,float a ,int n);,32,在定义同名函数时应注意: (1)返回类型不能区分函数, float add(int float); int add(int float);/错误 (2)如果形参中存在两个以上的形参类型存在隐式转换关系,则可能产生二义性, static double print(int i,double j) static double print(double i,int j) static void Main(string args) double x=print(5,5);/二义性 ,33,函
21、数的嵌套调用和递归调用,main函数 调用函数 A; ,函数 A 调用函数 B; ,函数 B ,函数的嵌套调用,34,例:函数的嵌套调用。数的比较。,static int MaxTwo(int x,int y) if(xy)return x;elsereturn y; static int MaxThree(int x,int y,int z) int temp;temp=MaxTwo(x,y);if(ztemp)return z;elsereturn temp; static void Main(string args) int result=MaxThress(2,7,4);Console
22、.WriteLine(result); ,35,函数的递归调用,1递归的基本概念,递归调用:一个函数直接或间接地调用了它本身,就称为函数的递归调用。 递归函数:在函数体内调用该函数本身。,int sub(int x) int y,z;if( ) z=sub(y);else return ; ,例如:,直接调用sub 函数本身,36,递归函数的执行过程,例:编一递归函数求n!。,思路:以求4的阶乘为例: 4!=4*3!,3!=3*2!,2!=2*1!,1!=1,0!=1。 递归结束条件:当n=1或n=0时,n!=1。 递归公式:,37,程序如下:,static float fact (int n
23、) float f=0;if(n0) Console.Write(“n0,error!“);else if (n=0 | n=1) f=1;else f=fact(n-1)*n;return (f); ,static void Main(string args ) int n; float y;Console.Write(“nInput n:“);n=int.Parse(Console.ReadLine();y=fact(n);Console.Write(“n“+n+“!=“+y): ,运行情况如下: Input a integer number:4 4!=24,38,递归调用过程,回 推 m
24、ain( ) fact(4) fact(3) fact(2) fact(1) y=fact(4); f=4*fact(3); f=3*fact(2); f=2*fact(1); f=1; return 24 return 6 return 2 return 1 递 推,39,编制递归函数的方法, 数值型问题递归函数的编程方法 对于数值型问题,首先要找出解题的数学公式,这个公式必须是递归定义的,且所处理的对象要有规律地递增或递减,然后确定递归结束条件。 【例4.10】编一递归函数求xn 。 思路:首先把xn转化成递归定义的公式,再找出递归结束条件:当n=0时,xn=1。,static long
25、xn(int x,int n) long f=0;if (n0) Console.Write(“n0,data error!n“);else if (n=0) f=1;else f=x*xn(x,n-1);return (f); static void Main(string args ) int n,x; long y;n=int.Parse(Console.ReadLine();x=int.Parse(Console.ReadLine();y=xn(x,n);Console.Write(y); ,40,程序如下:,程序运行情况如下: 2,10 1024,41, 非数值型问题递归函数的编程方
26、法,有些问题不能直接用数学公式求解。非数值型问题比数值型问题更难找出递归的算法。它不能用一个递归公式表示。解决这类问题首先要把问题将大化小,将繁化简。将一个复杂的问题化解成若干个相对简单的小问题,而某个小问题的解法与原问题解法相同,并且越来越简单直至有确定的解。,【例4.11】编制一递归函数,将一个十进制正整数(如:15613)转换成八进制数形式输出。,42,思路:十进制整数转换成八进制整数的方法是除8逆向取余。如图示。,43,该题实际上是要把一个十进制数除以8得到的余数逆向输出。就是先得到的余数后输出,最后得到的余数最先输出。 我们先由大化小:求八进制数变成求一系列余数的问题。求第一个余数是
27、将15613除以8取余,因为先得到的余数后输出,所以把这个余数存在一个变量m中,接下去求下一个余数。和求第一个余数的方法相同,只是被除数变成了15613除以8的整数商1951。因此,这是一个递归调用的问题。定义变量m存放余数,x存放被除数。递归算法描述如下:,2005年3月,44, 先求出余数m:m=x%8; 求x除以8取余后的整数商:x=x/8; 如果x不等于0,递归调用该函数,否则执行。 输出余数m。 返回调用点。,2005年3月,45,程序如下:,static void dtoo(int x) int m;m=x%8;x=x/8;if (x!=0) dtoo(x);Console.Wri
28、te(m); static void Main(string args ) int n;n=int.Parse(Console.ReadLine();Console.Write(n+“=(“); /.Write(“0=(“,n)dtoo(n);Console.Write( “)8n“); ,程序运行情况如下: 15613 15613=(36375)8,委托简介,委托是一种可以把引用存储为函数的类型。委托的声明非常类似于函数,但不带函数体,且要使用delegate关键字。委托的声明指定了一个函数签名,其中包含一个返回类型和参数列表。在定义了委托后,即可声明该委托类型的变量。接着将这个变量初始化为
29、与委托有相同签名的函数引用。以后就可以使用委托变量调用该函数,就象该变量是一个函数一样。即委托使方法变成变量使用。,委托,Multiply(int,int) .Divide(int,int) .,在运行时确定调用哪种方法,委托和方法必须具有相同的签名,- public delegate Call(int num1, int num2); -,定义委托 2-2,class Delegates / 委托定义public delegate int Call(int num1, int num2);class Math/ 乘法方法public int Multiply(int num1, int nu
30、m2)return num1*num2;/ 除法方法public int Divide(int num1, int num2) return num1/num2;,static void Main(string args) / 委托的对象Call objCall;/ Math 类的对象Math objMath = new Math();/ 将方法与委托关联起来objCall = new Call(objMath.Multiply);/ 将委托实例化result = objCall(5, 3);System.Console.WriteLine(“结果为 0“, result); ,将方法与委托关
31、联起来,例 using System; class Program delegate void processDelegate(int x );static void Func1(int x)int value = x;Console.WriteLine(“Now int Func1:x =0 n“,value); static void Func2(int x)int value = x * x;Console.WriteLine(“Now int Func2:x =0 n“,value);static void Main()processDelegate process; Console.WriteLine(“Enter 1 to Func1, 2 to Func2:“);string input = Console.ReadLine();int x = 2; /x=Convert.Toint32(input);if(input = “1“)process = new processDelegate(Func1);elseprocess = new processDelegate(Func2);process(x); ,委托示例的运行结果,