在编程中也沿用了继承的概念,在面向对象编程中,如C++和C#中都有类的继承。 继承(加上封装和多态性)是面向对象的编程的三个主要特性(也称为“支柱”)之一。 继承用于创建可重用、扩展和修改在其他类中定义的行为的新类。 其成员被继承的类称为“基类”,继承这些成员的类称为“派生类”。 派生类只能有一个直接基类。 但是,继承是可传递的。 如果 ClassB 派生出 ClassC,ClassA 派生出 ClassB,则 ClassC 会继承 ClassB 和 ClassA 中声明的成员。
v写在前面
无论是其他社区还是园子里,关于继承类的帖子或者文章已经可以说是数不胜数了,关于继承类的话题也是旧调重弹了。写这篇博文只是为了加深一下自己对继承的理解,所谓读书百遍其意自见嘛。这篇博文中部分内容来自我的读书(C#高级编程从开始接触C#到现在我已经是第五遍读了,每次读都有不一样的收获。)笔记,和一些自己的理解。
vC#继承
1.实现继承
如果要声明派生自另一个类的一个类,就可以使用下面的语法:
class LearningClass : BasicClass{ }
如果类(或结构)也派生自接口,则用逗号分隔列表中的基类和接口:
class LearningClass : BasicClass, BasicInterface, BasicInterface2{ }
对于结构,语法如下:
struct LearningStruct : BasicInterface, BasicInterface2{ }
使用结构的一个限制是结构不支持继承,但每个结 构都自动派生自system,ValueType。实际上还应更仔细一些:不能编码实现类型层次的结构,但结 构可以实现接口。换言之,结构并不支持实现继承,但支持接口继承。事实上,定义结构和类可以 总结为:
结构总是派生自system。ValueType,它们还可以派生自任意多个接口。
类总是派生自用户选择的另一个类,它们还可以派生自任意多个接口。
在类定义中没有指定基类,钭编译器就假定system.Object是基类。因此下面的两段代码生成相同的结果:
class LearningClass : Object{ } class LearningClass{ }
2.虚方法virtual:
有时候我们继承一个基类的某些方法希望能重新定义这个方法,我们可以把一个基类函数声明virtual,就可以在任何派生类中重写这个函数:
public class BasicClass { public virtual string GetMessage() { return "Basic class message"; } }
C#中虚函数的概念与标准00P的概念相同:可以在派生类中重写虚函数。在调用方法时,会调用该类对象的合适方法。在C#中,函数在默认情况下不是虚拟的,但(除了构造函数以外)可以显 式地声明为virtual。这遵循C++的方式,即从性能的角度来看,除非显式指定,否则函数就不是虚 拟的。而在Java中,所有的函数都是虚拟的。但C#的语法与C++的语法不同,因为C#要求在派生 类的函数重写另一个函数时,要使用override关键字显式声明:
class LearningClass : BasicClass { public override string GetMessage() { return "Learning class message"; } }
重写方法的语法避免了C++中很容易发生的潜在运行错误:当派生类的方法签名无意中与基类 版本略有差别时,该方法就不能重写基类的方法:在C#中,这会出现一个编译错误,因为编译器会 认为函数已标记为override,但没有重写其基类的方法。
成员字段和静态函数都不能声明为virtual,因为这个概念只对类中的实例函数成员有意义。
3.隐藏方法:
如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有分别声明为virtual和 override,派生类方法就会隐藏基类方法。
在大多数清况下,是要重写方法,而不是隐藏方法,因为隐藏方法会造成对于给定类的实例调用 错误方法的危险。但是,如下面的例子所示,C#语法可以确保开发人员在编译时收到这个潜在错误的 警告,从而使隐藏方弦【如果这确实是用户的本匐更加安全。这也是类库开发人员得到的版本方面的 · 好处
此时,编译时系统会发出警告。在C#中,要隐藏一个方法应使用new关键字声明,如下所示:
public class LearningClass : BasicClass { public new string GetMessage() { return "Learning class message"; } }
4.调用函数的基类版本:
C#有一种特殊的语法用于从派生类中调用方法的基类版本:base.()。例如,假定 派生类中的一个方法要返回基类的方法20%的返回值,就可以使用下面的语法:
public class BasicClass { public virtual float GetPrice() { return 1.5f; } } public class LearningClass : BasicClass { public float GetPrice() { return base.GetPrice() * 0.2f; } }
ps:可以使用base.()语法调用基类中的任何方法,不必从同一个方法的重载 中调用它。
5.抽象类和抽象函数:
C#允许把类和函数声明为abstract。抽象类不能实例化,而抽象函数不能直接实现,必须在非抽 象的派生类中重写。显然,抽象函数本身也是虚拟的(尽管也不需要提供virtual关键字,实际上,如 果提供了该关键字,就会产生一个语法错误。如果类包含抽象函数,则该类也是抽象的,也必须声 明为抽象的:
public abstract class BasicClass { public abstract float GetPrice(); } public class LearningClass : BasicClass { public override float GetPrice() { return 0.2f; } }
6.密封类和密封方法:
C#允许把类和方法声明为sealed。对于类,这表示不能继承该类;对于方法,这表示不能重写该方法。
在把类或方法标记为sealed时,最可能的情形是:如果要对库、类或自己编写的其他类作用域 之外的类或方法进行操作,则重写某些功能会导致代码混乱。也可以因商业原因把类或方法标记为 sealed,以防第三方以违反授权协议的方式扩展该类。但一般情况下,在把类或成员标记为sealed 时要小心,因为这么做会严重限制它的使用方式。即使认为它不能对继承自一个类或重写类的某个 成员发挥作用,仍有可能在将来的某个时刻,有人会遇到我们没有预料到的情形,此时这么做就很 有用。.NET基类库大量使用了密封类,使希望从这些类中派生出自己的类的第三方开发人员无法访 问这些类。例如,string就是一个密封类。
把方法声明为sealed也可以实现类似的目的,但很少这么做。
7.修饰符:
C#共有五种访问修饰符:public、private、protected、internal、protected internal。作用范围如下表:
访问修饰符 | 说明 |
public | 公有访问。不受任何限制。 |
private | 私有访问。只限于本类成员访问,子类,实例都不能访问。 |
protected | 保护访问。只限于本类和子类访问,实例不能访问。 |
internal | 内部访问。只限于本项目内访问,其他不能访问。 |
protected internal | 内部保护访问。只限于本项目或是子类访问,其他不能访问 |
C#成员类型的可修饰及默认修饰符如下表:
成员类型 | 默认修饰符 | 可被修饰符 |
enum | public | none |
class | private | public、protected、internal、private、 protected internal |
interface | public | none |
struct | private | public、internal、private |
修饰符可以应用于类型的成员,而且有不同的用途:
修饰符 | 应用于 | 说明 |
new | 函数成员 | 成员用相同的签名隐藏继承的成员 |
static | 所有成员 | 成员不作用于类的具体实例 |
virtual | 仅函数成员 | 成员可以由派生类重写 |
abstract | 仅函数成员 | 虚拟成员定义了成员的签名,但没有提供实例代码 |
override | 仅函数成员 | 成员重写了继承的虚拟或抽象成员 |
sealed | 类,方法和属性 | 对于类,不能继承自密封类。对于属性和方法,成员重写已继承的虚拟成员,但任何派生类中的任何成员都不能重写该成员。该修饰符必须与override一起使用 |
extern | 仅静态[DllImport]方法 | 成员在外部用另一种语言实现 |