继承是什么
很多类中有相似的数据,比如在一个游戏中,有Boss类,小怪类Enemy,这些类他们有很多相同的属性,也有不同的,这个时候我们可以使用继承来让这两个类继承自同一个类。
好处就是当我们需要修改某一个相同的函数时候,只需要改父类的函数就可以了
继承的类型
实现继承:
表示一个类型派生于一个基类型,它拥有该基类型的所有成员字段和函数。在实现继承中,派生类型采用基类型的每个函数的实现代码,除非在派生类型的定义中指定重写某个函数的实现代码。在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,这种类型的继承非常有用。
接口继承:
表示一个类型只继承了函数的签名,没有继承任何实现代码。在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。
多重继承:
一些语言(C++)支持所谓的“多重继承”,即一个类派生自多个类。使用多重继承的优点是有争议的:一方面,毫无疑问,可以使用多重继承编写非常复杂、但很紧凑的代码,。另一方面,使用多重实现继承的代码常常很难理解和调试。如前所述,简化健壮代码的编写工作是开发C#的重要设计目标。
因此,C#不支持多重实现继承。而C#允许类型派生自多个接口一一多重接口继承。这说明,C#类可以派生自另一个类和任意多个接口。
更准确地说,System.Object是一个公共的基类,所以每个C#类(除了Object类之)都有一个基类,还可以
有任意多个基接口。
this和base
this指向当前类 base指向父类
this是为了区分当前类的成员和函数里的同名变量
base是为了区分用的父类的成员和子类的成员,比如同名的成员函数,可以区分调用哪一个函数
虚方法Virtual
通过虚方法可以实现多态,重写子类方法(也可以用隐藏方法)
多态
多态(Polymorphism)是面向对象编程中的一个重要概念,指的是同一个操作作用于不同的对象上时,可以表现出不同的行为。在继承关系中,子类可以覆盖父类的方法,实现自己特定的行为,但通过父类的引用指向子类对象时,仍然可以调用父类中定义的方法,这就体现了多态的特性。
在实际编程中,多态可以通过继承和接口实现。在继承中,子类继承父类并重写父类的方法;在接口中,不同的类实现同一个接口,从而可以根据具体的对象调用相同的方法。
关键字
virtual主要用于实现运行时多态性,即能够在运行时根据对象的实际类型来确定调用的方法。而重写则是指子类覆盖了父类中的同名函数。
具体例子
父类(Move虚函数声明)
namespace _08_虚方法
{
class Enemy
{
public virtual void Move()
{
Console.WriteLine("敌人进行移动");
}
}
}
子类(override重写Move函数)
namespace _08_虚方法
{
class Boss:Enemy
{
public override void Move()
{
Console.WriteLine("Boss特有的移动方法");
}
}
}
测试
namespace _08_虚方法
{
internal class Program
{
static void Main(string[] args)
{
Boss b = new Boss();
b.Move();
//调用的是虚函数重写之后的子类方法
Enemy boss = new Boss();//父类声明,子类构造,本质是子类Boss
//因为没有声明子类方法和数据成员,只能调用父类的方法,子类的一些方法不能调用
boss.Move();
//调用的也是虚函数重写后的子类方法
Enemy enemy = new Enemy();
enemy.Move();//父类声明,父类构造,那就跟子类没什么关系了
}
}
}
错误写法(子类声明,父类构造)
Boss b = new Enemy();
//会报错
原因:
- 不符合常理,比如车类不一定是比亚迪
- 用子类声明子类有很多自己的方法和数据成员,父类构造是不完整的构造
隐藏方法
直接在子类重写一个父类的函数
父类
namespace _08_虚方法
{
class Enemy
{
public virtual void Move()
{
Console.WriteLine("敌人进行移动");
}
public void AI()
{
Console.WriteLine("敌人的AI");
}
}
}
子类
namespace _08_虚方法
{
class Boss:Enemy
{
public override void Move()
{
Console.WriteLine("Boss特有的移动方法");
}
public new void AI()
{
Console.WriteLine("Boss子类自己的AI");
}
}
}
测试
Boss boss1 = new Boss();
boss1.AI();
//会调用子类的隐藏方法AI
虚函数和隐藏函数的区别
namespace _08_虚方法
{
internal class Program
{
static void Main(string[] args)
{
Enemy boss = new Boss();//父类声明,子类构造,本质是子类Boss
//因为没有声明子类方法和数据成员,只能调用父类的方法,子类的一些方法不能调用
//如果是子类声明,父类构造,那是错的,第一不符合常理,比如车类不一定是比亚迪
//第二是,子类有很多自己的方法和数据成员,父类构造是不完整的构造
Enemy enemy1 = new Boss();
enemy1.AI();
//与虚方法不同的是,当用父类声明,子类构造时候,
//调用AI方法并不会判断它是否重写了,只会单一调用声明的那个父类的方法
//虚方法判断它是否被子类重写了,从而实现根据对象去选择用哪一个方法,实现了多态
}
}
}
总结
- 父类声明,子类构造,当调用父类函数时
- 虚函数会判断该函数是否被子类重写,从而去选择用什么函数,则子类重写函数,选择子类的函数,没有重写,选择父类的函数,这是多态
- 隐藏函数则只能单一调用父类函数(声明的类),谁声明的就调用谁的
- 所以虚函数这种根据构造使用的类去选择什么函数的,就是面向对象编程的多态
练习
internal class A
{
public virtual void Fun1(int i)
{
Console.WriteLine(i);
}
public void Fun2(A a)
{
a.Fun1(1);
Fun1(5);
}
}
internal class B:A
{
public override void Fun1(int i)
{
base.Fun1(i+1);
}
}
测试
static void Main(string[] args)
{
B b = new B();
A a = new A();
b.Fun2(a);
a.Fun2(b);
}
总结
在测试中,先用B类构造,B类声明,用对象b调用函数Fun2,参数是a对象,在Fun2中,先调用了a对象的Fun1函数,因为a对象是A类声明,A类构造,跟B类没关系,所以调用的是A类的Fun1,所以先打印出1,然后是Fun1(5);就是调用当前对象的Fun1函数,因为B类构造的,已经把Fun1函数重写了,就打印出5+1=6
a.Fun2(b)调用a对象的Fun2函数,参数是b子类对象,相对于A a = b,父类声明,子类构造,a.Fun1(1)调用子类重写的Fun1,虚函数的多态,子类函数里再把参数加1调用父类Fun1,结果是2,然后Fun1(5)调用当前对象a的Fun1函数就是5(A类构造的,所以Fun1没有重写)