.net基础---继承
1.继承的概念
1.1概念
现实生活中的事物都归属于一定得类别,比如,狮子是一种动物,为了在计算机中模拟这种关系,面向对象语言引入了继承(Inherit)的特性。
如图所示,类Animal代表动物,类Lion代表狮子,空心三角箭头代表继承关系

在构成继承关系的两个类中,Animal称为父类(Parent Class)或者基类(Base Class),Lion称为子类(Child Class)。
父类和子类之间拥有以下两种基本关系:
-
- 是一种(IS-A)关系:子类是父类的一种特例
- 扩充关系:子类拥有父类所没有的功能
1.2 在编程语言中实现继承:
1 class Animal 2 { 3 } 4 class Lion:Animal 5 { 6 }
1.3 继承条件下类成员的访问权限
| 使用场合 | C# | 说明 |
| Type(指类、接口等类型) | public | 访问不受限制 |
| internal | 访问范围限制同一程序集 | |
| Member(指类型中的成员,比如类中的字段) | public | 访问不受限制 |
| internal | 访问范围限制同一程序集 | |
| protected | 访问范围限制于自己或派生出来的子类型 | |
| protected internal |
在同一程序集内访问不受限制,在不同程序集中 仅由此类型派生出来的子类型可访问 |
|
| private |
仅自己可以访问 |
提示:C#中并非所有类都能被继承,声明为sealed的类不可继承,这种类被称为"密封类"。
2.类型转换
子类对象可以被当成父类对象使用,因此以下代码是合法的
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Parent p; 6 Son c = new Son(); 7 p = c;//正确,子类对象可以传给父类变量 8 } 9 10 } 11 public class Parent 12 { 13 } 14 public class Son : Parent 15 { 16 }
然而反过来就不可以,以下代码是错误的:
1 c=p;//错误,父类对象不能直接赋值给子类变量
如果确信父类变量中引用的对象是子类的类型,则可以通过强制转换进行赋值
1 c=(Son)p;
或者是使用as运算符
1 c=p as Son;
as运算符和强转的区别:
1 static void Main(string[] args) 2 { 3 //************错误************ 4 Parent p1=new Son(); 5 Daughter d1; 6 d1=(Daughter)p1;//这里运行会引发强制转换的异常 7 8 //************正确************ 9 Parent p2 = new Son(); 10 Daughter d2; 11 d2 = p2 as Daughter;//这里运行不会引发异常,但是会返回一个Null 12 if (d2 == null) 13 { 14 //空处理 15 } 16 } 17 18 } 19 public class Parent 20 { 21 } 22 public class Son : Parent 23 { 24 } 25 public class Daughter : Parent 26 { 27 }
提示:类型转换的时候尽量避免使用强制转换,多用as避免异常,记住as转换完判断是否为空
3.方法重载、隐藏和虚方法的调用
3.1 概念
总的来说,子类方法和父类方法之间的关系可以概括为3种:
-
- 扩充(Extend):子类定义的方法,在父类中没有同名的方法存在
- 重载(Overload):子类方法与父类方法同名,但是参数列表不一样
- 完全相同:子类方法与父类方法从方法名到参数完全一致
3.2 重载
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Child c = new Child(); 6 c.OverloadF();//调用父类的方法 7 c.OverloadF(100);//调用子类的重载方法 8 } 9 10 } 11 public class Parent 12 { 13 public void OverloadF() 14 { 15 16 } 17 } 18 public class Child : Parent 19 { 20 public void OverloadF(int i) 21 { 22 23 } 24 }
3.3 隐藏
当子类与父类拥有完全一样的方法时,称"子类隐藏了父类的同名方法",当分别位于父类和子类的两个方法完全一样时,调用哪个方法由对象变量的类型决定
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Child c = new Child(); 6 Parent p; 7 p = c; 8 p.OverloadF();//调用的父类的同名方法,因为父类和子类方法重名,调用方法是由变量类型决定的,结果为parent.HideF() 9 Console.ReadKey(); 10 } 11 12 } 13 public class Parent 14 { 15 public void OverloadF() 16 { 17 System.Console.WriteLine("Parent.HideF()"); 18 } 19 } 20 public class Child : Parent 21 { 22 public void OverloadF() 23 { 24 System.Console.WriteLine("Child.HideF()"); 25 } 26 }
要是想调用子类方法,要先进行强制转换
1 ((Child)p).HideF();
前面定义parent和child,会发出一个警告,虽然警告并不影响运行结果,但是不符合C#的语法规范,修改child类定义如下:
1 public class Child : Parent 2 { 3 public new void OverloadF() 4 { 5 System.Console.WriteLine("Child.HideF()"); 6 } 7 }
如果在子类方法中使用父类的隐藏同名方法,要使用base关键字
3.4 重写与虚方法调用
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Child c = new Child(); 6 Parent p; 7 p = c; 8 p.OverrideF();//结果是Child.OverrideF(),调用父类还是子类的方法是由父类变量引用的真实对象类型决定的,和父类变量自身类型无关 9 Console.ReadKey(); 10 } 11 12 } 13 public class Parent 14 { 15 public virtual void OverrideF() 16 { 17 System.Console.WriteLine("Parent.OverrideF()"); 18 } 19 } 20 public class Child : Parent 21 { 22 public override void OverrideF() 23 { 24 System.Console.WriteLine("Child.OverrideF()"); 25 } 26 }
面向对象拥有的"虚方法调用"特性,使我们可以只用同样的一个语句,在运行时根据对象类型而执行不同的操作
3.5 小心应用子类、父类间的隐藏和重写机制
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Parent p = new Parent(); 6 p.f();//Base.f() 7 Child c = new Child(); 8 c.f();//Child.f() 9 p = c; 10 p.f();//Child.f() 11 (p as Child).f();//Child.f() 12 (p as Parent).f();//Child.f() 13 Console.ReadKey(); 14 } 15 16 } 17 public class Parent 18 { 19 public virtual void f() 20 { 21 System.Console.WriteLine("Base.f()"); 22 } 23 } 24 public class Child : Parent 25 { 26 public override void f() 27 { 28 System.Console.WriteLine("Child.f()"); 29 } 30 }
上面的例子说明在使用"虚方法"时,具体调用的方法决定于对象变量引用对象的真实类型
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Parent p = new Parent(); 6 p.f();//Base.f() 7 Child c = new Child(); 8 c.f();//Child.f() 9 p = c; 10 p.f();//Base.f() 11 (p as Child).f();//Child.f() 12 (p as Parent).f();//Base.f() 13 Console.ReadKey(); 14 } 15 16 } 17 public class Parent 18 { 19 public void f() 20 { 21 System.Console.WriteLine("Base.f()"); 22 } 23 } 24 public class Child : Parent 25 { 26 public new void f() 27 { 28 System.Console.WriteLine("Child.f()"); 29 } 30 }
上面这个例子说明"隐藏",调用父类或者子类,不是由对象变量定义时的类型决定的,而是运行时的类型决定的
4.继承关系下访问字段
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Child c = new Child(); 6 Console.WriteLine(c.i);//200 7 Parent p = new Parent(); 8 Console.WriteLine(p.i);//100 9 p = c; 10 Console.WriteLine(p.i);//100 11 } 12 13 } 14 public class Parent 15 { 16 public int i = 100; 17 } 18 public class Child : Parent 19 { 20 public int i = 200; 21 }
从上面的例子可以看出:如果子类和父类有相同的字段,到底使用哪个字段,由对象变量定义的类型决定,而与程序运行时对象变量引用的真实对象类型无关(这个应该和隐藏方法一样)
同样这样写也是不合规范的要加new,尽量避免使用隐藏字段
5.值类型
所有的值类型都由一个特殊的类ValueType继承而来,而ValueType又继承自Object类,所以Object类也是所有值类型的父类。
所有的值类型都不可以再派生出子类。
值类型拥有一个隐含的构造函数,自动初始化其成员,.Net 规定数字类型变量初始化为0,C#要求所有值类型必须初始化才能使用,否则不能通过编辑
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int i; 6 i = i + 1;//无法通过编辑 7 } 8 9 }
需改成:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //写法一 6 int i = new int(); 7 i = i + 1; 8 //写法二 9 int i2 = 0; 10 i2 = i2 + 1; 11 } 12 13 }
6.继承条件下的对象创建与销毁
6.1 子类、父类构造函数的调用次序
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Child c = new Child(); 6 Console.ReadKey(); 7 //运行结果 8 //Parent默认构造函数 9 //Child默认构造函数 10 } 11 12 } 13 public class Parent 14 { 15 public Parent() 16 { 17 Console.WriteLine("Parent默认构造函数"); 18 } 19 } 20 public class Child : Parent 21 { 22 public Child() 23 { 24 Console.WriteLine("Child默认构造函数"); 25 } 26 }
上面例子可以看出,在创建子类对象时,首先调用父类的构造函数,在调用子类的构造函数,如果父类还有一个"祖父类"存在,则先会上溯到"祖父类",调用其构造函数,接着是父类,最后是子类,这种调用次序可以称为"尊重长辈"原则。
当对象析构时,会首先调用子类析构函数,再是父类,刚好和创建对象相反。
6.2 子类和父类构造函数的重载
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Child c = new Child("Hello"); 6 Console.ReadKey(); 7 8 } 9 } 10 public class Parent 11 { 12 public Parent() 13 { 14 Console.WriteLine("Parent默认构造函数"); 15 } 16 public Parent(string str) 17 { 18 Console.WriteLine("Parent带参构造函数被调用"); 19 } 20 21 } 22 public class Child : Parent 23 { 24 public Child() 25 { 26 Console.WriteLine("Child默认构造函数"); 27 } 28 public Child(string str) 29 : base(str) 30 { 31 Console.WriteLine("Child带参构造函数被调用"); 32 } 33 34 }
浙公网安备 33010602011771号