0%

【C#】类的继承


①共同

使用继承可以使一个类在创建时就具有另一个已知类的所有内容,大大节省了工作时间。 使用方法:

1
2
3
4
class Child :Parent
{
......
}

如上,可写为“Child继承自Parent”,或“Parent派生出Child”。 注意,Object是任何类的基类,可多辈派生,但子类只能有一个父类。

关键字base:

1.在派生类中使用“base.基类方法()”即可调用基类方法。 2.构造方法派生:在派生类的构造方法声明后接“:base()”即可调用基类的构造方法重写该构造方法。

继承构造函数

在实例化派生的对象时的顺序是:

实例成员初始化→基类构造函数→派生类构造函数

所以无论是否在初始化时调用基类构造函数,其都会被调用,这是一种隐式调用,当然也可以写成显式形式,如:

1
2
3
4
5
6
7
8
9
class Pet{
public Pet(){...}//基类构造函数
...
}
class Cat:Pet
{
Public Cat():Pet(){...}//使用冒号显式调用基类构造函数
...
}

需要注意的是,这里面构造方法Cat()与Pet()都会被执行一遍。 当然,这种冒号调用构造函数的方法更多的是用在类自身的构造函数之间互相调用

②相异

那么,若想在子类中添加相异于父类的内容该怎么办呢?

1.隐藏方法

该方法可以在子类中屏蔽父类的方法/字段,即,如果父类中存在一个方法,开发者却想在子类中添加一个与父类方法相异的重名方法,就可在子类中创建隐藏方法。 需要注意的是,隐藏方法需要与方法的重载区别,因此在隐藏“方法”时使用与原方法相同的方法名、类型、变量个数。 使用方法: 在子类型中创建类型、名字与原方法/字段相同的方法/字段,并且前面加关键字 new。

2.虚方法和多态

在实际编程工作时,我们倾向于使用更基本的类作为容器,比如笔类型可能派生出各种类型的笔:中性笔、铅笔等,每只笔有与其他笔相同的地方也有不同的地方,但我们在对每只笔进行操作时,倾向于以更大的单位即笔进行操作,将每种笔实例化为数组的值,这样就可以批量对笔进行集中处理。 因为各种笔的类型不同,但是都继承自大类型笔,这时,我们就可以利用形如 Pen Pen1=new Pencil(); 或 Pen[] Pens =new Pen[]{ new Pencil(), new markPen()}; 即,声明为父类型,但使用子类型的构造方法,来构造同样使用父类型作为容器的类型使用。但是需要注意的是该方法实例化的对象不可使用子类中新增的方法和字段。但是对于父类型和子类型都有的方法,就可以使用虚方法来进行区分。 比如,父类型Pen有方法

1
2
3
4
virtual public penWrite()
{
Console.WriteLine("Pen is Writing");
}

子类型Pencil有方法

1
2
3
4
override public PenWrite()
{
Console.WriteLine("Pencil is Writing");
}

在Main方法中执行以下代码:

1
2
Pen Pen1=new Pencil();
Pen1.PenWrite();

可输出“Pencil is Writing”,由此看来,程序执行了子类型中的函数,而非父类型中的函数,所以可用此方法使用数组分别调用每个子类型的特征。 注意: Ⅰ.重写虚方法必须具有相同的可访问性,且基类方法不能是private Ⅱ.不能重写static方法或者非虚方法 Ⅲ.方法、属性、索引器、事件,都可以声明为virtual或override。 例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Pet
{
public string Name;
public void PrintName()
{
Console.WriteLine(Name + "is speaking");
}
virtual public void Speak()
{
Console.WriteLine(Name + "Pet's Name is " + Name);
}
}

public class Dog : Pet
{
public Dog(string name)
{
Name = name;
}
new public void PrintName()
{
Console.WriteLine("宠物的名字是" + Name);
}
override public void Speak()
{
Console.WriteLine(Name + " is speaking:" + "wow");
}
}

public class Cat : Pet
{
public Cat(string name)
{
Name = name;
}
override public void Speak()
{
Console.WriteLine(Name + " is speaking:" + "meow");
}
}

class Program
{
static void Main(string[] args)
{
Pet[] pets = new Pet[] { new Dog("Jack"), new Cat("Tom") };
for (int i = 0; i<pets.Length; i++)
{
pets[i].Speak();
}
}
}

运行结果:

Jack is speaking:wow
Tom is speaking:meow

③抽象成员

抽象方法

抽象方法(abstract)比虚方法(virtual)更“虚”,它在基类中直接不写函数体,只能在派生类中重写该方法,否则无法被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Pet
{
....
abstract void Speak();//基类中不能写函数体
}
class Cat:Pet
{
...
override void Speak()
{
Console.WriteLine("Meow");
}
}//派生类中使用override重写抽象方法,并添加函数体

class Program
{
public static void Main(string[] args)
{
Cat cat=new Cat();
cat.Speak();
}
}

需要注意的是,不止可以抽象方法,还可以抽象属性、事件、索引。

抽象类

抽象类只有一个存在目的,就是被继承! 抽象类不能实例化,用abstract修饰。 抽象类可以包含抽象成员与普通成员,以及他们的任意组合。 抽象类的抽象成员在派生类中也需要用override关键字实现。 实际上,抽象类指的就是没有普遍性的基类,比如全世界有很多种语言,没有一个统一的语言可以被世界上所有人所共用。此时用基类声明语言,我们明知不会有一个统一的语言,就使用抽象类限制之。

④密闭类和密闭方法

密闭类可以防止其他人通过继承来修改该类,密闭方法可以防止其他人重写该方法。 有同学问,不将基类方法声明为virtual不就不能被重写了? 话是这样没错。但是,在派生类的派生类中的被重写(override)的方法默认还是可以被重写的,为了破除这个限制,我们才引用了密闭方法(sealed)。 密闭方法与密闭类的使用方法是一样的,都是在声明该方法/类之前使用sealed修饰符对其进行限制。

⑤接口

1.定义接口:

接口是声明一组函数成员,但不具体写出的一种引用类型。可以将其看作是一种类(class)。 接口有什么作用呢?接口可以作为许多类的某种共同方法普遍存在,比如猫、狗和蛇都会抓老鼠,我们就可以把抓老鼠设置为一个接口,在声明接口时声明一个数组。这样就可以在“让会抓老鼠的动物抓老鼠”时直接遍历调用接口即可。

1
2
3
4
5
6
7
interface CatchAnimal
{
void CatchMice();//默认为public,且不能添加访问修饰符
void CatchBrid();
void CatchFish();
......
}

接口只能被类实现,实现时,使用”:”,与派生相像,但不是派生。 一个类可以实现多个接口,但只能派生自一个基类。 类在实现接口时,必须实现接口的所有函数成员,且所有函数成员必须添加public访问修饰符。

1
2
3
4
5
6
7
class Cat:Pet,CatchAnimal //“猫”类继承自“宠物”类,且实现了“CatchAnimal”接口
{
public void CatchMice(){...};//必须添加public
public void CatchBrid(){...};
public void CatchFish(){...};
......//“猫”类的内容
}

2.调用接口

1
2
3
4
Cat cat =new Cat();
CatchAnimal catchs= cat;//声明与接口相关联的对象
c.CatchMice()//通过对象调用
catchs.CatchMisce()//通过接口调用

特殊情况:

1
2
3
4
Pet cat =new Cat();
CatchAnimal catchs=(CatchAnimal) cat;//声明与接口相关联的对象,声明时注意强制转换
//c.CatchMice() //此时不可通过对象调用
catchs.CatchMisce()//只能通过接口调用