我在学习这门语言的时候,每每碰到一个新的名词,都想问一句为什么会出现这样的概念?换句话说就是他有什么功能?
继承与多态:
在验证过程中,往测试平台中添加新的测试激励很平常的事,这样的话就需要对原来的测试平台进行改进,有的时候需要修改原来的代码甚至可能修改一些数据结构,这个过程中可能会导致在原来的验证平台中引入意外的错误。那么为了保证原有平台或数据结构不变,通过对已经有的基类进行引申或者扩展,从而完善整个验证平台。
从基类做扩展并产生新的子类的过程叫类的派生,当一个类被扩展并创建之后,该派生类就继承了其基类的数据成员、属性和方法,这就是类的继承。
继承后的类可以实现以下功能:
1.继承了原来类的方法,并可以修改
2.添加新的方法
3.添加新的数据成员
在实现以上功能的同时需要满足一定的规则:
1.子类继承父类的所有数据成员和方法
2.子类可以添加新的数据成员和方法
3.子类可以重写基类中的数据成员和方法
4.如果一个方法被重写,其必须保持和基类的原有定义有一致的参数
5.子类可以通过super操作符来引用父类中的方法和成员
6.被声明为local的数据成员和方法只能对自己可见,对外部和子类都不可见;对声明为protected的数据成员和方法,对外部不可见,对自身和子类可见。
语法定义:
class class_name extends base_class_name
endclass
例子:
class Packet;
integer status;//属性
task rst();//方法
status = 0;
endtask
endclass
class DerivedPacket extends Packet;
integer a,b,c;//添加的新属性
task showstatus();//添加的新方法
$display(status);
endtask
endclass
类的重写例子:
class Packet;
int status=4;//属性
function int chkatat(int s);//该方法返回值
return(status==s);
endfunction
end
endclass
class DerivedPacket extends Packet;
int status=15;//重写属性,将其值改为15
function void chkstat(int s);//该方法变成打印,而不是返回值
$display(status)
endfunction
endclass
关于子类对象与父类对象的赋值问题:
1.子类对象是父类对象的有效表示,可以赋值给父类对象;
2.父类对象可以通过$cast的方式尝试给子类对象赋值,并判定是否赋值合法
在访问对象的过程中一般要遵循下面的规则:
1.通过父类的对象去引用子类中重写的属性和方法,结果只会调用父类的属性和方法(和3表达的意思类似)
2.通过子类对象可以直接访问重写的属性和方法
3.在子类的扩展过程中,新增的属性和方法(包括重写的)对父类是不可见的(和1表达的意思类似)(不可见就是父类不知道子类进行了重写)
4.子类可以通过super操作符访问父类中的属性和方法,以区分于本身重写的属性和方法
例子:
module derived2base;
class Packet;
integer i =1;//父类属性
function integer get();//父类方法
get = i;
endfunction
endclass
class derivedPacket extends Packet;
integer i = 2;//子类属性,并重写为2
function integer get();//子类方法,并重写
get = -i;
endfunction
endclass
initial begin
derivedPacket ld,lp=new;//子类的实例化对象创建
Packet p =lp;//父类的实例化对象创建,并将子类对象赋值给父类对象
j = p.i;//j=1,而不是2,通过父类的对象去引用子类中的属性,结果还是调用父类的属性
j=p.get();//j=1,原因和上面一样
$cast(ld,p);//将p赋值给ld,并做合法检查
end
endmodule
///
那么好,到此有一定值得我们特别注意:子类中的重写的属性和方法在父类中不可见,换句话说就是父类不知道子类都做了些什么?这个时候如果我们希望子类都干了些什么事需要被父类知道,该怎么办呢?
虚方法:
我的理解就是:虚方法是专门针对父类监督子类而产生的,我们不是说了吗,子类对父类中的数据和方法进行了重写,然而对父类不可见!虚方法可以解决这个问题:
虚方法可以重写其所有基类中的方法,然而普通的方法被重写后只能在本身及其派生出的子类中有效!
例子:
class basePacket;
int A =1;
int B=2;
function void printA;
$display("basePacket::A is %d",A);
endfunction:printA
virtual function void printB;//声明为虚方法
$display("basePacket::B is %d",B);
endfunction:printB
endclass
class my_packet extends basePacket;
int A=3;//重写属性A
int B = 4;//重写属性B
function void printA;//重写方法
$display("my_packet::A is %d",A);
endfunction:printA
virtual function void printB;//重写方法
$dispaly("my_packet is ::%d",B);
endfunction:printB
base_Packet p1=new;
my_packet p2=new;
initial begin
p1.printA;//basePacket::A is 1
p1.printB;//basePacket::B is 2
p1=p2;//子类对象赋值给父类
p1.printA;//basePacket::A is 1
p1.printB;//my_packet:: B is 4
p2.printA;//my_packet:: A is 3
p2.printB;//my_packet:: B is 4
end
特别说明:
如果我们想要重写某个方法,可以在父类中声明为虚方法;一旦一个方法被声明为虚方法,他的后续继承过程中将永远是一个虚方法,不管重写的时候是否使用virtual关键字,也就是说,在父类中声明了虚方法,子类中重写的方法可以不使用关键字virtual。
二、所谓多态:当一个类派生出子类的时候,基类中的一些方法可能需要被重写,对象中的类型来决定调用方法的实现方式,通常这是一个动态的过程,动态的选择方法的实现方式叫多态。
封装可以隐藏实现细节,使代码模块化,继承可以扩展已经存在的代码模块,目的都是为了代码重用。而多态是为了实现接口的重用。
另一种认识:虚方法与重写的实现就是多态!
原因:前提是虚方法的使用,声明父类句柄,既可以指向父类对象也可以指向子类对象,当句柄指向父类对象的时候调用的是父类的方法,当指向子类对象的时候调用的是子类的方法,因此,当父类的句柄指向不同的子类对象的时候,虚方法就表现出了不同的实现方法,呈现多态!
虚方法
(1) 类中的方法可以在定义的时候通过添加virtual关键字来声明一个虚方法,虚方法是一个基本的多态性结构.
(2) 虚方法为具体的实现提供一个原型,也就是在派生类中,重写该方法的时候必须采用一致的参数和返回值.
(3) 虚方法可以重写其所有基类中的方法,然而普通的方法被重写后只能在本身及其派生类中有效。
(4) 每个类的继承关系只有一个虚方法的实现,而且是在最后一个派生类中。
总结:
基类方法是虚方法(virtual)(即动态绑定)时:
1)通过继承基类
2)子类的方法名,参数和返回值与基类一致
3)根据父类句柄指向的对象类型来确定调用的是子类的方法还是父类的方法;
PS:若基类的方法没有定义成virtual,那么SV 会根据句柄的类型,而不是对象的类型进行方法的调用;(绿皮书—P219,P227)
对于多态:记住virtual 和基类句柄对象类型来确定调用的方法!
OOP中多个子程序使用一个共同的名字的现象就叫做“多态”;