【原创】SystemVerilog中的多态和虚方法
封装可以隐藏实现细节,使代码模块化,继承可以扩展已经存在的代码模块,目的都是为了代码重用。多态是为了实现接口的重用。在SystemVerilog中,子类和父类之间多个子程序使用同一个名字的现象称为SystemVerilog的“多态(polymorphism)”特征。子类从父类扩展创建之后,子类就继承了父类的属性和方法,这是SystemVerilog的继承特征,但是这个继承特征需要遵循一定的规则:
v 子类继承父类的所有属性(local除外)和方法;
v 子类可以添加新的属性和方法;
v 子类可以重写父类中的属性和方法;
v 如果父类的一个方法被重写,子类必须保持和父类的原定义有一致的参数;
v 子类可以通过super(不能使用super.super方式)操作符引用父类中的方法和成员;
v 被声明为local的属性或方法只能对自身可见,而对于外部和子类不可见;
v 被声明为protected的属性或方法,对外部不可见,对于自身和子类可见;
我们知道,因为继承的特性,子类可以访问父类的属性,当子类对父类的方法进行重写,子类句柄访问的是重写后的方法。那么当父类句柄指向子类时,是否可以通过父类句柄访问子类的属性呢?默认情况下,父类句柄是不能直接访问子类的方法或者重写父类的方法。
【示例】

【仿真结果】

注释掉29行后,虽然pkt指向了spkt,但是pkt还是不能访问其子类spkt中的vdisp方法,仿真会报如下错误。

示例中,通过父类句柄直接访问子类的属性是不能访问到被子类的方法。那么通过父类句柄可以访问被子类重写的方法吗?请看下例。
【示例】

【仿真结果】

可见,虽然子类对父类中的方法进行了重写,并且将父类句柄指向了子类对象,但是父类句柄和子类句柄还是访问各自类型中的方法。那么如何可以实现通过父类句柄对子类重写属性的访问呢?
【示例】


【仿真结果】

示例中,pkt具有如下图的属性和方法,其中vdisp为虚方法,call_vdisp为pkt的方法,其中调用了vdisp,disp和call_disp均为非虚方法。在pkt没有指向spkt时,pkt访问disp、call_disp、vdisp、call_vdisp四种方法都是packet中的四种方法。

spkt具有的属性如下图:

在SystemVerilog中,子类对象的空间分配分为两个部分(如上图所示),一部分来自父类的继承部分,一部分是重写或新增的部分。spkt访问disp是,因为disp为其父类的非virtual方法,虽然sub_packet对其进行了重写,但是当spkt对其访问时,访问的是sub_packet定义的disp。vdisp访问情况同disp。当spkt访问call_disp时,因为sub_packet中没有定义call_disp,那么程序会访问内存中父类中的call_disp,call_disp是一个普通的方法,此时call_disp中调用的disp也是一个普通方法,所以此时的调用均为spkt对象中的pkt部分。当spkt仿call_vdisp时,因为call_vdisp是一个普通方法,所以此时会调用spkt中的pkt部分,但是在call_vdisp中调用的vdisp为pkt中的虚方法,并且该虚方法在spkt中进行了重定义,所以此时call_vdisp调用的vdisp为spkt中重写的vdisp。所以在spkt调用四个函数时,只有对call_disp的调用显示的是pkt中输出的信息。
当pkt指向spkt之后,pkt调用disp、call_disp、vdisp和call_vdisp这四种方法的实际调用访问顺序如下图所示:

当pkt调用这几种方法时,程序同样会找到spkt中的pkt部分,此时如果发现调用的方法是虚方法,那么就会在spkt中查找重写的对应的方法,如果确实存在重写的方法,那么此时就会调用spkt中的重写的方法。如果没有则会执行pkt部分的方法。
spkt调用disp、call_disp、vdisp和call_vdisp这四种方法的实际调用访问顺序如下图所示:

当spkt调用这几种方法时,对于spkt中的存在的方法将会直接调用,对于spkt不存在的则会访问pkt部分,在访问pkt中的方法时,如果其调用的方法为pkt中定义的虚方法,且该方法在spkt中重写,那么此时pkt中的该方法将调用spkt中重写过得方法,如示例中对call_vdisp的调用。
通过上述几个示例,可以看到声明一种方法为虚方法的方式就是在原来方法前添加关键字virtual,子类中重写的方法可以不用添加virtual关键字,并且子类中重写的方法要和父类中该方法的参数和返回值一致。当将父类中将要被重写的方法在声明时指定为virtual方法,那么当父句柄指向子类对象的空间,此时可以实现父句柄对子类重写父类方法的访问。在SystemVerilog中子类对象的空间分配分为两个部分,一部分来自父类的继承部分,一部分是重写或新增的部分。默认情况下,父类的方法是无法访问派生类的重写和新增部分的,如果希望重写的方法被父类看到,就需要依靠本示例中的virtual方法,而虚方法正是OOP中基本的多态性结构。那么,哪些方法要定义为virtual方法呢?经常需要对从父类继承的方法进行一定的修改以适应其应用的需要的方法,在父类中对该方法定义的时候需要添加virtual关键字将该方法声明为虚方法。
综上所述,关于虚方法我们得到以下注意事项:
当使用了virtual方法,那么SystemVerilog会根据对象的类型决定调用哪个虚方法,而非句柄的类型;
当没有使用virtual方法,那么SystemVerilog会根据句柄的类型决定调用哪个虚方法,而非对象的类型;
如果父类的一个方法被重写,子类必须保持和父类的原定义有一致的参数;
虚方法与重写的实现就是多态!当父类的对象指向不同的子类的时候,虚方法就表现出了不同的实现方法,呈现多态!
更多技术内容,可关注下图个人技术微信公众号,欢迎朋友们关注沟通!

本文纯属学习之用,欢迎指正文中不足,
封面图片若有侵权,请及时沟通!
【原创】SystemVerilog中的多态和虚方法的更多相关文章
- 聊聊 C# 中的多态底层 (虚方法调用) 是怎么玩的
最近在看 C++ 的虚方法调用实现原理,大概就是说在 class 的首位置存放着一个指向 vtable array 指针数组 的指针,而 vtable array 中的每一个指针元素指向的就是各自的 ...
- C#-面向对象的三大特性——多态(虚方法与重写、抽象类、接口)
多态 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果.在运行时,可以通过指向基类的指针,来调用实现派生类中的方法. 编译时的多态性:编译时的多态性是通过重载来实现的.对于非虚的成员来说 ...
- C#中的抽象方法和虚方法有什么区别?
抽象方法是只有定义.没有实际方法体的函数,它只能在抽象函数中出现,并且在子类中必须重写:虚方法则有自己的函数体,已经提供了函数实现,但是允许在子类中重写或覆盖.重写的子类虚函数就是被覆盖了.
- C#学习笔记(十四):多态、虚方法和抽象类
虚方法/非虚方法 < 实例方法 = 非静态方法 = 非类方法(非实例方法 = 静态方法 = 类方法) 函数签名(参数列表,或参数列表 + 返回类型) using System; using Sy ...
- C++中的多态与虚函数的内部实现
1.什么是多态 多态性可以简单概括为“一个接口,多种行为”. 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可 ...
- 转载总结 C# 多态(虚方法,抽象,接口实现)
前言:我们都知道面向对象的三大特性:封装,继承,多态.封装和继承对于初学者而言比较好理解,但要理解多态,尤其是深入理解,初学者往往存在有很多困惑,为什么这样就可以?有时候感觉很不可思议,由此,面向对象 ...
- C#中的抽象方法,虚方法,接口之间的对比
1.首先来看一看抽象类 抽象类是特殊的类,不能够被实例化:具有类的其他特性:抽象方法只能声明于抽象类中,且不包含任何实现 (就是不能有方法体),派生类也就是子类必须对其进行重写.另外,抽象类可以派生自 ...
- C#Protected和多态(虚方法)
Protected 在基类中定义后,能被派生类调用,但是不能被其他类调用. virtual 在基类中定义后,在派生类中能被重写. using System; using System.Collecti ...
- C++中的多态及虚函数大总结
多态是C++中很关键的一部分,在面向对象程序设计中的作用尤为突出,其含义是具有多种形式或形态的情形,简单来说,多态:向不同对象发送同一个消息,不同的对象在接收时会产生不同的行为.即用一个函数名可以调用 ...
随机推荐
- 风变编程(Python自学笔记)第11关-杀死那只“机”生虫
1.Debug:为程序排除错误. 2.SyntaxError:语法错误. 3.append()函数是列表的一个方法,要用句点.调用,且append()每次只能接受一个参数. 4.解决思路不清的两个工具 ...
- 最全的go语言的时间格式
该文可以快速在Go语言中获得时间的计算. 在Go中获取时间 如何获取当前时间 now := time.Now() fmt.Printf("current time is :%s", ...
- golang:函数总结
golang保留的函数 init(), main()是golang的保留函数,有如下特点: main() 只能用在main包中,仅可定义一个,init() 可定义任意包,可重复定义,建议只定义一个 两 ...
- [笔记] 《c++ primer》显示器程序 Chapter7
补充Sales_data没有体现出的其他类特性 Screen.h 1 #include <string> 2 #include <iostream> 3 4 class Scr ...
- SSH连接自动断开的解决方法(deb/rpm)
######### 修改后的: ## # tail -f -n 20 sshd_config#MaxStartups 10:30:60#Banner /etc/issue.net # Allow cl ...
- declaration may not appear after executable statement in block--转载
这个问题是在编译STM32的程序时遇到的,这个错误的原因是对于变量的声明不能放在可执行语句后面,必须在主函数开头声明变量.在程序中声明一个变量时,需要在可执行语句之前声明,否则会出现以上错误. 例: ...
- static在C/C++中的作用-(转自华山大师兄)
1.先来介绍它的第一条也是最重要的一条:隐藏.(static函数,static变量均可) 当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性.举例来说明.同时编译两个源文件 ...
- python工业互联网应用实战16-前后端分离模式之修改与删除
前一章节介绍了List页面的JQuery技术栈的迁移,这一章节我们花一些篇幅来说说修改/查看页面的技术栈迁移.相对于List的获取数据,修改页面涉及到数据Post提交到后台更新数据库.我们仍旧小步迭代 ...
- 简单读读源码 - dubbo多提供者(provider)配置方法
简单读读源码 - dubbo多提供者(provider)配置方法 消费者端dubbo的yml配置 dubbo: consumer: timeout: 300000 protocol: name: du ...
- 文件包含之包含了Linux文件描述符
0x00 原理 文件描述符是内核为了高效管理已被打开的文件所创建的索引,用于指向被打开的文件,所有执行I/O操作的系统调用都通过文件描述符. 翻译成人话- 可以认为是指向文件的一个指针,如果有文件 ...