(一)继承的类型

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. 如何通过WinDbg获取方法参数值

    引入 我们在调试的过程中,经常会通过查看方法的输入与输出来确定这个方法是否异常.那么我们要怎么通过 WinDbg 来获取方法的参数值呢? WinDbg 中主要包含三种命令:标准命令.元命令(以 . 开 ...

  2. python:**也不过如此嘛,这不也被我采集下来啦~

    前言 嗨喽!大家好呀,这里是小熊猫 知识点: 基本流程 fiddler抓包 开发环境: python 3.8 运行代码 pycharm 2021.2 辅助敲代码 requests 第三方模块 如果安装 ...

  3. python常见的错误提示处理

    python常见的错误有 NameError变量名错误 IndentationError代码缩进错误 AttributeError对象属性错误 TypeError类型错误 IOError输入输出错误 ...

  4. MIT 6.824 Lab2C Raft之持久化

    书接上文Raft Part B | MIT 6.824 Lab2B Log Replication. 实验准备 实验代码:git://g.csail.mit.edu/6.824-golabs-2021 ...

  5. 禁用Chrome自动更新

    删除下Update目录 C:\Program Files (x86)\Google\Chrome\

  6. selenium环境配置和八大元素定位

    一.环境配置 1.selenium下载安装 安装一:pip install selenium(多数会超时安装失败) 安装二:pip install -i https://pypi.tuna.tsing ...

  7. 浮点数(UVa11809)题解

    浮点数(UVa11809)题解 如题 计算机常用阶码-尾数的形式保存浮点数.如下所示,若阶码有6位,尾数有8位,可以表达的最大的浮点数为0.1111111112 * 2 ^ 1111112.注意小数点 ...

  8. Cow Picnic S

    题目描述: K(1≤K≤100)只奶牛分散在N(1≤N≤1000)个牧场.现在她们要集中起来进餐.牧场之间有M(1≤M≤10000)条有向路连接,而且不存在起点和终点相同的有向路.她们进餐的地点必须是 ...

  9. 2522-Shiro系列--使用缓存对认证session和授权Cache进行存储

    如何进行session的缓存? 原理: Shiro有1个类,AuthorizingRealm AuthenticatingRealm,里面有个获取认证信息的方法, AuthenticatingReal ...

  10. 快速新建并配置一个eslint+prettier+husky+commitlint+vue3+vite+ts+pnpm的项目

    前置准备 一台电脑 vscode pnpm vscode插件:ESLint v2.2.6及以上 vscode插件:Prettier - Code formatter v9.5.0及以上 vscode插 ...