到底什么是多态:
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为,父类引用变量可以指向子类对象;
- 要在继承中构成多态有两个条件:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
- 虚函数的重写:
派生类中有一个和基类完全相同的虚函数,则子类的虚函数重写了基类的虚函数,
- 多态的定义与使用格式:
定义格式: 父类类型 变量名=new 子类类型();
- 多态是同一个行为具有不同的表现形式或形态的能力;
- 多态就是同一个接口,使用不同的实例而执行不同的操作;
Fu f=new Zi();
多态的转型
向上转型:多态本身就是向上转型的过程
多态本身就是向上转型过的过程
//多态例子理解
public class demo00{
public static void main(String[] args){
People p=new Stu();
p.eat();
}
}
SV
-
继承的规则
- 子类继承父类的所有数据和成员方法
- 子类可以添加新的成员数据和方法
- 子类可以重写基类中的数据成员和方法
- 如果一个基类的方法被重写,其必须保持和基类的原定义一致的参数
- 子类可以通过super引用父类的方法和成员
- 被声明为local的数据成员和方法只能对自身可见,对于外部和子类不可见
- 被声明为protected的数据成员或方法,对外部不可见,对于自身和子类可见
-
对象空间分配:
- 类的对象被定义的时候,只是一个空的句柄,当其构造函数被调用的时候,才分配空间,其句柄指向该空间的入口地址
- 派生类的对象被定义的时候,也只是一个空句柄;
-
虚方法和多态:
class Packet; task build_payld(); ... endtask task bulid_packet(); build_payld(); endtask endclass classs DrvPacket extends Packet; task build_payld(); .... endtask endclass module test1; DrvPacjet der = new(); initial begin der.build_packet(); //这里执行父类的函数 end endmodule //派生类对象在调用其build_packet的过程中,会调用build_payld这个任务,而build_payld这个任务在父类扩展的过程中被重写过,但最终调用的仍是父类的任务;因为在默认情况下,子类重写的方法对父类是不可见的。 //派生类对象的空间分配分为两个部分,一部分来自父类的继承部分,一部分是重写或新增的部分,默认情况下,父类的方法是无法访问派生类的重写和新增部分的,这需要依靠虚方法和多态
-
虚方法:
- 类中的方法可以在定义的时候通过添加virtual关键字来声明一个虚方法,虚方法是一个基本的多态性结构
- 虚方法为具体的实现提供一个原型,也就是在派生类中,重写该方法必须采用一致的参数和返回值
- 虚方法可以重写其所有基类中的方法,而普通的方法被重写后只能在本身及其派生类中有效
- 每个类的继承关系只有一个虚方法的实现,而且是在最后一个派生类中
class Basepacket; int a = 1; int b = 2; function void printA; $display("BASE A is %0d",a); endfunction virtual function void printB; $display("BASE B is %0d",b); endfunction endclass class Mypacket extends Basepacket; int a = 3; int b = 4; function void PrintA; $display("Mypacket A is %0d",a); endfunction virtual function void printB; $display("Mypacket B is %0d",b); endfunction endclass Basepacket p1 =new(); Mypacket p2 =new(); initial begin p1.printA; //1 p1.printB; //2 p1 = p2; p1.printA; //1 p1指向p2后,p1.printA调用后,程序会调用内存中类对象P2中P1的部分,发现PrintA是一个普通方法,直接调用 p1.printB; //4 p1指向p2后,p1.printB调用后,程序会调用内存中类对象P2中P1的部分,发现PrintA是一个虚方法,咨询系统P2定义的时候是否重写了该方法,发现重写后,调用重写方法 p2.printA; //3 p2.printB; //4 end
-
-
讲解需要注意两个方面
- 父类指针指向子类对象,如果没有多态,则子类对象调用父类函数
- 父类指针指向子类对象,多态,则会调用子类中的重载函数
类型转换
-
动态转换:
$cast 允许对越界的数值进行检查
$cast(a,b) :将右边的值赋给左边的变量,成功返回1,如果数值因为越界导致赋值失败,则不进行赋值,返回0;
$cast(extend,base);向下类型转换
-
静态转换
i = int’(10.0); //转换非强制
factory
factory提供了与SV本身重载不同的重载功能
使用factory的crate方式创建实例,实例化时,UVM会通过factory内部的一张表格查看是否有相关重载记录,通过set_type_override_by_type系列方法在表格中添加实例,当查看到有重载记录的时候,会用新的类型替代旧的类型,父类指针指向子类实例。
factory机制的实现:
创建类的实例:
一般oop语言中,创建一个类的实例有两种方法:
在类的可见范围直接创建、
class A; endclass class B; A a; function new(); a = new(); endfunction endclass
使用参数化的类
class parameterized_class #(type T) T t; function new(); t = new(); endfunction endclass class A; endclass class B; paremrterized_class#(A) pa; function new(); pa =new(); endfunction endclass //pa实例化后,内部就创建了一个t的实例
factory提供了字符串创建一个类
class registry#(type T=uvm_object,string Tname=""); T inst; string name = Tname; endclass class my_driver; typedef registry#(my_driver,"my_driver") this_type; //向registry传递新定义类的类型和指针,创建实例 local static this_type me = get(); //把实例的指针和my_driver名字放在联合数组中,类似uvm util宏做的事 static function this_type get(); if(me != null ) begin me =new(); global_tab[me.name] = me ; end return me; endfunction function uvm_component create_component_by_name(string name) register#(uvm_object,"") me_ptr; me_ptr = global_name[name]; //当需要使用类名创建实例时,先从global_tab中找到类名的索引对应的registry实例的指针me_ptr,然后调用me_ptr的new函数,最终返回me_ptr.inst; me_ptr.inst = new("uvm_test_top",null); return me_ptr.inst; endfunction
从本质上来看,factory机制是对sv中new函数的重载,原始new函数功能太少,经过factory改良后,进行实例化的方法多了很多。
uvm提供了一系列factory创建实例的接口:
my_transcation tr; factory.create_object_by_name("my_transaction"); //根据类名创建object factory.create_object_by_type(my_transaction::get_type()); //根据类型创建object factory.create_component_by_name("my_transaction",get_full_name,"scb",this); //根据类名创建component,类名;父节点全名;新的component的名字;父节点指针 factory.create_component_by_type(my_transaction::get_type(),get_full_name(),"scb",this); //根据类型创建component,参数类似