《C#高级编程》【第四章】继承 -- 学习笔记

计算机程序,在很大的程度上是为了描述和解决现实问题。在面向对象语言中的类很好的采用了人类思维中抽象和分类的方法,类和对象的关系很好的反映了个体与同类群体的共同特征的关系。但是在诸多共同点之下还是存在着些许差异。于是面向对象语言中设计了继承机制,允许我们在保持原有类特性的基础上,进行拓展。由于类的继承和派生机制的引入,使得代码的重用性和可扩充性大大提高。利用这个机制我们还可以站在巨人的肩膀上就行开发---利用别人写好的类进行扩充,这样又可以提高我们的开发效率。在派生新类的过程一般来说有三个步骤:吸收基类成员,改造基类成员,添加新成员。

继承的概念说完,我们先看看C#中有哪些继承类型(不多说直接上图)。

在粗略的了解C#的继承类型之后,现在我们就来详细的看看。

在C#中继承方式默认为公共继承(public),所以不需要在基类名前添加限定符,这点与C++有些不同。在C++中有三种继承方式(public, protected, private),相比之下C#的继承方式就显得简洁多了。现在我们看看具体是如何继承的,语法如下:

①类与类之间的继承

[访问权限修饰符] class [自定义类名] : [基类名]
{
	//类体
}

②类(结构)与接口之间的继承

[访问权限修饰符] class [自定义类名] : [接口1的名称],[接口2的名称]
{
	//类体
}

(如果是结构,只要将关键字class换成struct就变成结构继承接口)

③混合继承

[访问权限修饰符] class [自定义类名] : [基类名],  [接口1的名称],[接口2的名称]
{
	//类体
}

(注意:如果在类定义中没有指定基类,C#编译器就和假定System.Object为基类,可以简写为object)

我们之前说派生新类有三个步骤,现在我们就展开的说一下:

1、对于吸收基类成员,其实就是对基类继承下来的成员不做任何改变。

2、对于改造基类成员:

原有类方法的处理方式对新类不合适,此时我们就需要在新类中对继承来的方法进行改造。又根据改造方式的不同可分为:虚方法和隐藏方法。

<1>虚方法

对从基类继承来的方法,进行改造(也就是重写)。

具体步骤:先把基类的函数声明为virtual,语法如下:

[访问权限修饰符] virtual [函数返回值类型] 函数名(参数表)
{
//函数体
}

在子类中,重写该函数时,使用override关键字,语法如下:

[访问权限修饰符] override [函数返回值类型] 函数名(参数表)
{
//重写的函数体
}

(注意:成员字段和静态函数都不能声明为virtual)

<2>隐藏方法

隐藏方法其实不能算做真正意义上的改造,只是在子类中重新定义了一个与原方法同名的方法。然后在把继承的方法隐藏。语法如下:

[访问权限修饰符] new [函数返回值类型] 函数名(参数表)
{
//重写的函数体
}

现在我们看看,这两种方式的改造有什么区别。比如说我们要继承的方法为A。那么可以这样简单的理解,在子类中,虚方法实际上是将继承的方法A删除,然后再用子类中新定义的方法A去替代原有的方法A。换而言之,使用虚方法(重写)时,子类中就只有一个方法A(这个方法A也就是我们重写的)。用上面的逻辑我们就可以得到:使用隐藏方法时,子类中有两个方法A(一个是从父类继承的,一个是在子类中新定义的)。那么在向上转型的时,我们在调用方法A的过程中,对于重写,我们调用的将是子类的方法A。对于隐藏方法,我们将是调用父类的方法A。

3、添加新成员,这个步骤是对原有类进行扩充,其添加成员的方式和普通类没有区别。

新类中,我们有时需要调用方法的基类版本,语法:base.<方法名称>(参数列表)

(注意:这个语法可以调用基类中的任何方法,不必从同一个方法的重载中去调用)

抽象类和抽象函数,这个有点不好解释。我们以汽车为例:抽象类就相当于所有车共有的属性。继承这些属性,对其进行扩展,那么我们就可以得到各种各样的车了。然而抽象函数相当于车具有的属性。声明抽象类和抽象函数使用abstract。

(注意:抽象类不能实例化,抽象函数不能直接实现,必须要在非抽象类的派生类进行重写)

我们有时出于商业目的或者是其他原因不希望自己编写的类,在其作用域之外的地方被调用。于是我们就引入了密封类和密封方法的概念。我们使用关键字sealed来声明密封类或密封方法。对于类,则表示不能继承该类,对于方法,表示不能重写。

在之前我们知道了单个类的构造函数工作过程现在我们看看派生类的构造函数,先上图

构造函数的执行顺序,最先调用的总是基类的构造函数。(其实和递归调用过程十分类似)

(注意:如果在子类中显式的调用基类的构造函数,那么编译器默认调用的基类构造函数为无参的。也就是说如果基类中不存在无参构造函数,那么编译器就会报错。)

我们回过头来再看看接口,其实你会发现,接口其实与抽象类有一点类似。不同之处在于接口中只能包含成员签名。接口不能有构造函数,也不能有字段,也不允许包含运算符重载。命名规则,定义接口名称时,以大写字母I开头,这样我们一见到接口名称就知道这是接口。另外我们使用interface关键字定义接口其语法如下:

[访问权限修饰符] interface 接口名
{
//接口体
}

在接口之间的继承,实际上就是一个加法的过程,也就是说继承父接口之后得到的子接口,其内部一定是大于等于父接口。接口还有一个强大的功能:接口引用。为什么说它强大呢?因为接口引用可以引用任何实现该接口的类。比如:我们可以构造数组,让数组的每个元素都是不同的类:

//ITest为接口,CTest1, CTest2为实现该接口的类
ITest[] t = new ITest[2];
t[0] = new CTest1();
t[1] = new CTest2();

这样的话,数组的每个元素都成为了不同类的类对象。

(注意:接口引用不能引用除上述之外的的类,否则编译器将会报错。)

利用这个特点,我们还可以有这样的用途:在某个方法中需要CTest1或CTest2作为参数时,其方法内容一致,那么这时,我们只要这样:

public void fun(ITest temp)
{
 //函数体
}

那么此时参数可以是CTest1,也可以是CTest2。换而言之,我们不必知道其引用的是什么类型,我们只需知道其对象实现了ITest的接口就够了。

以上就是C#中的继承机制。

时间: 2024-12-20 19:51:01

《C#高级编程》【第四章】继承 -- 学习笔记的相关文章

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

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

C#高级编程 25.5 System.Transactions学习笔记

在.NET 1.x中,基本上是通过ADO.NET实现对不同数据库访问的事务..NET 2.0增加了System.Transactions名称空间,为.NET应用程序带来了一个新的事务变成模型. 所有的事务组件或者类型均定义在System.Transactions程序集中的System.Transactions命名空间下,我们直接称基于此的事务为System.Transactions事务. System.Transactions事务变成模型使我们可以显式(通过System.Transactions

第四章 shell学习之sed命令和awk编程

sed命令 sed只是对缓冲区中原始文件的副本进行编辑,不改变源文件,所以要保存则要重定向到另一个文件 sed三种方式: 1.sed [选项] 'sed命令' 输入文件 2.sed [选项] -f sed脚本文件 输入文件 3../sed脚本文件 输入文件 其中3的sed脚本文件要以#! bin/sed -f等开头 选项: -n 不打印所有行到标准输出,默认先打印匹配的再打印所有 -e 关联多个sed命令 -f 调用sed脚本文件 定位文本: x x为指定行号 x,y 从x到y行 /patter

C#中面向对象编程机制之继承学习笔记

继承反应了类和类之间的关系. 世界上很多事物都是有共性的,共性的那一部分我们就抽象为基类,用于派生其它类,这样提高了代码的复用性,使得代码的结构清晰易读,而且易于代码的扩展和维护. C#的继承只能继承自一个基类,这一点不同于C++的继承. C#的继承具有传递性,即B继承自A,C继承自B,则C具有A的所有特性. C#的继承隐式为public的. 假如不在派生类构造器中显示调用一个基类构造器,编译器会自动插入对基类的默认构造器的一个调用,然后才会执行派生类构造器中的代码, 如果基类没有默认的构造器,

十四、Android学习笔记_Android回调函数触发的几种方式 广播 静态对象

一.通过广播方式: 1.比如登录.假如下面这个方法是外界调用的,那么怎样在LoginActivity里面执行登录操作,成功之后在回调listener接口呢?如果是平常的类,可以通过构造函数将监听类对象传入即可.但是在Activity中不能传递监听对象,所以考虑使用广播来实现. public void login(final LoginOnClickListener listener) { Intent intent = new Intent(context, LoginActivity.clas

java MySQL数据库编程 第四章 高级查询(二)

第四章 高级查询(二) (1)通过在子查询中使用EXISTS子句,可以对子查询中的行是否存在进行检查.子查询可以出现在表达式出现的如何位置 (2)子查询语句可以嵌套在SQL语句中任何表达式出现的位置. 一.EXISTS子查询 1.使用EXISTS语句判断该数据库对象是否存在: DROP TABLE IF EXISTS temp; 2. EXISTS作为WHERE语句的子查询: SELECT .....FROM 表名 WHERE EXISTS(子查询); 3. EXISTS关键字后面的参数是一个任

ASP.NET MVC5 高级编程 第5章 表单和HTML辅助方法

参考资料<ASP.NET MVC5 高级编程>第5版 第5章 表单和HTML辅助方法 5.1 表单的使用 5.1.1 action 和 method 特性 默认情况下,表单发送的是 HTTP Post 请求 EF 对于外键关系,数据库名称等也有约定.这些约定取代了以前需要提供给一个关系对象映射框架的所有映射和配置. GET 方法:GET 请求的所有参数都在URL中,因此可以为GET 请求建立书签. POST 方法:浏览器把输入值放入 HTTP 请求的主体中. 5.2 辅助方法 可以通过视图的H

ASP.NET MVC5 高级编程 第3章 视图

参考资料<ASP.NET MVC5 高级编程>第5版 第3章 视图 3.1 视图的作用 视图的职责是向用户提供界面. 不像基于文件的框架,ASP.NET Web Forms 和PHP ,视图本身并不被访问,浏览器,并不能直接指向一个视图并渲染它.相反视图被控制器渲染,因为控制器提供了渲染所需要的数据. 一般情况下,控制器需要向视图提供一些信息,所以这会传递一个数据转移对象,叫做模型.完成这一过程需要两部分操作,其中一个是检查由控制器提交的模型对象,另一个是将其内容转化为HTML格式. 3.2

ASP.NET MVC5 高级编程 第2章 控制器

参考资料<ASP.NET MVC5 高级编程>第5版 第2章 控制器 控制器:响应用户的HTTP 请求,并将处理的信息返回给浏览器. 2.1 ASP.NET MVC 简介 MVC 模式中的控制器(controller)主要负责响应用户的输入,并且在响应时修改(Model).通过这种方式,MVC 中的Controller 主要关注的是应用程序流,输入数据的处理,以及对相关视图(View)数据来源的提供 MVC 模式中 URL 首先告诉路由去实例化哪个控制器,调用哪个方法,并为该方法提供需要的参数

读高性能JavaScript编程 第四章 Conditionals

if else 和 switch    &&    递归 if else 和 switch 一般来说,if-else 适用于判断两个离散的值或者判断几个不同的值域.如果判断多于两个离散值,switch表达式将是更理想的选择. 如同 我们在写sql 总习惯把可以过滤掉更多的where 放在前面一样,当 if else 判断的离散值较多时也因该这么干. 二分搜索法: if (value == 0){ //要优化的代码 return result0; } else if (value == 1)