1. 程式人生 > >Scala的函式與方法

Scala的函式與方法

import java.io.Serializable
import scala.annotation.elidable
import scala.beans.BeanProperty
import scala.collection.immutable.HashMap
import scala.collection.mutable
import scala.reflect.ClassTag

/**
  * keyPoint
  * xx  08/07/25 09:00
  */
class ControlProgrammer extends FreeSpec {

  "函式和方法" - {

    "定義" - {

      "方法的定義" in {
        /*
         * 方法是組成類的一部分. 方法有名字、型別簽名, 有時方法上還有註解, 以及方法的功能。
         * 在類中定義的函式就是方法. 方法是類的一部分
         */

        class Demo {
          // 類中定義一個方法,方法名為method,無引數,無返回值,方法的功能是控制檯進行列印
          def method() = println("列印")
        }

      }

      "函式的定義" in {
        /*
         * Scala中的函式是一個完整的物件, 一個物件可以賦值給一個變數. Scala中用22個特質(trait)抽象出了函式的概念。這22特質從Function1到Function22
         * Scala中的函式繼承了這些Trait的類的物件
         * 函式的定義可以單獨定義,可以不依賴於類、介面或者object,而且獨立存在,獨立使用,並且可以賦值給變數。
         */
        val adder = (x: Int, y: Int) => x + y
        val adder2 = new Function2[Int, Int, Int]() {
          def apply(x: Int, y: Int): Int = {
            x + y
          }
        }
        // 以上adder和adder2其實是等價的

      }

    }

    "引數" - {

      "高階函式(函式作為引數)與頭等函式: " in {

        // 函式裡面呼叫函式, 把函式作為一個函式的引數呼叫或返回值為函式
        def apply(f: Int => String, v: Int) = f(v)

        def layout[A](x: A) = "[" + x.toString + "]"

        println(apply(layout, 10))

        // 以上是一個高階函式,同時它也是頭等函式。頭等函式的概念是:
        // Scala的函式就是頭等函式。你不僅可以定義和呼叫函式,還可以把它們寫成匿名的字面量,並把它們作為值傳遞。

      }

      "可變引數: 通過在引數的型別之後放一個星號來設定可變引數(可重複的引數)" in {

        // 可變引數. 類似於Java中的可變引數, 如String... , 本質上是一個數組
        def demo(x: Int, y: String*) = {
          println(x)
          for (s <- y)
            println(s)
        }

        demo(1, "1", "2", "3", "4")
      }

      "預設引數值(當你不傳遞引數時,給定預設引數)" in {

        // Scala中可以為函式指定預設引數值
        def demo(x: Int = 5, y: Int = 10) = x + y

        // 呼叫有引數的demo(1, 2), 此時預設引數無效
        val i = demo(1, 2)
        println(i) // 3

        // 呼叫無參demo(), 預設引數值生效
        val ii = demo()
        println(ii) // 15

      }

      "指定函式引數名:我們也可以通過指定函式引數名,並且不需要按照順序向函式傳遞引數" in {

        def demo(x: Int, y: Int) = x + y

        // 顯而易見, 引數名指定後, 引數順序可以無序
        val i = demo(y = 1, x = 2)
        println(i)

      }

    }

    "使用" - {

      "函式的類別和使用方式" - {

        "本地函式:所在函式的外部不可以訪問,定義在函式內的函式又稱之為區域性函式,函式裡面巢狀函式,這個過程稱為函式巢狀" in {

          // 本地函式所在函式的外部不可以訪問本地函式, 本地函式可以接收外部函式傳遞過來的引數
          def one(x: Int, y: Int) = {

            // two為本地函式, 在one函式外部無法直接呼叫two, two可以使用one傳遞過來的引數, 此函式也被稱為區域性函式
            def two(x: Int, y: Int) = x << y

            two(x, y)
          }

          val unit = one(2, 3)

          println(unit)

        }

        "部分應用函式:沒有設定完整的引數的函式,它也是一個偏應用函式" in {

          def log(s: String, i: Int) = {
            println("s: " + s + ", i: " + i)
          }

          log("秒", 5)
          log("秒", 6)
          log("秒", 7)

          // 以上如果我呼叫三次log, 那麼log函式被呼叫三次,因為s引數一直不變,此時可以用偏應用函式做修改
          val res = log("秒", _: Int) // 指定改變的Int為 _ 未定, 此時這也是一個部分應用函式

          // 使用偏應用函式後log被執行一次, 然後呼叫res, 得到相同的結果
          res(5)
          res(6)
          res(7)

        }

        "匿名函式: 匿名函式的寫法,箭頭左邊是引數列表,右邊是函式體。" in {

          val sum = (x: Int, y: Int) => x + y
          println(sum(1, 2))

        }

        "函式柯里化: 柯里化(Currying)指的是將原來接受兩個引數的函式變成新的接受一個引數的函式的過程" in {

          //非柯里化函式定義
          def demo(x: Int, y: Int) = x + y

          //柯里化使用多個引數列表
          def demo1(x: Int)(y: Int) = x + y

          println(demo(2, 8))
          println(demo1(2)(8)) // 這裡呼叫demo1(2)(8)實際上是依次呼叫兩個普通函式,第一次呼叫使用一個引數x,返回一個函式型別的值,第二次使用引數y呼叫這個函式型別的值

          val a = demo1(2) _ // _是指定第二個引數的佔位符
          val b = a(8)
          println("b: " + b) // 以上兩個過程就是 val b = demo1(2)(8)

        }

        "函式傳名呼叫(call-by-name): 該方法在變數名和變數型別使用 => 符號來設定傳名呼叫" in {

          // 獲取當前時間納秒方法, 返回long
          def time() = System.nanoTime()

          // 指定高階函式, 引數為long, 可以傳入返回值為long的函式為引數
          def demo(x: => Long) = println("demo方法的引數是:" + x)

          println(demo(time()))
        }

        "Scala 遞迴函式: 遞迴函式意味著函式可以呼叫它本身" in {

          // 滿足條件就返回int值, 否則遞迴
          def demo(x: Int, y: Int): Int = {
            if (x > y) x else demo(x, y - 1)
          }

          demo(2, 3)

        }

        "尾遞迴" in {

          // 因為遞迴過程中呼叫的方法都堆積在堆疊, 直到最後結束, 所以出現了尾遞迴, 每次遞迴的值都會返回, 也就意味著每次資源的釋放

          def factorial(n: Int): Int = { // 遞迴求階乘
            if (n <= 1) 1 else n * factorial(n - 1)
          }

          // 尾遞迴
          def factorial1(acc: Int, n: Int): Int =
            if (n <= 1) acc else factorial1(acc * n, n - 1)

          val int = factorial1(1, 5)
          println(int)

        }

        "當函式只有一個引數的時候,函式引用可以使用{}來代替()" in {
          def demo(x: Int) = x << 2

          // a和b是等值操作
          val a = demo(1)
          val b = demo {
            1
          }

          val bool = a.compareTo(b)
          println(bool)
        }

      }

    }

    "區別" - {

      /*
       * 方法不能作為單獨的表示式而存在(引數為空的方法除外),而函式可以
       * 函式可以作為一個引數傳遞到方法中, 而方法不行
       * Scala中無法直接操作方法, 如果要操作方法, 必須先將其轉換成函式.
       * 函式必須有引數列表, 方法可以沒有
       * 方法名是方法呼叫,而函式名只是代表函式物件本身
       */

      // 方法不能作為單獨的表示式而存在(引數為空的方法除外),而函式可以
      def m(x: Int) = x << 2

      val h = (x: Int) => x << 2
      // m
      h

      // 函式必須有引數列表, 方法可以沒有
      // val a = => 2   函式
      def b() = println("方法")

      // 方法用方法名呼叫, 函式名代表函式物件本身
      def c() = 5

      val d = () => 5
      c()
      d

      // 函式可以作為一個引數傳遞到方法中, 而方法不行
      def apply(f: Int => String, v: Int) = f(v)

      def layout[A](x: A) = "[" + x.toString + "]"

      apply(layout, 10)

    }

  }

}