《第3章面向对象编程基础.ppt》由会员分享,可在线阅读,更多相关《第3章面向对象编程基础.ppt(103页珍藏版)》请在三一办公上搜索。
1、第3章 面向对象编程基础,3.1 面向对象概念对象是面向对象编程的基本成分。什么是对象?可以说任何事物都是对象。对象可以是现实世界中的一个物理对象,也可以是抽象的概念或规则。对象可用它本身的一组属性和操作功能来定义。例如:对于自行车对象,它的颜色、车轮个数等是对象的状态,它能行驶、能变速是对象的功能;对于电视机对象,它的屏幕大小、色彩、频道数等是它的属性,能打开、关闭、更换频道、调整音量等是它的操作。,3.1.1 对象、类、实例化,在面向对象程序设计技术中,对象是具有属性(又称状态)和操作(又称方法、行为方式和消息等)的实体。对象的属性表示了它所处于的状态;对象的操作则用来改变对象的状态达到特
2、定的功能。对象有一个惟一的标识名以区别于其他对象,对象有固定的对外接口,是对象在约定好的运行框架和消息传递机制中与外界通信的通道。对象是面向对象技术的核心,是构成系统的基本单元,所有的面向对象的程序都是由对象来组成的。类是在对象之上的抽象,它为属于该类的全部对象提供了统一的抽象描述。所以类是一种抽象的数据类型,它是对象的模板,对象则是类的具体化,是类的实例。例如:“一台PANDA电视机”等价于“这是电视机类的一个实例”。类与对象的关系如图3.1所示。,3.1.1 对象、类、实例化,图3.1 类与对象关系,3.1.2 面向对象程序设计语言的三大原则,一个面向对象的语言在处理对象时,必须遵循的三个
3、原则是:封装、继承、多态。1.封装所谓“封装”,就是用一个框架把数据和代码组合在一起,形成一个对象。遵循面向对象数据抽象的要求,一般数据都被封装起来,也就是外部不能直接访问对象的数据,外部能见到的只有提供给外面访问的公共操作(也称接口,对象之间联系的渠道)。在C#中,类是支持对象封装的工具,对象则是封装的基本单元。封装的对象之间进行通讯的一种机制叫做消息传递。消息是向对象发出的服务请求,是面向对象系统中对象之间交互的途径。消息包含要求接收对象去执行某些活动的信息,以及完成要求所需的其他信息(参数)。发送消息的对象不需要知道接收消息的对象如何对请求予以响应。接收者接收了消息,它就承担了执行指定动
4、作的责任,作为消息的答复,接收者将执行某个方法,来满足所接收的请求。,3.1.2 面向对象程序设计语言的三大原则,2.继承继承是面向对象编程技术的一块基石,通过它可以创建分等级层次的类。例如,创建一个汽车的通用类,它定义了汽车的一般属性(如:车轮、方向盘、发动机、车门)和操作方法(如:前进、倒退、刹车、转弯等)。从这个已有的类可以通过继承的方法派生出新的子类,卡车、轿车、客车等,它们都是汽车类的更具体的类,每个具体的类还可增加自己一些特有的东西。如图3.2所示,更一般地表示如图3.3所示。,图3.2 汽车类的派生,图3.3 类的继承,2.继承,继承是父类和子类之间共享数据和方法的机制,通常把父
5、类称为基类,子类称为派生类。一个基类可以有任意数目的派生类,从基类派生出的类还可以被派生,一群通过继承相联系的类就构成了类的树型层次结构。如果一个类有两个或两个以上直接基类,这样的继承结构被称为多重继承或多继承。在现实世界中这种模型屡见不鲜,如:一些组合功能的产品像沙发床,它既有沙发的功能,又有床的功能,沙发床应允许同时继承沙发和床的特征。如图3.4所示,更一般地表示如图3.5所示,图3.4多继承示意,图3.5 类的多继承,2.继承,尽管多继承从形式上看比较直观,但在实现上多继承可能引起继承操作或属性的冲突。当今的很多语言已不再支持多继承,C#语言也对多继承的使用进行了限制,它通过接口来实现。
6、接口可以从多个基接口继承。接口可以包含方法、属性、事件和索引器。一个典型的接口就是一个方法声明的列表,接口本身不提供它所定义的成员的实现。所以接口不能被实例化,一个实现接口的类再以适当的方式定义接口中声明的方法。如图3.6所示。,图3.6类的接口,3.1.2 面向对象程序设计语言的三大原则,3.多态性多态性就其字面上的意思是:多种形式或多种形态。在面向对象编程中,多态是指同一个消息或操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在面向对象编程中,多态性有两种,一种是静态多态,一种是动态多态。当在同一个类中,直接调用一个对象的方法时候,系统在编译时,根据传递的参数个数、参数类型以及
7、返回值的类型等信息决定实现何种操作,这就是所谓的静态绑定。当在一个有着继承关系的类层次结构中,间接调用一个对象的方法时候,也就是说,调用经过基类的操作,这种调用只有到系统运行时,才能根据实际情况决定实现何种操作,这就是所谓的动态绑定。C#支持这两种类型的多态,在实现多态上C#可以有几种方式:接口多态性、继承多态性、通过抽象类实现的多态性。,3.2 类,正如前节所述,对象是面向对象语言的核心,数据抽象和对象封装是面向对象技术的基本要求,而实现这一切的主要手段和工具就是类。从编程语言的角度讲,类就是一种数据结构,它定义数据和操作这些数据的代码。把握面向对象编程的重要一步就是区分类与对象,类是对其成
8、员的一种封装,对类进行对象实例化,并在其数据成员上实施操作才是完成现实任务的根本。实例化后的类为对象,其核心特征便是拥有了一份自己特有的数据成员拷贝。这些为对象所持有的数据成员称之为实例成员。不为对象所持有的数据成员称之为静态成员,在类中用static修饰符声明。类的成员包含数据成员(常量、域、事件)和函数成员(方法、属性、索引器、操作符、构造函数、析构函数等)。,3.2.1 类的声明,要定义一个新的类,首先要声明它。语法形式:属性集信息opt 类修饰符opt class 类名:类基 opt 类主体 opt其中:属性集信息:是C#语言提供给程序员为程序中定义的各种实体附加一些说明信息,这是C#
9、语言的一个重要特征。类修饰符:可以是以下表3.1所列的几种之一或者是它们的有效组合,但在类声明中,同一修饰符不允许出现多次。,表3.1 类的修饰符,3.2.1 类的声明,类基:它定义该类的直接基类和由该类实现的接口。当多于一项时,用逗号“,”分隔。如果没有显式地指定直接基类,那么它的基类隐含为object。最简单的类声明语法形式:class 类名 类成员 例如:class Point/Point类的访问权限缺省为public int x,y;,3.2.2 类的成员,类的定义包括类头和类体两部分,其中类体用一对大花括号 括起来,类体用于定义该类的成员。类体语法形式:类成员声明 opt 类成员由两
10、部分组成,一个是类体中以类成员声明形式引入的类成员,另一个则是直接从它的基类继承而来的成员。类成员声明主要包括:常数声明、字段声明、方法声明、属性声明、事件声明、索引器声明、运算符声明、构造函数声明、析构函数声明、静态构造函数、类型声明等。当字段、方法、属性、事件、运算符和构造函数声明中含有static修饰符时,则表明它们是静态成员,否则就是实例成员。类成员声明中可以使用以下5种访问修饰符中的一种:public,private,protected,internal,protected internal。当类成员声明不包含访问修饰符时,缺省约定访问修饰符为private。,3.2.2 类的成员,
11、1常数声明常数声明一般语法形式:属性集信息 opt 常数修饰符 opt const 类型 标识符=常数表达式,标识符=常数表达式 0+其中:常数修饰符可以是:new、public、protected、internal、private。类型必须是:sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、string、枚举类型或引用类型。常数表达式的值类型应与目标类型相一致,或者可以通过隐式转换转换成目标类型。2字段声明字段声明的一般语法形式:属性集信息 opt 字段修饰符 opt 类型 变量声明列表;其
12、中:变量声明列表:标识符或者用逗号“,”分隔的多个标识符,并且变量标识符还可用赋值号“=”设定初始值。,3.2.2 类的成员,【例3.1】通过构造函数对只读字段赋值。using System;public class Areapublic readonly double Radius;/Radius是只读字段private double x,y;public double Size;public static double Sum=0.0;public Area()Radius=1.0;/通过构造函数对radius赋值class Testpublic static void Main()Are
13、a s1=new Area();Console.WriteLine(Radius=0,Size=1,Sum=2,s1.Radius,s1.Size,Area.Sum);/静态字段通过类访问Area.Sum,实例字段通过对象访问s1.SizeConsole.Read();,【例3.1】,运行结果如图3.7所示。,图3.7 对只读字段赋值的运行结果,3.2.3 构造函数,当定义了一个类之后,就可以通过new运算符将其实例化,产生一个对象。为了能规范、安全地使用这个对象,C#提供了对对象进行初始化的方法,这就是构造函数。在C#中,类的成员字段可以分为实例字段和静态字段,与此相应的构造函数也分为实例构
14、造函数和静态构造函数。1实例构造函数声明实例构造函数的声明语法形式:属性集信息 opt 构造函数修饰符 opt 标识符(参数列表 opt):base(参数列表 opt)opt:this(参数列表 opt)opt 构造函数语句块 其中:构造函数修饰符:public、protected、internal、private、extern。一般地,构造函数总是public类型的。如果是private类型的,表明类不能被外部类实例化。标识符(参数列表 opt):标识符是构造函数名,必须与这个类同名,不声明返回类型,并且没有任何返回值。这与返回值类型为void的函数不同。构造函数参数可以没有,也可以有一个或
15、多个。这表明构造函数在类的声明中可以有函数名相同,但参数个数不同或者参数类型不同的多种形式,这就是所谓的构造函数重载。,3.2.3 构造函数,【例3.2】Time类的构造函数及其重载。using System;public class Timeprivate int hour,minute,second;public Time()hour=minute=second=0;public Time(int h)hour=h;minute=second=0;public Time(int h,int m)hour=h;minute=m;second=0;,public Time(int h,int
16、m,int s)hour=h;minute=m;second=s;class Teststatic void Main()Time t1,t2,t3,t4;/对t1,t2,t3,t4分别调用不同的构造函数t1=new Time();t2=new Time(8);t3=new Time(8,30);t4=new Time(8,30,30);,3.2.3 构造函数,【例3.3】构造函数初始化。实例对象创建时,根据不同的参数调用相应的构造函数完成初始化。using System;class Point public double x,y;public Point()x=0;y=0;public Po
17、int(double x,double y)this.x=x;/当this在实例构造函数中使用时,this.y=y;/它的值就是对该构造的对象的引用。,class Testpublic static void Main()Point a=new Point();Point b=new Point(3,4);/用构造函数初始化对象Console.WriteLine(a.x=0,a.y=1,a.x,a.y);Console.WriteLine(b.x=0,b.y=1,b.x,b.y);Console.Read();运行结果如图3.8所示。,图3.8 用不同的构造函数完成的对象初始化的运行结果,3.
18、2.3 构造函数,上例中声明了一个类Point,它提供了两个重载的构造函数。一个是不带参数的Point构造函数和一个是带有两个double参数的Point构造函数。如果类中没有提供这些构造函数,那么CLR会自动提供一个缺省构造函数的。但一旦类中提供了自定义的构造函数,系统则不提供缺省构造函数,这一点需要特别注意,否则系统编译会报出错。如图3.9所示。,图3.9 程序编译出错信息,3.2.3 构造函数,【例3.4】Point类只定义了一个带两个参数的构造函数,在创建Point对象a时,编译会报告出错信息。using System;class Pointpublic double x,y;publ
19、ic Point(double x,double y)this.x=x;/当this在实例构造函数中使用时,this.y=y;/它的值就是对该构造的对象的引用。class Testpublic static void Main()Point a=new Point();/出错Point b=new Point(3,4);/用构造函数初始化对象Console.WriteLine(a.x=0,a.y=1,a.x,a.y);Console.WriteLine(b.x=0,b.y=1,b.x,b.y);,3.2.3 构造函数,【例3.5】派生类构造函数及其调用。using System;class P
20、ointprivate int x,y;public Point()x=0;y=0;Console.WriteLine(Point()constructor:0,this);public Point(int x,int y)this.x=x;this.y=y;Console.WriteLine(Point(x,y)constructor:0,this);,class Circle:Pointprivate double radius;public Circle()/缺省约定调用基类的无参构造函数Point()Console.WriteLine(Circle()constructor:0,thi
21、s);public Circle(double radius):base()this.radius=radius;Console.WriteLine(Circle(radius)constructor:0,this);public Circle(int x,int y,double radius):base(x,y)this.radius=radius;Console.WriteLine(Circle(x,y,radius)constructor:0,this);,class Test static void Main()Point a=new Point();Circle b=new Cir
22、cle(3.5);Circle c=new Circle(1,1,4.8);Console.Read();运行结果如图3.10所示。,图3.10 派生类对象在初始化时,调用构造函数的次序,3.2.3 构造函数,2.静态构造函数声明静态构造函数的声明语法形式:属性集信息 opt 静态构造函数修饰符 opt 标识符()静态构造函数体 其中:静态构造函数修饰符:extern static 或者 static extern 如果有extern修饰,则说明这是一个外部静态构造函数,不提供任何实际的实现,所以静态构造函数体仅仅是一个分号。标识符():标识符是静态构造函数名,必须与这个类同名,静态构造函数不
23、能有参数。静态构造函数体:静态构造函数的目的是用于对静态字段进行初始化,所以它只能对静态数据成员进行初始化,而不能对非静态数据成员进行初始化。静态构造函数是不可继承的,而且不能被直接调用。只有创建类的实例或者引用类的任何静态成员时,才能激活静态构造函数,所以在给定的应用程序域中静态构造函数至多被执行一次。如果类中没有声明静态构造函数,而又包含带有初始设定的静态字段,那么编译器会自动生成一个默认的静态构造函数。,3.2.3 构造函数,【例3.6】静态构造函数。using System;class Screen static int Height;static int Width;int Cur_
24、X,Cur_Y;static Screen()/静态构造函数,对类的静态字段初始化 Height=768;Width=1024;,3.2.4 析构函数,一般来说,创建一个对象时需要用构造函数初始化数据,与此相对应释放一个对象时就用析构函数。所以析构函数是用于实现析构类实例所需操作的方法。声明语法形式:属性集信息 opt extern opt 标识符()析构函数体其中:标识符必须与类名相同,但为了区分构造函数,前面需加“”表明它是析构函数。析构函数不能写返回类型,也不能带参数,因此它不可能被重载,当然它也不能被继承,所以一个类最多只能有一个析构函数。一个类如果没有显式地声明析构函数,则编译器将自
25、动产生一个缺省默认的析构函数。析构函数不能由程序显式地调用,而是由系统在释放对象时自动调用。如果这个对象是一个派生类对象,那么在调用析构函数时也会产生链式反应,首先执行派生类的析构函数,然后执行基类的析构函数,如果这个基类还有自己的基类,这个过程就会不断重复,直到调用Object类的析构函数为止,其执行顺序正好与构造函数相反。,3.2.4 析构函数,【例3.7】析构函数的调用次序。using System;public class Point private int x,y;Point()Console.WriteLine(Points destructor);public class Cir
26、cle:Point private double radius;Circle()/缺省约定调用基类的无参构造函数Point()。Console.WriteLine(Circles destructor);,class Test public static void Main()Circle b=new Circle();b=null;GC.Collect();GC.WaitForPendingFinalizers();Console.Read();运行结果如图3.11所示。,图3.11 析构函数的调用次序,3.3 方法,3.3.1 方法的声明方法是按照一定格式组织的一段程序代码,在类中用方法声
27、明的方式来定义。方法声明语法形式:属性集信息 opt 方法修饰符 opt 返回类型 方法名(形参表 opt)方法体 其中:方法修饰符有如表3.2所示。,表3.2 方法的修饰符,3.3.1 方法的声明,方法修饰符中public、protected、private、internal、protected internal属于访问修饰符,用于表示访问的级别,默认情况下,方法的访问级别为public。访问修饰符也可以和其它的方法修饰符有效地组合在一起,但某些修饰符是互相排斥的。表3.3所列的组合被视为非法。,表3.3 修饰符的无效组合,3.3.1 方法的声明,【例3.8】下面程序中的StackTp类定义
28、了几个方法以模拟实现一个压栈操作。using System;class StackTp int MaxSize;int Top;int StkList;public StackTp()/构造函数MaxSize=100;Top=0;StkList=new int MaxSize;public StackTp(int size)/构造函数MaxSize=size;Top=0;StkList=new int MaxSize;,public bool isEmptyStack()/方法if(Top=0)return true;else return false;public bool isFullSt
29、ack()if(Top=MaxSize)return true;elsereturn false;public void push(int x)StkListTop=x;Top+;,class Test public static void Main()StackTp ST=new StackTp(20);string s1;if(ST.isEmptyStack()/调用方法isEmptyStack()s1=Empty;else s1=not Empty;Console.WriteLine(Stack is+s1);for(int i=0;i20;i+)ST.push(i+1);if(ST.i
30、sFullStack()/调用方法isFullStack()s1=Full;else s1=not Full;Console.WriteLine(Stack is+s1);Console.Read();,【例3.8】,运行结果如图3.12所示。,图3.12 StockTp类的压栈操作的运行结果,3.3.2 方法的参数,参数的功效就是能使信息在方法中传入或传出,当声明一个方法时,包含的参数说明是形式参数(形参)。当调用一个方法时,给出的对应实际参量是实在参数(实参),传入或传出就是在实参与形参之间发生的,在C#中实参与形参有四种传递方式。1.值参数在方法声明时不加修饰的形参就是值参数,它表明实参
31、与形参之间按值传递。当这个方法被调用时,编译器为值参数分配存储单元,然后将对应的实参的值拷贝到形参中。实参可以是变量、常量、表达式,但要求其值的类型必须与形参声明的类型相同或者能够被隐式的转化为这种类型。这种传递方式的好处是在方法中对形参的修改不影响外部的实参,也就是说数据只能传入方法而不能从方法传出,所以值参数有时也被称为入参数。,3.3.2 方法的参数,【例3.9】下面的程序演示了当方法Sort传递的是值参数时,对形参的修改不影响其实参。using System;class Myclass public void Sort(int x,int y,int z)int tmp;/tmp是方法
32、Sort的局部变量/将x,y,z按从小到大排序if(xy)tmp=x;x=y;y=tmp;if(xz)tmp=x;x=z;z=tmp;if(yz)tmp=y;y=z;z=tmp;,class Test static void Main()Myclass m=new Myclass();int a,b,c;a=30;b=20;c=10;m.Sort(a,b,c);Console.WriteLine(a=0,b=1,c=2,a,b,c);Console.Read();运行结果如图3.13所示:,图3.13 方法的值参数传递的运行结果,【例3.9】,3.3.2 方法的参数,【例3.10】下面程序演示
33、的是当方法传递的是一个引用对象(例如数组时)时,对形参的修改会影响到实参。using System;class Myclass public void SortArray(int a)int i,j,pos,tmp;for(i=0;ia j)pos=j;if(pos!=i)tmp=ai;ai=apos;apos=tmp;,class Teststatic void Main()Myclass m=new Myclass();int score=87,89,56,90,100,75,64,45,80,84;m.SortArray(score);for(int i=0;iscore.Length;
34、i+)Console.Write(score0=1,i,scorei);if(i=4)Console.WriteLine();Console.Read();运行结果如图3.14所示。,图3.14 引用对象作为参数传递的运行结果,3.3.2 方法的参数,2.引用参数引用与值参数不同,引用参数并不创建新的存储单元,它与方法调用中的实在参数变量同处一个存储单元。因此,在方法内对形参的修改就是对外部实参变量的修改。【例3.11】将【例3.9】程序中Sort方法的值参数传递方式改成引用参数传递,这样在方法Sort中对参数x、y、z按从小到大的排序影响了调用它的实参a、b、c。using System;c
35、lass Myclass public void Sort(ref int x,ref int y,ref int z)int tmp;/tmp是方法Sort的局部变量/将x,y,z按从小到大排序if(xy)tmp=x;x=y;y=tmp;if(xz)tmp=x;x=z;z=tmp;if(yz)tmp=y;y=z;z=tmp;,【例3.11】,class Teststatic void Main()Myclass m=new Myclass();int a,b,c;a=30;b=20;c=10;m.Sort(ref a,ref b,ref c);Console.WriteLine(a=0,b=
36、1,c=2,a,b,c);Console.Read();运行结果如图3.15所示。,图3.15 引用参数传递方式的运行结果,2.引用参数,使用ref参数的注意点:(1)ref关键字仅对跟在它后面的参数有效,而不能应用于整个参数表。例如Sort方法中x,y,z都要加ref修饰。(2)在调用方法时,也用ref修饰实参变量,因为是引用参数,所以要求实参与形参的数据类型必须完全匹配,而且实参必须是变量,不能是常量或表达式。(3)在方法外,ref参数必须在调用之前明确赋值,在方法内,ref参数被视为初始值已赋过。,3.3.2 方法的参数,3.输出参数在参数前加out修饰符的被称为输出参数,它与ref参数
37、很相似,只有一点除外,就是它只能用于从方法中传出值,而不能从方法调用处接受实参数据。在方法内out参数被认为是未赋过值的,所以在方法结束之前应该对out参数赋值。【例3.12】在下面程序4-7中,求一个数组元素中的最大值、最小值以及平均值。希望得到三个返回值,显然用方法的返回值不能解决,而且这三个值必须通过计算得到,初始值没有意义,所以解决方案可以定义三个out参数。using System;class Myclass public void MaxMinArray(int a,out int max,out int min,out double avg)int sum;sum=max=min
38、=a0;for(int i=1;imax)max=ai;if(aimin)min=ai;sum+=ai;,【例3.12】,avg=sum/a.Length;class Teststatic void Main()Myclass m=new Myclass();int score=87,89,56,90,100,75,64,45,80,84;int smax,smin;double savg;m.MaxMinArray(score,out smax,out smin,out savg);Console.Write(Max=0,Min=1,Avg=2,smax,smin,savg);Console
39、.Read();,【例3.12】,运行结果如图3.16所示。,ref和out参数的使用并不局限于值类型参数,它们也可用于引用类型来传递对象。,3.输出参数,【例3.13】下面程序定义了两个方法,一个是Swap1,一个是Swap2,它们都有两个引用对象作参数,但Swap2的参数加了ref修饰,调用这两个方法产生的结果是不一样的。using System;class Myclass public void Swap1(string s,string t)string tmp;tmp=s;s=t;t=tmp;public void Swap2(ref string s,ref string t)st
40、ring tmp;tmp=s;s=t;t=tmp;,【例3.13】,class Teststatic void Main()Myclass m=new Myclass();string s1=ABCDEFG,s2=134567;m.Swap1(s1,s2);Console.WriteLine(s1=0,s1);/s1,s2的引用并没有改变Console.WriteLine(s2=0,s2);m.Swap2(ref s1,ref s2);/s1,s2的引用互相交换了Console.WriteLine(s1=0,s1);Console.WriteLine(s2=0,s2);Console.Read
41、();运行结果如图3.17所示。,图3.17 Swap1和Swap2两个不同参数传递方式的运行结果,3.3.2 方法的参数,4.参数数组一般而言,调用方法时其实参必须与该方法声明的形参在类型和数量上相匹配,但有时候我们更希望灵活一些,能够给方法传递任意个数的参数,比如在三个数中找最大、最小和在5个数中找最大、最小甚或任意多个数中找最大、最小能使用同一个方法。C#提供了传递可变长度的参数表的机制,即使用params关键字来指定一个参数可变长的参数表。【例3.14】下面程序演示了Myclass类中的方法MaxMin有一个参数数组类型的参数,那么在调用这个方法是所具有的灵活性。using Syste
42、m;class Myclass public void MaxMin(out int max,out int min,params int a)if(a.Length=0)/如果可变参数为零个,可以取一个约定值或产生异常max=min=-1;return;,【例3.14】,max=min=a0;for(int i=1;imax)max=ai;if(aimin)min=ai;class Test static void Main()Myclass m=new Myclass();int score=87,89,56,90,100,75,64,45,80,84;int smax,smin;m.Ma
43、xMin(out smax,out smin);/可变参数的个数可以是零个 Console.WriteLine(Max=0,Min=1,smax,smin);m.MaxMin(out smax,out smin,45,76,89,90);/在四个数之间找最大、最小,【例3.14】,Console.WriteLine(Max=0,Min=1,smax,smin);m.MaxMin(out smax,out smin,score);/可变参数也可接受数组对象 Console.WriteLine(Max=0,Min=1,smax,smin);Console.Read();运行结果如图3.18所示。,
44、图3.18 含有参数数组的MaxMin方法被调用的运行结果,3.3.2 方法的参数,从上面例程中可以看出设立可变参数非常方便也很实用。但在使用时要注意以下几点:(1)一个方法中只能声明一个params参数,如果还要其他常规参数,则params参数应放在参数表的最后。(2)用params修饰符声明的参数是一个一维数组类型,例如,可以是int,string,double,或int,string 等,但不能是int,string,等。(3)由于params参数其实是一个数组,所以在调用时可以为参数数组指定零个或多个参数,其中每个参数的类型都应与参数数组的元素类型相同或能隐式地转换。(4)当调用具有p
45、arams参数的方法时,可以作为一个元素列表(如:m.MaxMin(smax,smin,45,76,89,90);)或作为一个数组(如:m.MaxMin(out smax,out smin,score);)传递给params参数。(5)无论采用哪种方式来调用方法,params参数都是作为一个数组被处理。所以在方法内可以使用数组的长度属性来确定在每次调用中所传递参数的个数。(6)params参数在内部会进行数据的复制,不可能将params修饰符与ref和out修饰符组合起来用。所以在这个方法中即使对参数数组的元素进行了修改,在这个方法之外的数值也不会发生变化。,3.3.3 静态方法与实例方法,类
46、的数据成员可以分静态字段和实例字段。静态字段是和类相关联的,不依赖特定对象的存在,实例字段是和对象相关联的,访问实例字段依赖于实例的存在。因此,根据静态字段和实例字段的特性,构造函数将其分为静态构造函数和实例构造函数,方法也将其分为静态方法和实例方法。通常若一个方法声明中含有static修饰符,则表明这个方法是静态方法,同时说明它只对这个类中的静态成员操作,不可以直接访问实例字段。若一个方法声明中不包含static修饰符,则该方法是一个实例方法。一个实例方法的执行与特定对象关联,所以需要一个对象存在。实例方法可以直接访问静态字段和实例字段。,3.3.3 静态方法与实例方法,【例3.15】下面是
47、一个商品销售的简单管理程序。每一种商品对象要存储的是商品总数及商品的单价。每销售一件商品要计算销售额和库存。cashRegister类将销售总额定义成静态变量cashSum,那么访问cashSum的方法productCost也就定义成静态方法,而MakeSale方法是计算销售额及库存的,所以定义成实例方法。请注意它们在使用中的不同。using System;class cashRegister int numItems;/商品总数double cost;/商品单价static double cashSum;/cashSum是静态变量,计销售总额 public cashRegister(int
48、numItems,double cost)this.numItems=numItems;this.cost=cost;public cashRegister()numItems=0;cost=0.0;,【例3.15】,static cashRegister()cashSum=0.0;/this.cashSum=0.0;错误,静态方法不允许使用thispublic void makeSale(int num)/实例方法this.numItems-=num;cashSum+=cost*num;/实例方法可以访问静态成员public static double productCost()/静态方法,
49、只能访问静态成员return cashSum;/return this.cashSum;错误,静态方法不能使用thispublic int productCount()return numItems;,【例3.15】,class staticTeststatic void Main()cashRegister Candy=new cashRegister(200,1);cashRegister Chips=new cashRegister(500,3.5);Candy.makeSale(5);Console.Write(Candy.numItems=0,Candy.productCount()
50、;/调用实例方法与对象Candy相关联Console.WriteLine(cashSum=0,cashRegister.productCost();/调用静态方法与类cashRegister相关联Chips.makeSale(10);Console.Write(Chips.numItems=0,Chips.productCount();/调用实例方法与对象Chips相关联Console.WriteLine(cashSum=0,cashRegister.productCost();/cashSum计Candy和Chips售出总价 Console.Read();,【例3.15】,运行结果如图3.1