十四、重写与多态

news2024/11/24 17:43:13

重写、多态

上一讲是,子类对父类横向上的扩展

这一讲是,子类对父类纵向上的扩展

在这里插入图片描述

方法重写

使用override关键字重写父类的方法

将父类原本方法的逻辑更新成新版本的逻辑

注:仅能重写可见的父类成员,并且重写要保持签名一致。

签名一致:对函数来说即,返回类型,名称,参数列表

python中对象有类型,但是变量是没有类型的,变量的类型永远是跟着对象走的。在Python中没有代差,重写就看不到多态的效果。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OverrideExample {
    internal class Program {
        static void Main(string[] args) {
            /* 重写表示:
             * 在继承链上不同的对象实例调用同一个方法,
             * 就会触发相对应的版本的方法
             */
            var car = new Car();
            car.Run();//触发的是Car版本的Run方法
            //Car is running.

            var vehicle = new Vehicle();
            vehicle.Run();//触发的是基类版本的Run方法
            //I'm running.
        }
    }
    class Vehicle {
        /* virtual关键字
         * 表示该方法可被子类重写
         */
        public virtual void Run() {
            Console.WriteLine("I'm running.");
        }
    }
    class Car : Vehicle {
        /* override关键字
 * 表示重写父类的方法
 * 此时Car这个类中,只有一个Run方法,就是这个类体中的那个重写了的方法
 */
        public override void Run() {
            Console.WriteLine("Car is running.");
        }
    }
}

注:当一个类重写了父类的方法后,再被其他类所继承,那么其他类实际上继承的是Car版本的重写后的方法。

using System;

namespace OverrideExample {
    internal class Program {
        static void Main(string[] args) {
            var raseCar = new RaseCar();
            raseCar.Run();
            //Car is Running.
            Console.ReadLine();
        }
    }
    class Vehicle {
        public virtual void Run() {
            Console.WriteLine("I'm running.");
        }
    }
    class Car : Vehicle {
        public override void Run() {
            Console.WriteLine("Car is running.");
        }
    }
    class RaseCar : Car {
        /* 此时有子类继承Car
         * 那么Rase类继承的是Car版本的Run方法
         */
    }
}

方法隐藏

如果签名相同的方法在基类和派生类中都进行了声明,但是该方法没有分别声明为virtualoverride,派生类就会隐藏基类方法(要使用new关键字进行声明)。隐藏的话,也就是看不到了,实际这个方法还存在。

也就是说,派生类中看不到和基类同名的那个方法了。

using System;

namespace OverrideExample {
    internal class Program {
        static void Main(string[] args) {
            // 父类引用指向子类对象
            Animal a = new Dog();
            // 调用非虚方法,根据引用的静态类型,调用父类的方法
            a.Eat(); // 输出 Animal eats
                     // 调用虚方法,根据对象的动态类型,调用子类的方法
            a.Sleep(); // 输出 Dog sleeps
            Console.ReadKey();
        }
    }
    // 基类
    public class Animal {
        // 非虚方法
        public void Eat() {
            Console.WriteLine("Animal eats");
        }
        // 虚方法
        public virtual void Sleep() {
            Console.WriteLine("Animal sleeps");
        }
    }

    // 子类
    public class Dog : Animal {
        /* 虽然继承了父类的同名方法,但是因为子类中也有同名的方法,
         * 所以子类会隐藏父类的同名的方法,也就是说子类见不到父类的方法
         * 表现在,子类类型引用的对象访问不到父类的同名方法。
         * 
         * 隐藏父类的非虚方法
         */
        //public new void Eat()
        //这两者是等同的
        public void Eat() {
            Console.WriteLine("Dog eats");
        }
        // 重写父类的虚方法
        public override void Sleep() {
            Console.WriteLine("Dog sleeps");
        }
    }
}

总结一下:

如果使用派生类声明的对象,调用隐藏方法会调用派生类的,如果使用基类声明对象,那么就会调用基类的隐藏方法。

三、虚方法与隐藏方法的区别

1、重写和隐藏的定义
重写:继承时发生,在派生类中重新定义基类中的方法,派生类中的方法和基类的方法是一样的 。例如:基类方法声明为virtual(虚方法),派生类中使用override声明此方法的重写。

隐藏:基类方法不做声明(默认为非虚方法),在派生类中使用new声明此方法的隐藏(new可写可不写)。

2、重写和隐藏的区别
重写(virtaul)时,定义的变量为基类或派生类, 赋值为派生类时,皆调用基类的重写方法(会从派生类中查找有重写则调用 ,没则调用基类方法)。
隐藏(new)时,定义的变量为基类,则调用基类的方法(不管赋值是派生类还是基类),定义的变量为派生类则调用派生类的方法。
————————————————

原文链接:https://blog.csdn.net/qq_44034384/article/details/106652112

多态

主要表现在方法和属性上面。

父类的同一种行为,在不同的子类上有不同的实现,这种实现叫做多态。

C#中的多态是指同一个接口或方法,使用不同的对象或参数,可以实现不同的功能或行为。多态可以提高代码的灵活性和可扩展性,实现对象之间的解耦。

C#中的多态可以分为两种:

  • 静态多态:也叫编译时多态,是指在编译时就确定了函数的调用。静态多态主要通过函数重载运算符重载来实现。函数重载是指在同一个类中,可以定义多个同名但参数不同的函数,根据传入的参数类型和个数来决定调用哪个函数。运算符重载是指可以为用户自定义的类型重新定义运算符的含义和行为,使得运算符可以用于操作这些类型的对象。
  • 动态多态:也叫运行时多态,是指在运行时才确定了函数的调用。动态多态主要通过虚方法抽象类接口来实现。虚方法是指在父类中用virtual关键字声明的方法,可以在子类中用override关键字重写,实现不同的功能。抽象类是指用abstract关键字声明的类,不能被实例化,只能作为基类,包含抽象方法和非抽象方法。抽象方法是指在抽象类中用abstract关键字声明的没有方法体的方法,必须在子类中重写。接口是指用interface关键字声明的一种特殊的抽象类,只包含抽象成员,不能包含字段和构造函数,可以被类实现,实现类必须实现接口中的所有成员。

下面的例子主要是,通过虚方法来展现多态性。最下面有个综合示例。

using System;

namespace OverrideExample {
    internal class Program {
        static void Main(string[] args) {
            /* 多态:
             * 使用一个父类的变量,引用一个子类的实例
             * 使用这个变量调用被重写的成员的时候,
             * 总是能调用到 与所引用的对象实例相对应的 版本
             */
            Vehicle veh = new Car();
            veh.Run();//调用的Car类的版本

            Vehicle veh2 = new RaseCar();
            veh2.Run();//调用到RaseCar类的版本

            Console.ReadLine();
        }
    }
    class Vehicle {
        public virtual void Run() {
            Console.WriteLine("I'm running.");
        }
    }
    class Car : Vehicle {
        public override void Run() {
            Console.WriteLine("Car is running.");
        }
    }
    class RaseCar : Car {
        public override void Run() {
            Console.WriteLine("Rase Car is running.");
        }
    }
}
using System;

namespace OverrideExample {
    internal class Program {
        static void Main(string[] args) {
            /* 多态:
             * 使用一个父类的变量,引用一个子类的实例
             * 使用这个变量调用被重写的成员的时候,
             * 总是能调用到 与所引用的对象实例相对应的 版本
             */
            Vehicle veh = new Car();
            veh.Run();//调用的Car类的版本

            Vehicle veh2 = new RaseCar();
            veh2.Run();//调用到RaseCar类的版本

            Console.ReadLine();
        }
    }
    class Vehicle {
        public virtual void Run() {
            Console.WriteLine("I'm running.");
        }
    }
    class Car : Vehicle {
        public override void Run() {
            Console.WriteLine("Car is running.");
        }
    }
    class RaseCar : Car {
        public override void Run() {
            Console.WriteLine("Rase Car is running.");
        }
    }
}

综合例子:

using System;

namespace OverrideExample {
    internal class Program {
        static void Main(string[] args) {
            // 创建一个Shape数组,存放不同的Shape对象
            Shape[] shapes = new Shape[2];
            shapes[0] = new Circle(5, "red");
            shapes[1] = new Rectangle(10, 8, "blue");

            // 遍历数组,调用每个对象的Area和Draw方法
            foreach (Shape shape in shapes) {
                Console.WriteLine($"The area is {shape.Area()}");
                shape.Draw();
            }

            // 创建一个IColor数组,存放不同的IColor对象
            IColor[] colors = new IColor[2];
            colors[0] = new Circle(5, "red");
            colors[1] = new Rectangle(10, 8, "blue");

            // 遍历数组,调用每个对象的GetColor方法
            foreach (IColor color in colors) {
                Console.WriteLine($"The color is {color.GetColor()}");
            }
        }
    }
    // 定义一个抽象类Shape,包含一个抽象方法Area和一个虚方法Draw
    public abstract class Shape {
        public abstract double Area();
        public virtual void Draw() {
            Console.WriteLine("Drawing a shape.");
        }
    }

    // 定义一个接口IColor,包含一个抽象方法GetColor
    public interface IColor {
        string GetColor();
    }

    // 定义一个Circle类,继承自Shape类,实现IColor接口,重写Area和Draw方法,实现GetColor方法
    public class Circle : Shape, IColor {
        private double _radius;
        private string _color;

        public Circle(double radius, string color) {
            _radius = radius;
            _color = color;
        }

        public override double Area() {
            return Math.PI * _radius * _radius;
        }

        public override void Draw() {
            Console.WriteLine("Drawing a circle.");
        }

        public string GetColor() {
            return _color;
        }
    }

    // 定义一个Rectangle类,继承自Shape类,实现IColor接口,重写Area和Draw方法,实现GetColor方法
    public class Rectangle : Shape, IColor {
        private double _length;
        private double _width;
        private string _color;

        public Rectangle(double length, double width, string color) {
            _length = length;
            _width = width;
            _color = color;
        }

        public override double Area() {
            return _length * _width;
        }

        public override void Draw() {
            Console.WriteLine("Drawing a rectangle.");
        }

        public string GetColor() {
            return _color;
        }
    }
}

重写属性

using System;

namespace OverrideExample {
    internal class Program {
        static void Main(string[] args) {
            Vehicle veh = new Vehicle();
            veh.Run();
            Console.WriteLine(veh.Speed);

            Vehicle car = new Car();
            car.Run();
            Console.WriteLine(car.Speed);

            Console.ReadLine();
        }
    }
    class Vehicle {
        private int _speed;
        public virtual int Speed { 
            get => _speed; 
            set => _speed = value; 
        }
        public virtual void Run() {
            Console.WriteLine("I'm running.");
            _speed = 100;
        }
    }
    class Car : Vehicle {
        private int _rpm;
        public override void Run() {
            Console.WriteLine("Car is running.");
            _rpm = 5000;
        }
        public override int Speed { 
            get => _rpm / 100; 
            set => _rpm = value * 100; 
        }
    }
    class RaseCar : Car {
        public override void Run() {
            Console.WriteLine("Rase Car is running.");
        }
    }
}

总结

方法隐藏是指当子类声明了一个与父类签名相同的非虚方法时,会隐藏父类中的同名非虚方法。这种情况下,调用该方法时,会根据引用的静态类型来决定调用哪个类的方法,而不是根据对象的动态类型。

这与方法重写不同,方法重写是指当子类重写了父类的虚方法时,会覆盖父类中的同名虚方法。这种情况下,调用该方法时,会根据对象的动态类型来决定调用哪个类的方法,而不是根据引用的静态类型。

无论是重写基类的方法还是重写基类的属性,这两者都必须是 virtualabstractoverride

不能重写非虚的、静态的方法或属性

// 基类
public class Animal
{
    // 非虚方法
    public void Eat()
    {
        Console.WriteLine("Animal eats");
    }
    // 虚方法
    public virtual void Sleep()
    {
        Console.WriteLine("Animal sleeps");
    }
}

// 子类
public class Dog : Animal
{
    // 隐藏父类的非虚方法
    public new void Eat()
    {
        Console.WriteLine("Dog eats");
    }
    // 重写父类的虚方法
    public override void Sleep()
    {
        Console.WriteLine("Dog sleeps");
    }
}

// 测试
public class Test
{
    public static void Main(string[] args)
    {
        // 父类引用指向子类对象
        Animal a = new Dog();
        // 调用非虚方法,根据引用的静态类型,调用父类的方法
        a.Eat(); // 输出 Animal eats
        // 调用虚方法,根据对象的动态类型,调用子类的方法
        a.Sleep(); // 输出 Dog sleeps
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1494682.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第五节 JDBC驱动程序类型

JDBC驱动程序是什么? JDBC驱动程序在JDBC API中实现定义的接口,用于与数据库服务器进行交互。 例如,使用JDBC驱动程序,可以通过发送SQL或数据库命令,然后使用Java接收结果来打开数据库连接并与数据库进行交互。 JDK…

Java中常见的 IO 方式

冯诺依曼结构中计算机结构被分为 5 大部分:运算器、控制器、存储器、输入设备、输出设备,输入设备向计算机输入数据,输出设备接收计算机输出的数据。从计算机结构的视角来看的话, I/O 描述了计算机系统与外部设备之间通信的过程。…

JMeter VS RunnerGo :两大主流性能测试工具对比

说起JMeter,估计很多测试人员都耳熟能详。它小巧、开源,还能支持多种协议的接口和性能测试,所以在测试圈儿里很受欢迎,也是测试人员常用的工具,不少企业也基于JMeter建立起自己的自动化测试能力,提升工作效…

体验Node.js的安装和运行

Node.js概述 Node.js是一个基于Chrome V8引擎的JavaScript运行环境。它允许JavaScript代码在服务器端运行,使得开发人员可以使用同一种语言编写前端和后端的代码。Node.js使用事件驱动、非阻塞I/O模型,使其轻量且高效,非常适合数据密集型的实…

java性能调优面试,程序员Java视频

前言 很多人在打算自学Java的时候或许都没有思考过Java的应用方向,市场需要什么样的人才,企业对你有什么要求等等一系列问题;或许你只听说这个行业薪资高…然后懵懵懂懂的上路,不得要害。 对于零基础来学习Java,你或…

怎样才能考上南京大学的计算机研究生?

附上南大与同层次学校近四年的分数线对比,整体很难 添加图片注释,不超过 140 字(可选) 添加图片注释,不超过 140 字(可选) 我本人是双非一本的计算机专业,23考研一战上岸的&#xf…

【C语言】还有柔性数组?

前言 也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。C99中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。 欢迎关注个人主页:逸狼 创造不易&#xf…

LinkedList集合源码分析

LinkedList集合源码分析 文章目录 LinkedList集合源码分析一、字段分析二、构造函数分析三、方法分析四、总结 看到实现了Deque 就要联想到这个数据结构肯定是属于双端队列了。Queue 表示队列,Deque表示双端队列。 一、字段分析 LinkedList 字段很少,就…

Vector Search和专用Search Nodes:现已正式发布

我们非常高兴地推出了 Atlas Vector Search 和 Search Nodes 的正式发布版本 (GA),为 Atlas 平台增添了更多价值。 自从在公开预览版中发布 Atlas Vector Search 和带有 Search Nodes 的专用基础架构以来,我们注意到,对于使用向量优化搜索节…

【数据集】MSWEP(多源加权集合降水)再分析数据

MSWEP全球降水数据 数据概述数据下载参考数据概述 MSWEP(Multi-Source Weighted-Ensemble Precipitation)降水数据集是一种高分辨率、全球覆盖的降水数据产品,它融合了多种来源的降水信息,包括卫星遥感数据、雷达观测、地面气象站观测数据以及数值天气预报模型的输出。MSW…

C switch 语句

一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。 语法 C 语言中 switch 语句的语法: switch(expression){case constant-expression :statement(s);break; /* 可选的 */ca…

Redis线程模型解析

引言 Redis是一个高性能的键值对(key-value)内存数据库,以其卓越的读写速度和灵活的数据类型而广受欢迎。在Redis 6.0之前的版本中,它采用的是一种独特的单线程模型来处理客户端的请求。尽管单线程在概念上似乎限制了其扩展性和并…

C++ 之AVL树

AVL树 AVL树的基本概念AVL树的平衡因子、AVL树的旋转avl的双旋旋转的4种情况 AVL树的基本概念 AVL树的平衡因子、 AVL树的旋转 当平衡因子的高度差过大时,就要选择。所谓的选择其实也是一种压扁的操作 在本例中 新插入的蓝色结点使得不在平衡。 我们看上图就能…

【CSP试题回顾】201604-1-折点计数

CSP-201604-1-折点计数 解题代码 #include <iostream> #include <vector> #include <algorithm> using namespace std;int n, pointSum;int main() {cin >> n;vector<int>myData(n);for (int i 0; i < n; i){cin >> myData[i];}// 统…

深色系可视化界面看腻了,来点浅色系?安排,20页来了。

只要不放在大屏上展示&#xff0c;贝格前端工场还是非常推崇浅色系的可视化界面&#xff0c;把它作为配色的首选 。浅色系可视化界面具有以下几个优势&#xff1a; 清晰明了 浅色系界面通常使用明亮的颜色&#xff0c;如白色、浅灰色等&#xff0c;使界面元素更加清晰可见。这…

文件MD5校验码的安全性及重要性

title: 文件MD5校验码的安全性及重要性 date: 2024/3/6 18:13:20 updated: 2024/3/6 18:13:20 tags: MD5原理文件校验下载验证数据库一致性安全性保障计算方法MD5安全防护 文件MD5&#xff08;Message Digest Algorithm 5&#xff09;是一种常用的哈希算法&#xff0c;用于验证…

Docker部署(ruoyi案例接上篇Docker之部署前后端分离项目)实施必会!!!!

文章目录 Docker部署前端 Docker部署前端 接上篇博主已经部署好后端Docker部署后端&#xff0c;现在来讲解怎么部署前端 MySQL和redis是不依赖其他任何一个东西的&#xff0c; ruoyi-admin是因为你启动项目的时候是必须连接数据库的 现在去单独启动它 docker start ruoyi-a…

Python爬虫:设置随机 User-Agent

Python爬虫&#xff1a;设置随机 User-Agent 在Python中编写爬虫时&#xff0c;为了模拟真实用户的行为并防止被服务器识别为爬虫&#xff0c;通常需要设置随机的User-Agent。你可以使用fake-useragent库来实现这一功能。首先&#xff0c;你需要安装fake-useragent库&#xff…

【web网页制作】html+css网页制作企业网站办公用品主题(3页面)【附源码】

企业网站目录 涉及知识写在前面一、网页主题二、网页效果Page1、主页Page2、用品展示Page3、关于我们 三、网页架构与技术3.1 脑海构思3.2 整体布局3.3 技术说明书 四、网页源码4.1 主页模块源码4.2 源码获取方式 作者寄语 涉及知识 办公用品企业主题HTML网页制作&#xff0c;…

进程调度算法(先来先服务/短作业优先)代码实现

最近在复习408操作系统进程时&#xff0c;决定用代码模拟调度算法来熟悉详细过程! 选择两个好写点的算法进行练习!!! 以下代码使用c语言。优先队列和队列直接使用STL容器!!! 引入头文件 #include<iostream> #include<queue> #include<map> using namespace …