一、先看一段示例代码
下面这段 C# 代码定义了一个父类ParentClass和一个继承自它的子类ChildClass,并在Main方法中创建子类对象、调用同名方法。建议先自己猜一下输出顺序,再往下看分析。
using System; public class ParentClass { public ParentClass() { Console.WriteLine("父类构造函数。"); } public void print() { Console.WriteLine("I'm a Parent Class。"); } } public class ChildClass : ParentClass { public ChildClass() { Console.WriteLine("子类构造函数。"); } public void print(string x) { Console.WriteLine(x); } public static void Main() { ChildClass child = new ChildClass(); child.print(); child.print("打印子类"); } }二、运行结果
编译并运行上面的代码,控制台输出如下:
父类构造函数。 子类构造函数。 I'm a Parent Class。 打印子类可以看到,创建ChildClass对象时,父类的构造函数先于子类的构造函数执行;而child.print()调用的是从父类继承来的无参print方法,child.print("打印子类")调用的则是子类自己定义的重载版本。
三、继承和执行是两码事
首先要说的是,继承和执行是两码事。
- 继承:指子类是否具有和父类一样的类成员,而不用重新定义或实现一遍。继承的目的就是重用。
- 执行:指在类的内部或外部使用这些成员的过程。
换句话说,继承描述的是“类与类之间的成员归属关系”,执行描述的是“运行时成员如何被调用”。二者经常同时出现,但概念上并不等价。
四、构造函数为什么先执行父类
执行构造函数,是指在使用某个类时,需要创建该类的对象并为这个对象的所有成员分配内存的过程。如果该类继承了某个父类,那么首先就要为父类的成员分配内存,自然就会先执行父类的构造函数。
因此,在 C# 中,子类继承父类后,调用子类的非静态构造函数时会默认先调用基类的无参构造函数,这是一条语言规则,而不是“函数继承”。函数本身是无法被继承的,继承只作用于类这一层级。
补充:如果父类没有无参构造函数,子类构造函数必须通过
: base(...)显式指定要调用的父类构造函数,否则编译不通过。
五、关于“重写成员的内存覆盖”
当子类构造时,如果碰到被override重写的成员,那么这个曾经在父类构造时被分配的内存地址,会在子类构造时被新的地址覆盖掉。这也是为什么在父类构造函数中调用虚方法时,实际执行的可能已经是子类的实现——这一点在实际开发中需要特别小心。
六、小结
- 继承解决的是成员复用问题,执行解决的是成员调用问题,两者不要混为一谈。
- 创建子类对象时,父类构造函数先执行,子类构造函数后执行。
- 子类默认调用父类无参构造函数;父类没有无参构造函数时,必须用
: base(...)显式指定。 - 方法重载(
print()与print(string))与继承是两回事,调用哪个版本由参数列表决定。