C#中级教程学习笔记003-继承

本文深入探讨C#中的继承、多重继承、虚方法、隐藏方法、this和base关键字,抽象类与密封类,以及接口的使用,包括构造函数和修饰符的应用实例。通过实例展示如何利用这些概念进行高效编程和设计.

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

3. 继承

3.1 继承

3.2 多重继承

3.3 实现继承

3.5 Example: 继承

3.6 虚方法

3.7 隐藏方法

3.8 this和base关键字

3.9 抽象类

3.10 sealed密封类和密封方法

3.11 派生类的构造函数

3.12 修饰符

3.13 接口


3. 继承

        很多类中有相似的数据,比如在一个游戏中,有Boss类,小怪类Enemy,这些类他们有很多相同的属性,也有不同的,这个时候我们可以使用继承来让这两个类继承自同一个类。

3.1 继承

        实现继承:

               表示一个类型派生于一个基类型,它拥有该基类型的所有成员字段和函数。

        在实现继承中,派生类型采用基类型的每个函数的实现代码,除非在派生类型的定义中指定重写某个函数的实现代码。

        在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,这种类型的继承非常有用。

        接口继承:

               表示一个类型只继承了函数的签名,没有继承任何实现代码。

        在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。

3.2 多重继承

        一些语言(如C++)支持所谓的 “多重继承”,即一个类派生自多个类。

        使用多重继承的优点是有争议的:

                一方面,毫无疑问,可以使用多重继承编写非常复杂、 但很紧凑的代码。

                另一方面,使用多重实现继承的代码常常很难理解和调试。

        C#不支持多重实现继承。

        C#允许类型派生自多个接口— — 多重接口继承。

        这说明,C#类可以派生自另一个类和任意多个接口。

        更准确地说,System.Object 是一个公共的基类,所以每个 C#(除了Object类之外)都有一个基类,还可以有任意多个基接口。

3.3 实现继承

        如果要声明派生自另一个类的一个类,就可以使用下面的语法:

        class MyDerivedClass : MyBaseclass

        {

                // functions and data members here

        }

        如果类(或 结构)也 派生 自接口,则用逗号分隔列表中的基类和接口:

        public class MyDerivedClass: MyBaseClass , IInterface1 , IInterface2

        {

                // etc.

        }

3.5 Example: 继承

        基类敌人类( hp speed 方法 ai move )。

        派生出来的类:

               boss类

               type1enemy类

               type2enemy类

 

3.5.1 Enemy.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_1
{
    class Enemy
    {
        private float hp;
        private float speed;

        public float HP
        {
            get { return hp; }
            set { hp = value; }
        }

        public float Speed
        {
            get { return speed; }
            set { speed = value; }
        }

        public void AI()
        {
            Console.WriteLine("Enemy 的 AI 方法");
        }

        public void Move()
        {
            Console.WriteLine("Enemy 的 Move 方法");
        }
    }
}

3.5.2 Boss.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_1
{
    class Boss : Enemy  // 继承自 Enemy
    {
        public void Attack()
        {
            this.AI();
            this.Move();
            // this.hp = 100;  // 父类的私有成员和函数,不能在子类中访问
            this.HP = 100;
            Console.WriteLine("Boss 的 Attack 方法");
        }
    }
}

3.5.3 Type1Enemy.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_1
{
    class Type1Enemy : Enemy
    {
    }
}

3.5.4 Type2Enemy.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_1
{
    class Type2Enemy : Enemy
    {
    }
}

3.5.5 Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 继承

namespace Lesson_3_1
{
    class Program
    {
        static void Main(string[] args)
        {
            Boss boss = new Boss();
            // boss.AI();  // 继承:父类里面所有的数据成员和函数成员都会继承到子类里面
            boss.Attack();
            Console.WriteLine("----------------------- 分割 1");

            Enemy enemy = new Boss();  // 父类Enemy声明的对象enemy,可以使用子类去构造new Boss()
            // Boss boss2 = new Enemy();  // 子类声明的对象,不能用父类去构造,因为会缺少子类特有的数据,所以不允许
            
            // enemy虽然是使用父类进行了声明,但实际是指向子类构造的对象,
            //     所以本质上是一个子类类型的,我们可以通过强制转换转换为子类类型
            Boss boss3 = (Boss)enemy;
            boss3.Attack();
            Console.WriteLine("----------------------- 分割 2");

            Enemy enemy4 = new Enemy();
            // 一个对象是什么类型,主要是看它是通过什么类型去构造的
            // 这里enemy4使用了父类构造的,只有父类的数据,所以不能强制转换成子类类型
            // Boss boss4 = (Boss)enemy4;  


            Console.ReadKey();
        }
    }
}

3.6 虚方法

        把一个基类函数声明为virtual,就可以在任何派生类中重写该函数:

        class MyBaseClass{

              public virtual string VirtualMethod(){

                     return "Method is called in base class";

              }

        }

        在派生类中重写另外一个函数时,要使用override关键字显示声明

        class MyDerivedClass:MyBaseClass{  

              public override string VirtualMethod(){

                     return "Method is called in derivedclass.";

              }

        }

3.6.1 Example:虚方法

3.6.1.1 Enemy.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_2
{
    class Enemy
    {
        public virtual void Say()
        {
            Console.WriteLine("Enemy Say");
        }

        public virtual void Attack()
        {
            Console.WriteLine("Enemy Attack");
        }
    }
}

3.6.1.2 Boss.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_2
{
    class Boss : Enemy
    {
        // 使用 override,这里是重写了Say
        public override void Say()
        {
            Console.WriteLine("Boss Say");
        }

        // 没有使用 override,这里是覆盖了Attack
        public void Attack()
        {
            Console.WriteLine("Boss Attack");
        }
    }
}

3.6.1.3 Program.cs

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_2
{
    class Program
    {
        static void Main(string[] args)
        {
            Enemy enemy = new Boss();  // 声明的父类变量,使用子类构造创建
            enemy.Attack();  // 在Boss中没有使用override的重写Attack,这里调用的是:父类Enemy的方法
            enemy.Say();     // 在Boss中使用override的重写Say,这里调用的是:子类Boss的方法

            Console.ReadKey();
        }
    }
} 

 

3.7 隐藏方法

        如果签名相同的方法在基类和派生类中都进行了声明,但是该方法没有分别声明为virtual和override,派生类就会隐藏基类方法。(要使用new关键字进行声明)

        基类

        class MyBaseClass{

                public int MyMethod(){

                }

        }

        派生类(在派生类中把基类同名的方法隐藏掉了)

        class MyDerivedClass :MyBaseClass{

               public new void MyMethod()        {

               }   

        }

3.7.1 Example: 隐藏

3.7.1.1 Enemy.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_3
{
    class Enemy
    {
        // 使用 virtual
        public virtual void Attack()
        {
            Console.WriteLine("Enemy Attack");
        }

        // 使用 virtual
        public virtual void Run()
        {
            Console.WriteLine("Enemy Run");
        }

        // 没有使用 virtual
        public void Walk()
        {
            Console.WriteLine("Enemy Walk");
        }

        // 没有使用 virtual
        public void Say()
        {
            Console.WriteLine("Enemy Say");
        }
    }
}

3.7.1.2 Boss.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_3
{
    class Boss : Enemy
    {
        // 使用 override
        public override void Attack()
        {
            Console.WriteLine("Boss Attack");
        }

        // 没有使用 virtual
        public void Run()
        {
            Console.WriteLine("Boss Run");
        }

        // 使用 new
        public new void Walk()
        {
            Console.WriteLine("Boss Walk");
        }

        // 没有使用 new
        public void Say()
        {
            Console.WriteLine("Boss Say");
        }
    }
}

3.7.3 Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 隐藏方法

namespace Lesson_3_3
{
    class Program
    {
        static void Main(string[] args)
        {
            Enemy enemy = new Boss();
            enemy.Attack();  // 调用的是子类的方法,父类使用了virtual,并且子类使用了override,需要两个同时满足,才会是重写,才会调用的是子类的
            enemy.Run();     // 调用的是父类的方法
            enemy.Walk();    // 调用的是父类的方法
            enemy.Say();     // 调用的是父类的方法,可以发现,声明函数时,可以不写new,都是覆盖父类方法         
        }
    }
}

3.8 this和base关键字

        this可以访问当前类中定义的字段,属性和方法,有没有this都可以访问,有this可以让IDE-VS编译器给出提示,另外当方法的参数跟字段重名的时候,使用this可以表明访问的是类中的字段

        base可以调用父类中的公有方法和字段,有没有base都可以访问,但是加上base.IED工具会给出提示,把所有可以调用的字段和方法罗列出来方便选择

 

3.8.1 Example:this和base

3.8.1.1 Enemy.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_3
{
    class Enemy
    {
        // 使用 virtual
        public virtual void Attack()
        {
            Console.WriteLine("Enemy Attack");
        }

        // 使用 virtual
        public virtual void Run()
        {
            Console.WriteLine("Enemy Run");
        }

        // 没有使用 virtual
        public void Walk()
        {
            Console.WriteLine("Enemy Walk");
        }

        // 没有使用 virtual
        public void Say()
        {
            Console.WriteLine("Enemy Say");
        }
    }
}

3.8.1.2 Boss.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_3
{
    class Boss : Enemy
    {
        public float mp;
        public void SetMp(float mp)
        {
            this.mp = mp;  // 使用this区分mp,this.mp表示是本类中的mp变量
        }

        // 使用 override
        public override void Attack()
        {
            base.Attack();  // 使用base访问父类中的方法
            this.Walk();    // 使用this访问本类中的方法
            Console.WriteLine("Boss Attack");
        }

        // 没有使用 virtual
        public void Run()
        {
            Console.WriteLine("Boss Run");
        }

        // 使用 new
        public new void Walk()
        {
            Console.WriteLine("Boss Walk");
        }

        // 没有使用 new
        public void Say()
        {
            Console.WriteLine("Boss Say");
        }
    }
}

3.8.1.3 Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 隐藏方法

namespace Lesson_3_3
{
    class Program
    {
        static void Main(string[] args)
        {
            Enemy enemy = new Boss();
            enemy.Attack();  // 调用的是子类的方法,父类使用了virtual,并且子类使用了override,需要两个同时满足,才会是重写,才会调用的是子类的
            enemy.Run();     // 调用的是父类的方法
            enemy.Walk();    // 调用的是父类的方法
            enemy.Say();     // 调用的是父类的方法,可以发现,声明函数时,可以不写new,都是覆盖父类方法         
        }
    }
}

运行结果:

 

3.9 抽象类

        C#允许把类和函数声明为 abstract。

        抽象类不能实例化

        抽象类可以包含普通函数和抽象函数。

        抽象函数就是只有函数定义没有函数体。

        抽象函数本身也是虚拟的Virtual(只有函数定义,没有函数体实现)。

        在子类(非抽象)继承抽象类时,子类必须实现所有的抽象方法。

        类是一个模板,那么抽象类就是一个不完整的模板,我们不能使用不完整的模板去构造对象。

        abstract class Building{

               public abstract decimal CalculateHeatingCost();

        }

3.9.1 Example:抽象类

3.9.1.1 AbsBird.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_4
{
    // 使用 abstract 声明一个抽象类
    abstract class AbsBird
    {
        private int speed = 10;
        public int Speed
        {
            get { return speed; }
            set { speed = value > 0 ? value : 0; }
        }

        public abstract void Fly();  // 抽象函数,不能有函数体,即{ }
    }
}

3.9.1.2 Crow.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_4
{
    // 继承自 抽象类
    class Crow : AbsBird
    {
        // 子类中,必须将抽象方法实现
        public override void Fly()
        {
            Console.WriteLine("Crow Fly, speed = {0}", this.Speed);
        }
    }
}

3.9.3 Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 抽象类

namespace Lesson_3_4
{
    class Program
    {
        static void Main(string[] args)
        {
            // AbsBird bird = new AbsBird();  // 不能实例化抽象类
            AbsBird bird = new Crow();  // 可以将子类实例化对象,赋值给抽象类变量
            bird.Fly();  // 抽象方法一定是virtual的,Crow类中override重写了Fly,所以这里实际调用的是Crow(子类)中的Fly方法

            Console.ReadKey();
        }
    }
}

运行结果:

 

3.10 sealed密封类和密封方法

        C#允许把类和方法声明为 sealed。

        对于类,使用sealed,这表示不能继承该类。

        对于方法,使用sealed,表示不能重写该方法。必须是子类重写(override)父类方法时,才能使用sealed进行声明。

        sealed class SealedClass

        {

        }

   

        class Crow : BaseClass

        {

                 // 必须是子类重写父类方法时,才能使用sealed

                public sealed override void Fly()

                {

                }

        }

3.11 派生类的构造函数

        构造函数(会先调用父类的,然后是子类的)。

        1. 在子类中调用父类的默认构造函数(无参)

       public class MyDerivedClass{

              public MyDerivedClass():base(){

                     //do something

              }

       }

       在这里 :base()可以直接不写,因为默认会调用父类中的默认构造函数。

 

        2. 调用有参数的构造函数

       public class MyDerivedClass{

              public MyDerivedClass(string name):base(name){

                     //do something

              }

       }

3.11.1 Example: 派生类构造函数

3.11.1.1 Base.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_5
{
    class Base
    {
        public Base()
        {
            Console.WriteLine("Base no params");
        }

        public Base(string name)
        {
            Console.WriteLine("Base name = {0}", name);
        }
    }
}

3.11.1.2 Derived.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_5
{
    class Derived : Base
    {
        public Derived()
        : base()  // 调用父类无参构造函数
        {
            Console.WriteLine("Derived no params");
        }

        public Derived(int hp)
        // : base()  // 当我们在子类中没有显示调用父类的构造函数时,会默认自动调用父类的无参构造函数
        {
            Console.WriteLine("Derived hp = {0}", hp);
        }

        public Derived(string name)
            : base(name)  // 调用父类的有参构造函数
        {
            Console.WriteLine("Derived name = {0}", name);
        }
    }
}

3.11.1.3 Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 派生类构造函数

namespace Lesson_3_5
{
    class Program
    {
        static void Main(string[] args)
        {
            Derived dd = new Derived();

            Derived dd2 = new Derived(10);

            Derived dd3 = new Derived("张三");

            Console.ReadKey();
        }
    }
}

运行结果:

 

3.12 修饰符

3.12.1 修饰符

        修饰符,用来修饰类型或者成员的关键字。

        修饰符可以指定方法的可见性。

 

        public 和private修饰字段和方法的时候,表示该字段或者方法能不能通过对象去访问,只有public的才可以通过对象访问,private(私有的)只能在类模板内部访问。

        protected 保护的,当没有继承的时候,它的作用和private是一样的,当有继承的时候,protected表示可以被子类访问的字段或者方法。

3.12.2 类的修饰符

        public class PubClass { }

        class MyClass { }

        前者(PubClass)可以在别的项目下访问,后者不行。

        比如,假设有两个项目A,B。

                在项目A中,有一个public class APubClass,有一个class AMyClass,有一个class AMmClass。

                那么,在项目A中,可以自由的使用APubClass、AMyClass、AMmClass类型来创建对象。

                在项目B中,首先需要引入项目A;之后,在需要使用的C#文件里面增加:using A;然后,在项目B中,只能使用项目A的APubClass类型来创建实例。其他的两个类型不能在项目B中被使用,因为AMyClass、AMmClass这两个类型,不是是public的。

3.12.3 其他修饰符

 

        static可以修饰字段或者方法。修饰字段的时候,表示这个字段是静态的数据,叫做静态字段或者静态属性。修饰方法的时候,叫做静态方法,或者静态函数。

        使用static修饰的成员,只能通过类名访问,不能通过对象访问(静态成员可以看出是共享成员,不属于单个对象,所以不能通过对象访问。需要通过类名访问。)。

        当我们构造对象的时候,对象中只包含了普通的字段,不包含静态字段。

        静态方法,可以访问静态变量,不能访问非静态变量。

        普通方法,可以方法静态变量(不可以使用this.xxx,可以使用类名class.xxx),也可以访问非静态变量。

3.12.4 Example: 静态变量和静态方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 静态变量和静态方法

namespace Lesson_3_6
{
    class TestClass
    {
        public static int y;  // 静态变量
        private int x;

        public static void WriteStatic()
        {
            Console.WriteLine("static function y = " + y);
            // 静态方法,属于类,不属于某个对象,所以没有this,也不能访问非静态变量
            // Console.WriteLine("x = " + x);  // Console.WriteLine("x = " + this.x);
        }

        public void WriteA()
        {
            // 非静态方法,可以访问静态变量,但不能使用this.xxx方式访问静态变量,因为静态变量属于类不属于某个对象
            Console.WriteLine("y = " + y);
            Console.WriteLine("TestClass.y = " + TestClass.y);

            Console.WriteLine("x = " + this.x);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            TestClass.y = 100;  // 通过类名,给静态变量赋值
            TestClass.WriteStatic();  // 通过类名,调用静态方法

            TestClass tc = new TestClass();
            tc.WriteA();

            TestClass.y = 200;
            tc.WriteA();  // 静态变量发生改变时,对象访问它时候也是最新值。
            // 可以静态变量还可以理解为,所有对象共用的属性。比如:人有很多(即每个人是一个对象),但大家共用一个地球。

            Console.ReadKey();
        }
    }
}

运行结果:

 

3.13 接口

3.13.1 定义和实现接口

        定义一个接口在语法上跟定义一个抽象类完全相同,但不允许提供接口中任何成员的实现方式。

        一般情况下,接口只能包含方法,属性,索引器和事件的声明

        接口不能有构造函数,也不能有字段,接口也不允许运算符重载。

        接口定义中不允许声明成员的修饰符,接口成员都是公有的。

        一般,声明一个接口,以大写的I开头,表示是一个接口类。

        接口不能被实例化;

        继承接口的类,必须实现接口中的所有的声明接口。

        定义接口(飞翔功能)

           public interface IHandler{

                  public void Test();

           }

    实现接口

           public class MyClass : IHandler {

       }

    类,可以继承多个接口:

        public class MyClass : IHandler, IHandler2, IHandler3 {

       }

 

3.13.4 派生的接口

        接口,可以继承接口。

        public interface IHandler {

        }

        public interface IDerived : IHandler {  // 接口继承接口

        }

 

3.13.4 Example:接口

3.13.4.1 IFace.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson_3_7
{
    // 使用 interface声明接口
    interface IFace
{
// int a;  // 接口不能定义变量
    // void Func() {}  // 接口中的方法不能实现

        // public void Method();  // 不能有修饰符public
        void Method();  // 默认所有都是public
    }

    interface IFace2 : IFace  // 接口继承接口
    {
        void Method2();
    }

    interface IFace3  // 接口继承接口
    {
        void Method3();
    }
}

3.13.4.2 Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 接口

namespace Lesson_3_7
{
    class TestClass : IFace2, IFace3
    {
        // 接口中的声明,必须全部实现,并且为public权限
        public void Method() { Console.WriteLine("Method ----------"); }
        public void Method2() { Console.WriteLine("Method2 ----------"); }
        public void Method3() { Console.WriteLine("Method3 ----------"); }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IFace fc = new TestClass();
            fc.Method();
            Console.WriteLine("----------------------------------------- 分割");

            IFace2 fc2 = new TestClass();
            fc2.Method();
            fc2.Method2();
            Console.WriteLine("----------------------------------------- 分割");

            IFace3 fc3 = new TestClass();
            fc3.Method3();
            Console.WriteLine("----------------------------------------- 分割");

            TestClass tc = (TestClass)fc3;
            tc.Method();
            tc.Method2();
            tc.Method3();
            Console.WriteLine("----------------------------------------- 分割");

            Console.ReadKey();
        }
    }
}

运行结果:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

公众号:程序喵星人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值