zoukankan      html  css  js  c++  java
  • Scala函数式对象-有理数

    有理数类的表示


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

    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

  • 相关阅读:
    toj 2975 Encription
    poj 1797 Heavy Transportation
    toj 2971 Rotating Numbers
    zoj 2281 Way to Freedom
    toj 2483 Nasty Hacks
    toj 2972 MOVING DHAKA
    toj 2696 Collecting Beepers
    toj 2970 Hackle Number
    toj 2485 Card Tric
    js页面定位,相关几个属性
  • 原文地址:https://www.cnblogs.com/duanxz/p/9509748.html
Copyright © 2011-2022 走看看