[读书日志]从零开始学习Chisel 第六篇:Scala面向对象编程——特质(敏捷硬件开发语言Chisel与数字系统设计)

ops/2025/1/12 13:27:32/
3.4特质
3.4.1什么是特质

特质使用trait开头,它与单例对象很像,两者都不能有输入参数,但单例对象是具体的,特质是抽象的。两者都不能用new实例化,类,单例对象,特质三者内部都可以包含字段和方法,以及其他类,单例对象,特质的定义。

特质可以被其他类,单例对象和特质“混入”。混入在超类调用上采用线性化机制,在其他方面其实和继承是一样的。某个类混入一个特质后,会包含特质的所有公有成员,可以通过override来重写特质的成员。Scala只允许继承自一个类,但是对特质的混入数量没有限制,因此用来替代多重继承的语法。要混入一个特质,可以使用关键字extend,已被占用后可以使用with混入其他特质。

scala">scala> class A {| val a = "Class A"| }
// defined class Ascala> trait B {| val b = "Trait B"| }
// defined trait Bscala> trait C {| val c = "Trait C"| }
// defined trait Cscala> object D extends A with B with C
// defined object Dscala> D.a
val res7: String = Class Ascala> D.b
val res8: String = Trait Bscala> D.c
val res9: String = Trait C

特质也定义了一个类型,可以指向混入该特质的对象。

scala">scala> trait A
// defined trait Ascala> class B extends A
// defined class Bscala> val x: A = new B
val x: A = B@39159b14
3.4.2特质的层次

特质也可以继承自其他类,或混入任意多个特质,对混入有一个限制条件,即要混入该特质的类/单例对象/特质,它的超类必须是待混入特质的超类,或是待混入特质的超类的子类。换句话说,一个特质如果想要“继承”(注意,这里的“继承”实际上是“混入”,只是为了理解特质用来替代多重继承的特质。)另一个特质,那么它只能继承比它层级更低的特质。

scala">scala> class A
// defined class Ascala> class B extends A
// defined class Bscala> class C
// defined class Cscala> trait D extends A
// defined trait Dscala> trait E extends B
// defined trait Escala> class Test1 extends D
// defined class Test1scala> class Test2 extends A with D
// defined class Test2scala> class Test3 extends B with D
// defined class Test3scala> class Test4 extends C with D
-- Error: --------------------------------------------------------------------------------------------------------------
1 |class Test4 extends C with D|                           ^|                           illegal trait inheritance: superclass C does not derive from traitD's superclass A
1 error foundscala> class Test5 extends A with E
-- Error: --------------------------------------------------------------------------------------------------------------
1 |class Test5 extends A with E|                           ^|                           illegal trait inheritance: superclass A does not derive from traitE's superclass B
1 error found

画图表示如下:

在这里插入图片描述

Test4继承自classC,它混入的特质是traitD,该特质的超类是classA,它不是classC的超类或超类的子类,因此无法混入;Test5继承自classA,它混入的traitE继承自classB,但classBclassA的子类,因此不能混入。如果把它们换一下顺序,使Test6继承classB而混入一个超类是classA的特质traitD,那么则可以运行。这个例子说明,一个类或特质的超类是哪个,是由extends关键字决定的。

scala">scala> class Test6 extends B with D
// defined class Test6
3.4.3混入特质的简便方法

如果要快速构建一个混入某些特质的实例,可以使用这个语法:

new Trait1 with Trait2 ... {definition}

这句话定义了一个匿名类,匿名类混入了这些特质。还可以在最前面加上一个想要继承的超类:

new SuperClass with Trait1 with Trait2 ... {definition}

3.4.4特质的线性化叠加计算

多重继承中一个很明显的问题是当子类调用超类的方法时,若多个超类都有该方法的不同实现,那么需要额外的语法确定具体调用哪个版本。Scala的特质采取一种线性化的规则来调用特质中的方法,在特质中,super调用是动态绑定的,按特质本身的定义,无法确定super调用的具体行为,直到特质混入某个类或别的特质,有了具体的超类方法,才能确定super的行为,这是实现线性化的基础。

若要通过混入特质来实现某个方法的线性叠加,有以下几个要求:

  1. 需要在特质中定义同名同参的方法,并加关键字组合abstract override,这个关键词不是重写,只能用于特质中;
  2. 该特质必须混入某个拥有该方法的具体定义的类中,这个类定义了该方法的最终行为;
  3. 需要混入特质进行线性化计算的类,定义时不能立即混入特质,这样做会让编译器认为这个类是在重写末尾那个特质的方法。应该先定义这个类的子类来混入特质,然后构造子类的对象,或者直接用3.4.3所述方法快速构造子类对象;
  4. 特质对该方法的定义必须出现super.方法名(参数)
  5. 方法的执行顺序遵循线性化计算公式,起点是公式从左往右第一个特质,外部传入的参数由起点接收,起点的super.方法名(参数)会调用起点右边第一个特质的同名方法,并把起点的计算结果作为参数传递,以此类推。最后结果会回到最左边的类本身。
  6. 这个类实际上直接或间接重写或实现了基类的方法,如果定义中也出现了super.方法名(参数),则会调用它的上一层超类的实现版本。如果这个类没有重写,那就要有继承自超类的实现。

线性化计算公式:

  1. 最左边是类本身;
  2. 在类的右边写下定义时最后混入的那个特质,并接着往右按继承顺序写下该特质的所有超类和超特质;
  3. 继续往右写下倒数第二个混入的特质,以及其超类和超特质,直到写完所有特质;
  4. 所有重复项值保留最右边的那个,并在最右边加上AnyRefAny

现在我们编写这样的一个test.scala文件:

scala">abstract class A {def m(s: String) : String
}class X extends A {def m(s: String) = "X ->" + s
}trait B extends A {abstract override def m(s: String) = super.m("B ->" + s)
}trait C extends A {abstract override def m(s: String) = super.m("C ->" + s)
}trait D extends A {abstract override def m(s: String) = super.m("D ->" + s)
}trait E extends C {abstract override def m(s: String) = super.m("E ->" + s)
}trait F extends C {abstract override def m(s: String) = super.m("F ->" + s)
}class G extends X {override def m(s: String) = "G ->" + s
}val x = new G with D with E with F with B
@main def test() = println(x.m("End"))

最终,需要混入特质进行线性化运算的类G在定义时没有立即混入特质,只是继承了classX,且通过new G with D with E with F with B构建了G的匿名子类的对象。类A是一个抽象类,类X实现了抽象方法m,类G重写了X的m,其余特质也用abstract override重写了m,这保证m最后会回到G。A的m返回类型“String”是必须的。

根据线性化计算公式,我们一步步进行计算。注意类X也就是这个类G继承的类不参与运算,最左边是类本身:G ->,在类的右边写下定义时最后混入的特质,然后往右按照继承顺序写下该特质所有的超类和超特质,全部写完后再写第二个混入的类,以此类推。因此第二个写下的是最后混入的特质B和它的超类:G -> B -> A接着写类F。以此类推,我们写出:

G -> B -> A -> F -> C -> A -> E -> C -> A -> D -> A

之后我们删除其中重复的部分:

G -> B -> F -> E -> C -> D -> A

之后补上AnyRefAny

G -> B -> F -> E -> C -> D -> A -> AnyRef -> Any

起点是B,传入“End”,B输出“B -> End”,之后再传给F,输出“F -> B -> End”,继续向右调用,最后到A时没有操作可以执行,转回G的m,最终得到运行结果是:

G ->D ->C ->E ->F ->B ->End

如果G的m也有super或没有重写,那么会调用X的m,导致最后最左边多一个X,如果在定义G时立即混入特质,则相当于普通的方法重写,如果上一层超类是抽象类,立即引入会报错。


http://www.ppmy.cn/ops/149112.html

相关文章

14:00面试,15:00就出来了,问的问题过于变态了。。。

从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到2月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…

(超详细)Maven安装配置、以及在IDEA中创建Maven项目

一、登录官网下载Maven Download Apache Maven – Maven 根据自己所需要进行下载,如果是windows系统就下载zip文件,Linux系统就下载gz文件 我下载的版本是3.6.3,下面是网盘链接: 百度网盘链接: https://pan.baidu.com/s/1Ytoprb…

算法面试1

简述yolov1的网络架构 YOLOv1网络结构包括24层卷积层用来提取图像的特征,2层全连接层回归得到7730(141420)的张量。   网络结构大概如下:输入的是4484483通道的图像,就是RGB图像,然后用64个卷积核大小是…

Clojure语言的多线程编程

Clojure语言的多线程编程 在现代软件开发中,多线程编程是一项重要的技能。它使程序能够在同一时间执行多个任务,充分利用多核处理器的性能。在众多编程语言中,Clojure作为一门函数式编程语言,提供了强大的并发支持。本文将深入探…

【C++】C++11(二)

目录 九、可变参数模板十、lambda表达式10.1 C98中的一个例子10.2 lambda表达式10.3 lambda表达式语法10.3.1 lambda表达式各部分说明10.3.2 捕获列表说明 10.4 函数对象与lambda表达式 十一、包装器11.1 function包装器11.2 bind 十二、线程库12.1 线程12.1.1 thread类的简单介…

高校心理教育辅导系统|基于Springboot的高校心理教育辅导系统设计与实现(源码+数据库+文档)

高校心理教育辅导系统目录 目录 基于Springboot的高校心理教育辅导系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、学生功能模块的实现 (1)学生登录界面 (2)留言反馈界面 (3)试卷列表界…

在Proteus软件仿真STM32F103寄存器玩俄罗斯方块之第一篇

这篇文章主要是代码分享,以及简单解析。 对代码哪里不理解的,欢迎找我讨论哈。 和上篇代码结构不一样的地方是多文件和分模块,使得工程更加有条理。 这篇文章主要是单片机引脚的输出操作,和PID的那篇文章高度重合,原因…

用c实现C++类(八股)

在 C 语言中,虽然没有内建的面向对象编程(OOP)特性(如封装、继承、多态),但通过一些编程技巧,我们仍然可以模拟实现这些概念。下面将用通俗易懂的方式,逐步介绍如何在 C 中实现封装、…