(一)继承的类型

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);
}
}

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

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

      5种基本数据类型 可以直接对值操作 判断引用类型 var result = instanceof Array 执行环境 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这 ...

  2. unix环境高级编程第四章笔记

    文件和目录 start fstart lstart函数 一旦给出pathname, start函数就返回了与此命名文件有关的信息结构 #include <sys/start> int st ...

  3. UNIX系统高级编程——第四章-文件和目录-总结

    文件系统: 以UNIX系统V文件系统为例: 磁盘分为区,每个分区都有自己的文件系统: ​ i节点是固定长度的记录项,包含了文件的相关信息.目录项包含文件名和i节点号.stat结构中除文件名和i节点编号 ...

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

      对DOM的两个主要的扩展 Selectors API HTML5  Element Traversal 元素遍历规范 querySelector var body = document.query ...

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

      闭包 有权访问另一个函数作用域中的变量的函数 匿名函数 函数没有名字 少用闭包 由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存.过度使用闭包可能会导致内存占用过多,我们建议读者 ...

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

      EcmaScript有两种属性 数据属性 和 访问器属性 数据属性有4个特性 Configurable Enumerable Writable Value   前三个值的默认值都为false   ...

  7. 读书笔记 - js高级程序设计 - 第五章 引用类型

      引用类型 和 类 不是一个概念 用typeof来检测属性是否存在 typeof args.name == "string"  需要实验 访问属性的方法 .号和[] 一般情况下要 ...

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

    启用严格模式 "use strict" 这是一个 pragma 编译指示 让编码意图更清晰  是一个重要原则 5种简单数据类型 Undefined Null Boolean Num ...

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

    读书笔记 - js高级程序设计 - 第十三章 事件   canvas 具备绘图能力的2D上下文 及文本API 很多浏览器对WebGL的3D上下文支持还不够好   有时候即使浏览器支持,操作系统如果缺缺 ...

随机推荐

  1. Java获取汉字的大小写拼音码(汉字的拼音首字母)

    import java.io.UnsupportedEncodingException; /** * 获取拼音码 * * @author xmj * */ public class GetPinyin ...

  2. Android 12(S) 图像显示系统 - HWC HAL 初始化与调用流程

    必读: Android 12(S) 图像显示系统 - 开篇 接口定义 源码位置:/hardware/interfaces/graphics/composer/ 在源码目录下可以看到4个版本的HIDL ...

  3. 从 1.5 开始搭建一个微服务框架——日志追踪 traceId

    你好,我是悟空. 前言 最近在搭一个基础版的项目框架,基于 SpringCloud 微服务框架. 如果把 SpringCloud 这个框架当做 1,那么现在已经有的基础组件比如 swagger/log ...

  4. LMC7660即-5V产生电路

    LMC7660为小功率极性反转电源转换器,通过LMC7660电路产生-5V电压,其芯片管脚定义如下表所示. LMC7660负电压产生电路如下图所示. 其中6脚当供电电压大于等于5V时该脚必须悬空,当供 ...

  5. IO流原理及流的分类

    IO原理 I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于 处理设备之间的数据传输.如读/写文件,网络通讯等. Java程序中,对于数据的输入/输出操作以"流(st ...

  6. 常用类-LocalDate、LocalTime、LocalDateTime

    详情解释请看下方代码区 点击查看代码 @Test public void test1(){ //实例化:now()获取当前日期.时间.日期 + 时间 LocalDate localDate = Loc ...

  7. Java开发学习(十一)----基于注解开发bean作用范围与生命周期管理

    一.注解开发bean作用范围与生命周期管理 前面使用注解已经完成了bean的管理,接下来将通过配置实现的内容都换成对应的注解实现,包含两部分内容:bean作用范围和bean生命周期. 1.1 环境准备 ...

  8. 共享手机中的VXN流量给其他设备使用

    此篇博文讲的什么 不想看废话的,直接看这里就行了: 手机端(IOS,已越狱)装的传统的VXN,没法直接共享流量给其他设备用,可以在手机端开放个socketsserver,我现在用的ssh,它也能提供这 ...

  9. ArrayDeque(JDK双端队列)源码深度剖析

    ArrayDeque(JDK双端队列)源码深度剖析 前言 在本篇文章当中主要跟大家介绍JDK给我们提供的一种用数组实现的双端队列,在之前的文章LinkedList源码剖析当中我们已经介绍了一种双端队列 ...

  10. 如何用车辆违章查询API接口进行快速开发

    最近公司项目有一个车辆违章查询显示的小功能,想着如果用现成的API就可以大大提高开发效率,所以在网上的API商店搜索了一番,发现了 APISpace,它里面的车辆违章查询API非常符合我的开发需求. ...