方法:包括任务或者函数,对对象内的数据进行操作。
对象:包括数据成员和方法。
过程编程语言:数据和方法都是独立的,没有任何联系,一个方法可以对多个数据操作,多个函数可以对同一个数据操作。
面向对象的编程语言:数据和方法被封装到一个对象中,对象和外部的接口通过方法来实现,是一个层次化的实现结构,可以例化复用。
在SV中采用的是面对对象的编程语言,一旦抽象层次被确定,就可以映射到用户自定义类型,这种用户自定义类型就是类(class),类可以将数据和方法封装到一起。
类
一个对象通过类来定义,类相当于对象的一个模板,定义了数据和方法。对象是类的一个例化和实现。
类:银行卡类
对象:工商卡、交行卡、建行卡...
方法:利息计算、添加交易、余额计算...
①类的基本概念
属性(property):类中定义的数据成员
方法(methood):类中定义的函数或任务
句柄(handle):指向对象的指针,即对象地址的入口
用户使用对象包括三个步骤:
1.定义类:class class_name;...endclass
即class 类型名,使用该类型名可以声明类对应的对象。
2.声明对象:class_name object_identifier;
对象的声明可以在module、program class function task等地方声明
Sv除了支持单个对象,还支持以对象数组的方式例化:
class_name object_identifier_arry[];//对象的动态数组,同样需要NEW()例化;
用类型名声明的对象的标识符是例化对象的句柄,也就是例化对象的地址,然而对象的声明并不意味着创建(例化),声明没有实际分配空间,此时句柄无效,值为null
因此还需要构造函数来创建例化对象。
3.通过构造函数new()来创建对象的例化
object_identifier =new();
通过构造函数给对象分配空间后,该对象的标识符,也就是句柄才有效,代表例化对象的入口地址。
因此,一个完整的用户使用对象的创建:
class ether_packet;
print(); 定义一个ether_packet的类,内部包含了print、load
load_frame(); 等方法。
Endclass
module/task/program/function/class //对象的声明位置很灵活
ether_packet e1_pkt,e2_pkt; //声明两个对象:e1_pkt和e2_pkt
initial begin
e1_pkt=new(164); 使用构造函数为对象分配空间
e2_pkt=new(88); 分配空间后,其句柄代表了对象入口的地址
e1_packet.print;
e1_packet.load_frame; 通过.操作符,访问对象中的成员
end
endmodule/task/program/function/class
②构造函数
1.如果一个被声明的对象还没被构造,也就是对象的句柄值为null,如果此时句柄被引用,将会产生错误。
可以通过下列代码来检测空句柄:If (object_identifier==null) $stop;
2.对象通过new()函数来初始化,若数据成员没有在new()中初始化,则为默认值。
在构造函数中,数据成员可以被赋初值:
function new()
addr=3;
foreach(data[i])
data[i]=i;
endfunction
也可以通过参数传递对数据成员初始化:
function new(logic[31:0] addr=3,d=5)
this.addr=addr;
foreach(addr[i])
data[i]=5;
endfunction
3.存储空间不再被对象占用时,SV会像java一样,自动收回例化,即自动析构。
module new_struct_example();
`include ether_packet.sv
ether_packet e1_pkt,e2_pkt; //定义两个以太包类的对象
initial begin
e2_pkt=new(98); //构造函数给e2_pkt这个句柄分配空间,传递98给len变量
e1_pkt=e2_pkt;//并没有给e1_pkt分配空间,而是将句柄e2_pkt赋值给e1_pkt
e1_pkt=new(118);//重新调用构造函数,原来分配的空间自动析构
e1_pkt-new(119);:最终e1_pkt和e2_pkt所指向的空间
e2_pkt=new(200);
③静态属性与方法
1.静态属性
每一个类的例化(对象)都拥有其自身的变量,且一个类的不同对象的成员变量是不共享的,而有时候需要所有对象共享一个变量,这时可以在类的定义中用关键字:static来声明数据成员。例:static int addr=0;
2.静态方法
静态方法对类的所有对象都可以访问;即便类没有被例化,该静态方法也可以在外部被调用;静态方法的声明也是使用static关键字:static function next_id()
注意:静态方法不能访问非静态成员,因此:
class id;
int current=0;
static function int next_id();
next_id=++current; //出错,因为current为非静态变量,静态方法不能访问,
Endfunction 因此需修改current为:static int current=0;
endclass
注意:静态方法指:类内部方法的生命周期
具有静态生命周期的方法:方法内部的参数和变量的生命周期
class twotasks;
static task foo();...endtask //静态方法:具有自动变量生命周期的静态方法
task statiic bar();...endtask //非静态方法:具有静态变量生命周期的非静态方法
④this操作符
关键字this被用来明确的引用当前类的属性或方法;对应着一个预定义的对象的句柄,这个句柄可以在对象内部使用,并可以访问内部成员。
注意:关键字this只能在非静态方法中使用。
module this_class_example();
class this_class;
integer x; //此处x为类的一个属性
function new(integer x) //此处X为构造函数new的一个自变量
this.x=x; //若没有this,则此处访问的x是构造函数的自变量,为了
endfunction 访问类的属性,使用了关键字this进行限定。
endclass
endmodule
⑤对象的赋值与赋值
1.赋值与浅复制
//赋值 //复制
packet p1; packet p1;
p1=new; p1=new;
packet p2; packet p2;
p2=p1; p2=new p1;
赋值没有产生新的存储空间,因为new仅执行了一次,赋值行为只是把地址入口传入另一个句柄,两个句柄指向的是同一个对象。
复制执行了两次new操作,产生了新的存储空间,且原存储空间所存的属性也被复制了过去,该复制行为属于浅复制,因为它只能复制属性和句柄,其包含的对象并不会被复制。例如:
module shallow_copy();
class A;
integer j=5;
endclass
class B;
integer i=1
A a=new; //在B类中例化了A类属性变量a
endclass
Initial begin
B b1=new;
B b2=new b1; //b2是b1的一个再复制,b2中也有变量i和对象a
b1.i=10;
b1.a.j=50;
test=b2.i; //b2中的i不随b1中的i的改变而改变(b2.i=1)
test=b2.a.j; //由于复制仅复制了对象a的句柄,并没有为对象a开辟新的存储空间,也就是并没有复制对象a,因此,b2中的对象a和b1中的对象a是同一个对象,b1和b2共享同一个对象,所以b2.a.j=50
因此,该复制方式称为浅复制,主要是由于该方式不会复制对象。
2.深度复制
module shallow_copy();
class A;
integer j=5;
endclass
class B;
integer i=1
A a=new;
function copy(B in_b); //在B类中声明copy函数
this.i=in_b.i; //this关键字限定此处的i为类属性,而不是copy函数自变量
this.a.j=in_b.a.j;
endfunction
endclass
initial begin
B b1=new;
B b2=new; 分别为两个对象b1和b2开辟存储空间,这样对象b1和b2都
b2.copy(b1); 各自独立的对象a,再利用copy函数,将b1的值赋给b2。
end
endmodule
⑥块外声明
将方法定义在类型声明体的外部:
首先:再类内声明方法的原型:使用限定符extern和其他限定符(local、protected、vietual):class packet
extern protected virtual function int send(int value);
限定符 方法原型
endclass
其次:在类外定义完整的方法实现:使用类名与一对冒号::对方法名做限定
function int packet :: send(int value); //注意是对方法名send的限定
...
endfunction