面向对象---继承

143 阅读6分钟

继承是什么

很多类中有相似的数据,比如在一个游戏中,有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();
//会报错

原因

  1. 不符合常理,比如车类不一定是比亚迪
  2. 用子类声明子类有很多自己的方法和数据成员,父类构造是不完整的构造

隐藏方法

直接在子类重写一个父类的函数

父类

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方法并不会判断它是否重写了,只会单一调用声明的那个父类的方法
            //虚方法判断它是否被子类重写了,从而实现根据对象去选择用哪一个方法,实现了多态
        }
    }
}

总结

  1. 父类声明,子类构造,当调用父类函数时
  2. 虚函数会判断该函数是否被子类重写,从而去选择用什么函数,则子类重写函数,选择子类的函数,没有重写,选择父类的函数,这是多态
  3. 隐藏函数则只能单一调用父类函数(声明的类),谁声明的就调用谁的
  4. 所以虚函数这种根据构造使用的类去选择什么函数的,就是面向对象编程的多态

练习

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没有重写)