题外话:因为类六有关索引器的内容比较复杂,我想精心准备出经典实例和通俗易懂的讲解内容再发表博客,所以我先跳过索引器,接着学习继承。
         本节课我们来学习一下类的继承,在面向对象二中我已经讲述了继承的概念、优点,但是没有举出具体的代码实例讲解,在这篇博客中,不会再次细致的讲解继承的原理,建议同学们最好是先阅读面向对象二系列后,再学这篇博客,我们现在来回忆一下继承的概念。
继承

       继承是面向对象理论中最重要的一项机制,通过继承一个现有的类,新创建的类可以不需要再写任何代码,就可以按照继承来的父类中的合适的访问权限直接拥有所继承的类的功能,同时还可以创建自己的专门功能。 就向我在面向对象二中提到的,继承的优点就是:当类与类中有共同的类成员时,完全可以提炼出共同的类成员,单独的编写一个类,让其他的只要想包含这个类中共同的类成员的类来继承我们单独编写的类,使得代码可以共享,避免重复。
      单独编写的类我们习惯的称为父类,或基类,而继承这个父类的类我们称为子类或派生类。要想使用继承,就必须注意到,当两个类A和B之间具备“B is A”就关系时,就可以用到继承,那么B就可以说是A的子类或派生类,A是B的父类或基类。我们不能把猴子和人看成存在继承关系的两个类,但是如果现在还有一个动物类,那么猴子是动物,人也是动物,就可以把动物作为人和猴子的父类。
     于此同时,当我们要修改类中共同的成员时,只需要修改父类中的共同类成员,就能达到修改继承这个父类的子类们的目的,而如果我们想单独的修改子类中的类成员,是不会影响到父类的,更不会影响到从父类中继承的其他子类们,这说明了 继承关系只会向下传递,子类本身的调整并不会影响基类。
       请同学们注意,我说的子类们,说明一个父类可以有多个子类,但是一个子类只可以继承一个父类(和多个接口,接口我会在下一篇博客中讲解),以前听课的时候,每讲到一个子类只能有一个父类时,也就是说一个儿子只能有一个爸爸时,学生们就会捣乱,所以我也要养成一种这样描述的习惯:就是“一个儿子类只能有一个含DNA的亲生父亲类”,相信这样说应该不会有什么疏漏了,DNA就是他们共同的类成员,当然这个DNA也不可能完成一致,否则就是一个人了,也就没有我们创建子类的目的了,在程序继承中我们把从父类中继承下来的用非private修饰,父亲所独有的用private修饰,同时就算儿子从父亲那继承下了的,我们也可以重新的再次赋值。
         下面我们来看看继承的基本语法:
          class SonClassName:BaseClassName
                {
                }
         其中的SonClassName为新创建的子类,BaseClassName为基类,符号“:”定义了两个类的继承关系,也就是SonClassName类继承自BaseClassName类,通过继承,子类可以访问到基类中的不法成员,基类可以借助使用访问修饰符来有条件的开放基类的功能,供派生类继承。SonClassName类包含了BaseClassName类中所有非private修饰的类成员,这里所提到的private指的是访问修饰符,因为 访问修饰符直接关系到继承结构的限制,虽然面向对象二中也有具体的讲解,这里我们还是再重温一下:
           public修饰符修饰的A类类成员,所有的非A类的类的方法中,都可以通过A类的类名(静态的A类成员)或A类的对象a(实例的A类成员)来访问。
           protected修饰符修饰的A类类成员,在所有的A类的子类的方法中,都可以直接的访问到,不需要引用过程,而在所有的非A类的类的方法中,都无法访问。
           private修饰符修饰的A类类成员,在所有的A类的类成员中,都可以直接的访问到(除嵌套类),嵌套类可以通过A类的类名(静态的A类成员)或A类的对象a(实例的A类成员)来访问,而在所有的非A类的类的方法中,都无法访问。
          可以看出,修饰符的权限逐步缩小,在编写继承类代码时,如果父类中的成员,不需要外部类来访问,为了包含父类的成员,尽量避免public修饰符的乱用,而是应该采用protected修饰符,会更加的安全、合理。
            我们来看下面的实例,实例中包括四个类,一个包括入口方法的Program类,一个GFClass、一个FClass、一个SClass。在Program类的Main方法中执行对SClass的实例化,创建son对象,我们来看看继承的访问权限,特别是在创建son对象时构造方法的方法继承顺序。
继承实例
InBlock.gif    1namespace hello
InBlock.gif    2{
InBlock.gif    3         //创建一个爷爷类GFClass
InBlock.gif    4         class GFClass
InBlock.gif    5        {
InBlock.gif    6                 //定义一个私有成员MyMoney,私有字段是无法被继承的
InBlock.gif    7                 int MyMoney = 5;
InBlock.gif    8
InBlock.gif    9                 //定义两个受保护的字段,受保护的字段允许子类访问到    
InBlock.gif 10                 protected string GFhouse = "一套GFHouse房子";
InBlock.gif 11                 protected int GFMoney = 10;
InBlock.gif 12
InBlock.gif 13                 //定义爷爷类的构造函数,这样在创建爸爸类和儿子类的对象时,都会先执行所以父类以上的构造方法
InBlock.gif 14                 public GFClass()
InBlock.gif 15                {
InBlock.gif 16                         //显示出私有字段的值
InBlock.gif 17                        Console.WriteLine( "我是爷爷!我自己留点养老钱:{0}万,这是我自己的钱,私有财产呀!", MyMoney);
InBlock.gif 18                        Console.WriteLine();
InBlock.gif 19                }
InBlock.gif 20
InBlock.gif 21                 //定义一个爷爷类中成员爷爷说的方法,注意它是public的。
InBlock.gif 22                 public void GFSay()
InBlock.gif 23                {
InBlock.gif 24                         //显示出爷爷类中的成员字段的值。
InBlock.gif 25                        Console.WriteLine( "我是爷爷!我给你们留下:{0},还留下:{1}万.", GFhouse, GFMoney);
InBlock.gif 26                }
InBlock.gif 27        }
InBlock.gif 28
InBlock.gif 29         //创建父亲类,继承爷爷类
InBlock.gif 30         class FClass : GFClass
InBlock.gif 31        {
InBlock.gif 32                 //定义一个静态的公有的字段,目的是在Main方法中直接用父亲类的类名引用出字段的值
InBlock.gif 33                 public static int MyMoney = 5;
InBlock.gif 34
InBlock.gif 35                 //定义两个受保护的字段,受保护的字段允许子类访问到
InBlock.gif 36                 //注意Fhouse字段,在构造函数中,重新将继承的爷爷类的中的GFhouse字段值,赋给了Fhouse字段。
InBlock.gif 37                 //说明GFhouse可以直接的访问到。
InBlock.gif 38                 protected string Fhouse = "无房子";
InBlock.gif 39                 protected int FMoney = 15;
InBlock.gif 40
InBlock.gif 41                 //定义父亲类的构造函数,这样在创建儿子类的对象时,都会先执行所以父类以上的构造方法
InBlock.gif 42                 public FClass()
InBlock.gif 43                {
InBlock.gif 44                         //因为父亲没有房子,住的是爷爷的房子,就采用了Fhouse的值继承GFhouse的值的方式传递下去,
InBlock.gif 45                         //放到了构造函数中,目的是只要创建父亲类或儿子类的对象时,继承房子的事件就一定会发生。
InBlock.gif 46                        Fhouse = GFhouse;
InBlock.gif 47
InBlock.gif 48                         //显示出私有字段的值
InBlock.gif 49                        Console.WriteLine( "我是爸爸,我没有房子!继承了你爷爷的房子。我把财产拿出:{0}万,做慈善,用我的名字去取。", MyMoney);
InBlock.gif 50                        Console.WriteLine();
InBlock.gif 51                }
InBlock.gif 52
InBlock.gif 53                 //定义一个爸爸类中成员爸爸说的方法,注意它是public的
InBlock.gif 54                 public void FSay()
InBlock.gif 55                {
InBlock.gif 56                        Console.WriteLine( "我是爸爸,留下:{0}万,你们爷爷留下来的钱我也没动。", FMoney);
InBlock.gif 57                        Console.WriteLine();
InBlock.gif 58                }
InBlock.gif 59        }
InBlock.gif 60
InBlock.gif 61         //定义一个儿子类,继承爸爸类,此处注意不能再继承爷爷类,儿子类中继承了所有父亲类中的非private成员,爷爷类中public成员
InBlock.gif 62         class SClass : FClass
InBlock.gif 63        {
InBlock.gif 64                 // //定义一个儿子类中成员儿子说的方法,注意它是public的
InBlock.gif 65                 public void SSay()
InBlock.gif 66                {
InBlock.gif 67                         //可以直接访问到爷爷说的方法,因为是public修饰的
InBlock.gif 68                        Console.Write( "爷爷对我说:");
InBlock.gif 69                        GFSay();
InBlock.gif 70                        Console.WriteLine();
InBlock.gif 71                         //可以直接访问到爸爸说的方法,因为是public修饰的
InBlock.gif 72                        Console.Write( "爸爸对我说:");
InBlock.gif 73                        FSay();
InBlock.gif 74                        Console.WriteLine();
InBlock.gif 75
InBlock.gif 76                         //可以直接访问到爸爸的受保护字段Fhouse,因为有继承爸爸类的构造函数,
InBlock.gif 77                         //此时爸爸类中的Fhouse的值不再是“无房子”,而是“一套GFhouse房子”
InBlock.gif 78                        Console.WriteLine( "我现在住的是,爸爸继承下来的:" + Fhouse);
InBlock.gif 79                        
InBlock.gif 80                         //打印出儿子可以用的爷爷留下的钱和爸爸留下的钱
InBlock.gif 81                        Console.WriteLine( "我现在一共用的遗产:{0}万", FMoney + GFMoney);
InBlock.gif 82                }
InBlock.gif 83        }
InBlock.gif 84             class Program
InBlock.gif 85        {
InBlock.gif 86                 static void Main( string[] args)
InBlock.gif 87                {
InBlock.gif 88                         //创建一个儿子类的实例son
InBlock.gif 89                        SClass son = new SClass();
InBlock.gif 90
InBlock.gif 91                         //用对象son调用儿子说的方法
InBlock.gif 92                        son.SSay();
InBlock.gif 93                        Console.WriteLine();
InBlock.gif 94                        
InBlock.gif 95                         //因为父亲类中的MyMoney字段是静态的,所以使用父亲类的类名引用
InBlock.gif 96                        Console.WriteLine( "我要把捐出爸爸留下的慈善金{0}万",FClass.MyMoney);
InBlock.gif 97                 }
InBlock.gif 98        }
InBlock.gif 99        
InBlock.gif100}
 
运行结果
我是爷爷!我自己留点养老钱:5万,这是我自己的钱,私有财产呀!
我是爸爸,我没有房子!继承了你爷爷的房子。我把财产拿出:5万,做慈善,用我的名字去取。
爷爷对我说:我是爷爷!我给你们留下:一套GFHouse房子,还留下:10万.
爸爸对我说:我是爸爸,留下:15万,你们爷爷留下来的钱我也没动。

我现在住的是,爸爸继承下来的:一套GFHouse房子
我现在一共用的遗产:25万
我要把捐出爸爸留下的慈善金5万
      通过这个实例的注解,希望大家能够明确的体会到访问权限的作用,不知道大家能不能发现一点,就是第81行中能够访问到爷爷类中的protected成员GFMoney,这就说明了protected也是允许隔代继承的。
     同学们可以在课下试试以下两个操作:一,在GFClass中和FClass中定义的MyMoney是在其子类中访问不到的,因为他们是private修饰的(在类中缺省修饰符的字段都是private修饰的);二,在Main方法中,无法访问到GFClass中和FClass中定义的protected和private修饰的字段,验证一下访问修饰符的权限作用。
  构造方法的继承 

     在这个实例中用到了构造方法的继承,在继承关系中,构造方法不同于一般的方法成员,基类和派生类的构造方法都是分别独立的,在类四中我们讲到了在创建A类对象a时,编译器首先要做的就是对所有A类的基类以上的构造方法先执行一次,然后在执行自己的构造方法。 在类四中,我只是举了基类中的构造器都是无参时的情况,其实继承中构造方法的继承是很复杂的,构造函数的继承是很多企业面试题当中的重要考点,下面我们来通过实例来总结一下构造方法的继承原则:
    如果在子类的构造方法中,没有任何的指定的继承父类的哪个构造方法时,C#会默认的调用没有参数的构造方法,同时我们要知道这样一个机制,就是当一个类中规定了有参的构造函数时,无参的构造函数编译器是不会再默认的创建了,所以为了避免子类中出现无参的构造方法,我们最好是显式的调用基类的构造方法,必须最好养成在创建父类时,及时父类的无参构造函数无作用,也应该把它写上去,避免子类在创建对象时,出现构造方法的向上继承出现错误。
    显式的调用基类的构造方法使用base关键字,如
     public SonClass(int i,int j):base ([i,j])
    {
     }
      其中base指的就是基类,base()指的就是基类的构造方法,当然,“()”中代表的是参数。使用构造方法继承时要注意两个原则:一、子类中构造方法有两个参数,那么它有三种继承父类构造方法的方式:无参、一个参数、二个参数,当然父类必须有这3种;二、如果父类中只有一个无参构造方法的,而无其他构造方法时,那么子类中必须也构造一个无参的构造方法,这时这个有两个参数的构造方法就只有会有一种父类的无参构造方法的继承。
   下面我们来看这个例子体会我上面所说的两个原则:
构造方法的base继承
InBlock.gif 1 //定义一个父类F
InBlock.gif 2         class F
InBlock.gif 3        {
InBlock.gif 4                 //定义F中的无参构造方法
InBlock.gif 5                 public F()
InBlock.gif 6                {
InBlock.gif 7                        Console.WriteLine( "如果子类中的构造器没有明确的使用:base([…]),创建实例时将会出现我无参构造器!");
InBlock.gif 8                }
InBlock.gif 9                 //定义F中的有一个参构造方法
InBlock.gif10                 public F( string i)
InBlock.gif11                {
InBlock.gif12                        Console.WriteLine( "想在创建实例中出现我,必须在子类的构造方法后使用:base(i)");
InBlock.gif13                }
InBlock.gif14                 //定义F中的有两个参构造方法
InBlock.gif15                 public F( string i, string j)
InBlock.gif16                {
InBlock.gif17                        Console.WriteLine( "子类中没有使用:base(i,j)来继承我这个构造方法,所以创建s2不会出现我这句话");
InBlock.gif18                }
InBlock.gif19        }
InBlock.gif20         //定义一个子类S,继承父类F
InBlock.gif21         class S:F
InBlock.gif22        {
InBlock.gif23                 //定义F中的无参构造方法
InBlock.gif24                 //如果子类中定义了无参构造器,那么父类中一定也要定义无参构造器,
InBlock.gif25                 //因为如果父类中有有参构造器后,编译器默认的无参构造器就会失去.
InBlock.gif26                 public S()
InBlock.gif27                {
InBlock.gif28                        Console.WriteLine( "有我就必须规定父类的无参构造器");
InBlock.gif29                }
InBlock.gif30
InBlock.gif31                 //定义S中的有一个参构造方法,没有继承用父类中的有参构造。
InBlock.gif32                 public S( string i)
InBlock.gif33                {
InBlock.gif34                        Console.WriteLine(i);
InBlock.gif35                }
InBlock.gif36                
InBlock.gif37                 //定义了两个参数的子类构造方法,继承了一个参数的构造方法
InBlock.gif38                 //此处说明,子类可以根据base(参数)中参数的个数,调用父类中的相应的构造器
InBlock.gif39                 //如果缺省的时候,就会调用无参的构造器。
InBlock.gif40                 public S( string    i, string    j): base(i)
InBlock.gif41                {
InBlock.gif42                        Console.WriteLine( "我没有使用:base(i,j)来继承父类两参构造方法,继承一个参数的构造方法,所以创建s2时只会出现调用父类的有一个参构造方法");
InBlock.gif43                }
InBlock.gif44
InBlock.gif45        }
InBlock.gif46         class Program
InBlock.gif47        {
InBlock.gif48                 static void Main( string[] args)
InBlock.gif49                {
InBlock.gif50                         //会调用两个无参的构造方法
InBlock.gif51                        S s = new S();
InBlock.gif52                        Console.WriteLine( "-------");
InBlock.gif53
InBlock.gif54                     // 因为子类的一参构造方法没有直接调用父类中的一参构造方法,所以结果应该是调用父无参和子一参
InBlock.gif55                        S s1 = new S( "我是s1,调用了父类无参构造器和本类中使用一参构造器");
InBlock.gif56                        Console.WriteLine( "-------");
InBlock.gif57
InBlock.gif58                         //因为子类的两参构造方法直接调用父类中的一参构造方法,所以结果应该是调用父一参和子两参
InBlock.gif59                        S s2 = new S( "使用两参构造器", "注意父类的出现的构造方法");
InBlock.gif60                        Console.WriteLine( "-------");
InBlock.gif61                }
InBlock.gif62        }
 
结果如下:
如果子类中的构造器没有明确的使用:base([…]),创建实例时将会出现我无参构造器!
有我就必须规定父类的无参构造器
-------
如果子类中的构造器没有明确的使用:base([…]),创建实例时将会出现我无参构造器!
我是s1,调用了父类无参构造器和本类中使用一参构造器
-------
想在创建实例中出现我,必须在子类的构造方法后使用:base(i)
我没有使用:base(i,j)来继承父类两参构造方法,继承一个参数的构造方法,所以创建s
2时只会出现调用父类的有一个参构造方法
-------
请按任意键继续. . .
     看了上面的例子,相信大家应该会使用构造方法的继承了,使用过程一定要注意那两个原则。
Object类

     在c#中继承扮演了很重要的角色,所有的类至少都会继承一个基类Object类,当一个类没有明确的注明是继承那个类时,它自身是继承Object类的,这时同学们就会应该考虑这样一个问题,为什么我们在创建一个没有指明继承哪个类的对象时,对象中会不会包含Object类的方法呢?
     答案是一定的,我们来看上一个实例,在S类和F父类中,除了构造方法没有定义任何类成员,在我们创建s时,点的时候也就是引用s的成员时,会出现四个成员:
 
        S类因为继承了F类,就无法再继承其他类了,怎么会用这四个Object类的成员,说明了什么呢?当S的父类F类因为没有明确的继承类,就继承了Object类,F中包括了这四个类,所以S类中也包括了Object类的成员。我们可以这样说Object类是任何类的基类,即使你的有自己的父类,也的能够用到Object类的成员的,均会继承Object类的所有基本功能。
Object类位于类库的System命名空间下,本身提供了下面六个方法成员:
Equals:用来判断调用此方法的对象与指定的对象是否相同,返回True和False来代表相同和不同。
GetHashCode:特定类的哈希函数,用于哈希演算与类似哈希表的数据表结构。
GetType:取得目前实例对象的类
ReferenceEquals:比较指定的对象是否引用到相同的实例。
ToString:转换为字符串的方法。
Finalize:在垃圾回收机制开始执行前,给对象提供进行对象资源释放或是清空操作的机会。
 我用下面的例子来讲述其中3种常见用法:
Object类的方法实例
InBlock.gif 1 //定义一个F类
InBlock.gif 2         class F
InBlock.gif 3        {
InBlock.gif 4                 //定义一个公有的i字段
InBlock.gif 5                 public int i;
InBlock.gif 6        }        
InBlock.gif 7         class Program
InBlock.gif 8        {
InBlock.gif 9                 static void Main( string[] args)
InBlock.gif10                {
InBlock.gif11                     //创建F类的两个对象f和f1
InBlock.gif12                        F f = new F();
InBlock.gif13                        F f1 = new F();
InBlock.gif14                     //使用Equals方法,判断f是否是f1
InBlock.gif15                        Console.WriteLine( "对象f是否是对象f1:"+f.Equals(f1));
InBlock.gif16                        Console.WriteLine();
InBlock.gif17
InBlock.gif18                         //定义一个Object类型的type变量使用GetType方法接收i的数据类型,打印出来
InBlock.gif19                         object type=    f.i.GetType();
InBlock.gif20                         Console.WriteLine( "i的类型是" + type);
InBlock.gif21                         Console.WriteLine();
InBlock.gif22
InBlock.gif23                         //使用.ToString()方法将i转换为字符串类型,再用GetType方法接收i的数据类型,打印出来
InBlock.gif24                         Console.WriteLine( "i的类型是" + f.i.ToString().GetType());
InBlock.gif25                }
InBlock.gif26        }
 
结果如下:
对象f是否是对象f1:False
i的类型是System.Int32
i的类型是System.String
        实际上这六种方法是public的修饰的虚方法,关于虚方法的定义,我们下节课类八继承中的多态性:方法重写。我们将讲解到。

0

收藏

叶子文文

160篇文章,147W+人气,12粉丝

Ctrl+Enter 发布

发布

取消

f92360e227f9d91cdff7ea95120630ef.png
left-qr.jpg

扫一扫,领取大礼包

0

分享
qr-url?url=https%3A%2F%2Fblog.51cto.com%2Fleafwf%2F185719
叶子文文
noavatar_middle.gif