据类型求绝对值方法使用不同的方法名,如用abc()求整型数绝对值,labs()求长整型数绝对值,fabs()求浮点数绝对值。而在C#语言中,可以使用函数重载特性,对这三个函数定义同样的函数名,但使用不同的参数类型。下面是实现方法: using System;
public class UseAbs
{ public int abs(int x)//整型数求绝对值 { return(x<0 ? -x:x);}
public long abs(long x)//长整型数求绝对值 {return(x<0 ? -x:x);}
public double abs(double x)//浮点数求绝对值 {return(x<0 ? -x:x);} }
class Class1
{ static void Main(string[] args) { UseAbs m=new UseAbs(); int x=-10; long y=-123;
double z=-23.98d; x=m.abs(x); y=m.abs(y); z=m.abs(z);
Console.WriteLine(\} }
类的对象调用这些同名方法,在编译时,根据调用方法的实参类型决定调用那个同名方法,计算不同类型数据的绝对值。这给编程提供了极大方便。 1.10.5 操作符重载
操作符重载是将C#语言中的已有操作符赋予新的功能,但与该操作符的本来含义不冲突,使用时只需根据操作符出现的位置来判别其具体执行哪一种运算。操作符重载,实际是定义了一个操作符函数,操作符函数声明的格式如下:
static public 函数返回类型 operator 重新定义的操作符(形参表)
C#语言中有一些操作符是可以重载的,例如:+ - ! ~ ++ -- true false * / % & | ^ << >> == != > < >= <=等等。但也有一些操作符是不允许进行重载的,例如:=, &&, ||, ?:, new, typeof, sizeof, is等。
下边的例子,定义一个复数类,并且希望复数的加减乘除用符号+,-.*,/来表示。 using System;
class Complex//复数类定义
{ private double Real;//复数实部 private double Imag;//复数虚部
public Complex(double x,double y)//构造函数 { Real=x; Imag=y; }
static public Complex operator - (Complex a)//重载一元操作符负号,注意1个参数
{ return (new Complex(-a.Real,-a.Imag));}
static public Complex operator +(Complex a,Complex b)//重载二元操作符加号 { return (new Complex(a.Real+b.Real,a.Imag+b.Imag));} public void Display()
{ Console.WriteLine(\}
class Class1
{ static void Main(string[] args) { Complex x=new Complex(1.0,2.0); Complex y=new Complex(3.0,4.0); Complex z=new Complex(5.0,7.0); x.Display();//显示:1+(2)j y.Display();//显示:3+(4)j z.Display();//显示:5+(7)j z=-x;//等价于z=opeator-(x) z.Display();//显示:-1+(-2)j z=x+y;//即z=opeator+(x,y) z.Display();//显示:4+(6)j } }
1.10.6 this关键字
每个类都可以有多个对象,例如定义Person类的两个对象: Person P1=new Person(\李四\Person P2=new Person(\张三\因此P1.Display()应显示李四信息,P2.Display()应显示张三信息,但无论创建多少个对象,只有一个方法Display(),该方法是如何知道显示那个对象的信息的呢?C#语言用引用变量this记录调用方法Display()的对象,当某个对象调用方法Display()时,this便引用该对象(记录该对象的地址)。因此,不同的对象调用同一方法时,方法便根据this所引用的不同对象来确定应该引用哪一个对象的数据成员。this是类中隐含的引用变量,它是被自动被赋值的,可以使用但不能被修改。例如:P1.Display(),this引用对象P1,显示李四信息。P2.Display(),this引用对象P2,显示张三信息。 1.11 类的多态性 在面向对象的系统中,多态性是一个非常重要的概念。C#支持两种类型的多态性,第一种是编译时的多态性,一个类的对象调用若干同名方法,系统在编译时,根据调用方法的实参类型及实参的个数决定调用那个同名方法,实现何种操作。编译时的多态性是通过方法重载来实现的。C#语言的方法重载以及操作符重载和C++语言的基本一致。
第二种是运行时的多态性,是在系统运行时,不同对象调用一个名字相同,参数的类型及个数完全一样的方法,会完成不同的操作。C#运行时的多态性通过虚方法实现。在类的方法声明前加上了virtual修饰符,被称之为虚方法,反之为非虚方法。C#语言的虚方法和C++语言的基本一致。下面的例子说明了虚方法与非虚方法的区别: using System; class A
{ public void F()//非虚方法 { Console.Write(\
public virtual void G()//虚方法 { Console.Write(\}
class B:A//A类为B类的基类
{ new public void F()//覆盖基类的同名非虚方法F(),注意使用new { Console.Write(\
public override void G()//覆盖基类的同名虚方法G(),注意使用override { Console.Write(\}
class Test
{ static void F2(A aA)//注意,参数为A类引用变量 { aA.G();}
static void Main() { B b=new B(); A a1=new A();
A a2=b;//允许基类引用变量引用派生类对象,a2引用派生类B对象b a1.F();//调用基类A的非虚方法F(),显示A.F
a2.F();//F()为非虚方法,调用基类A的F(),显示A.F b.F();//F()为非虚方法,调用派生类B的F(),显示B.F
a1.G();//G()为虚方法,因a1引用基类A对象,调用基类A的G(),显示A.G
a2.G();//G()为虚方法,因a2引用派生类B对象,调用派生类B的G(),显示B.G F2(b);//实参为派生类B对象,由于A aA=b,调用派生类B的函数G(),显示B.G F2(a1);//实参为基类A对象,调用A类的函数G(),显示A.G } }
那么输出应该是:
A.F A.F B.F A.G B.G B.G A.G
注意例子中,不同对象调用同名非虚方法F()和同名虚方法G()的区别。a2虽然是基类引用变量,但它引用派生类对象b。由于G()是虚方法,因此a2.G()调用派生类B的G(),显示G.F。但由于F()是非虚方法,a2.F()仍然调用基类A的F(),显示A.F。或者说,如果将基类引用变量引用不同对象,或者是基类对象,或者是派生类对象,用这个基类引用变量分别调用同名虚方法,根据对象不同,会完成不同的操作。而非虚方法则不具备此功能。
方法F2(A aA)中,参数是A类类型,F2(b)中形参和实参的关系是:A aA=b,即基类引用变量aA引用派生类对象b,aA.G()调用派生类B的函数G(),显示B.G。同理,F2(a1)实参为基类A对象,调用A类的函数G(),显示A.G。
在类的基本概念一节中,定义一个描述个人情况的类Person,其中公有方法Display()用来显示个人信息。在派生雇员类Employee中,覆盖了基类的公有方法Display(),以显示雇员新增加的信息。我们希望隐藏这些细节,希望无论基类还是派生类,都调用同一个显示方法,根据对象不同,自动显示不同的信息。可以用虚方法来实现,这是一个典型的多态性例子。例子
using System;
public class Person
{ private String name=\张三\类的数据成员声明 private int age=12;
protected virtual void Display()//类的虚方法
{ Console.WriteLine(\姓名:{0},年龄:{1}\ }
public Person(string Name,int Age)//构造函数,函数名和类同名,无返回值 { name=Name; age=Age; }
static public void DisplayData(Person aPerson)//静态方法
{ aPerson.Display();//不是静态方法调用实例方法,如写为Display()错误 } }
public class Employee:Person//Person类是基类 { private string department; private decimal salary;
public Employee(string Name,int Age,string D,decimal S):base(Name,Age) { department=D; salary=S; }
protected override void Display()//重载虚方法,注意用override { base.Display();//访问基类同名方法
Console.WriteLine(\部门:{0} 薪金:{1} \} }
class Class1
{ static void Main(string[] args)
{ Person OnePerson=new Person(\李四\
Person.DisplayData(OnePerson);//显示基类数据
Employee OneEmployee=new Employee(\王五\财务部\ Person.DisplayData(OneEmployee); //显示派生类数据 } }
运行后,显示的效果是: 姓名: 李四,年龄:30 姓名: 王五,年龄:40
部门:财务部 薪金:2000 1.12 抽象类和抽象方法
抽象类表示一种抽象的概念,只是希望以它为基类的派生类有共同的函数成员和数据成员。抽象类使用abstract修饰符,对抽象类的使用有以下几点规定: ? 抽象类只能作为其它类的基类,它不能直接被实例化。
? 抽象类允许包含抽象成员,虽然这不是必须的。抽象成员用abstract修饰符修饰。 ? 抽象类不能同时又是密封的。
? 抽象类的基类也可以是抽象类。如果一个非抽象类的基类是抽象类,则该类必须通过覆盖来实现所有继承而来的抽象方法,包括其抽象基类中的抽象方法,如果该抽象基类从其它抽象类派生,还应包括其它抽象类中的所有抽象方法。
请看下面的示例:
abstract class Figure//抽象类定义 { protected double x=0,y=0;
public Figure(double a,double b) { x=a; y=b; }
public abstract void Area();//抽象方法,无实现代码 }
class Square:Figure///类Square定义
{ public Square(double a,double b):base(a,b) {}
public override void Area()//不能使用new,必须用override { Console.WriteLine(\矩形面积是:{0}\}
class Circle:Figure///类Square定义 { public Circle(double a):base(a,a) {}
public override void Area()
{ Console.WriteLine(\园面积是:{0}\}
class Class1
{ static void Main(string[] args) { Square s=new Square(20,30); Circle c=new Circle(10); s.Area();
c.Area(); } }
程序输出结果为: 矩形面积是:600 园面积是:314
抽象类Figure提供了一个抽象方法Area(),并没有实现它,类Square和Circle从抽象类Figure中继承方法Area(),分别具体实现计算矩形和园的面积。
在类的基本概念一节中,定义一个描述个人情况的类Person,它只是描述了一个人最一般的属性和行为,因此不希望生成它的对象,可以定义它为抽象类。
注意:C++程序员在这里最容易犯错误。C++中没有对抽象类进行直接声明的方法,而认为只要在类中定义了纯虚函数,这个类就是一个抽象类。纯虚函数的概念比较晦涩,直观上不容易为人们接受和掌握,因此C#抛弃了这一概念。 1.13 密封类和密封方法
有时候,我们并不希望自己编写的类被继承。或者有的类已经没有再被继承的必要。C#提出了一个密封类(sealed class)的概念,帮助开发人员来解决这一问题。 密封类在声明中使用sealed修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#编译器将提示出错。理所当然,密封类不能同时又是抽象类,因