1.39k likes | 1.53k Views
第 4 章 面向对象编程进阶. 4.1 类的继承与多态 当今的面向对象编程语言都提供了继承和多态的功能, C# 作为一种面向对象的高级编程语言也具有这样的特点。继承是面向对象语言的基本特征,是实现代码复用的手段。继承使得在原有的类基础之上,对原有的程序进行扩展,从而提高程序开发的速度,实现代码的复用。同一种方法作用于不同对象可以产生不同的结果,这就是多态性。它是在基类中使用虚方法,在其派生类中使用重载实现的。. 4.1.1 继承. 1. 使用继承
E N D
第4章 面向对象编程进阶 4.1类的继承与多态 当今的面向对象编程语言都提供了继承和多态的功能,C#作为一种面向对象的高级编程语言也具有这样的特点。继承是面向对象语言的基本特征,是实现代码复用的手段。继承使得在原有的类基础之上,对原有的程序进行扩展,从而提高程序开发的速度,实现代码的复用。同一种方法作用于不同对象可以产生不同的结果,这就是多态性。它是在基类中使用虚方法,在其派生类中使用重载实现的。
4.1.1 继承 • 1. 使用继承 • 继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。使用继承而产生的类被称为派生类或者子类,而被继承的类则称为基类、超类或父类。客观世界中的许多事物之间往往都是具有相同的特征,具有继承的特点。图4.1和图4.2是两个采用类的层次图表示继承的例子。
1. 使用继承 图4.1 类的层次结构1 图4.2 类的层次结构2
4.1.1 继承 • 【例4.1】类的继承。 • using System; • //定义基类Shape • public class Shape • { • protected string Color; • public Shape() • {;} • public Shape(string Color) • { • this.Color = Color; • } • public string GetColor() • { • return Color; • } • }
【例4.1】 • //定义Circle类,从Shape类中派生 • public class Circle : Shape • { • private double Radius; • public Circle(string Color, double Radius) • { • this.Color = Color; • this.Radius = Radius; • } • public double GetArea() • { • return System.Math.PI * Radius * Radius; • } • }
【例4.1】 • // 派生类Rectangular,从Shape类中派生 • public class Rectangular:Shape • { • protected double Length, Width; • public Rectangular() • { • Length=Width=0; • } • public Rectangular(string Color,double Length, double Width) • { • this.Color = Color; • this.Length = Length; • this.Width = Width; • } • public double AreaIs() • { • return Length*Width; • }
【例4.1】 • public double PerimeterIs()//周长 • { • return (2*(Length+Width)); • } • } • // 派生类Square,从 Rectangular类中派生 • public class Square: Rectangular • { • public Square(string Color,double Side) • { • this.Color = Color; • this.Length=this.Width = Side; • } • } • public class TestInheritance • { • public static void Main()
【例4.1】 • { • Circle Cir= new Circle("orange",3.0); • Console.WriteLine("Circle color is {0},Circle area is {1}", • Cir.GetColor(),Cir.GetArea()); • Rectangular Rect= new Rectangular("red",13.0, 2.0); • Console.WriteLine("Rectangualr color is {0},Rectangualr area is {1}, • Rectangular perimeter is {2}", Rect.GetColor(),Rect.AreaIs(), Rect.PerimeterIs()); • Square Squ= new Square("green",5.0); • Console.WriteLine("Square color is {0},Square Area is {1}, Square perimeter is {2}", Squ.GetColor(),Squ.AreaIs(), Squ.PerimeterIs()); • } • } • 运行结果如图4.3所示。 图4.3 运行结果
4.1.1 继承 • 2. base关键字 • 例4.1程序中的square类也可以改写为: • // 派生类Square,从 Rectangular类中派生 • public class Square: Rectangular • { • public Square(string Color,double Side):base(Color,Side,Side) • { ; } • } • 关键字base的作用是调用Rectangular类的构造函数并将Square类的变量初始化。如果将Square类改写成: • // 派生类Square,从 Rectangular类中派生 • public class Square: Rectangular • { • public Square(string Color,double Side) • { ; } • }
2. base关键字 • 实际上这种情况调用的是父类的无参构造函数,而不是有参构造函数,等同于: • // 派生类Square,从 Rectangular类中派生 • public class Square: Rectangular • { • public Square(string Color,double Side):base() • { ; } • } • base关键字除了能调用基类对象构造函数,还可以调用基类的方法。在下例中,Employee类的GetInfoEmployee方法使用了base关键字调用基类Person的GetInfoPerson方法。
2. base关键字 • 【例4.2】base调用基类的方法。 • using System; • public class Person • { • protected string Phone = "444-555-666"; • protected string Name = "李明"; • public void GetInfoPerson() • { • Console.WriteLine("Phone: {0}", Phone); • Console.WriteLine("Name: {0}", Name); • } • } • class Employee : Person • { • public string ID = "ABC567EFG"; • public void GetInfoEmployee()
【例4.2】 • { • // 调用基类Person的GetInfo方法 • base.GetInfoPerson(); • Console.WriteLine("Employee ID: {0}",ID); • } • } • class TestClass • { • public static void Main() • { • Employee Employees = new Employee(); • Employees.GetInfoEmployee(); • } • }
4.1.1 继承 • 3. 继承中的构造函数与析构函数 • 在前面讨论过派生类的构造函数会隐式调用父类的无参构造函数。那么,如果父类也是派生于其它类,会是什么样的情形呢?C#的执行顺序是这样的:根据层次链找到最顶层的基类,先调用基类的构造函数,再依次调用各级派生类的构造函数。而析构函数的次序正好相反。 • 【例4.3】继承中的构造函数与析构函数。 • using System; • public class BaseLifeSample • { • public static void Main() • { • Son s1 = new Son(); • } • } • public class Grandsire
【例4.3】 • { • public Grandsire() • { • Console.WriteLine("调用Grandsire的构造函数"); • } • ~Grandsire() • { • Console.WriteLine("调用Grandsire的析构函数"); • } • } • public class Father : Grandsire • { • public Father() • { • Console.WriteLine("调用Father的构造函数"); • } • ~Father() • { • Console.WriteLine("调用Father的析构函数"); • } • }
【例4.3】 • public class Son : Father • { • public Son() • { • Console.WriteLine("调用Son的构造函数"); • } • ~Son() • { • Console.WriteLine("调用Son的析构函数"); • } • } • 运行结果如图4.4所示。 图4.4 运行结果
4.1.1 继承 • 4. System.Object类 • C#所有类都派生于System.Object类。在定义类时如果没有指定派生于哪一个类,系统就默认其派生于Object类。例4.2中Shape的定义就等同于: • public class Shape :System.Object • { • … • } • System.Object类常见的公有方法是: • Equals:如果两个对象具有相同值时,方法将返回true。 • GetHashCode:方法返回对象的值的散列码。 • ToString:通过在派生类中重写该方法,返回一个表示对象状态的字符串。
4.1.2多态 • 多态也是面向对象语言的基本特征之一,是指在程序执行之前无法根据函数名和参数确定调用哪一个操作,而是程序执行过程中,根据实际运行情况动态确定,从而带来编程高度的灵活性。实现多态的方法是使用虚方法。 • 1. 虚方法的重载 • 在类的方法前加上关键字virtual, 则该方法被称为虚方法。通过对虚方法的重载,实现在程序运行过程中确定调用的方法。需要注意的是这里所讲的重载与第3章所讲的通过参数类型与参数个数的不同实现的重载含义是不同的。 • 【例4.4】虚方法的重载。 • using System; • class A • { • public void F() { Console.WriteLine("A.F"); } • public virtual void G() { Console.WriteLine("A.G"); } • }
【例4.4】 • class B: A • { • new public void F() { Console.WriteLine("B.F"); } • public override void G() { Console.WriteLine("B.G"); } • } • class Test • { • static void Main() • { • B b = new B(); • A a = b; • a.F(); • b.F(); • a.G(); • b.G(); • } • }
【例4.4】 • 在A 类定义了提供了非虚的F和虚方法 G, 派生类B 则对方法F实现覆盖,对虚方法G • 实现了虚方法的的重载。“A a = b”实际上a仍旧是一个b对象。输出结果为: • A.F • B.F • B.G • B.G • 在例4.1中计算圆形和矩形的面积分用了两个不同的方法GetArea和AreaIs,也可以通过 • 虚方法实现。将Shape中增加虚方法: • public virtual double GetArea() • { • return 0.0; • } • 在Circle类中对虚方法重载: • public override double GetArea() • { • return System.Math.PI * radius * radius; • } • 在Rectangular类中对虚方法重载: • public override double GetArea() • { • return Length*Width; • }
4.1.2多态 • 【例4.5】用虚方法的实现重载 • using System; • //定义基类Shape • public class Shape • { • protected string Color; • public Shape() • {;} • public Shape(string Color) • { • this.Color = Color; • } • public string GetColor() • { • return Color; • } • public virtual double GetArea() • { • return 0.0; • } • }
【例4.5】 • //定义Circle类,从Shape类中派生 • public class Circle : Shape • { • private double Radius; • public Circle(string Color, double Radius) • { • this.Color = Color; • this.Radius = Radius; • } • public override double GetArea() • { • return System.Math.PI * Radius * Radius; • } • } • // 派生类Rectangular,从Shape类中派生 • public class Rectangular:Shape
【例4.5】 • { • protected double Length, Width; • public Rectangular(string Color,double Length, double Width) • { • this.Color = Color; • this.Length = Length; • this.Width = Width; • } • public override double GetArea() • { • return Length*Width; • } • public double PerimeterIs() // 周长 • { • return (2*(Length+Width)); • } • }
【例4.5】 • // 派生类Square,从 Rectangular类中派生 • public class Square: Rectangular • { • public Square(string Color,double Side):base(Color,Side,Side) • {;} • } • public class TestInheritance • { • public static void Main() • { • Circle Cir= new Circle("orange",3.0); • Console.WriteLine("Circle color is {0},Circle area is {1}", Cir.GetColor(),Cir.GetArea()); • Rectangular Rect= new Rectangular("red",13.0, 2.0); • Console.WriteLine("Rectangualr color is {0},Rectangualr area is {1}, Rectangular perimeter is
【例4.5】 • {2}", Rect.GetColor(),Rect.GetArea(), Rect.PerimeterIs()); • Square Squ= new Square("green",5.0); • Console.WriteLine("Square color is {0},Square Area is {1}, Square perimeter is {2}", Squ.GetColor(),Squ.GetArea(), Squ.PerimeterIs()); • Shape Shp= Cir; • Console.WriteLine("Circle area is {0}", Shp.GetArea()); • Shp = Rect; • Console.WriteLine("Rectangualr area is {0}",Shp.GetArea()); • } • }
4.1.2多态 • 2. 抽象类与抽象方法 • 抽象类是一种特殊的基类,并不与具体的事物联系。抽象类的定义使用关键字abstract。在 • 图4.2 类的层次结构中,并没有“图形”这样具体事物,所以可以将“图形”定义为抽象类,派 • 生了“圆形”和“四边形”这样一些可以产生具体实例化的普通类。需要注意的是,抽象类是不 • 能被实例化,它只能作为其它类的基类。将Shape类定义为抽象类: • public abstract class Shape • { • … • } • 在抽象类中也可以使用关键字abstract定义抽象方法,要求所有的派生非抽象类都要重载实 • 现抽象方法。引入抽象方法的原因在于抽象类本身是一种抽象的概念,有的方法并不要具 • 体的实现,而是留下来让派生类来重载实现。Shape类中GetArea方法本身没什么具体的 • 意义,而只有到了派生类Circle类和Rectangular才可以计算具体的面积。 • 抽象方法写法: • public abstract double GetArea(); • 则派生类重载实现为: • public override double GetArea() • { • … • }
4.1.2多态 • 【例4.6】抽象类和抽象方法的实现 • using System; • //定义基类Shape • public abstract class Shape • { • protected string Color; • public Shape() • {;} • public Shape(string Color) • { • this.Color = Color; • } • public string GetColor() • { • return Color; • } • public abstract double GetArea(); • }
【例4.6】 • // 定义Circle类,从Shape类中派生 • public class Circle : Shape • { • private double Radius; • public Circle(string Color, double Radius) • { • this.Color = Color; • this.Radius = Radius; • } • public override double GetArea() • { • return System.Math.PI * Radius * Radius; • } • }
【例4.6】 • // 派生类Rectangular, 从Shape类中派生 • public class Rectangular:Shape • { • protected double Length, Width; • public Rectangular(string Color,double Length, double Width) • { • this.Color = Color; • this.Length = Length; • this.Width = Width; • } • public override double GetArea() • { • return Length*Width; • } • public double PerimeterIs() //周长 • { • return (2*(Length+Width)); • } • }
【例4.6】 • // 派生类Square, 从 Rectangular类中派生 • public class Square: Rectangular • { • public Square(string Color,double Side):base(Color,Side,Side) • {;} • } • public class TestInheritance • { • public static void Main() • { • Circle Cir= new Circle("orange",3.0); • Console.WriteLine("Circle color is {0},Circle area is {1}", Cir.GetColor(),Cir.GetArea()); • Rectangular Rect= new Rectangular("red",13.0, 2.0);
【例4.6】 • Console.WriteLine("Rectangualr color is {0},Rectangualr area is {1}, Rectangular perimeter is {2}", Rect.GetColor(),Rect.GetArea(), Rect.PerimeterIs()); • Square Squ= new Square("green",5.0); • Console.WriteLine("Square color is {0},Square Area is {1}, Square perimeter is {2}", Squ.GetColor(),Squ.GetArea(), Squ.PerimeterIs()); • } • }
4.1.2多态 • 3. 密封类和密封方法 • 抽象类是作为基类,不能被实例化,由其它类继承。相对应的还有一种不能被其它类继承 • 的类,叫密封类,使用sealed关键字定义。如果Rectangular类定义为密封类 : • public class rectangular:Shape • { • … • } • 这样Rectangular类的派生类Square就不再保留,否则,就会出错。 • 如果类的方法声明包含 sealed 修饰符,称该方法为密封方法。类的实例方法声明包含 • sealed 修饰符,则必须同时使用override修饰符。使用密封方法可以防止派生类进一步重 • 写该方法。如果将圆形Circle类的GetArea方法定义为密封类,必须先将Shape类GetArea • 方法定义为: • public virtual double GetArea() • { • … • } • 然后在Circle类中实现密封方法: • public sealed override double GetArea() • { • … • }
4.2 操作符重载 • 如果有一个复数Complex类对一元操作符“++”重载,可以写成: • public static Complex operator ++(Complex a) • { • … • } • 对二元操作符“+”可以写成: • public static Complex operator +(Complex a, Complex b) • { • … • }
4.2 操作符重载 • 一元操作符有一个参数,二元操作符有二个参数。重载操作符开始必须以public static修饰。可以重载的操作符包括: • 一元操作符:+ - ! ~ ++ -- true false • 二元操作符:+ - * / % & | ^ << >> == != > < >= <= • 下面的操作符要求同时重载,不能重载中间的单独一个: • 一元操作符:true和false • 二元操作符:== 和!=, >和<, >=和<= • 操作符重载给类的一些操作带来了方便。两个复数的实部相加运算写成: • public static double Add(complex a, complex b) • { • return a.r+b.r • } • 这样的写法不够简洁,并且类的成员修饰符不为public时就不能这样直接的操作。
4.2 操作符重载 • 【例4.7】操作符重载的实现 • using System; • class Complex • { • double r, v; //r+ v i • public Complex(double r, double v) • { • this.r=r; • this.v=v; • } • // 二元操作符”+”重载 • public static Complex operator +(Complex a, Complex b) • { • return new Complex(a.r+b.r, a.v+b.v); • }
【例4.7】 • // 一元操作符”-”重载 • public static Complex operator -(Complex a) • { • return new Complex(-a.r,-a.v); • } • // 一元操作符”++”重载 • public static Complex operator ++(Complex a) • { • double r=a.r+1; • double v=a.v+1; • return new Complex(r, v); • } • public void Print() • { • Console.Write(r+" + "+v+"i\n"); • } • }
【例4.7】 • class Test • { • public static void Main() • { • Complex a=new Complex(3,4); • Complex b=new Complex(5,6); • Complex c=-a; • c.Print(); • Complex d=a+b; • d.Print(); • a.Print(); • Complex e=a++; // 先赋值后++ • a.Print(); • e.Print(); • Complex f=++a; // 先++后赋值 • a.Print(); • f.Print(); • } • }
4.2 操作符重载 • 在操作符重载中,返回值往往需要“new”一个新的complex对象。 • 另外一种的操作符重载类型是用户定义的数据类型转换,它实现不同类型之间的转换,包括显式转换和隐式转换两种方式。 • 编程中往往需要将一个类型转换成另外一个类型。例如将int转换成double,它们是系统已经预定义的类型,编译器知道如何来执行它们的转换, 具体内容在下一节“类型转换”中讨论。如果它们中间有的类型不是编译器预定义的类型,编译器将不知道执行转换,解决的方法是使用用户定义的数据类型转换。 • 转换过程不会丢失数据而出现异常,就采用隐式转换。如果转换过程有可能丢失数据,就要采用显式转换。 • 隐式类型转换的写法如: • public static implicit operator Square(double s) • { • … • } • 实现double向Square转换功能。关键字explicit实现显式类型转换: • public static explicit operator double(Square s) • { • … • }
【例4.8】用户定义的数据类型转换。 • using System; • class Square • { • private double Side; • public Square(int s) • { • Console.WriteLine("int类型参数构造函数"); • Side=(double)s; • } • public Square(double s) • { • Console.WriteLine("double类型参数构造函数"); • Side=s; • } • // 重写object类的ToString()方法. • public override string ToString() • { • Console.WriteLine("重写object类的ToString()方法"); • return this.Side.ToString(); • }
【例4.8】 • // 重载”+” 操作符,参数为两个square类 • public static Square operator + (Square x,Square y) • { • Console.WriteLine("重载+操作符,参数为两个square类"); • return new Square(x.Side+y.Side); • } • // 重载”+”操作符,参数一个为square类,一个为double类型 • public static Square operator + (Square x,double y) • { • Console.WriteLine("重载+操作符,参数一个为square类,一个为double类型"); • return new Square(x.Side+y); • } • // 重载”+”操作符,参数一个为square类,一个为int类型 • public static Square operator + (Square x,int y) • { • Console.WriteLine("重载+操作符,参数一个为square类,一个为int类型"); • return x +(double)y; //调用上面的重载”+”操作符 • }
【例4.8】 • // 隐式类型转换,实现double类型转换为Square • public static implicit operator Square(double s) • { • Console.WriteLine("隐式类型转换,实现double类型转换为Square "); • return new Square(s); • } • // 隐式类型转换,实现int类型转换为Square • public static implicit operator Square(int s) • { • Console.WriteLine("隐式类型转换,实现int类型转换为Square "); • return new Square((double)s); • } • // 重载” == ”操作符 • public static bool operator ==(Square x,Square y) • { • Console.WriteLine("重载== 操作符,两个参数都为square类"); • return x.Side==y.Side; • }
【例4.8】 • // 重载” !=”操作符 • public static bool operator !=(Square x,Square y) • { • Console.WriteLine("重载!=操作符,两个参数都为square类"); • return !(x==y); //调用前面的重载”==”操作符 • } • // 当重载”==”和”!=”的同时还要重载object类的GetHashCode()和Equals(),否则编译器会出现警告 • // 也可以不重写这两个方法,对运行结果没有影响 • public override bool Equals(object o) • { • return this==(Square)o; • } • public override int GetHashCode() • { • return (int)Side; • }
【例4.8】 • // 重载” > ”操作符 • public static bool operator >(Square x,Square y) • { • Console.WriteLine("重载 >操作符,两个参数都为square类"); • return x.Side>y.Side; • } • // 重载” <”操作符 • public static bool operator <(Square x,Square y) • { • Console.WriteLine("重载<操作符,两个参数都为square类"); • return x.Side<y.Side; • } • // 重载” <= ”操作符 • public static bool operator <=(Square x,Square y) • { • Console.WriteLine("重载<=操作符,两个参数都为square类"); • return (x<y) || (x==y); //调用重载的操作符” ==”和”<” • }
【例4.8】 • // 重载” >=”操作符 • public static bool operator >=(Square x,Square y) • { • Console.WriteLine("重载>=操作符,两个参数都为square类"); • return (x>y) || (x==y); //调用重载的操作符” ==”和” >” • } • public static void Main() • { • Square s1=new Square(10); • Square s2=new Square(20); • Square s3=s1+s2; // 调用operator + (Square,Square) • Console.WriteLine(s3); // 调用重写object类的ToString()方法 • Console.WriteLine(s3+15); // 调用重写的operator + (Square,int)以及ToString() • Console.WriteLine(s3+1.5); // 调用重写的operator + (Square,double)和ToString() • s3=10; // 调用隐式转换public static implicit operator Square(int ) • Console.WriteLine(s3); • Square s4=10;
【例4.8】 • Console.WriteLine(s1==s4); // 调用== 操作符 • Console.WriteLine(s1!=s4); // 调用 != 操作符 • Console.WriteLine(s1>s2); // 调用 >操作符 • Console.WriteLine(s1<=s4); // 调用<=操作符 • } • } • 运行结果如图4.6所示。 • Square与double 和int 之间有两个隐式转换。从double以及int隐式转换过程 • 不会丢失数据而出现异常,所以采用了隐式转换。 • Square类默认的基类是Object类,所以可以重载Object类的方法ToString。 • WriteLine方法默认的参数为String对象,所以在输出之前都要调用ToString方 • 法,将参数转换为String类型。
【例4.8】 图4.6 运行结果
4.3 类型转换 • 实际编程中,经常用到类型的相互之间转换问题,如一个int类型要转换成一个long类型。类型转换方法分为隐式类型转换和显式类型转换,也可以采用Convert提供的方法实现类型转换。 • 4.3.1 隐式类型转换 • 隐式类型转换不需要加任何声明就可以实现的类型转换,规则简单。 • 1. 隐式数值转换 • 数值转换是指在整数类型、实数类型和字符类型之间的转换。sbyte类型向int类型转换是一种隐式数值类型转换,转换一般不会失败,也不会丢失数据。如: • sbyte a = 100; • int b = a; • 隐式数值类型转换如表4.1所示。 • 从int到long,long到float、double等几种类型转换时可能导致精度的下降,但不导致数量上的丢失。从表4.1中可以看出,任何的原始类型,如果值的范围完全包含在其它类型的值范围内,那么就能进行隐式转换。需要注意的是char类型可以转换为其它的整数或实数类型,但不存在其它类型转换为char类型。
1. 隐式数值转换 表4.1 隐式数值类型转换
1. 隐式数值转换 • 【例4.9】隐式数值转换。 • using System; • class ReferenceConversion • { • static void Main() • { • char a = 'm'; • int b = a; • Console.WriteLine("a equals:{0}",a); • Console.WriteLine("b equals:{0}",b); • } • } • 运行结果如下: • a equals:m • b equals:107 • 如果这样写: • int b = 7; • char a = b; • Console.WriteLine("a equals:{0}",a); • Console.WriteLine("b equals:{0}",b); • 编译器将报错:无法将类型“int”隐式转换为“char”。
4.3.1 隐式类型转换 • 2. 隐式枚举转换 • 隐式枚举转换只允许将十进制0转换为枚举类型的变量。 • 【例4.10】隐式枚举转换。 • using System; • enum Color • { • Red,Green,Blue • } • class EnumConversion • { • static void Main() • { • Color a = Color.Red; • Console.WriteLine("a equals:{0}",a); • a = 0; • Console.WriteLine("a equals:{0}",a); • } • } • 如果写a = 1或其它数值,编译器提示:无法将类型“int”隐式转换为“Color”。
4.3.1 隐式类型转换 • 3. 隐式引用转换 • 类型s向类型t隐式引用转换的条件是:s是从t派生来的,且s和t可以是接口或类。两个数组的之间的隐式转换的条件是:两个数组的维数相同,元素都是引用类型,且存在数组元素的隐式引用转换。例如: • class Employee // 隐含继承自System.Object • { • … • } • class App • { • Employee e; • object o = e; • … • } • 使用隐式转换的因为在于Employee是派生自Object类。