0.前言
当同一操作作用于不同对象,能有不同的解释从而产生不同的结果,这就叫做多态,多态在验证中被大量使用
多态的实现基础是什么?
1.多态的实现基础是继承,没有继承就没有多态
2.多态通过子类覆盖父类中的虚函数(虚方法)来实现
多态和继承的区别?
多态是父类使用子类的方法;而继承是子类使用父类的方法
虚方法和多态的关系?
虚方法是多态实现的基础
关于多态在SV之OOP基础知识这篇文章中有更加通俗的解释,可参见这篇文章。既然虚方法是多态实现的基础,那先来了解一下什么是虚方法。
1.虚方法
类中的方法在定义的时候通过添加virtual关键字来声明一个虚方法,虚方法是一个基本的多态性结构
虚方法为具体的实现提供一个原型,也就是在派生类中,重写该方法的时候必须采用一致的参数和返回值
虚方法可以重写其所有基类中的方法,而普通的方法被重写后只能在本身及其派生类中有效。
每个类的继承关系只有一个虚方法的实现,而且是在最后一个派生类中
通过图更好的理解虚方法:
由上图可见,首先声明了一个父类packet并在父类中定义了2个变量和一个普通方法和一个虚方法;其次定义一个子类my_packet继承父类,并重新定义了父类中的变量和方法;然后分别为父类和子类声明一个句柄并创建对象,这时p1所指向的内存空间就是父类,而p2指向的内存空间就是子类并且子类中重新定义的变量和函数覆盖了父类;再然后进行了一个句柄的复制操作,这使p1不再指向父类的内存空间,其被释放,p1指向了子类p2的内存空间,这里需要注意,p1其实指向的是p2中父类的部分!对于普通方法display_a,其打印的是父类的a,并没有被子类覆盖;而对于虚方法display_b,程序同样找到类对象p2中p1的部分, 此时发现display_b是个虚方法,这时其会咨询系统,查看整个p2在定义的时候是否 重写了该方法.系统发现在p2中(除了p1外),确实重写了该方法,为此,程序会直接调 用p2重写的实现
其对应代码如下:
class packet;//父类
int a=1;
int b=2;
function void display_a;//普通的方法
$display("packet::a is %0d",a);
endfunction
virtual function void display_b;//虚方法
$display("packet::b is %0d",b);
endfunction
endclass
class my_packet extends packet;//子类
int a=3;//子类重新定义父类的变量,会将父类的变量值覆盖
int b=4;
function void display_a;
$display("packet::a is %0d",a);//a=3
endfunction
virtual function void display_b;
$display("packet::b is %0d",b);//b=4
endfunction
endclass
module example1;
packet p1;
my_packet p2;
initial begin
p1=new;
p2=new;
p1=p2;//使父类句柄指向子类
p1.display_a;//调用的父类方法
p1.display_b;//被子类覆盖,调用的子类方法
p2.display_a;//调用子类重新定义的方法
p2.display_b;//调用子类重新定义的方法
end
endmodule
仿真结果:
可见p1=p2后,p1中的普通方法display_a指向的是父类的a=1, 虚方法display_b指向的是子类的b=4;而p2指向的都是自己的数值
所以当遇到句柄的复制时,要注意是同一类的句柄复制,还是父子类的句柄复制,它们是不一样的!另外如果使p2=p1是不行的,会编译错误!
关于虚方法更多要点可见systemverilog-虚方法这篇文章。
2.类型转换
父类指向子类,也就是子类转换为父类,是向上类型的转换
子类指向父类,也就是父类转换为子类,是向下类型的转换
向上类型的转换是安全的,向下不安全
2.1 枚举类型的转换
枚举类型变量可以赋值给int型变量,反过来int型变量也可以转换为枚举类型。
program example;
typedef enum{RED,BLUE,GREEN}COLOR_E;
COLOR_E color,c2;
int c;
initial begin
color=BLUE;
c=color;//可以将枚举类型的变量赋值给int类型的变量
$display("c=%0d",c);
c++;
$cast(color,c);//int类型转换成枚举类型
$display("color is %0s",color.name);
c2=COLOR_E'(c);
$display("c2 is %0d/%0s",c2,color.name);
end
endprogram
仿真结果:
2.2 句柄类型的转换
2.2.1 向上类型的转换
子类向父类的转换,即父类句柄指向子类句柄叫向上类型的转换。
用图来理解:
上述代码如下:
class father;
string m_name;
function new (string name);//有参数的new函数
m_name = name;
endfunction
function void print ();
$display("Hello %s", m_name);
endfunction
endclass
class child0 extends father;
string car = "car";
function new (string name);
super.new(name);//因为父类new有参数,这里必须用super new
endfunction
endclass
class child1 extends father;
string plane = "plane";
function new (string name);
super.new(name);//因为父类new有参数,这里必须用super new
endfunction
endclass
module top;
father f;
child0 c0;
child1 c1;
initial begin
f = new("father");//为父类的
f.print();//调用父类的方法,father
c0 = new("child0");
f = c0;//使父类句柄指向子类c0
f.print();//调用的还是父类方法,但是被子类传参,child0
c1 = new("child1");
f = c1;//使父类句柄指向子类c1
f.print();//调用的还是父类方法,但是被子类传参,child1
end
endmodule
仿真结果:
可见该代码通过子类向父类传参的方式,实现了使用父类的函数打印子类参数的功能。
2.2.2向下类型的转换
父类向子类的转换,即子类句柄指向父类句柄叫向上类型的转换。
用图来理解:
上述代码如下:
class father;
string m_name;
function new (string name);//有参数的new函数
m_name = name;
endfunction
function void print ();
$display("Hello %s", m_name);
endfunction
endclass
class child0 extends father;
string car = "car";
function new (string name);
super.new(name);//因为父类new有参数,这里必须用super new
endfunction
endclass
class child1 extends father;
string plane = "plane";
function new (string name);
super.new(name);//因为父类new有参数,这里必须用super new
endfunction
endclass
module top;
father f;
child0 c0;
child1 c1;
child1 c2;
initial begin
f = new("father");
f.print();
c0 = new("child0");
f = c0;
f.print();
c1 = new("child1");
f = c1;
f.print();//以上和2.2.1没差别
c1.plane="big_plane";//更改c1对象的变量
$cast(c2,f);//强制c2->f,而f->c1,所以导致c2->c1,这就是向下类型的转换
f.print();//此时f->c1,所以打印的还是c1传的参数
$write(", has %s",c2.plane)//由于c2->f->c1,所以c2->c1,所以c2.plane是c1的plane名字
end
endmodule
仿真结果:
这里需要注意的是,只有当前父类指针指向的对象和待转换对象 的类型一致时,cast才会成功,比如上述的c2和c1必须是同一类声明的不同句柄,如果让c2->f->c0,那会转换失败!