1 类型转换
类有三个要素:封装,继承,多态
概述
- 类型转换可以分为静态转换和动态转换
- 静态转换即需要在转换的表达式前加上单引号即可,该方式并不会对转换值做检查。如果发生转换失败,我们也无从得知 eg int'(4.0)
- 动态转换即需要使用系统函数$cast(tgt, src)做转换
- 静态转换和动态转换均需要操作符号或者系统函数介入,统成为显式转换
- 不需要进行转换的一些操作,我们称为隐式转换。例如赋值语句右侧是4位的矢量,左侧是5位矢量
动态转换
- 当我们使用类的时候,类句柄的向下转换,即父类句柄转换为子类句柄时,需要使用$cast( )函数进行转换,否则会出现编译错误
note:将子类句柄赋值给父类句柄是合法的,但分别利用子类句柄和父类句柄调用相同对象的成员时,会有不同表现
子类句柄赋值于父类句柄
1 class Transaction; 2 rand bit [31:0] src; 3 function void display(input string prefix = ""); 4 $display("%sTransction: src = %0d", prefix, src); 5 endfunction 6 endclass 7 class BadTr extends Transcation; 8 bit basd_crc; 9 function void display(input string prefix = ""); 10 $display("%sBadTr: bad_crc = %b", prefix, bad_crc); 11 super.display(prefix); 12 endfunction 13 endclass 14 15 Transaction tr; 16 BadTr bad, bad2;
Transaction tr;
BadTr bad;
bad = new(); //构建BadTr扩展对象
tr = bad; // 基类句柄指向扩展对象
$display(tr.src);//显示基类对象的变量成员
tr.display(); //父类的display
- 将一个父类句柄赋值给子类句柄并不总是非法的
- SV编译器对这种直接赋值的做法是禁止的,也就是说父类句柄是否真正指向了一个子类对象, 赋值给子类句柄时,编译都将出现错误
- 一旦源对象跟目的句柄是同一类型,或者是目的句柄的扩展类,$cast()函数执行即会成功,返回1
2 虚方法
1 class basic_test; 2 int fin; 3 int def = 100; 4 function new(); 5 $display("basic_test::new"); 6 endfunction 7 task test(); 8 $display("basic_test::test"); 9 endtask 10 endclass 11 12 class test_wr extends basic_test; 13 int def = 200; 14 function new(); 15 super.new(); 16 $display("test_wr::new"); 17 endfunction 18 task test(); 19 super.test(); 20 $display("test_wr::test"); 21 endtask 22 endclass
note:子类也继承了父类的def = 100, super.def即可找到
- 类的继承是从继承成员变量和成员方法两个方面
- 从例码中可以看到test_wr类和test_rd类分别继承了basic_test类的成员变量以及成员方法
- 除了介绍的类的封装和继承,关于类的多态性也是必须关注的
- 正是由于类的多态性,使得用户在设计和实现类时,不需要担心句柄指向的对象类型是父类还是子类,只要通过虚方法,就可以实现动态绑定,或者在SV中称为动态方法查找
非虚函数的调用
basic_test t; test_wr wr; initial begin wr = new(); t = wr; $display("wr test starts"); wr.test(); $display("wr test ends"); $display("t test starts"); t.test(); $display("t test ends"); end output results: #wr test starts #basic_test::test #test_wr:test #wr test ends #t test starts #basic_test::test #t test ends
- 在执行wr.test()时,由于wr类型为test_wr,则索引到的test()应该为test_wr类的方法test
- 由于在test_wr::test中显式调用了super.test(),则会先执行basic_test::test,然后在执行test_wr::test中其余的代码
- 默认情况下,子类覆盖的方法并不会继承父类同名的方法,而只有通过super.method()的方式显式执行,才会达到继承父类方法的效果
- 当wr对象的句柄传递给t后,由于t本身是basic_test类,所以执行t.test时,t只会搜寻basic_test::test方法
- 将已经在编译阶段就可以确定下来调用方法所处作用域的方式成为静态绑定,与之相对的是动态绑定
- 动态绑定指的是在调用方法时,会在运行时来确定句柄指向对象的类型,在动态指向应该调用的方法
- 为了实现动态绑定,将basic_test::test定义为虚方法
可以通过虚方法的使用来实现类成员方法调用时的动态查找,用户无需担心使用的是父类句柄还是子类句柄,因为最终都会实现动态方法查找,执行正确的方法
建议
- 在为父类定义方法时,如果该方法日后可能会被覆盖或者继承,那么应该声明为虚方法
- 虚方法如果要定义,应该尽量定义在底层父类中。这是因为如果virtual是声明在类继承关系的中间层类,那么只有从该中间类到其子类的调用链中会遵循动态查找,而最底层类到该中间类的方法调用仍然会遵循静态查找
- 虚方法通过virtual声明,只需要声明一次即可。如上述代码,只需要将basic_test::test声明为virtual,而其子类则无需再次声明,当然再次声明来表明该方法的特性也是可以的
- 虚方法的继承也需要遵循相同的参数和返回类型
3 对象拷贝
赋值和拷贝
- 声明变量和创建对象是两个过程,也可以一步完成
Packet p1;
p1 = new();
- 如果将p1赋值给另外一个变量p2,那么依然只有一个对象,只是指向这个对象的句柄有p1和p2
- 以下这种方式表示p1和p2代表两个不同的对象,在创建p2对象时,将从p1拷贝其成员变量如integer、string和句柄等,该种拷贝方式称为浅拷贝(shallow copy)
Packet p1; Packet p2; p1 = new; p2 = new p1;
概述
- 对于拷贝,对象的拷贝要比其他SV的变量类型都让人当心
- 对象的拷贝无法无法通过“=”实现
note:没有new是句柄的拷贝
class basic_test; ... virtual function void copy_data(basic_test t); t.def = def; t.fin = fin; endfunction virtual function basic_test copy(); basic_test t = new(0); copy_data(t); return t; endfunction endclass class test_wr extends basic_test; ... function void copy_data(basic_test t);//此时的t是父类的句柄 test_wr h; super.copy_data(t);//对于当前子类对象的父类句柄做了操作 $cast(h, t); h.def = def; endfunction function basic_test copy(); test_wr t = new(); copy_data(t); return t; endfunction endclass
总结
- 将成员拷贝函数copy_data()和新对象生成函数copy()分为两个方法,这样使得子类继承和方法复用较为容易
- 为了保证父类和子类的成员均可以完成拷贝,将拷贝方法声明为虚方法,且遵循只拷贝该类的域成员的原则,父类的成员拷贝应由父类的拷贝方法完成
- 在实现copy_data()过程中应该注意句柄的类型转换,保证转换后的句柄可以访问类成员变量
4 回调函数
5 参数化的类