【全栈计划 —— 编程语言之C#】总结深入面向对象三大特性之二 —— 继承性

简介: 【全栈计划 —— 编程语言之C#】总结深入面向对象三大特性之二 —— 继承性

面向对象三大特


继承 (Inheritance) 是面向对象语言中的重要特征之一。


在 C# 语言中仅支持单重继承,主要用于解决代码重用问题。

为了将继承关系灵活运地用到程序设计中,在 C# 语言中提供了接口来解决多重继承的关系。


在编程中灵活地使用类之间的继承关系能很好地利于重用代码和节省开发时间

也就是说,当A继承于B之后,A在拥有B含有的全部类成员的同时,还可以特立独行的拥有它自己的成员。


【举例】

我现在有一个Student类有如下属性:

姓名、性别、身份证号、联系方式、专业、年级

同时有个Teacher类有如下属性:

姓名、性别、身份证号、联系方式、职称、工资号

可以很明显的发现,姓名、性别、身份证号、联系方式这四个熟悉是在重复造轮子。

那么此时其实可以考虑声明一个Person类,在这个类中放置这四个属性,然后让Student类和Teacher类继承于Person类,那么Student类中只是需要额外声明专业和年级两个属性,Teacher类中只需要额外声明职称和工资号两个属性。一定程度上的减轻我们重复编码的动作。


继承使用的语法:

<访问修饰符> class <基类>
{
 ...
}
class <派生类> : <基类>
{
 ...
}

上述案例中的Person类在官方定义中是被称为基类(其实可能更习惯称为父类),Student类和Teacher类被称为Person类的派生类(也是通常称呼它俩是子类)


Objecte类


Object 类是 C# 语言中最原始、最重要的类。

不管是 C# 系统所提供的标准类,还是用户自行編写的类,都是从Object类直接或间接继承而来,它是类层次结构中的顶级类,即 C# 树型类层次结构的根。


当编码者没有明确指出当前类的父类的时候,默认是认为该类从Object类继承而来。


身为最原始父类的Object类在功能上比较有限的,其提供了四个常用方法:Equals方法、GetHashCode方法、GetType方法、ToString方法。


在实际开发中,咱们大多数需要去重写这个四个方法。


Objecte中的四大元老方法


① Equal方法

Equals 方法主要用于比较两个对象是否相等,如果相等则返回 True,否则返回 False。

如果是引用类型的对象,则用于判断两个对象是否引用了同一个对象。

至于判断是否为同一对象,我的理解是判断是不是同一个地址


知识点一:Equals 方法提供了两个,一个是静态的,一个是非静态的


静态的可以直接使用了,至于非静态的,则需要创建对象,通过对象调用此方法了。

Equals (object ol, object o2); //静态方法
Equals (object o); //非静态方法

【课堂演示】

首先创建 Person 类,在Person类中可以不必写任何代码,然后创建一个Test类,在这个类中,编写一个 Test_Equals的方法,用于测试如何比较两个对象是否相等。

最后在Main函数中创建Test类的对象,并调用Test_Equals方法。


测试效果:

微信图片_20221020144150.png

测试总结:

Equals用于引用类型的对象的比较时,比较的是其地址

测试代码:

  /// <summary>
    /// Main函数
    /// </summary>
    class Program
    {
        public static void Main(string[] args)
        {
            Test t = new Test();
            t.Test_Equals();
            Console.ReadKey();
        }
    }
    /// <summary>
       /// Person 类
       /// </summary>
        public class Person
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Sex { get; set; }
        }
    /// <summary>
        /// 获取引用类型地址,比较引用类型地址的方法
        /// </summary>
        public void Test_Equals()
        {
            try
            {
                Person pp = new Person();
                Person ee = new Person();
                Person tt = pp; //对象tt 是由 对象pp 直接赋值
                pp.Id = 99;
                pp.Name = "test";
                pp.Sex = "nan";
                // 获取对象的地址
                var addr2 = getMemory(pp);
                var addr3 = getMemory(ee);
                var addr4 = getMemory(tt);
                Console.WriteLine("对象pp的地址是" + addr2);
                Console.WriteLine("对象ee的地址是" + addr3);
                Console.WriteLine("对象tt的地址是" + addr4);
                Console.WriteLine();
                Console.ForegroundColor =  ConsoleColor.DarkRed;
                Console.WriteLine("Equals 方法主要用于比较两个对象是否相等,如果相等则返回 True,否则返回 False。\n" +
                    "下面切实对Equals(静态方法) 进行使用:");
                Console.ResetColor();
                Console.WriteLine("对象pp和对象ee是否相等:{0}",Equals(pp,ee));
                Console.WriteLine();
                Console.WriteLine("对象tt直接由对象pp赋值:");
                Console.WriteLine("对象pp和对象tt是否相等:{0}", Equals(pp, tt));
                Console.WriteLine();
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("Equals 方法主要用于比较两个对象是否相等,如果相等则返回 True,否则返回 False。\n" +
                    "下面切实对Equals(非静态方法) 进行使用:");
                Console.ResetColor();
                Console.WriteLine("对象pp和对象ee是否相等:{0}", ee.Equals(pp));
            }
            catch (Exception ex)
            {
            }
        }
        // 获取引用类型的内存地址方法    
        public string getMemory(object o) 
        {
            GCHandle h = GCHandle.Alloc(o, GCHandleType.WeakTrackResurrection);
            IntPtr addr = GCHandle.ToIntPtr(h);
            return "0x" + addr.ToString("X");
        }
    }

② GetHashCode方法

GetHashCode 方法返回当前 System.Object 的哈希代码,每个对象的哈希值都是固定的。

【课堂演示】

创建两个 Person 类的对象,并分别计算其哈希值。

测试效果:

微信图片_20221020144237.png

测试代码

class Program
    {
        public static void Main(string[] args)
        {
            //Test t = new Test();
            //t.Test_Equals();
            Person person1 = new Person();
            Person person2 = new Person();
            Console.WriteLine("person1对象的哈希值是:"+person1.GetHashCode());
            Console.WriteLine("person2对象的哈希值是:"+person2.GetHashCode());
            Console.ReadKey();
        }
    }

③ GetType方法

作用:

GetType 方法用于获取当前实例的类型,返回值为 System.Type 类型。

注意点:

GetType 方法不含任何参数,是非静态方法。


【课堂演示】

【实例】创建字整数类型的变量、浮点类型变量、符串类型的变量、以及 Person 类的对象,并分别使用 GetType 方法获取其类型并输出。


测试效果:

微信图片_20221020144346.png

测试代码

    class Program
    {
        public static void Main(string[] args)
        {
            int a = 520;
            double b = 13.14;
            string c = "1314520";
            Person d = new Person();
            Console.WriteLine(a.GetType());
            Console.WriteLine(b.GetType());
            Console.WriteLine(c.GetType());
            Console.WriteLine(d.GetType());
            Console.ReadKey();
        }
    }

④ ToString方法

作用:

ToString 方法返回一个对象实例的字符串,在默认情况下将返回类类型的限定名。

这个方法我觉得挺常用的,更多的是根据自己的需求,重写ToString方法,返回自己需求的字符串信息。


【课堂演示】

创建整数类型的变量、浮点类型变量以及 Object 类的对象和Person类型的对象,并分别使用 ToString 方法获取其字符串的表现形式并输出。

测试效果:

微信图片_20221020144427.png

测试总结:

可以浅总结出来,在没有重写的ToString 中,值类型输出的结果是当前的数值,对于应用类型,输出的是当前对象的类名称。

测试代码:

    class Program
    {
        public static void Main(string[] args)
        {
            Int32 a = 520;
            Object b = new Object();
            double c = 13.14;
            Person d = new Person();
            Console.WriteLine("值类型(Int32类型)的字符串的表现形式:{0}", a.ToString());
            Console.WriteLine("值类型(double类型)的字符串的表现形式:{0}", c.ToString());
            Console.WriteLine("引用类型(Object类型)字符串的表现形式:{0}", b.ToString()); 
            Console.WriteLine("引用类型(Person类型)字符串的表现形式:{0}", d.ToString());
            Console.ReadKey();
        }
    }

继承中的四个关键字


① 调用父类成员方法 —— base


1)继承关系中属性、方法之间的关系

当子类和父类定义了同名的方法,在子类中的对象是调用不到父类中的同名方法的,调用的是子类中的方法。

子类如果需要调用父类中的成员可以借助 base 关键字来完成,具体的用法如下:

base. 父类成员


用户在程序中会遇到 this 和 base 关键字,this 关键字代表的是当前类的对象,而 base 关键字代表的是父类中的对象。


在Student类和Teacher类都继承于Person类的情况,它们都有自己的Print方法用于输出信息。

微信图片_20221020144537.png

倘若不加base关键字,直接调用两个派生类中的Print方法,会得到如下结果:

在这里插入图片描述微信图片_20221020144602.png

在需要利用父类中方法时,此时需要base关键字的帮助了:微信图片_20221020144627.png

测试结果:微信图片_20221020144645.png

2)继承关系中构造器之间的关系

默认情况下,在子类的构造器中都会自动调用父类的无参构造器,如果需要调用父类中带参数的构造器才使用:base(参数)的形式。

【默认情况演示】

微信图片_20221020144700.png

测试结果:微信图片_20221020144727.png

【调用子类有参数构造器演示】微信图片_20221020144744.png

测试结果:

微信图片_20221020144801.png那么,倘若想调用父类的有参数构造器,就需要:base(参数)来实现了

微信图片_20221020144821.png

测试结果:微信图片_20221020144834.png

测试总结:

如果需要调用父类中带参数的构造器才使用:base(参数)的形式。


② 增加程序延展性 —— virtual 以及 abstract


1)virtual(虚方法)

virtual 关键字能修饰方法、属性、索引器以及事件等,用到父类的成员中

其语法形式如下:

//修饰属性
public  virtual  数据类型  属性名{get; set; }
//修饰方法
访问修饰符  virtual  返回值类型方法名
{
    语句块;
}

注意点:virtual 关键字不能修饰使用 static 修饰的成员。


virtual 关键字用于在基类中修饰方法。virtual的使用会有两种情况:


情况1:在基类中定义了virtual方法,但在派生类中没有重写该虚方法。那么在对派生类实例的调用中,该虚方法使用的是基类定义的方法。

微信图片_20221020144945.png

拓展:

此时在子类Student类中的方法是没有修饰符的,倘若加上new 关键字来使该子类中的方法隐藏,得到的结果是一样的

new 修饰符(C# 参考)

微信图片_20221020145004.png

咱们来浅看一下输出的结果:

image.png

测试代码:

       public class Person
        {
            public int Id { get; set; }=1001;
            public string Name { get; set; } = "张三";
            public string Sex { get; set; } = "男";
            public string Cardid { get; set; } = "0x1111";
            public string Tel { get; set; } = "13390782367";
            public Person()
            {
                //Console.WriteLine("这是父类Person中的无参数构造器");
            }
            public Person(string str)
            {
                 Console.WriteLine("这是父类Person中的有参数构造器,输入的参数是"+str);
            }
            public virtual void Print()
            {
                Console.WriteLine("编号:" + Id);
                Console.WriteLine("姓名:" + Name);
                Console.WriteLine("性别:" + Sex);
                Console.WriteLine("身份证号:" + Cardid);
                Console.WriteLine("联系方式:" + Tel);
            }
        }
 class Student:Person
    {
        public string Major { get; set; } = "计科技";
        public string Grade { get; set; } = "2001";
        public void Print()
        {
            Console.WriteLine("专业:" + Major);
            Console.WriteLine("年级:" + Grade);
        }
    }
 class Program
    {
        public static void Main(string[] args)
        {
            Person p = new Person();
            Console.ForegroundColor =  ConsoleColor.DarkRed;
            Console.WriteLine("这是父类Person自己的Print方法(虚方法)输出的内容:");
            p.Print();
            Console.ResetColor();
            Console.WriteLine();
            Console.ForegroundColor =  ConsoleColor.Cyan;
            Console.WriteLine("在子类Student不重写该虚方法的情况下:");
            Person stu1 = new Student();
            stu1.Print();
            Console.ResetColor();
            Console.ReadKey();
        }
    }

情况2:在基类中定义了virtual方法,然后在派生类中使用override重写该方法。那么在对派生类实例的调用中,该虚方法使用的是派生重写的方法。

微信图片_20221020145113.png

测试结果:image.png

那么总结来说,在对父类使用virtual关键字的时候,在其继承的子类声明另一个同名方法且没有使用override重写的时候,默认调用父类中的方法。

但是假如重写了,可以理解为子类方法更优秀了,那么系统就调用这个重写的方法。


2)abstract(抽象方法)


可以看成是没有实现体的虚方法。


抽象方法声明使用,是必须被派生类覆写的方法,抽象类就是用来被继承的;如果类中包含抽象方法,那么类就必须定义为抽象类,不论是否还包含其他一般方法;抽象类不能有实体的。


抽象方法的定义语法:

访问修饰符  abstract  方法返回值类型  方法名(参数列表);

【随堂演练】

创建抽象类 ExamResult,并在类中定义学号(Id)、数学 (Math)、英语 (English) 成绩的属性,定义抽象方法计算总成绩。

分别定义数学专业和英语专业的学生类继承抽象类 ExamResult,重写计算总成绩的方法并根据科目分数的不同权重计算总成绩。

其中,数学专业的数学分数占60%、英语分数占40%;英语专业的数学分数占40%、英语分数占60%。


测试结果:

微信图片_20221020145306.png

这个小练习蛮简单的,其作用也就仅限于熟悉上手定义抽象类和重写抽象方法

测试代码

 abstract class ExamResult
    {
        //学号
        public int Id { get; set; }
        //数学成绩
        public double Math { get; set; }
        //英语
        public double English { get; set; }
        //计算总成绩
        public abstract void Total();
    }
class MathMajor : ExamResult
    {
        public override void Total()
        {
            double total = Math * 0.6 + English * 0.4;
            Console.WriteLine("学号为"+Id+"的数学专业的同学的最终成绩为:"+total);
        }
    }
class EnglishMajor : ExamResult
    {
        public override void Total()
        {
            double tatol = Math * 0.4 + English * 0.6;
            Console.WriteLine("学号为"+Id+"的英语专业的同学的最终成绩为"+tatol);
        }
    }

总结:

在实际应用中,子类仅能重写父类中的虚方法或者抽象方法,当不需要使用父类中方法的内容时,将其定义成抽象方法,否则将方法定义成虚方法。


③ 声明密封类或密封方法 —— sealed


sealed 关键字的含义是密封的,使用该关键字能修饰类或者类中的方法,修饰的类被称为密封类、修饰的方法被称为密封方法。


密封方法必须出现在子类中,并且是子类重写的父类方法,即 sealed 关键字必须与 override 关键字一起使用。


【随堂演练】

创建一个计算面积的抽象类 AreaAbstract ,并定义抽象方法计算面积。


定义矩形类继承该抽象类,并重写抽象方法,将其定义为密封方法;

定义圆类继承该抽象类,并重写抽象方法,将该类定义为密封类。


此处还可以积累C#如何输出指定位数的浮点数。

参考文章


参考代码

abstract class AreaAbstract
    {
        public abstract void Total();
    }
class Rectangle : AreaAbstract
    {
        //设置自己的长和宽的熟悉
        public double Len { get; set; }
        public double Wid { get; set; }
        //重写抽象方法,并定义为密封方法
        public sealed override void Total()
        {
            Console.WriteLine("该矩形的面积是"+Len*Wid);
        }
    }
sealed class Round : AreaAbstract
    {
        //计算圆面积的半径熟悉
        public double R { get; set; }
        public override void Total()
        {
            //积累C# 浮点类型输出指定位数
            Console.WriteLine("该圆的面积是{0:f3}",R*Math.PI);
        }
    }

Round 类不能被继承:微信图片_20221020145411.pngRectangle 类中的 Area 方法不能被重写。微信图片_20221020145438.png

总结


① C#仅支持单继承,用于解决代码重用问题。C#使用接口解决多重继承。

② Object 类是 C# 语言中最原始、最重要的类。旗下有Equals()、GetHashCode()、GetTyep()、ToString()四个方法,我们在实际使用的时候,大多数是需要重写这四个方法的。

③ 在子类中想要使用父类中的某个方法,需要使用base关键字

④ 子类仅能重写父类中的虚方法或者抽象方法,当不需要使用父类中方法的内容时,将其定义成抽象方法,否则将方法定义成虚方法。


相关文章
|
11天前
|
编译器 C# 开发者
C# 9.0 新特性解析
C# 9.0 是微软在2020年11月随.NET 5.0发布的重大更新,带来了一系列新特性和改进,如记录类型、初始化器增强、顶级语句、模式匹配增强、目标类型的新表达式、属性模式和空值处理操作符等,旨在提升开发效率和代码可读性。本文将详细介绍这些新特性,并提供代码示例和常见问题解答。
28 7
C# 9.0 新特性解析
|
1月前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
116 65
|
10天前
|
C# 开发者
C# 10.0 新特性解析
C# 10.0 在性能、可读性和开发效率方面进行了多项增强。本文介绍了文件范围的命名空间、记录结构体、只读结构体、局部函数的递归优化、改进的模式匹配和 lambda 表达式等新特性,并通过代码示例帮助理解这些特性。
22 2
|
2月前
|
编译器 C# Android开发
震惊!Uno Platform 与 C# 最新特性的完美融合,你不可不知的跨平台开发秘籍!
Uno Platform 是一个强大的跨平台应用开发框架,支持 Windows、macOS、iOS、Android 和 WebAssembly,采用 C# 和 XAML 进行编程。C# 作为其核心语言,持续推出新特性,如可空引用类型、异步流、记录类型和顶级语句等,极大地提升了开发效率。要在 Uno Platform 中使用最新 C# 特性,需确保开发环境支持相应版本,并正确配置编译器选项。通过示例展示了如何在 Uno Platform 中应用可空引用类型、异步流、记录类型及顶级语句等功能,帮助开发者更好地构建高效、优质的跨平台应用。
203 59
|
29天前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
33 1
|
2月前
|
安全 C#
C# 面向对象编程的三大支柱:封装、继承与多态
【9月更文挑战第17天】在C#中,面向对象编程的三大支柱——封装、继承与多态,对于编写安全、可维护、可复用的代码至关重要。封装通过访问修饰符和属性保护数据;继承允许子类继承父类的属性和方法,实现代码复用和多态;多态则提高了代码的灵活性和通用性。掌握这三大概念能显著提升C#编程能力,优化开发效率和代码质量。
|
2月前
|
存储 C#
C# 一分钟浅谈:继承与多态性的实践
【9月更文挑战第2天】本文从基础入手,详细介绍了面向对象编程中继承与多态性的核心概念。通过 `Animal`、`Dog` 和 `Cat` 类的示例代码,展示了如何利用继承重用代码及多态性实现不同对象对同一方法的多样化响应,帮助读者更好地理解和应用这两个重要概念,提升面向对象编程能力。
43 3
|
3月前
|
开发框架 .NET 编译器
总结一下 C# 如何自定义特性 Attribute 并进行应用
总结一下 C# 如何自定义特性 Attribute 并进行应用
|
3月前
|
C#
C#中的类和继承
C#中的类和继承
43 6
|
3月前
|
图形学 C# 开发者
全面掌握Unity游戏开发核心技术:C#脚本编程从入门到精通——详解生命周期方法、事件处理与面向对象设计,助你打造高效稳定的互动娱乐体验
【8月更文挑战第31天】Unity 是一款强大的游戏开发平台,支持多种编程语言,其中 C# 最为常用。本文介绍 C# 在 Unity 中的应用,涵盖脚本生命周期、常用函数、事件处理及面向对象编程等核心概念。通过具体示例,展示如何编写有效的 C# 脚本,包括 Start、Update 和 LateUpdate 等生命周期方法,以及碰撞检测和类继承等高级技巧,帮助开发者掌握 Unity 脚本编程基础,提升游戏开发效率。
79 0