有理数类的表示


实现规范:支持有理数的加减乘除,并支持有理数的规范表示

1.定义Rational


首先,考虑用户如何使用这个类,我们已经决定使用“Immutable”方式来使用Rational对象,我们需要用户在定义Rational对象时提供分子和分母。

class Rational(n:Int, d:Int)

可以看到,和Java不同的是,Scala的类定义可以有参数,称为类参数,如上面的n、d。Scala使用类参数,并把类定义和主构造函数合并在一起,在定义类的同时也定义了类的主构造函数。因此Scala的类定义相对要简洁些。

Scala编译器会编译Scala类定义包含的任何不属于类成员和类方法的其它代码,这些代码将作为类的主构造函数。比如,我们定义一条打印消息作为类定义的代码:

scala> class Rational (n:Int, d:Int) {
| println("Created " + n + "/" +d)
| }
defined class Rational scala> new Rational(1,2)
Created 1/2
res0: Rational = Rational@22f34036

可以看到创建Ratiaonal对象时,自动执行类定义的代码(主构造函数)。

2.重新定义类的toString方法


上面的代码创建Rational(1,2),Scala 编译器打印出Rational@22f34036,这是因为使用了缺省的类的toString()定义(Object对象的),缺省实现是打印出对象的类名称+“@”+16进制数(对象的地址),显示结果不是很直观,因此我们可以重新定义类的toString()方法以显示更有意义的字符。

在Scala中,你也可以使用override来重载基类定义的方法,而且必须使用override关键字表示重新定义基类中的成员。比如:

scala> class Rational (n:Int, d:Int) {
| override def toString = n + "/" +d
| }
defined class Rational scala> val x= new Rational(1,3)
x: Rational = 1/3 scala> val y=new Rational(5,7)
y: Rational = 5/7

3.前提条件检查


前面说过有理数可以表示为 n/d(其中d、n为正数,而d不能为0)。对于前面的Rational定义,我们如果使用0,也是可以的。

怎么解决分母不能为0的问题呢?面向对象编程的一个优点是实现了数据的封装,你可以确保在其生命周期过程中是有效的。对于有理数的一个前提条件是分母不可以为0,Scala中定义为传入构造函数和方法的参数的限制范围,也就是调用这些函数或方法的调用者需要满足的条件。Scala中解决这个问题的一个方法是使用require方法(require方法为Predef对象的定义的一个方法,Scala环境自动载入这个类的定义,因此无需使用import引入这个对象),因此修改Rational定义如下:

scala> class Rational (n:Int, d:Int) {
| require(d!=0)
| override def toString = n + "/" +d
| }defined class Rational
scala> new Rational(5,0)
java.lang.IllegalArgumentException: requirement failed at scala.Predef$.require(Predef.scala:211) ... 33 elided

可以看到,如果再使用0作为分母,系统将抛IllegalArgumentException异常。

4.添加成员变量


前面我们定义了Rational的主构造函数,并检查了输入不允许分母为0。下面我们就可以开始实行两个Rational对象相加的操作。我们需要实现的函数化对象,因此Rational的加法操作应该是返回一个新的Rational对象,而不是返回被相加的对象本身。我们很可能写出如下的实现:

class Rational (n:Int, d:Int) {
require(d!=0)
override def toString = n + "/" +d
def add(that:Rational) : Rational = new Rational(n*that.d + that.n*d,d*that.d)}

实际上编译器会给出编译错误。

这是为什么呢?尽管类参数在新定义的函数的访问范围之内,但仅限于定义类的方法本身(比如之前定义的toString方法,可以直接访问类参数),但对于that来说,无法使用that.d来访问d。因为that不在定义的类可以访问的范围之内。此时需要定类的成员变量。

注:后面定义的case class类型编译器自动把类参数定义为类的属性,这是可以使用that.d等来访问类参数)。

修改Rational定义,使用成员变量定义如下:

class Rational (n:Int, d:Int) {
require(d!=0)
val number =n
val denom =d
override def toString = number + "/" +denom
def add(that:Rational) = new Rational( number * that.denom + that.number* denom, denom * that.denom )}

要注意的我们这里定义成员变量都使用了val,因为我们实现的是“immutable”类型的类定义。number和denom以及add都可以不定义类型,Scala编译能够根据上下文推算出它们的类型。

scala> val oneHalf=new Rational(1,2)
oneHalf: Rational = 1/2 scala> val twoThirds=new Rational(2,3)
twoThirds: Rational = 2/3 scala> oneHalf add twoThirds
res0: Rational = 7/6 scala> oneHalf.number
res1: Int = 1

5.自身引用


Scala 也使用this来引用当前对象本身,一般来说访问类成员时无需使用this,比如实现一个lessThan方法,下面两个实现是等效的。

第一种:

def lessThan(that:Rational) = this.number * that.denom < that.number * this.denom

第二种:

def lessThan(that:Rational) = number * that.denom < that.number * denom

但如果需要引用对象自身,this就无法省略,比如下面实现一个返回两个Rational中比较大的一个值的一个实现:

def max(that:Rational) = if(lessThan(that)) that else this

其中的this就无法省略。

6.辅助构造函数


在定义类时,很多时候需要定义多个构造函数,在Scala中,除主构造函数之外的构造函数都称为辅助构造函数(或是从构造函数),比如对于Rational类来说,如果定义一个整数,就没有必要指明分母,此时只要整数本身就可以定义这个有理数。我们可以为Rational定义一个辅助构造函数,Scala定义辅助构造函数使用 this(…)的语法,所有辅助构造函数名称为this。

def this(n:Int) = this(n,1)

所有Scala的辅助构造函数的第一个语句都为调用其它构造函数,也就是this(…)。被调用的构造函数可以是主构造函数或是其它构造函数(最终会调用主构造函数)。这样使得每个构造函数最终都会调用主构造函数,从而使得主构造函数称为创建类单一入口点。在Scala中也只有主构造函数才能调用基类的构造函数,这种限制有它的优点,使得Scala构造函数更加简洁和提高一致性。

7.私有成员变量和方法


Scala 类定义私有成员的方法也是使用private修饰符,为了实现Rational的规范化显示,我们需要使用一个求分子和分母的最大公倍数的私有方法gcd。同时我们使用一个私有变量g来保存最大公倍数,修改Rational的定义:

scala> class Rational (n:Int, d:Int) {
| require(d!=0)
| private val g =gcd (n.abs,d.abs)
| val number =n/g
| val denom =d/g
| override def toString = number + "/" +denom
| def add(that:Rational) =
| new Rational(
| number * that.denom + that.number* denom,
| denom * that.denom
| )
| def this(n:Int) = this(n,1)
| private def gcd(a:Int,b:Int):Int =
| if(b==0) a else gcd(b, a % b)
| }
defined class Rational scala> new Rational ( 66,42)
res0: Rational = 11/7

注意gcd的定义,因为它是个回溯函数,必须定义返回值类型。Scala 会根据成员变量出现的顺序依次初始化它们,因此g必须出现在number和denom之前。

8.定义运算符


本篇还将接着上篇Rational类,我们使用add定义两个Rational对象的加法。两个Rational加法可以写成x.add(y)或者x add y。
即使使用 x add y还是没有 x + y来得简洁。
我们前面说过,在Scala中,运算符(操作符)和普通的方法没有什么区别,任何方法都可以写成操作符的语法。比如上面的 x add y。
而在Scala中对方法的名称也没有什么特别的限制,你可以使用符号作为类方法的名称,比如使用+、-和*等符号。因此我们可以重新定义Rational如下:

class Rational (n:Int, d:Int) {
require(d!=0)
private val g =gcd (n.abs,d.abs)
val numer =n/g
val denom =d/g
override def toString = numer + "/" +denom
def +(that:Rational) = new Rational( numer * that.denom + that.numer* denom, denom * that.denom )
def * (that:Rational) = new Rational( numer * that.numer, denom * that.denom)
def this(n:Int) = this(n,1)
private def gcd(a:Int,b:Int):Int = if(b==0) a else gcd(b, a % b)}

这样就可以使用 +、*号来实现Rational的加法和乘法。+、*的优先级是Scala预设的,和整数的+、-、*和/的优先级一样。下面为使用Rational的例子:

scala> val x= new Rational(1,2)
x: Rational = 1/2 scala> val y=new Rational(2,3)
y: Rational = 2/3 scala> x+y
res0: Rational = 7/6 scala> x+ x*y
res1: Rational = 5/6

从这个例子也可以看出Scala语言的扩展性,你使用Rational对象就像Scala内置的数据类型一样。

9.Scala中的标识符


从前面的例子我们可以看到Scala可以使用两种形式的标志符,字符数字和符号。字符数字使用字母或是下划线开头,后面可以接字母或是数字,符号“$”在Scala中也看作为字母。然而以“$”开头的标识符为保留的Scala编译器产生的标志符使用,应用程序应该避免使用“$”开始的标识符,以免造成冲突。

Scala的命名规则采用和Java类似的camel命名规则(驼峰命名法),首字符小写,比如toString。类名的首字符还是使用大写。此外也应该避免使用以下划线结尾的标志符以避免冲突。

符号标志符包含一个或多个符号,如+、:和?。对于+、++、:::、<、 ?>、 :->之类的符号,Scala内部实现时会使用转义的标志符。例如对:->使用$colon$minus$greater来表示这个符号。因此,如果你需要在Java代码中访问:->方法,你需要使用Scala的内部名称$colon$minus$greater。

混合标志符由字符数字标志符后面跟着一个或多个符号组成,如 unary_+为Scala对+方法的内部实现时的名称。

字面量标志符为使用‘’定义的字符串,比如 ‘x’ 、‘yield’ 。 你可以在‘’之间使用任何有效的Scala标志符,Scala将它们解释为一个Scala标志符,一个典型的使用是 Thread的yield方法, 在Scala中你不能使用Thread.yield()是因为yield为Scala中的关键字, 你必须使用 Thread.‘yield’()来使用这个方法。

10.方法重载


和Java一样,Scala也支持方法重载,重载的方法参数类型不同而使用同样的方法名称,比如对于Rational对象,+的对象可以为另外一个Rational对象,也可以为一个Int对象,此时你可以重载+方法以支持和Int相加

def + (i:Int) = new Rational (numer + i * denom, denom)

11.隐式类型转换


上面我们定义Rational的加法,并重载+以支持整数,r + 2,当如果我们需要 2 + r如何呢?

可以看到 x + 3没有问题,3 + x就报错了,这是因为整数类型不支持和Rational相加。我们不可能去修改Int的定义(除非你重写Scala的Int定义)以支持Int和Rational相加。如果你写过.Net代码,这可以通过静态扩展方法来实现,Scala提供了类似的机制来解决这种问题。

如果Int类型能够根据需要自动转换为Rational类型,那么 3 + x就可以相加。Scala通过implicit def定义一个隐含类型转换,比如定义由整数到Rational类型的转换如下:

implicit def intToRational(x:Int) = new Rational(x)

其实此时Rational的一个+重载方法是多余的, 当Scala计算 2 + r,发现 2(Int)类型没有可以和Rational对象相加的方法,Scala环境就检查Int的隐含类型转换方法是否有合适的类型转换方法,类型转换后的类型支持+ r,一检查发现定义了由Int到Rational的隐含转换方法,就自动调用该方法,把整数转换为Rational数据类型,然后调用Rational对象的 +方法。从而实现了Rational类或是Int类的扩展。关于implicit def的详细介绍将由后面的文章来说明,隐含类型转换在设计Scala库时非常有用。

转自:https://www.jianshu.com/p/61268f438485

Scala函数式对象-有理数的更多相关文章

  1. Scala学习笔记——函数式对象

    用创建一个函数式对象(类Rational)的过程来说明 类Rational是一种表示有理数(Rational number)的类 package com.scala.first /** * Creat ...

  2. Scala 基础(5)—— 构建函数式对象

    有了 Scala 基础(4)—— 类和对象 的前提,现在就可以来构建一个基于 Scala 的函数式对象. 下面开始构造一个有理数对象 Rational. 1. 主构造方法和辅助构造方法 对于每一个类的 ...

  3. Scala编程--函数式对象

    本章的重点在于定义函数式对象,也就是说,没有任何可变状态的对象的类.作为运行的例子,我们将创造若干把分数作为不可变对象建模的类的变体.在这过程中,我们会展示给你Scala面向对象编程的更多方面:类参数 ...

  4. Scala函数式编程进阶

    package com.dtspark.scala.basics /** * 函数式编程进阶: * 1,函数和变量一样作为Scala语言的一等公民,函数可以直接赋值给变量: * 2, 函数更长用的方式 ...

  5. Scala实战高手****第5课:零基础实战Scala函数式编程及Spark源码解析

    Scala函数式编程 ----------------------------------------------------------------------------------------- ...

  6. 9、scala函数式编程-集合操作

    一.集合操作1 1.Scala的集合体系结构 // Scala中的集合体系主要包括:Iterable.Seq.Set.Map.其中Iterable是所有集合trait的根trai.这个结构与Java的 ...

  7. Scala函数式编程(三) scala集合和函数

    前情提要: scala函数式编程(二) scala基础语法介绍 scala函数式编程(二) scala基础语法介绍 前面已经稍微介绍了scala的常用语法以及面向对象的一些简要知识,这次是补充上一章的 ...

  8. Scala函数式编程(四)函数式的数据结构 上

    这次来说说函数式的数据结构是什么样子的,本章会先用一个list来举例子说明,最后给出一个Tree数据结构的练习,放在公众号里面,练习里面给出了基本的结构,但代码是空缺的需要补上,此外还有预留的test ...

  9. scala 函数式编程之集合操作

    Scala的集合体系结构 // Scala中的集合体系主要包括:Iterable.Seq.Set.Map.其中Iterable是所有集合trait的根trai.这个结构与Java的集合体系非常相似. ...

随机推荐

  1. BZOJ 2002:Bounce 弹飞绵羊(分块)

    2002: [Hnoi2010]Bounce 弹飞绵羊 Time Limit: 10 Sec  Memory Limit: 259 MB Submit: 14944  Solved: 7598 [Su ...

  2. Atcoder 1973:こだわり者いろはちゃん / Iroha's Obsession

    C - こだわり者いろはちゃん / Iroha's Obsession Time limit : 2sec / Memory limit : 256MB Score : 300 points Prob ...

  3. Codeforces 766C:Mahmoud and a Message(DP)

    题目链接:http://codeforces.com/problemset/problem/766/C 题意 有一个长度为n的字符串,第二行有26个数字,位置1~26对应为a~z的字母,数值表示该字母 ...

  4. nginx负载均衡、nginx ssl原理及生成密钥对、nginx配制ssl

    1.nginx负载均衡 新建一个文件:vim /usr/local/nginx/conf/vhost/load.conf写入: upstream abc_com{ip_hash;server 61.1 ...

  5. 在学习linux基础入门时的一些问题总结(1)

    本周在实验楼完成了<linux基础入门>的21个实验,虽然之前已经学习过linux的相关课程,对linux下的命令也有一些了解和实践,但完成这21个实验以及35个练习题仍然遇到了许多的问题 ...

  6. (22)bootstrap 初识 + Font Awesome(字体图标库)

    bootstrap作用就是简化布局 bootstrap是基于JQ的,所以内部代码使用的是jq语法 所以要使用bs,必须先倒入 1.head标签内倒入bs的css文件  <link rel=&qu ...

  7. hdu6441 Find Integer (费马大定理)

    #include<bits/stdc++.h> using namespace std; int main() { int T; scanf("%d",&T); ...

  8. python str使用笔记(更新)

    判断字符串是否以某个串为结尾: str.endswith(strtmp) 返回True/False >>> strs='aba' >>> strs.endswith ...

  9. Linux----版本选择

    此文摘自老男孩老师课堂: 下载地址:http://man.linuxde.net/download/

  10. numpy.array

    关于python中的二维数组,主要有list和numpy.array两种. 好吧,其实还有matrices,但它必须是2维的,而numpy arrays (ndarrays) 可以是多维的. 我们主要 ...