类的封装
- 类是一种包含数据和方法(function、task)的类型
- 例如一个数据包,可能被定义为一个类,类中可以包含指令、地址、队列ID、时间戳和数据等成员
- packet这个类可以在其中对这些数据做初始化,设置指令,读取该类的状态以及检查队列ID
- 每个packet类型化的具体对象及数据成员都不相同,然而packet类作为描述这些数据的抽象类型,将其对应的数据成员和操作这些数据成员的方法都定义在其中
- OOP术语
- 类(class):包含成员变量和成员方法
- 对象(object):类实例化后的对象
- 句柄(handle):指向对象的指针
- 原型(prototype):程序的声明部分,包含程序名,返回类型和参数
构建函数
-
SV并不像C++一样要求复杂的存储空间开辟和销毁手段,二十采用了像java一样空间自动开辟和回收的手段
-
SV在类的定义时,只需要构建函数(constructor),而不需要定义析构函数(destructor)
-
类在定义时,需要定义构建函数,如果未定义,则系统会自动帮助定义一个空的构建函数(没有形式参数,函数体亦为空)
-
对象在创建时,需要先声明再例化,同时进行也可以
// 定义一个类,类名:Packet class Packet; function new(); // 构造函数,没有返回值,也不需要void endfunction endclass Packet p = new(); // Packet:类,p:句柄,new():实例化出一个对象
- 对象在运行之前,不会实例化,这时候对象p指向的是null
- 对象在开始运行后,才会实例化,p才会指向一个句柄
-
构建函数中传参数
// 定义类 class Packet; function new(int inival); command = inival; endfunction endclass initial begin // 例化 Packet p = new(6); $display(p.command); end
静态成员(变量/方法)
-
成员方法可以声明为静态
-
静态方法无法访问非静态成员(方法/变量),否则会发生编译报错
-
静态变量
class Packet; integer command; static int my_data; function new(int inival); command = inival; endfunction endclass initial begin Packet p1,p2; // 静态变量存储在类中,可以通过类直接获取 $display("static data is %0d", Packet::my_data); // p1.my_data拿的是类中的值,这时还没有例化对象 $display("static data is %0d", p1.my_data); $display("static data is %0d", p2.my_data); end
-
静态方法
class Packet; integer command; static int my_data; function static my_static_func(); $display("this is static function"); endfunction function new(int inival); // 构造函数,没有返回值,也不需要void command = inival; endfunction endclass initial begin Packet p1,p2; // 静态方法在类中,不例化对象也能调用 p1.my_static_func(); p2.my_static_func(); end
this
-
this是用来明确索引当前所在对象的成员(变量/参数/方法)
-
this只可以用来在类的非静态成员、约束和覆盖组中使用
-
this的使用可以明确所指向变量的作用域
class Packet; integer x; function new(integer x); this.x = x; // this.x表示对象的x变量,而非传进来的x endfunction endclass
赋值和拷贝
-
声明变量和创建对象是两个过程,也可以一步完成
Packet p1; // 声明变量 p1 = new(); // 例化对象 Packet p2 = new() // 声明+例化
-
如果就将p1赋值给另外一个变量p11,那么依然只有一个对象,只是指向这个对象的句柄有两个:p1、p11
p11 = p1; // 赋值
-
以下这种方式表示p1和p12代表两个不同的对象,在创建p12时,将从p1拷贝其成员变量,这种拷贝方式称为浅拷贝(shallow copy)
Packet p1; Packet p12; p1 = new(); p2 = new p1; // 注意,这里的new不能加()
-
浅拷贝中,只拷贝了对象的值,如果对象某个变量变量另一个对象,那拷贝生成的对象那个变量也会指向相同的变量
- 深拷贝(deep copy)指的是拷贝对象和对象变量指向的对象,所有都是新开辟空间存储,SV没有提供深拷贝,需要自己自定义
数据的隐藏和封装
- 类的成员(变量/方法)默认情况下是公告属性的,这表示不管在类的内部和外部均可以访问该成员(变量/方法)
- 对于商业开发,类的提供方会限制一些类成员的外部访问权限,继而隐藏成员的更对细节
- 这种方式也使得类的外部访问接口更加精简,减轻了类的维护工作量,也使得类在修改时便于与旧版本保持兼容
- 数据隐藏的方式是的类的测试和维护都变得更为简单
local
:只有该类可以访问此成员,子类和外部均无法访问protected
:表示该类及其子类可以访问此成员,外部无法访问
类的继承
-
通过
extends
,创建子类// 父类 class ParentPacket; ... endclass // 子类 class ChildPacked extends ParentPacket; ... endclass
-
子类会默认调用父类的
new()
函数 -
父类
new()
方法需要接收参数时,子类new()
方法中不能再默认调用父类的new()
方法,必需使用super.new()
并传参对父类的方法进行调用 -
子类和父类有相同的变量时,各自类的方法会修改各自类中的变量
-
子类的对象可以赋值给父类的句柄
class Packet; integer i = 1; function new(); i = 2; endfunction endclass class ChildPacket extends Packet; integer i = 1; integer k = 5; function new(); i = 3; endfunction endclass Packet tmp; ChildPacket p = new(); tmp = p; // 子类的对象可以赋值给父类的句柄 $display("p.i is %0d", p.i) // 这里拿到的是子类中的i,例化之后i=3 $display("tmp.i is %0d", tmp.i) // 这里tmp虽然指向的是子类的对象,但是tmp是父类的句柄,因此tmp.i=2 $display("tmp.k is %0d", tmp.k) // 因为父类中不存在k这个变量,tmp.k就找不到,这段代码编译会报错
-
父类的对象不能赋值给子类的句柄,编译会报错,因为可能会造成内存的非法访问