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

3.Scala面向对象编程

3.1类和对象
3.1.1类

类是用class开头的代码定义,定义完成后可以用new+类名的方式构造一个对象,对象的类型是这个类。类中定义的var和val类型变量称为字段,用def定义的函数称为方法。字段也称为实例变量,因为每个被构造出来的对象都有自己的字段,但所有的对象公用同样的方法。也就是说,定义一个类之后,每个对象的变量存储在独立的空间,互不相同;每个对象的方法是一样的,存储在同样的空间。使用new创建的对象可以赋值给一个变量,赋值给用val定义的变量,则这个变量之后不得被赋值给新的对象。代码如下:

Scala 复制代码
scala> class Students {
     | var name = "None"
     | def register(n:String) = name = n
     | }
// defined class Students
​
scala> val stu = new Students
val stu: Students = Students@4fc454d5
​
scala> stu.name
val res29: String = None
​
scala> stu.register("Bob")
​
scala> stu.name
val res30: String = Bob
​
scala> stu.register("Jia")
​
scala> stu.name
val res31: String = Jia
​
scala> stu = new Students
-- [E052] Type Error: --------------------------------------------------------------------------------------------------
1 |stu = new Students
  |^^^^^^^^^^^^^^^^^^
  |Reassignment to val stu
  |
  | longer explanation available when compiling with `-explain`
1 error found

类的成员都是公共的,在 外部都可以被访问。如果不想被其他对象访问,可以在变量前加上private关键字。

3.1.2类的构造方法

主构造方法

Scala不同于C++,Python等语言,Scala没有专门的主构造语法,在类名后可以定义若干参数列表用于传递参数,参数会在初始化对象是传给类中的变量。类内部非字段,非方法的代码都被当做"主构造方法"。

Scala 复制代码
scala> class Students(n:String) {
     | val name = n
     | println("A student named" + n + " has been registered.")
     | }
// defined class Students
​
scala> val stu = new Students("Tom")
A student namedTom has been registered.
val stu: Students = Students@33004de5

这里println既不是字段,也不是方法,所以被当成主构造函数的一部分。

辅助构造方法

在类的内部使用 def this(...)定义一个辅助构造方法,其第一步的行为必须是调用该类的另一个构造方法,即第一句必须是this(...)要么是主构造方法,要么是之前的另一个辅助构造方法。这种规则让任何构造方法最终都会调用类的主构造方法,使得主构造方法成为类的单一入口。

Scala 复制代码
scala> class Students(n:String) {
     | val name = n
     | def this() = this("None")
     | println("A student named " + n + " has been registered.")
     | }
// defined class Students
​
scala> val stu = new Students
A student named None has been registered.
val stu: Students = Students@6f319f62
​
scala> val stu2c= new Students("Jia")
A student named Jia has been registered.
val stu2c: Students = Students@5a2e250b

这时存在两个构造函数,主构造函数需要传入一个String类型的量用于初始化,辅助析构函数不需要传入,因为它已经把这个量定义为了None。去掉这个析构函数,新创建对象时不传入String会导致报错:

Scala 复制代码
scala> class Students(n:String) {
     |      val name = n
     |      println("A student named " + n + " has been registered.")
     |      }
// defined class Students
​
scala> val stu = new Students
-- [E171] Type Error: --------------------------------------------------------------------------------------------------
1 |val stu = new Students
  |          ^^^^^^^^^^^^
  |          missing argument for parameter n of constructor Students in class Students: (n: String): Students
1 error found

不能定义两个有歧义的辅助构造函数,这样写编译器无法确认在无参构造时究竟该调用哪一个构造函数:

Scala 复制代码
scala> class Students(n:String) {
     | val name = n
     | def this() = this("None")
     | def this() = this("Jia")
     | println("A student named " + n + " has been registered.")
     | }
-- [E120] Naming Error: ------------------------------------------------------------------------------------------------
4 |def this() = this("Jia")
  |    ^
  |    Double definition:
  |    def <init>(): Students in class Students at line 3 and
  |    def <init>(): Students in class Students at line 4
  |    have the same type after erasure.
  |
  |    Consider adding a @targetName annotation to one of the conflicting definitions
  |    for disambiguation.
1 error found

析构函数

Scala不需要定义析构函数。

私有主构造方法

在类的定义时加入private关键字,则主构造函数是私有的,外部无法调用主构造函数创建对象,只能使用其他公有的辅助构造方法或者工厂方法(专门用于构造对象的方法)

Scala 复制代码
scala> class Students private (n:String,m:Int) {
     | val name = n
     | val score = m
     | def this(n:String) = this(n,100)
     | println(n + "'s score is" + m)
     | }
// defined class Students
​
scala> val stu = new Students("Bill",90)
-- [E007] Type Mismatch Error: -----------------------------------------------------------------------------------------
1 |val stu = new Students("Bill",90)
  |                       ^^^^^^^^^
  |                       Found:    (String, Int)
  |                       Required: String
  |
  | longer explanation available when compiling with `-explain`
1 error found
​
scala> val stu = new Students("Bill")
Bill's score is 100
val stu: Students = Students@45f675a4

主构造方法需要传入两个参数,但它是用private修饰的,所以外部无法调用。定义的辅助析构函数指定了第二个参数是100,只需要传入一个参数,因此外部可以调用这个辅助析构方法。

3.1.3重写toString方法

在构造一个Students类的对象时,Scala解释器打印了一串信息Students@45f675a4,这其实来自Students类的toString方法,这个方法返回一个字符串,构造完一个对象时被自动调用,这个方法是由所有Scala类隐式继承而来的,默认的toString方法将会简单地打印类名,一个"@"符号和一个十六进制数。如果向打印更多有用信息,可以自定义toString方法,但需要加上override关键字。

Scala 复制代码
scala> class Students(n:String) {
     | val name = n
     | override def toString = "A student named " + n + "."
     | }
// defined class Students
​
scala> val stu = new Students("Jia")
val stu: Students = A student named Jia.
3.1.4方法重载

与C++类似,Scala支持方法重载,即在类内可以定义多个同名的方法,但参数(主要是参数类型)不一样,这个方法有很多不同版本,这称为方法重载。函数真正的特征是它的参数,而非函数名或返回类型。它与重写定义不同,重写是子类覆盖了超类的某个方法。

3.1.5类参数

Scala允许在类参数前加上val或var来修饰,这样可以在类的内部生成一个与参数同名的公有字段。还可以使用privateprotectedoverride来表面字段的权限。如果参数没有任何关键字,那么它就仅仅是参数,不是类的成员,只能用初始化字段赋值,内部无法修改,外部无法访问。(如前文中的参数,加上val也算加上了关键字,可以从外部访问,内部可以修改)

Scala 复制代码
scala> class Students(val name: String, var score: Int) {
     | def exam(s:Int) = score = s
     | override def toString = name + "'s score is " + score + "."
     | }
// defined class Students
​
scala> val stu = new Students("Tim",90)
val stu: Students = Tim's score is 90.
​
scala> stu.exam(100)
​
scala> stu.score
val res32: Int = 100
3.1.6单例对象与伴生对象

除了使用new创建一个对象,也可以使用object,但它没有参数和构造方法,且数量只能有一个,因此被称为单例对象。如果某个单例对象和某个类同名,则称单例对象是这个类的伴生对象,这个类称为这个单例对象的伴生类。伴生类和伴生对象必须在同一个文件中,并且可以互相访问对方的所有成员。单例对象和类一样,也可以定义字段和方法,也可以包含别的类和单例对象的定义。单例对象也可以用于大宝某方面功能的函数系列成为一个工具集,或者包含主函数成为程序的入口。

object后面定义的单例对象名可以认为是这个单例对象的名称标签,因此可以通过据点符号访问单例对象的成员,也可以赋值给一个变量。

Scala 复制代码
scala> object B {val b = "a singleton object"}
// defined object B
​
scala> B.b
val res33: String = a singleton object
​
scala> val x = B
val x: B.type = B$@3c4f0087
​
scala> x.b
val res34: String = a singleton object

定义一个类,就定义了一个类型;但定义单例对象并没有新产生一种类型,根本上来说,每个单例对象的类型都是object.type,伴生对象也没有定义类型,而是由伴生类定义的。但是,不同的object定义之间并不能互相赋值,它可以继承自超类或混入特质,因此定义的两个object对象并不是相同的类型:

Scala 复制代码
scala> object X
// defined object X
​
scala> object Y
// defined object Y
​
scala> var x = X
var x: X.type = X$@304b2629
​
scala> x = Y
-- [E007] Type Mismatch Error: -----------------------------------------------------------------------------------------
1 |x = Y
  |    ^
  |    Found:    Y.type
  |    Required: X.type
  |
  | longer explanation available when compiling with `-explain`
1 error found
3.1.7工厂对象与工厂方法

如果定义一个专门用来构造某一个类的对象的方法,那么这种方法被称为工厂方法,包含这些工厂方法集合的单例对象,称为工厂对象。一般工厂对象会定义在伴生对象中,使用工厂方法的好处是可以不用直接使用new来实例化对象,改用方法调用,并且方法名是任意的,对外隐藏了类的实现细节。

首先我们创建一个students.scala的文件,在里面写入以下内容:

Swift 复制代码
class Students(val name: String, var score: Int) {
  def exam(s:Int) = score = s
  override def toString = name + "'s score is " + score + "."
}
​
object Students {
  def registerStu(name: String, score:Int) = new Students(name,score)
}

之后,编译这个文件:

Scala 复制代码
jia@J-MateBookEGo:~/scala_test$ vim students.scala
jia@J-MateBookEGo:~/scala_test$ scalac students.scala

按照书上的方法,直接启动scala并使用import Students._导入会报错:

Scala 复制代码
scala> import Students._
-- [E006] Not Found Error: ---------------------------------------------------------------------------------------------
1 |import Students._
  |       ^^^^^^^^
  |       Not found: Students
  |
  | longer explanation available when compiling with `-explain`
1 error found

这个时候我们需要添加class的路径,假设我们现在处于students.scala文件的路径下,经过编译会生成后缀是.class的文件,如果我们在这个文件的同路径下,使用这个代码导入class:

bash 复制代码
scala -classpath .

如果要添加其他路径的类,使用如下代码:

bash 复制代码
scala -classpath /path/to/classes

执行完这个指令后会直接进入scala解释器,使用如下代码调用:

Scala 复制代码
scala> import Students._
​
scala> val stu = registerStu("Tim",100)
val stu: Students = Tim's score is 100.

其中,object定义的对象称为工厂对象,其中def定义的函数registerStu是工厂方法。

3.1.8 apply方法

apply方法是一个特殊名字的方法,如果定义了这个方法,可以显式调用 对象.apply(参数),也可以隐式调用 对象(参数),如果apply是无参方法,应该写出空括号。通常在伴生对象中定义名为apply的工厂方法,就能通过 伴生对象名(参数)来构造一个对象。也经常在类中定义一个与类相关,具有特定行为的apply方法。

我们在students2.scala中编写如下代码:

Scala 复制代码
class Students2(val name:String, var score: Int) {
  def apply(s:Int) = score = s
  def display() = println("Current core is " + score + ".")
  override def toString = name + "'s score is '" + score + "."
}
​
object Students2 {
  def apply(name: String, score: Int) = new Students2(name, score)
}

执行编译指令,并添加库路径:(这一步在之后不再赘述)

Scala 复制代码
jia@J-MateBookEGo:~/scala_test$ vim students2.scala
jia@J-MateBookEGo:~/scala_test$ scalac students2.scala
jia@J-MateBookEGo:~/scala_test$ scala -classpath .

在解释器中执行如下代码:

Scala 复制代码
scala> val stu2 = Students2("Jack",60)//隐式调用伴生对象中的工厂方法
val stu2: Students2 = Jack's score is 60.
​
scala> stu2(80)//隐式调用类的apply方法
​
scala> stu2.display()
Current core is 80.
3.1.9主函数

主函数是Scala程序的唯一入口,程序是从主程序开始执行的。要提供这样的入口,则必须在某个单例对象中定义一个名为main的函数,而且该函数只有一个参数,类型为字符串Array[String],函数的返回类型是Uint。任何符合条件的单例对象都能成为程序的入口。

编写一个main.scala文件,将其与之前编写的students2.scala一起编译:

Scala 复制代码
//Start.scala
object Start {
  def main(args:Array[String]) = {
    try {
      val score = args(1).toInt
      val s = Students2(args(0), score)
      println(s.toString)
    } catch {
      case ex: ArrayIndexOutOfBoundsException => println("Arguments are deficient!")
      case ex: NumberFormatException => println("Second argument must be a Int!")
    }
  }
}

编译和执行指令:

Scala 复制代码
jia@J-MateBookEGo:~/scala_test$ scala Start.scala students2.scala -- Tom
Arguments are deficient!
jia@J-MateBookEGo:~/scala_test$ scala Start.scala students2.scala -- Tom aaa
Second argument must be a Int!
jia@J-MateBookEGo:~/scala_test$ scala Start.scala students2.scala -- Tom 100
Tom's score is 100.
相关推荐
穆姬姗几秒前
【Python】论文长截图、页面分割、水印去除、整合PDF
开发语言·python·pdf
graceyun3 分钟前
牛客网刷题 ——C语言初阶(5操作符)——OR76 两个整数二进制位不同个数
c语言·开发语言
huaqianzkh5 分钟前
了解RabbitMQ的工作原理
开发语言·后端·rabbitmq
BinaryBardC10 分钟前
Ruby语言的数据结构
开发语言·后端·golang
chusheng184023 分钟前
基于 Python Django 的西西家居全屋定制系统(源码+部署+文档)
开发语言·python·django·家具定制系统·python 全屋家具定制系统·python 家居定制
꧁坚持很酷꧂25 分钟前
Qt天气预报系统界面关闭
开发语言·数据库·qt
graceyun29 分钟前
C语言初阶习题(6指针)【21】打印水仙花数
c语言·开发语言
牛马baby30 分钟前
Java高频面试之SE-09
java·开发语言·面试
橘子海全栈攻城狮1 小时前
【源码+文档+调试讲解】项目申报小程序
java·开发语言·servlet·微信小程序·小程序
跳河轻生的鱼1 小时前
海思Linux(一)-Hi3516CV610的开发-ubuntu22_04环境创建
linux·单片机·学习·华为