【读书笔记】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();
则构造函数的执行顺序如下:
- 执行System.Object.Object()构造函数;
- 执行MyBaseClass.MyBaseClass()构造函数;
- 执行MyClass.MyClass()构造函数。
如果用以下方式实例化MyClass:
- MyClass myClass = new MyClass(4);
则构造函数的执行顺序如下:
- 执行System.Object.Object()构造函数;
- 执行MyBaseClass.MyBaseClass()构造函数;
- 执行MyClass.MyClass(int i)构造函数。
最后,使用如下方式实例化MyClass:
- MyClass myClass = new MyClass(4, 8);
则构造函数的执行顺序如下:
- 执行System.Object.Object()构造函数;
- 执行MyBaseClass.MyBaseClass()构造函数;
- 执行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);
这个时候构造函数的执行顺序如下:
- 执行System.Object.Object()构造函数;
- 执行MyBaseClass.MyBaseClass(int i)构造函数;
- 执行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);
这个时候构造函数的执行顺序如下:
- 执行System.Object.Object()构造函数;
- 执行MyBaseClass.MyBaseClass()构造函数;
- 执行MyClass.MyClass(int i,int j)构造函数;
- 执行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#高级编程 第四章 继承的更多相关文章
- 读书笔记 - js高级程序设计 - 第四章 变量 作用域 和 内存问题
5种基本数据类型 可以直接对值操作 判断引用类型 var result = instanceof Array 执行环境 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这 ...
- unix环境高级编程第四章笔记
文件和目录 start fstart lstart函数 一旦给出pathname, start函数就返回了与此命名文件有关的信息结构 #include <sys/start> int st ...
- UNIX系统高级编程——第四章-文件和目录-总结
文件系统: 以UNIX系统V文件系统为例: 磁盘分为区,每个分区都有自己的文件系统: i节点是固定长度的记录项,包含了文件的相关信息.目录项包含文件名和i节点号.stat结构中除文件名和i节点编号 ...
- 读书笔记 - js高级程序设计 - 第十一章 DOM扩展
对DOM的两个主要的扩展 Selectors API HTML5 Element Traversal 元素遍历规范 querySelector var body = document.query ...
- 读书笔记 - js高级程序设计 - 第七章 函数表达式
闭包 有权访问另一个函数作用域中的变量的函数 匿名函数 函数没有名字 少用闭包 由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存.过度使用闭包可能会导致内存占用过多,我们建议读者 ...
- 读书笔记 - js高级程序设计 - 第六章 面向对象的程序设计
EcmaScript有两种属性 数据属性 和 访问器属性 数据属性有4个特性 Configurable Enumerable Writable Value 前三个值的默认值都为false ...
- 读书笔记 - js高级程序设计 - 第五章 引用类型
引用类型 和 类 不是一个概念 用typeof来检测属性是否存在 typeof args.name == "string" 需要实验 访问属性的方法 .号和[] 一般情况下要 ...
- 读书笔记 - js高级程序设计 - 第三章 基本概念
启用严格模式 "use strict" 这是一个 pragma 编译指示 让编码意图更清晰 是一个重要原则 5种简单数据类型 Undefined Null Boolean Num ...
- 读书笔记 - js高级程序设计 - 第十五章 使用Canvas绘图
读书笔记 - js高级程序设计 - 第十三章 事件 canvas 具备绘图能力的2D上下文 及文本API 很多浏览器对WebGL的3D上下文支持还不够好 有时候即使浏览器支持,操作系统如果缺缺 ...
随机推荐
- 03 转换css元素的类别
03 转换css元素的类别 通过设置display属性 属性 作用 block 块级 inline 行内 inline-block 行内块级 接来下 就跟着小demo来学习吧! 不懂可以看看!!!什么 ...
- 4种方法教你如何查看java对象所占内存大小
摘要:本文讲述4种查看java对象所占内存大小的方法 本文分享自华为云社区<查看java对象所占内存大小>,作者:xiewenci. 计算java对象所占内存大小 1.使用jdk8自带AP ...
- Hdfs存储策略
一.磁盘选择策略 1.1.介绍 在HDFS中,所有的数据都是存在各个DataNode上的.而这些DataNode上的数据都是存放于节点机器上的各个目录中的,而一般每个目录我们会对应到1个独立的盘,以便 ...
- Day03 HTML标记
文本标题 <h1>一级标题</h1> <h2>二级标题</h2> <h3>三级标题</h3> <h4>四级标题< ...
- Trie 树进阶学习笔记
前言 没脑子选手发现自己什么都不会 ... \(\text{More and more vegetables, What should I do?}\) 正文 Trie 树简介 大概是人类的话都知道吧 ...
- JDBCTools 第一个版本
JDBCToolV1: package com.dgd.test; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax ...
- 线程池的概念&原理和线程池的代码实现
线程池:一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源.工作原理:可以用一张图来简洁明了说明: 合理利用线程池能够带来三个好处∶1.降低 ...
- vue封装原生的可预览裁剪上传图片插件H5,PC端都可以使用
思路:1.先做出一个上传的图片的上传区 <!-- 上传区 --> <label for="fileUp"> <div class="upBo ...
- C++指针和结构体基础知识
学习C++首先要回忆起C语言当中的指针和结构体知识,本文作者将通过一段代码来总结指针和结构体基础知识:指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址.就像其他变量或常量一样,您必须在使 ...
- NOI / 2.5基本算法之搜索-6044:鸣人和佐助详解
总时间限制: 1000ms 内存限制: 65536kB 题目 佐助被大蛇丸诱骗走了,鸣人在多少时间内能追上他呢? 已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置.地图上的每个位置都可以走到, ...