【读书笔记】C#高级编程 第四章 继承

(一)继承的类型

1、实现继承和接口继承

在面向对象的编程中,有两种截然不同的继承类型:实现继承和接口继承。

  • 实现继承:表示一个类型派生于一个基类型,它拥有该基类型的所有成员字段和函数。在实现继承中,派生类型采用基类型的每个函数代码,除非在派生类型的定义中指定重写某个函数的实现代码。在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,这种类型的继承非常有用。
  • 接口继承:表示一个类型只继承了函数的签名,没有继承任何实现代码。在需要制定该类型具有某些可用的特性时,最好使用这种类型的继承。

2、多重继承

C#不支持多重实现继承,但允许类型派生自多个接口——多重接口继承。准确地说,因为System.Object是一个公共的基类,所以每个C#类(除Object类)都有一个基类,还可以有任意多个基接口。

3、结构和类

结构不支持实现继承,但支持接口继承。

定义结构和类可以总结为:

  • 结构总是派生自System.ValueType,它们还可以派生自任意多个接口。
  • 类总是派生自System.Object或用户选择的另一个类,它们还可以派生自任意多个接口。

(二)实现继承

声明派生自另一个类的类,语法如下:

例子:

以下代码创建了MyBaseClass类、MyClass类,其中MyClass类派生自MyBaseClass类

class MyBaseClass
{
}
class MyClass : MyBaseClass
{
}

如果类(或结构)也派生自接口,则用逗号分隔列表中的基类和接口:

class MyClass : MyBaseClass,IInterface1,IInterface2
 {
}

对于结构,语法如下:

public struct MyStruct: IInterface1, IInterface2
{
}

1、虚方法

把一个基类函数声明为virtual,就可以在任何派生类中重写该函数:

class MyBaseClass
{
    public virtual string VirtualMethod()
    {
        return "虚方法";
    }
}

在C#中,函数在默认情况下不是虚拟的,但(出构造函数以外)可以显式地声明为virtual。同时C#要求在派生类在重写基类中的函数时,要使用override关键字显式声明:

class MyClass : MyBaseClass, IInterface1, IInterface2
{
    public override string VirtualMethod()
    {
        return "重写了基类的虚方法";
    }
}

2、隐藏方法

如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有分别声明为virtual和override,派生类方法就会隐藏基类方法。

例子:

以下代码中,基类和派生类有一个相同的方法ShowName,当我们需要隐藏基类中的方法时应该显示地使用关键字new,否则编译器会发出警告。隐藏了基类方法,我们不能通过派生类访问基类方法,只能通过派生类自己访问。

class MyBaseClass
{
    public void ShowName(string name)
    {
        Console.WriteLine(name);
    }
}
class MyClass : MyBaseClass, IInterface1,
{
    public new void ShowName(string name)
    {
        Console.WriteLine("派生类"+name);
    }
}

在大多数情况下,我们应该重写方法,而不是隐藏方法,因为隐藏方法会造成对于给定类的实例调用错误方法的危险。

3、调用函数的基类版本

C#有一种特殊的语法用于从派生类中调用方法的基类版本:base.<MethodName>()。

例子:

以下代码在基类打印出名字的基础上,再打一行欢迎语

class MyBaseClass
{
    public virtual void  ShowName(string name)
    {
        Console.WriteLine(name);
    }
}
class MyClass : MyBaseClass, IInterface1, IInte
{
    public override void ShowName(string name)
    {
        base.ShowName(name);
        Console.WriteLine("欢迎您的光临!");
    }
}

运行以上代码,结果如下:

4、抽象类和抽象函数

C#允许把类和函数声明为abstract。抽象类不能实例化,而抽象函数不能直接实现,必须在非抽象的派生类中重写。如果类包含抽象函数,则该类也是抽象的,也必须声明为抽象类。

例子:

以下代码创建了一个抽象的类和一个抽象方法

abstract class MyAbstractClass {
    public abstract void FirstAbstractMethod();
}

5、密封类和密封方法

C#允许把类和方法声明为sealed。对于类,这表示不能继承该类;对于方法,这表示不能重写该方法。

例子:

sealed class FisrtSealedClass
{ }
class ReadyClass : FisrtSealedClass
{ }

此时编译器会报错

把方法声明为sealed

例子:

class MyBaseClass
{
    public virtual void  ShowName(string name)
    {
        Console.WriteLine(name);
    }
}
class MyClass : MyBaseClass, IInterface1, IInterface2
{
    public sealed override void ShowName(string name)
    {
        base.ShowName(name);
        Console.WriteLine("欢迎您的光临!");
    }
}

要在方法或属性上使用sealed关键字,必须先从基类上把它声明为要重写的方法或属性。如果基类要密封,则不把它声明为virtual即可。

6、派生类的构造函数

例子:

class MyBaseClass
{
    public MyBaseClass() { }
    public MyBaseClass(int i) { }
}
class MyClass : MyBaseClass
{
    public MyClass() { }
    public MyClass(int i) { }
    public MyClass(int i,int j) { }
}

如果用以下方式实例化MyClass:

MyClass myClass = new MyClass();

则构造函数的执行顺序如下:

  1. 执行System.Object.Object()构造函数;
  2. 执行MyBaseClass.MyBaseClass()构造函数;
  3. 执行MyClass.MyClass()构造函数。

如果用以下方式实例化MyClass:

MyClass myClass = new MyClass(4);

则构造函数的执行顺序如下:

  1. 执行System.Object.Object()构造函数;
  2. 执行MyBaseClass.MyBaseClass()构造函数;
  3. 执行MyClass.MyClass(int i)构造函数。

最后,使用如下方式实例化MyClass:

MyClass myClass = new MyClass(4, 8);

则构造函数的执行顺序如下:

  1. 执行System.Object.Object()构造函数;
  2. 执行MyBaseClass.MyBaseClass()构造函数;
  3. 执行MyClass.MyClass(int i,int j)构造函数。

还可以使用base()和this()来改变构造函数的执行顺序

class MyBaseClass
{
    public MyBaseClass() { }
    public MyBaseClass(int i) { }
}
class MyClass : MyBaseClass
{
    public MyClass() { }
    public MyClass(int i) { }
    public MyClass(int i, int j) : base(i) { }
}

这个时候我们在通过如下方式实例化MyClass:

MyClass myClass = new MyClass(4, 8);

这个时候构造函数的执行顺序如下:

  1. 执行System.Object.Object()构造函数;
  2. 执行MyBaseClass.MyBaseClass(int i)构造函数;
  3. 执行MyClass.MyClass(int i,int j)构造函数。

base关键字指定.NET实例化过程使用基类中有指定参数的构造函数。

class MyBaseClass
{
    public MyBaseClass() { }
    public MyBaseClass(int i) { }
}
class MyClass : MyBaseClass
{
    public MyClass() { }
    public MyClass(int i) : this(i, 5) { }
    public MyClass(int i, int j) { }
}

这个时候我们在通过如下方式实例化MyClass:

MyClass myClass = new MyClass(4);

这个时候构造函数的执行顺序如下:

  1. 执行System.Object.Object()构造函数;
  2. 执行MyBaseClass.MyBaseClass()构造函数;
  3. 执行MyClass.MyClass(int i,int j)构造函数;
  4. 执行MyClass.MyClass(int i)构造函数。

this关键字关键字指定.NET实例化过程使用当前类中有指定参数的构造函数。

但需要注意的是不要无限循环:

class MyClass : MyBaseClass
{
    public MyClass() : this(1){ }
    public MyClass(int i) : this() { }
}

以上代码通过this相互指定,造成了无限循环。

通过以上代码的分析,我们可以知道构造函数的调用顺序是先调用System.Object,再按照层次结构由上向下进行,直到达到编译器要实例化的类为止。

无论在派生类上使用什么构造函数(默认构造函数或非默认构造函数),除非明确指定,否则就使用基类的默认构造函数。

(三)修饰符

修饰符,即应用于类型或成员的关键字。

1、可见性修饰符

修饰符 应用于 说明
public 所有类型或成员 任何代码均可以访问该项
protected 类型和内嵌类型的所有成员 只有派生的类型能访问该项
internal 所有类型或成员 只能在包含它的程序集中访问该项
private 类型和内嵌类型的所有成员 只能在它所属的类型中访问该项
protected internal 类型和内嵌类型的所有成员 只能在包含它的程序集和派生类型的任何代码中访问该项

2、其他修饰符

修饰符 应用于 说明
new  函数成员 成员用相同的签名隐藏继承的成员
static 所有成员 成员不作用与类的具体实例
virtual 仅函数成员 成员可以由派生类重写
abstract 仅函数成员 虚拟成员定义了成员的签名,但没有提供实现代码
override 仅函数成员 成员重写了继承的虚拟或抽象成员
sealed 类、方法和属性 对于类,不能继承自密封类。对于属性和方法,成员重写已继承的虚拟成员,但任何派生类中的成员都不能重写该成员。该修饰符必须与override一起使用
extern 仅静态[DllImport]方法 成员在外部用另一种语言实现

(四)接口

如果一个类派生自一个接口,声明这个类就会实现某些函数。

接口只能包含方法、属性、索引器和事件的声明。

不能实例化接口,它只能包含其成员的签名。

接口总是共有的,不能声明为虚拟或静态的。

接口通常以字母I开头,以便知道这是一个接口。

1、定义和实现接口

首先我们定义一个接口,该接口功能是一个说话的接口

interface ITalk {
    void talk(string str);
}

接下来我们通过类Chinese来实现该接口

class Chinese : ITalk
{
    public void talk(string str)
    {
        Console.WriteLine("中文语音:" + str);
    }
}

还可以定义一个American来实现该接口

class American : ITalk {
    public void talk(string str)
    {
        Console.WriteLine("English:" + str);
    }
}

通过接口的继承,我们实现了不同国家的人都可以说话这样一个功能,外部调用时我们通过接口引用可以很方便的调用。

2、接口的派生

当ITalk不满足需求时,假定这个时候需要给Chinese和American添加一个Speak功能时,可以通过接口的派生来实现

interface ILanguage : ITalk
{
    void Speak(string str);
}

改变后的接口实现

class Chinese : ILanguage
{
    public void Speak(string str)
    {
        Console.WriteLine("中文演讲:" + str);
    }

    public void talk(string str)
    {
        Console.WriteLine("中文语音:" + str);
    }
}
class American : ILanguage
{
    public void Speak(string str)
    {
        Console.WriteLine("English Speak:" + str);
    }

    public void talk(string str)
    {
        Console.WriteLine("English:" + str);
    }
}
时间: 2024-10-14 10:00:57

【读书笔记】C#高级编程 第四章 继承的相关文章

读书笔记 - js高级程序设计 - 第四章 变量 作用域 和 内存问题

5种基本数据类型 可以直接对值操作 判断引用类型 var result = instanceof Array 执行环境 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中 执行环境的销毁 某个执行环境中的所有代码执行完毕后 该环境被销毁 保存在其中的所有变量了函数定义也会随之销毁 作用域链中的对象 全局执行环境的变更对象始终都是作用域链中的最后一个对象 没有块级作用域 if 和 for 内的变量 外部也可以访问 标记清除 不同浏览器 只不过垃圾时间的长短不同 引

读书笔记 - js高级程序设计 - 第七章 函数表达式

闭包 有权访问另一个函数作用域中的变量的函数 匿名函数 函数没有名字 少用闭包 由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存.过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包 模块模式   增强的模块模式   特权方法 有权访问私有变量的公有方法叫做特权方法 块级作用域   实现单例的特权方法  

读书笔记 - js高级程序设计 - 第三章 基本概念 -

启用严格模式 "use strict" 这是一个 pragma 编译指示 让编码意图更清晰  是一个重要原则 5种简单数据类型 Undefined Null Boolean Number String 1种复杂数据类型 Object 检测数据类型的方法 typeof 有如下值: undefined boolean object string number function typeof Null object 意在保存对象还没有保存对象的变量的初始值最好是什么 null 八进制的第一位

读书笔记 - js高级程序设计 - 第六章 面向对象的程序设计

EcmaScript有两种属性 数据属性 和 访问器属性 数据属性有4个特性 Configurable Enumerable Writable Value 前三个值的默认值都为false 举例 Object.defineProperty( person, "name", { writable:false, value:"niko"} ) ; 一旦属性定义为不可配置的,就不能再把它变回可配置的了 读取属性 的特性 var descriptor  = Object.ge

读书笔记 - js高级程序设计 - 第十一章 DOM扩展

对DOM的两个主要的扩展 Selectors API HTML5 Element Traversal 元素遍历规范 querySelector var body = document.querySelector("body"); var myDiv = document.querySelector("#myDiv"); 取得id为myDiv的元素 var selected = document.querySelector(".selected")

读书笔记 - js高级程序设计 - 第十五章 使用Canvas绘图

读书笔记 - js高级程序设计 - 第十三章 事件 canvas 具备绘图能力的2D上下文 及文本API 很多浏览器对WebGL的3D上下文支持还不够好 有时候即使浏览器支持,操作系统如果缺缺乏必要的绘图驱动程序,则浏览器即使支持了也没用   <canvas> var drawing = document.getElementById("drawing"); if( drawing.getContext ){ drawing.getContext("2d"

Hadoop学习笔记(7) ——高级编程

Hadoop学习笔记(7) ——高级编程 从前面的学习中,我们了解到了MapReduce整个过程需要经过以下几个步骤: 1.输入(input):将输入数据分成一个个split,并将split进一步拆成<key, value>. 2.映射(map):根据输入的<key, value>进生处理, 3.合并(combiner):合并中间相两同的key值. 4.分区(Partition):将<key, value>分成N分,分别送到下一环节. 5.化简(Reduce):将中间结

[读书笔记]了不起的node.js(四)

这周的学习主要是nodejs的数据库交互上,并使用jade模板一起做了一个用户验证的网站.主要是遇到了一下几个问题. 1.mongodb版本过低 npm ERR! Not compatible with your operating system or architecture: [email protected] 0.9.9只支持linux,darwin,freebsd这几个系统,最新版本已支持wins. 2.nodejs进行insert操作后:无法读取结果 1 app.post('/sign

读书笔记-----Java并发编程实战(一)线程安全性

线程安全类:在线程安全类中封装了必要的同步机制,客户端无须进一步采取同步措施 示例:一个无状态的Servlet 1 @ThreadSafe 2 public class StatelessFactorizer implements Servlet{ 3 public void service(ServletRequest req,ServletResponse resp){ 4 BigInteger i = extractFromRequest(req); 5 BigInteger[] fact