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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了读书笔记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);
    }
}

 

以上是关于读书笔记C#高级编程 第四章 继承的主要内容,如果未能解决你的问题,请参考以下文章

《C#高级编程》读书笔记

《C#高级编程》读书笔记(二十):核心XAML

《C#高级编程》读书笔记

《C#高级编程》读书笔记:关于数组

《C#高级编程》读书笔记:运算符

《C#高级编程》读书笔记:委托