8.1 什么是继承
一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。通过继承,一个新建子类从已有的父类那里获得父类的特性。
从另一角度说,从已有的类(父类)产生一个新的子类,称为类的派生。
- 父类:被继承的类称为父类,也称为基类、超类;
- 子类:继承了的类称为子类,也称为派生类;
- 关系:IS-A
继承关系是向下传递的。
一个基类可以派生出多个派生类,每一个派生类又可以作为基类再派生出新的派生类,因此基类和派生类是相对而言的,同时基类与派生类也是一个成对的概念,一个孤立的类既不可能是基类也不可能是派生类。
一个派生类有且只能有一个基类,即C#不支持多重继承机制。
8.2 使用继承机制
8.2.1 基类和派生类
C#中继承的语法格式如下:
基类的成员如果只想让子类访问,即只让父类和子类之间沟通,基类成员的修饰符通常使用protected修饰。
namespace ConsoleApplication9
{public class Person{protected string ssn ;protected string name ;public void Display(){Console.WriteLine("姓名:{0}",name);Console.WriteLine("编号:{0}",ssn);}}public class Employeer : Person{public void Set(){ssn = "11-222-333-444";name = "张三";}} class Program{static void Main(string[] args){Employeer zs = new Employeer();zs.Set();zs.Display();Console.ReadLine();}}
}
C#语言的继承机制有如下规则:
- 派生类应当被看作是基类所具有的特性与功能的继承与扩展,而不是简单的派生类“大于”基类;
- 派生类不能“选择性”的继承基类的属性与方法,必须继承基类的所有特性与方法;
- 派生类可以在继承基类的基础上自由定义自己特有的成员;
- 基类的构造方法与析构方法不能被派生类继承,除此之外的其他成员都能被继承,基类成员的访问方式不影响他们成为派生类的成员;
- 派生类中继承的基类成员和基类中的成员只是相同,并非同一个成员。
8.2.2 base关键字与基类成员的访问
在派生类中使用base关键字即可指代当前类的父类,但只限于构造函数、实例方法和实例属性访问器中使用,如:
namespace ConsoleApplication9
{public class Person{protected string ssn = "11-222-444";protected string name = "张三";public void Display(){Console.WriteLine("姓名:{0}", name);Console.WriteLine("编号:{0}", ssn);}}public class Employeer : Person{public string id = “110723001";public void GetInfo(){Console.WriteLine("I D: {0}",id);base.Display();}}class Program{static void Main(string[] args){Employeer zs = new Employeer();zs.GetInfo();Console.ReadLine();}}
}
8.2.3 base关键字与构造函数的访问
继承机制并不能使派生类具有基类的带参数的构造方法,要想通过访问基类的构造方法为派生类中的基类子对象进行初始化则需要通过base关键字。
namespace ConsoleApplication9
{public class Person{protected string ssn = "11-222-333-444";protected string name = "张三";public void Display(){Console.WriteLine("姓名:{0}", name);Console.WriteLine("编号:{0}", ssn);}public Person(){Console.WriteLine("我是父类中不带参数的构造函数");}public Person(string i){Console.WriteLine("我是父类中带一个参数的构造函数,传递来的参数是:"+i);}public Person(string i, string j){Console.WriteLine("我是父类中带两个参数的构造函数,传递来的参数分别是:\n"+i+"\n"+j);}} public class Employeer : Person{public string id = "070423001";public void GetInfo(){Console.WriteLine("I D: {0}",id);base.Display();}public Employeer(){Console.WriteLine("我是子类中不带参数的构造函数!");}public Employeer(string e) : base(){Console.WriteLine("我是子类中带一个参数的构造函数,我要求继承父类中不带参数的构造函数,我的传递参数是:"+e);}public Employeer(string a, string b): base(b){Console.WriteLine(“我是子类中带两个参数的构造函数,我要求继承父类中带一个参数的构造函数,我的传递参数分别是:\n"+a+"\n"+b);} }class Program{static void Main(string[] args){Employeer zs = new Employeer(“信息”,“信管");// zs.GetInfo();Console.ReadLine();}}
}
显式的调用基类的构造方法使用base关键字,如:
public SonClass(int i,int j):base ([i,j]){}
C#中父类构造函数与子类构造函数:
子类的构造器无法从父类继承,但子类的构造器一定要调用父类的构造器,如果没有显式地声明调用哪个,系统将默认调用无参的那个。
实验:用C#编写一个程序,使用Animal和Mammal两个类来说明一般动物和哺乳动物的继承关系。Animal具有名称、所属门类等属性,需要提供方法实现接收和显示这些属性的值。Mammal类具有代表哺乳动物习性的属性,这些属性表明哺乳动物与其他类型动物的区别。同样地,需要提供方法实现接收和显示这些属性的值。
8.3 virtual、override及new关键字
多态性主要的表现形式就是在继承中当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。当父类和子类的成员签名(方法名)相同的时候,我们可以定义让子类的这个同名成员以何种形式显示出来,父类的这个的成员在子类中又用何种方式存在,这种多面的表现方法我们称为多态。
如果要更改基类的数据和功能,也就是说子类中可以以自己的方式去实现父类的功能,有两种方式:
- 在子类中可以使用override关键字重写基类中用virtual关键字修饰的虚拟的基成员;
- 可以在子类中使用new关键字让派生成员替换基成员,此时基类成员只是被有意的隐藏掉了。
8.3.1 virtual、override关键字
使用virtual关键字修饰的方法称为是虚方法,在一个类中如果某个方法需要被派生类继承,并且需要在派生类中修改方法的内容时可将该方法定义为虚方法。在派生类中如果需要重写该方法,可在派生类中定义同名的方法,其前加上override关键字修饰。在派生类中对虚方法使用override修饰后,基类中的该方法在派生类中将被屏蔽。
namespace ConsoleApplication9
{public class Person{protected string ssn = "11-222-333-444";protected string name = "张三";public virtual void Display() {Console.WriteLine("姓名:{0}", name);Console.WriteLine("编号:{0}", ssn);}}public class Employeer : Person{public string id = "070423001";public override void Display(){Console.WriteLine("I D: {0}",id);base.Display();}}
class Program
{static void Main(string[] args){Person p = new Person();Console.WriteLine("基类的成员:");p.Display();Employeer e = new Employeer();Console.WriteLine("派生类的成员:");e.Display();Console.ReadLine();}}
}
- 有virtual关键字一定要有override吗?不一定。
- 有override关键字一定要有virtual吗?一定。
- 因此,virtual是使用override的前提条件。
8.3.2 new关键字
new关键字用来修饰派生类中一个方法,是全新定义一个子类的方法,这个方法只属于子类,与基类的方法无关,该方法和基类中方法只是同名,但二者并无任何关联。因此这个方法只在子类中起作用,而不影响基类的方法,也就是说new方法就是子类新定义的方法。
在使用new的时候对于父类中同名的方法而言既可以是虚方法,也可以不是虚方法。
namespace ConsoleApplication9
{public class Person{protected string ssn = "11-222-333-444";protected string name = "张三";public void Display() {Console.WriteLine("姓名:{0}", name);Console.WriteLine("编号:{0}", ssn);}}public class Employeer : Person{public string id = "070423001";public new void Display(){Console.WriteLine("I D: {0}",id);base.Display();}}
class Program
{static void Main(string[] args){Person p = new Person();Console.WriteLine("基类的成员:");p.Display();Employeer e = new Employeer();Console.WriteLine("派生类的成员:");e.Display();Console.ReadLine();}}
}
在创建对象时,如果是子类对象,它调用的是子类中修改后的同名方法,产生一个运行结果。而如果声明了父类的一个对象,父类对象调用同名的父类方法时,产生了另外一个运行结果。
using System;
using System.Collections.Generic;
using System.Text;namespace ConsoleApplication7
{class Parent{public void F() //定义非虚方法{Console.WriteLine("基类的F方法被调用");}public virtual void G() //定义虚方法{Console.WriteLine("基类的G方法被调用");}}class Child : Parent{public new void F() { Console.WriteLine("派生类的F方法被调用");}public override void G() { Console.WriteLine("派生类的G方法被调用");}}
class Test
{static void Main() { Child b = new Child(); Parent a = b; a.F();b.F();a.G();b.G();Console.ReadLine();}}
}
当出现里氏替代现象时:
- 覆盖(override)时,访问父类子类皆调用子类的重写方法;
- 重写(new)时,访问父类则调用父类的方法,访问子类则调用子类的方法。
- override--覆盖(父类的没了,始终是子类的)
- new--新的(父子共存)
里氏替代原则:
父类对象引用子类实例,子类实例赋值给父类对象。
8.4 sealed关键字与密封类
在实际编程过程中,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed class)的概念,来描述这一问题。密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。
如果在一个类的继承体系中不想再使一个虚方法被覆盖,可以使用sealed override (密封覆盖)便可以达到这种目的。注意这里一定是sealed和override同时使用,也一定是密封覆盖一个虚方法,或者一个被覆盖(而不是密封覆盖)了的虚方法。密封一个非虚方法是没有意义的。
using System;
using System.Collections.Generic;
using System.Text;namespace ConsoleApplication7
{class Parent{public void F(){Console.WriteLine("基类的F方法被调用");}public virtual void G(){Console.WriteLine("基类的G方法被调用");}}class Child : Parent{new public void F() {Console.WriteLine("派生类的F方法被调用");}public sealed override void G() {Console.WriteLine("派生类的G方法被调用");}}
class Test
{static void Main() {Child b = new Child(); Parent a = b; a.F();b.F();a.G();b.G();Console.ReadLine();}}
}
1. sealed——“断子绝孙”
即:密封类不会有子类,所以是“断子绝孙”。
2. new——“你是我的,我是我的”
即:好比是不用祖宗的东西,而是用自己创造(new)的东西。
3. virtual——“为了子孙后代”
即:virtual是为了让子孙后代可以实现各自的梦想而做的。
4. override——“一手遮天”
即:override好比不但不用祖宗的那套,而且还自己创一套新功夫代替祖宗那套。
8.5 Abstract关键字与抽象类
{public abstract class Animal{public abstract void Eat();}public class Cat : Animal{public override void Eat(){Console.WriteLine("Cat Eat");}}class Program{static void Main(string[] args){Cat c = new Cat();c.Eat();Console.ReadLine();}}
}
8.6 多态性
从系统实现的角度看,多态性分为两类:静态多态性和动态多态性。
- 以前学过的方法重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个方法,因此静态多态性又称编译时的多态性。静态多态性是通过方法的重载实现的。
- 动态多态性是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态性。动态多态性是通过虚方法实现的。
namespace ConsoleApplication9
{public class Plane{public int wheels;protected int weight;public Plane(){ }public Plane(int w1, int w2){wheels = w1;weight = w2;}public virtual void Report(){Console.WriteLine("飞机的轮子的数目是:{0},重量是:{1}",wheels,weight);}}public class Airliner : Plane{int passengers;public Airliner(int w1, int w2, int p) : base(w1, w2){passengers = p;}public override void Report(){Console.WriteLine("客机的轮子的数目是:{0},重量是:{1},乘客数量是:{2}",wheels,weight,passengers);}} class Program{static void Main(){Plane p = new Plane();Airliner a = new Airliner(2, 300, 100);p.Report();p = a;p.Report();Console.ReadLine();}}
}