【C#学习】继承和多态

涉及到的关键字

  1. virtual 基类里声明虚方法
  2. override 派生类重写基类的虚方法,是多态的关键技术
  3. new 派生类覆盖基类的方法(无论是否为虚方法)
  4. base 派生类显式调用基类的非默认构造函数
  5. abstract 声明抽象类或抽象方法 
  6. sealed 声明密封类和密封方法

基础

C# 不支持多重继承,一个类只能有一个直接基类。重写、覆盖的问题都来自于基类的引用来引用派生类实例的情况。

继承、封装和多态是面向对象编程的三个基本要素。
其成员被继承的类叫基类也称父类,继承其成员的类叫派生类也称子类。

派生类隐式获得基类的除构造函数和析构函数以外的所有成员。
派生类只能有一个直接基类,所以C#并不支持多重继承但一个基类可以有多个直接派生类。
继承是可以传递的
即:
如果 ClassB 派生出 ClassC,ClassA 派生出 ClassB,则 ClassC 会继承 ClassB 和 ClassA 中声明的成员。
复制代码
  class  A
    {
        
public   void  Sum( int  i, int  j)
        {
            
int  sum  =  i  +  j;
            Console.WriteLine(
" I am A ,my sum ={0} " ,sum);
        }
    }
    
class  B : A
    {
        
public   void  Minus( int  i, int  j)
        {
            
int  minus  =  i  -  j;
            Console.WriteLine(
" I am B ,my minus ={0} " , minus);
            this.Sum(3, 4);
        }

    }
     class  InheritanceTest1
    {
        
static   void  Main( string [] args)
        {
            B b 
=   new  B();
            b.Minus(
3 4 );
            Console.Read();
        }
    }

结果:I am B ,my minus=-1 

     I am A ,my sum =  7
复制代码
试想一下,当基类Sum()方法是私有时,派生类还会继承该方法吗?
经过本人测试,没有在B类找到该方法,那么是不是它就没有被继承呢? 其实不是的,私有成员其实已经被继承了,
但是它们却不可以被访问,因为私有成员只能被声明它们的类或结构体中才可访问,所以看上去像是没有被继承。

如果我们想降低访问基本,我们可以把基类Sum()方法定义为protected。
能够阻止 某个类被其他类继承吗?
答案是可以的,C#提供了一个 sealed  修饰符 此修饰符会阻止其他类从该类继承。
复制代码
    sealed   class  A
    {
        
int  test;
        
public   void  Sum( int  i, int  j)
        {
            
int  sum  =  i  +  j;
            Console.WriteLine(
" I am A ,my sum ={0} " ,sum);
        }
    }
    
class  B : A 
    {
        
public   void  Minus( int  i, int  j)
        {
            
int  minus  =  i  -  j;
            Console.WriteLine(
" I am B ,my minus ={0} " , minus);
          
   this .Sum( 3 4 );        //编译器会报错     
        }
     }
复制代码

 前面说过,派生类隐式获得基类的除构造函数和析构函数以外的所有成员。
 那么我们该如何获得基类的构造函数和自身的构造函数呢?
 我们知道基类的初始化工作由基类的构造函数完成,派生类的初始化工作则有派生类的构造函数完成
但是这样就产生了派生类构造函数的执行顺序问题
当基类没有构造函数,派生类也没有构造函数时,派生类新曾成员的初始化工作由其他公有函数来完成。

复制代码
  public    class  A
    {
        
int  test = 0 ;
        
public   void  sum()
        {
            test
++ ;
            Console.WriteLine(
" I am test ={0} "  ,test);
        }

    }
    
class  B : A 
    {
        
int  i;
        
public   void  PrintInt()
        {
            i 
=   3 ;
            Console.WriteLine(
" I am i ={0} " , i);
        }
    }
    
class  InheritanceTest1 
    {
        
static   void  Main( string [] args)
        {
            B b 
=   new  B();
            b.PrintInt();
            Console.Read();
        }
    }

结果:I am i=3 

复制代码

 

如果只有派生类定义构造函数时,只需构造派生类对象即可。对象的基类部分使用默认构造函数来自动创建。
当基类和派生类都定义有构造函数时,那么执行顺序会怎样呢?
如果基类中是没有参数的构造函数
如果基类中是没有参数的构造函数,在派生类中可以自定义有参数的构造函数
复制代码
public    class  A
    {
        
int  test = 0 ;
         public  A()
        {
            test 
=   5 ;
            Console.WriteLine(
" I am A 公有默认构造函数 ,test={0} " , test);
        }

    }
    
class  B : A 
    {

    }
    
class  InheritanceTest1 
    {
        
static   void  Main( string [] args)
        {
            B b 
=   new  B();
            Console.Read();
        }
    }

结果:I am A 公有默认构造函数 ,test=5

复制代码
由此可以看见,基类的构造函数被执行,在派生类中被调用。
如果基类定义了带有参数的构造函数,那么此构造函数必须被执行,且在派生类中实现该构造函数,此时我们可以使用base关键字
复制代码
      class  A
    {
        
int  test = 0 ;
         public  A( int  i)
        {
            test 
=  i;
            Console.WriteLine(
" I am A 公有有参构造函数 ,test={0} " , test);
        }
    }
    
class  B : A 
    {

         public  B( int  j): base (j)
        {
            Console.WriteLine(
" I am B 公有有参构造函数,j={0} " ,j);
        }
    }
    
class  InheritanceTest1 
    {
        
static   void  Main( string [] args)
        {
            B b 
=   new  B( 1 );
            Console.Read();
        }
    }

结果:I am A 公有有参构造函数 ,test=1     

     I am B 公有有参构造函数,j=1

 

复制代码
由此可见:  派生类隐式执行基类中带有参数的构造函数,在程序中基类定义了带有参数的构造函数,
在其派生类中被继承,并使用 base 关键字调用基类中的构造函数来传送参数。
我们可以从代码中看到在创建派生类的对象后,程序首先运行的是基类的构造函数中的内容,然后才是派生类中的内容。
如果派生类的基类也是派生类,则每个派生类只需负责其直接基类的构造,不负责间接基类的构造
并且其执行构造函数的顺序是从最上面的基类开始的,直到最后一个派生类结束。

方法重写(override)

用关键字 virtual 修饰的方法,叫虚方法。可以在子类中用override声明同名的方法,这叫“重写”。相应的没有用virtual修饰的方法,叫做实方法。override修饰符和virtual修饰符成对出现。方法重写可以实现动态多态性
重写会改变父类方法的功能。
看下面演示代码:
#region 重写

public class C1
{
    public virtual string GetName() // 虚方法
    {
        return "徐明祥";
    }
}

public class C2 : C1
{
    public override string GetName() // 方法重写
    {
        return "xumingxiang";
    }
}

 C1 c1 = new C1();
 Console.WriteLine(c1.GetName());//输出“徐明祥”

 C2 c2 = new C2();
 Console.WriteLine(c2.GetName());//输出“xumingxiang”
 //重点看这里
 C1 c3 = new C2();
 Console.WriteLine(c3.GetName());//输出“xumingxiang”

#endregion


方法覆盖/隐藏(new)


在子类中用 new 关键字修饰 定义的与父类中同名的方法,叫覆盖。C#建议使用new关键字明确方法覆盖,也可以不写new。
覆盖不会改变父类方法的功能。
看下面演示代码:
#region 覆盖

public class C1
{
    public string GetName()  // 实方法
    {
        return "徐明祥";
    }
}

public class C2 : C1
{
    public new string GetName() // 方法覆盖
    {
        return "xumingxiang";
    }
}

C1 c1 = new C1();
Console.WriteLine(c1.GetName());//输出“徐明祥”

C2 c2 = new C2();
Console.WriteLine(c2.GetName());//输出“xumingxiang”
//重点看这里,和上面的重写作比较
C1 c3 = new C2();
Console.WriteLine(c3.GetName());//输出“徐明祥”

#endregion


覆盖与重写的区别


1:不管是重写还是覆盖都不会影响父类自身的功能(废话,肯定的嘛,除非代码被改)。
2:当用子类创建父类的时候,如 C1 c3 = new C2(),重写会改变父类的功能,即调用子类的功能;而覆盖不会,仍然调用父类功能。
3:虚方法、实方法都可以被覆盖(new),抽象方法,接口 不可以。
4:抽象方法,接口,标记为virtual的方法可以被重写(override),实方法不可以。
5:重写使用的频率比较高,实现多态;覆盖用的频率比较低,用于对以前无法修改的类进行继承的时候。

构造函数和析构函数的调用顺序

构造函数和析构函数的调用顺序相反,构造函数从基类到派生类依次调用,析构函数反过来。

抽象类和抽象方法

抽象方法必须包含在抽象类中,也就是说,一旦一个类包含抽象方法,就应该被声明为抽象类。
  • 抽象类用 abstract 修饰,无法用new 来实例化。但可以用抽象类的引用来引用派生类对象。
  • 抽象方法 用 abstract 修饰,抽象方法没有实现代码,但是此抽象类的派生必须重写
  • 如果抽象类的派生类是非抽象的,则必须重写(用override修饰)基类的所有抽象方法
  • 如果抽象类的派生类是抽象的,则必须重写(用override修饰)基类的所有抽象方法,且重写方法要么提供具体实现,要么也是抽象的(同时用override和abstract修饰
也就是说,无论派生类是不是抽象的,都必须重写(override)基类的抽象方法,如果没提供实现,还得用abstract修饰。


密封类和密封方法

被sealed修饰的类无法被继承,被sealed修饰的方法(必须是重写方法)无法被重写(override)。sealed在修饰方法时必定和override一起( 注意,看似没有继承基类的类其实也继承了Object类,所以每个类都可以有override方法,即Object中定义的virtual方法)。由于sealed类无法被继承,所以sealed类的方法也肯定不会被重写,所以sealed修饰方法只在非sealed类才有意义。

        // base class 
        internal class Student
        {
            // name field
            private string name; 
            public string Name
            {
                get { return this.name; }
            }
            // id field
            private int id;
            public int Id
            {
                get { return this.id; }
            }
            
            public Student(string n, int i)
            {
                name = n;
                id = i; 
            }

            // override ToString() method
            public override string ToString()
            {
                return string.Format("name = {0}, id = {1}", name ,id);
            }

            // sealed Equals() method
            public sealed override bool Equals(object obj)
            {
                if(obj is Student && ((Student)obj).Id == this.Id)
                    return true;
                else 
                    return false;
            }
        }

        // Graduate inherits Student 
        internal class Graduate: Student 
        {
            private int age; 
            public int Age
            {
                get { return age; }
            }

            public Graduate(string name, int id, int age) : base(name, id)
            {
                this.age = age; 
            }

            public override string ToString()
            {
                return string.Format("name = {0}, id = {1}, age = {2}", Name, Id, age);
            }

            //public override bool Equals(object obj) // 不能重写sealed方法
            //{

            //}
        }

        public static void Main(String[] args)
        {
            Student s1 = new Student("Rick", 1); 
            Console.WriteLine(s1); // name = Rick, id = 1
            Student s2 = new Graduate("Morty", 2, 17);  
            Console.WriteLine(s2); // name = Morty, id = 2, age = 17 
            Console.WriteLine(s1.Equals(s2));  //false 
        }


你可能感兴趣的:(C#)